mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
[ios] Support guarding concurrent reads and writes to Annotations
Since iOS reads Annotations in-process, this CL updates the iOS intermediate dump handler to check each Annotation to see if it supports guarding concurrent reads and writes using ScopedSpinGuard. For any such Annotation, the in-process dump handler now tries (without spinning) to obtain the ScopedSpinGuard for the Annotation before reading its memory. If the ScopedSpinGuard cannot immediately be obtained, the in-process dump handler just skips writing the memory of the Annotation to the intermediate dump. (I'd like to follow up and thread down a Params object so we can experiment with adding an optional timeout to make this more reliable.) Change-Id: Ie6c9849fac94ab89b36364b07aea62326cabe552 Bug: crashpad:437 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4031730 Commit-Queue: Ben Hamilton <benhamilton@google.com> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
parent
212b8f6b8c
commit
c7d9c710f2
@ -22,12 +22,15 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "build/build_config.h"
|
#include "build/build_config.h"
|
||||||
#include "snapshot/snapshot_constants.h"
|
#include "snapshot/snapshot_constants.h"
|
||||||
#include "util/ios/ios_intermediate_dump_writer.h"
|
#include "util/ios/ios_intermediate_dump_writer.h"
|
||||||
#include "util/ios/raw_logging.h"
|
#include "util/ios/raw_logging.h"
|
||||||
|
#include "util/ios/scoped_vm_map.h"
|
||||||
#include "util/ios/scoped_vm_read.h"
|
#include "util/ios/scoped_vm_read.h"
|
||||||
|
#include "util/synchronization/scoped_spin_guard.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
@ -1163,6 +1166,13 @@ void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
|
|||||||
IOSIntermediateDumpWriter::ScopedArray annotations_array(
|
IOSIntermediateDumpWriter::ScopedArray annotations_array(
|
||||||
writer, IntermediateDumpKey::kAnnotationObjects);
|
writer, IntermediateDumpKey::kAnnotationObjects);
|
||||||
ScopedVMRead<Annotation> current;
|
ScopedVMRead<Annotation> current;
|
||||||
|
|
||||||
|
// Use vm_read() to ensure that the linked-list AnnotationList head (which is
|
||||||
|
// a dummy node of type kInvalid) is valid and copy its memory into a
|
||||||
|
// newly-allocated buffer.
|
||||||
|
//
|
||||||
|
// In the case where the pointer has been clobbered or the memory range is not
|
||||||
|
// readable, skip reading all the Annotations.
|
||||||
if (!current.Read(annotation_list->head())) {
|
if (!current.Read(annotation_list->head())) {
|
||||||
CRASHPAD_RAW_LOG("Unable to read annotation");
|
CRASHPAD_RAW_LOG("Unable to read annotation");
|
||||||
return;
|
return;
|
||||||
@ -1173,6 +1183,12 @@ void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
|
|||||||
index < kMaxNumberOfAnnotations;
|
index < kMaxNumberOfAnnotations;
|
||||||
++index) {
|
++index) {
|
||||||
ScopedVMRead<Annotation> node;
|
ScopedVMRead<Annotation> node;
|
||||||
|
|
||||||
|
// Like above, use vm_read() to ensure that the node in the linked list is
|
||||||
|
// valid and copy its memory into a newly-allocated buffer.
|
||||||
|
//
|
||||||
|
// In the case where the pointer has been clobbered or the memory range is
|
||||||
|
// not readable, skip reading this and all further Annotations.
|
||||||
if (!node.Read(current->link_node())) {
|
if (!node.Read(current->link_node())) {
|
||||||
CRASHPAD_RAW_LOG("Unable to read annotation");
|
CRASHPAD_RAW_LOG("Unable to read annotation");
|
||||||
return;
|
return;
|
||||||
@ -1187,6 +1203,36 @@ void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For Annotations which support guarding reads from concurrent writes, map
|
||||||
|
// their memory read-write using vm_remap(), then declare a ScopedSpinGuard
|
||||||
|
// which lives for the duration of the read.
|
||||||
|
ScopedVMMap<Annotation> mapped_node;
|
||||||
|
std::optional<ScopedSpinGuard> annotation_guard;
|
||||||
|
if (node->concurrent_access_guard_mode() ==
|
||||||
|
Annotation::ConcurrentAccessGuardMode::kScopedSpinGuard) {
|
||||||
|
constexpr vm_prot_t kDesiredProtection = VM_PROT_WRITE | VM_PROT_READ;
|
||||||
|
if (!mapped_node.Map(node.get()) ||
|
||||||
|
(mapped_node.CurrentProtection() & kDesiredProtection) !=
|
||||||
|
kDesiredProtection) {
|
||||||
|
CRASHPAD_RAW_LOG("Unable to map annotation");
|
||||||
|
|
||||||
|
// Skip this annotation rather than giving up entirely, since the linked
|
||||||
|
// node should still be valid.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(https://crbug.com/crashpad/438): Pass down a `params` object into
|
||||||
|
// this method to optionally enable a timeout here.
|
||||||
|
constexpr uint64_t kTimeoutNanoseconds = 0;
|
||||||
|
annotation_guard =
|
||||||
|
mapped_node->TryCreateScopedSpinGuard(kTimeoutNanoseconds);
|
||||||
|
if (!annotation_guard) {
|
||||||
|
// This is expected if the process is writing to the Annotation, so
|
||||||
|
// don't log here and skip the annotation.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
|
IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
|
||||||
WritePropertyCString(writer,
|
WritePropertyCString(writer,
|
||||||
IntermediateDumpKey::kAnnotationName,
|
IntermediateDumpKey::kAnnotationName,
|
||||||
|
@ -70,6 +70,7 @@ source_set("xcuitests") {
|
|||||||
deps = [
|
deps = [
|
||||||
"../../build:ios_enable_arc",
|
"../../build:ios_enable_arc",
|
||||||
"../../build:ios_xctest",
|
"../../build:ios_xctest",
|
||||||
|
"../../client:common",
|
||||||
"../../test/ios/host:app_shared_sources",
|
"../../test/ios/host:app_shared_sources",
|
||||||
"../../third_party/edo",
|
"../../third_party/edo",
|
||||||
"../../util",
|
"../../util",
|
||||||
|
@ -15,8 +15,11 @@
|
|||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
#include <objc/runtime.h>
|
#include <objc/runtime.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#import "Service/Sources/EDOClientService.h"
|
#import "Service/Sources/EDOClientService.h"
|
||||||
#include "build/build_config.h"
|
#include "build/build_config.h"
|
||||||
|
#include "client/length_delimited_ring_buffer.h"
|
||||||
#import "test/ios/host/cptest_shared_object.h"
|
#import "test/ios/host/cptest_shared_object.h"
|
||||||
#include "util/mach/exception_types.h"
|
#include "util/mach/exception_types.h"
|
||||||
#include "util/mach/mach_extensions.h"
|
#include "util/mach/mach_extensions.h"
|
||||||
@ -322,6 +325,31 @@
|
|||||||
isEqualToString:@"same-name 3"]);
|
isEqualToString:@"same-name 3"]);
|
||||||
XCTAssertTrue([[dict[@"objects"][2] valueForKeyPath:@"#TEST# one"]
|
XCTAssertTrue([[dict[@"objects"][2] valueForKeyPath:@"#TEST# one"]
|
||||||
isEqualToString:@"moocow"]);
|
isEqualToString:@"moocow"]);
|
||||||
|
// Ensure `ring_buffer` is present but not `busy_ring_buffer`.
|
||||||
|
XCTAssertEqual(1u, [dict[@"ringbuffers"] count]);
|
||||||
|
NSData* ringBufferNSData =
|
||||||
|
[dict[@"ringbuffers"][0] valueForKeyPath:@"#TEST# ring_buffer"];
|
||||||
|
crashpad::RingBufferData ringBufferData;
|
||||||
|
XCTAssertTrue(ringBufferData.DeserializeFromBuffer(ringBufferNSData.bytes,
|
||||||
|
ringBufferNSData.length));
|
||||||
|
crashpad::LengthDelimitedRingBufferReader reader(ringBufferData);
|
||||||
|
|
||||||
|
std::vector<uint8_t> ringBufferEntry;
|
||||||
|
XCTAssertTrue(reader.Pop(ringBufferEntry));
|
||||||
|
NSString* firstEntry = [[NSString alloc] initWithBytes:ringBufferEntry.data()
|
||||||
|
length:ringBufferEntry.size()
|
||||||
|
encoding:NSUTF8StringEncoding];
|
||||||
|
XCTAssertEqualObjects(firstEntry, @"hello");
|
||||||
|
ringBufferEntry.clear();
|
||||||
|
|
||||||
|
XCTAssertTrue(reader.Pop(ringBufferEntry));
|
||||||
|
NSString* secondEntry = [[NSString alloc] initWithBytes:ringBufferEntry.data()
|
||||||
|
length:ringBufferEntry.size()
|
||||||
|
encoding:NSUTF8StringEncoding];
|
||||||
|
XCTAssertEqualObjects(secondEntry, @"goodbye");
|
||||||
|
ringBufferEntry.clear();
|
||||||
|
|
||||||
|
XCTAssertFalse(reader.Pop(ringBufferEntry));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDumpWithoutCrash {
|
- (void)testDumpWithoutCrash {
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include "client/crash_report_database.h"
|
#include "client/crash_report_database.h"
|
||||||
#include "client/crashpad_client.h"
|
#include "client/crashpad_client.h"
|
||||||
#include "client/crashpad_info.h"
|
#include "client/crashpad_info.h"
|
||||||
|
#include "client/ring_buffer_annotation.h"
|
||||||
#include "client/simple_string_dictionary.h"
|
#include "client/simple_string_dictionary.h"
|
||||||
#include "client/simulate_crash.h"
|
#include "client/simulate_crash.h"
|
||||||
#include "snapshot/minidump/process_snapshot_minidump.h"
|
#include "snapshot/minidump/process_snapshot_minidump.h"
|
||||||
@ -58,6 +59,9 @@ using Report = crashpad::CrashReportDatabase::Report;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr crashpad::Annotation::Type kRingBufferType =
|
||||||
|
crashpad::Annotation::UserDefinedType(42);
|
||||||
|
|
||||||
base::FilePath GetDatabaseDir() {
|
base::FilePath GetDatabaseDir() {
|
||||||
base::FilePath database_dir([NSFileManager.defaultManager
|
base::FilePath database_dir([NSFileManager.defaultManager
|
||||||
URLsForDirectory:NSDocumentDirectory
|
URLsForDirectory:NSDocumentDirectory
|
||||||
@ -251,7 +255,8 @@ UIWindow* GetAnyWindow() {
|
|||||||
NSDictionary* dict = @{
|
NSDictionary* dict = @{
|
||||||
@"simplemap" : [@{} mutableCopy],
|
@"simplemap" : [@{} mutableCopy],
|
||||||
@"vector" : [@[] mutableCopy],
|
@"vector" : [@[] mutableCopy],
|
||||||
@"objects" : [@[] mutableCopy]
|
@"objects" : [@[] mutableCopy],
|
||||||
|
@"ringbuffers" : [@[] mutableCopy],
|
||||||
};
|
};
|
||||||
for (const auto* module : process_snapshot->Modules()) {
|
for (const auto* module : process_snapshot->Modules()) {
|
||||||
for (const auto& kv : module->AnnotationsSimpleMap()) {
|
for (const auto& kv : module->AnnotationsSimpleMap()) {
|
||||||
@ -262,14 +267,18 @@ UIWindow* GetAnyWindow() {
|
|||||||
[dict[@"vector"] addObject:@(annotation.c_str())];
|
[dict[@"vector"] addObject:@(annotation.c_str())];
|
||||||
}
|
}
|
||||||
for (const auto& annotation : module->AnnotationObjects()) {
|
for (const auto& annotation : module->AnnotationObjects()) {
|
||||||
if (annotation.type !=
|
if (annotation.type ==
|
||||||
static_cast<uint16_t>(crashpad::Annotation::Type::kString)) {
|
static_cast<uint16_t>(crashpad::Annotation::Type::kString)) {
|
||||||
continue;
|
std::string value(
|
||||||
}
|
reinterpret_cast<const char*>(annotation.value.data()),
|
||||||
std::string value(reinterpret_cast<const char*>(annotation.value.data()),
|
|
||||||
annotation.value.size());
|
annotation.value.size());
|
||||||
[dict[@"objects"]
|
[dict[@"objects"]
|
||||||
addObject:@{@(annotation.name.c_str()) : @(value.c_str())}];
|
addObject:@{@(annotation.name.c_str()) : @(value.c_str())}];
|
||||||
|
} else if (annotation.type == static_cast<uint16_t>(kRingBufferType)) {
|
||||||
|
NSData* data = [NSData dataWithBytes:annotation.value.data()
|
||||||
|
length:annotation.value.size()];
|
||||||
|
[dict[@"ringbuffers"] addObject:@{@(annotation.name.c_str()) : data}];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [dict passByValue];
|
return [dict passByValue];
|
||||||
@ -418,12 +427,23 @@ UIWindow* GetAnyWindow() {
|
|||||||
"#TEST# same-name"};
|
"#TEST# same-name"};
|
||||||
static crashpad::StringAnnotation<32> test_annotation_four{
|
static crashpad::StringAnnotation<32> test_annotation_four{
|
||||||
"#TEST# same-name"};
|
"#TEST# same-name"};
|
||||||
|
static crashpad::RingBufferAnnotation<32> test_ring_buffer_annotation(
|
||||||
|
kRingBufferType, "#TEST# ring_buffer");
|
||||||
|
static crashpad::RingBufferAnnotation<32> test_busy_ring_buffer_annotation(
|
||||||
|
kRingBufferType, "#TEST# busy_ring_buffer");
|
||||||
|
|
||||||
test_annotation_one.Set("moocow");
|
test_annotation_one.Set("moocow");
|
||||||
test_annotation_two.Set("this will be cleared");
|
test_annotation_two.Set("this will be cleared");
|
||||||
test_annotation_three.Set("same-name 3");
|
test_annotation_three.Set("same-name 3");
|
||||||
test_annotation_four.Set("same-name 4");
|
test_annotation_four.Set("same-name 4");
|
||||||
test_annotation_two.Clear();
|
test_annotation_two.Clear();
|
||||||
|
test_ring_buffer_annotation.Push("hello", 5);
|
||||||
|
test_ring_buffer_annotation.Push("goodbye", 7);
|
||||||
|
test_busy_ring_buffer_annotation.Push("busy", 4);
|
||||||
|
// Take the scoped spin guard on `test_busy_ring_buffer_annotation` to mimic
|
||||||
|
// an in-flight `Push()` so its contents are not included in the dump.
|
||||||
|
auto guard = test_busy_ring_buffer_annotation.TryCreateScopedSpinGuard(
|
||||||
|
/*timeout_nanos=*/0);
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user