ios: Migrate ios/snapshot to writing intermediate dumps.
This migrates all the logic that used to live in ios/snapshots that
gathers all the various information during an exception.
Everything in InProcessIntermediateDumpHandler is considered
`RUNS-DURING-CRASH`.
Change-Id: I83bf04c946d410a83f55cd895b2bc45a6eb9c4d6
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2920547
Commit-Queue: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
GitOrigin-RevId: 40cd1b72cfd8a6c56092ba5f4840e625fa66bc24
diff --git a/client/BUILD.gn b/client/BUILD.gn
index aa1eaaa..5c18c6b 100644
--- a/client/BUILD.gn
+++ b/client/BUILD.gn
@@ -35,6 +35,8 @@
"crashpad_client_ios.cc",
"ios_handler/exception_processor.h",
"ios_handler/exception_processor.mm",
+ "ios_handler/in_process_intermediate_dump_handler.cc",
+ "ios_handler/in_process_intermediate_dump_handler.h",
"simulate_crash_ios.h",
]
}
@@ -165,13 +167,7 @@
sources += [
"crashpad_client_ios_test.mm",
"ios_handler/exception_processor_test.mm",
- ]
- sources -= [
- "annotation_list_test.cc",
- "annotation_test.cc",
- "crash_report_database_test.cc",
- "prune_crash_reports_test.cc",
- "settings_test.cc",
+ "ios_handler/in_process_intermediate_dump_handler_test.cc",
]
}
@@ -181,12 +177,12 @@
deps = [
":client",
+ "$mini_chromium_source_parent:base",
"../compat",
"../snapshot",
"../test",
"../third_party/googletest:googlemock",
"../third_party/googletest:googletest",
- "$mini_chromium_source_parent:base",
"../util",
]
diff --git a/client/annotation.h b/client/annotation.h
index ddfe8f6..edb963c 100644
--- a/client/annotation.h
+++ b/client/annotation.h
@@ -29,7 +29,11 @@
#include "build/build_config.h"
namespace crashpad {
-
+#if defined(OS_IOS)
+namespace internal {
+class InProcessIntermediateDumpHandler;
+} // namespace internal
+#endif
class AnnotationList;
//! \brief Base class for an annotation, which records a name-value pair of
@@ -167,6 +171,9 @@
protected:
friend class AnnotationList;
+#if defined(OS_IOS)
+ friend class internal::InProcessIntermediateDumpHandler;
+#endif
std::atomic<Annotation*>& link_node() { return link_node_; }
diff --git a/client/annotation_list.h b/client/annotation_list.h
index 9485c46..0b80768 100644
--- a/client/annotation_list.h
+++ b/client/annotation_list.h
@@ -16,9 +16,15 @@
#define CRASHPAD_CLIENT_ANNOTATION_LIST_H_
#include "base/macros.h"
+#include "build/build_config.h"
#include "client/annotation.h"
namespace crashpad {
+#if defined(OS_IOS)
+namespace internal {
+class InProcessIntermediateDumpHandler;
+} // namespace internal
+#endif
//! \brief A list that contains all the currently set annotations.
//!
@@ -77,6 +83,17 @@
//! \brief Returns an iterator past the last element of the annotation list.
Iterator end();
+ protected:
+#if defined(OS_IOS)
+ friend class internal::InProcessIntermediateDumpHandler;
+#endif
+
+ //! \brief Returns a pointer to the tail node.
+ const Annotation* tail_pointer() const { return tail_pointer_; }
+
+ //! \brief Returns a pointer to the head element.
+ const Annotation* head() const { return &head_; }
+
private:
// To make it easier for the handler to locate the dummy tail node, store the
// pointer. Placed first for packing.
diff --git a/client/crashpad_info.h b/client/crashpad_info.h
index ed7b9c1..62efc10 100644
--- a/client/crashpad_info.h
+++ b/client/crashpad_info.h
@@ -32,6 +32,10 @@
namespace internal {
+#if defined(OS_IOS)
+class InProcessIntermediateDumpHandler;
+#endif
+
//! \brief A linked list of blocks representing custom streams in the minidump,
//! with addresses (and size) stored as uint64_t to simplify reading from
//! the handler process.
@@ -223,6 +227,15 @@
kSignature = 'CPad',
};
+ protected:
+#if defined(OS_IOS)
+ friend class internal::InProcessIntermediateDumpHandler;
+#endif
+
+ uint32_t signature() const { return signature_; }
+ uint32_t version() const { return version_; }
+ uint32_t size() const { return size_; }
+
private:
// The compiler won’t necessarily see anyone using these fields, but it
// shouldn’t warn about that. These fields aren’t intended for use by the
diff --git a/client/ios_handler/in_process_intermediate_dump_handler.cc b/client/ios_handler/in_process_intermediate_dump_handler.cc
new file mode 100644
index 0000000..43552a7
--- /dev/null
+++ b/client/ios_handler/in_process_intermediate_dump_handler.cc
@@ -0,0 +1,1258 @@
+// Copyright 2021 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "client/ios_handler/in_process_intermediate_dump_handler.h"
+
+#include <mach-o/dyld_images.h>
+#include <mach-o/nlist.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/sysctl.h>
+#include <time.h>
+
+#include "base/cxx17_backports.h"
+#include "build/build_config.h"
+#include "snapshot/snapshot_constants.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+#include "util/ios/raw_logging.h"
+#include "util/ios/scoped_vm_read.h"
+
+namespace crashpad {
+namespace internal {
+
+namespace {
+
+#if defined(ARCH_CPU_X86_64)
+const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64;
+const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64;
+const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64;
+using thread_state_type = x86_thread_state64_t;
+#elif defined(ARCH_CPU_ARM64)
+const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64;
+const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64;
+const thread_state_flavor_t kDebugStateFlavor = ARM_DEBUG_STATE64;
+using thread_state_type = arm_thread_state64_t;
+#endif
+
+// From snapshot/mac/process_types/crashreporterclient.proctype
+struct crashreporter_annotations_t {
+ uint64_t version;
+ uint64_t message;
+ uint64_t signature_string;
+ uint64_t backtrace;
+ uint64_t message2;
+ uint64_t thread;
+ uint64_t dialog_mode;
+ uint64_t abort_cause;
+};
+
+//! \brief Manage memory and ports after calling `task_threads`.
+class ScopedTaskThreads {
+ public:
+ explicit ScopedTaskThreads(thread_act_array_t threads,
+ mach_msg_type_number_t thread_count)
+ : threads_(threads), thread_count_(thread_count) {}
+
+ ~ScopedTaskThreads() {
+ for (uint32_t thread_index = 0; thread_index < thread_count_;
+ ++thread_index) {
+ mach_port_deallocate(mach_task_self(), threads_[thread_index]);
+ }
+ vm_deallocate(mach_task_self(),
+ reinterpret_cast<vm_address_t>(threads_),
+ sizeof(thread_t) * thread_count_);
+ }
+
+ private:
+ thread_act_array_t threads_;
+ mach_msg_type_number_t thread_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTaskThreads);
+};
+
+//! \brief Log \a key as a string.
+void WriteError(IntermediateDumpKey key) {
+ CRASHPAD_RAW_LOG("Unable to write key");
+ switch (key) {
+// clang-format off
+#define CASE_KEY(Name, Value) \
+ case IntermediateDumpKey::Name: \
+ CRASHPAD_RAW_LOG(#Name); \
+ break;
+ INTERMEDIATE_DUMP_KEYS(CASE_KEY)
+#undef CASE_KEY
+ // clang-format on
+ }
+}
+
+//! \brief Call AddProperty with raw error log.
+//!
+//! \param[in] writer The dump writer
+//! \param[in] key The key to write.
+//! \param[in] value Memory to be written.
+//! \param[in] count Length of \a value.
+template <typename T>
+void WriteProperty(IOSIntermediateDumpWriter* writer,
+ IntermediateDumpKey key,
+ const T* value,
+ size_t count = 1) {
+ if (!writer->AddProperty(key, value, count))
+ WriteError(key);
+}
+
+//! \brief Call AddPropertyBytes with raw error log.
+//!
+//! \param[in] writer The dump writer
+//! \param[in] key The key to write.
+//! \param[in] value Memory to be written.
+//! \param[in] count Length of \a data.
+void WritePropertyBytes(IOSIntermediateDumpWriter* writer,
+ IntermediateDumpKey key,
+ const void* value,
+ size_t value_length) {
+ if (!writer->AddPropertyBytes(key, value, value_length))
+ WriteError(key);
+}
+
+kern_return_t MachVMRegionRecurseDeepest(task_t task,
+ vm_address_t* address,
+ vm_size_t* size,
+ natural_t* depth,
+ vm_prot_t* protection,
+ unsigned int* user_tag) {
+ vm_region_submap_short_info_64 submap_info;
+ mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
+ while (true) {
+ // Note: vm_region_recurse() would be fine here, but it does not provide
+ // VM_REGION_SUBMAP_SHORT_INFO_COUNT.
+ kern_return_t kr = vm_region_recurse_64(
+ task,
+ address,
+ size,
+ depth,
+ reinterpret_cast<vm_region_recurse_info_t>(&submap_info),
+ &count);
+ if (kr != KERN_SUCCESS) {
+ CRASHPAD_RAW_LOG_ERROR(kr, "vm_region_recurse_64");
+ return kr;
+ }
+
+ if (!submap_info.is_submap) {
+ *protection = submap_info.protection;
+ *user_tag = submap_info.user_tag;
+ return KERN_SUCCESS;
+ }
+
+ ++*depth;
+ }
+}
+
+//! \brief Adjusts the region for the red zone, if the ABI requires one.
+//!
+//! This method performs red zone calculation for CalculateStackRegion(). Its
+//! parameters are local variables used within that method, and may be
+//! modified as needed.
+//!
+//! Where a red zone is required, the region of memory captured for a thread’s
+//! stack will be extended to include the red zone below the stack pointer,
+//! provided that such memory is mapped, readable, and has the correct user
+//! tag value. If these conditions cannot be met fully, as much of the red
+//! zone will be captured as is possible while meeting these conditions.
+//!
+//! \param[in,out] start_address The base address of the region to begin
+//! capturing stack memory from. On entry, \a start_address is the stack
+//! pointer. On return, \a start_address may be decreased to encompass a
+//! red zone.
+//! \param[in,out] region_base The base address of the region that contains
+//! stack memory. This is distinct from \a start_address in that \a
+//! region_base will be page-aligned. On entry, \a region_base is the
+//! base address of a region that contains \a start_address. On return,
+//! if \a start_address is decremented and is outside of the region
+//! originally described by \a region_base, \a region_base will also be
+//! decremented appropriately.
+//! \param[in,out] region_size The size of the region that contains stack
+//! memory. This region begins at \a region_base. On return, if \a
+//! region_base is decremented, \a region_size will be incremented
+//! appropriately.
+//! \param[in] user_tag The Mach VM system’s user tag for the region described
+//! by the initial values of \a region_base and \a region_size. The red
+//! zone will only be allowed to extend out of the region described by
+//! these initial values if the user tag is appropriate for stack memory
+//! and the expanded region has the same user tag value.
+void LocateRedZone(vm_address_t* const start_address,
+ vm_address_t* const region_base,
+ vm_address_t* const region_size,
+ const unsigned int user_tag) {
+ // x86_64 has a red zone. See AMD64 ABI 0.99.8,
+ // https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/uploads/01de35b2c8adc7545de52604cc45d942/x86-64-psABI-2021-05-20.pdf#page=23.
+ // section 3.2.2, “The Stack Frame”.
+ // So does ARM64,
+ // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-Stacks-Red-Zone
+ // section "Respect the Stack’s Red Zone".
+ constexpr vm_size_t kRedZoneSize = 128;
+ vm_address_t red_zone_base =
+ *start_address >= kRedZoneSize ? *start_address - kRedZoneSize : 0;
+ bool red_zone_ok = false;
+ if (red_zone_base >= *region_base) {
+ // The red zone is within the region already discovered.
+ red_zone_ok = true;
+ } else if (red_zone_base < *region_base && user_tag == VM_MEMORY_STACK) {
+ // Probe to see if there’s a region immediately below the one already
+ // discovered.
+ vm_address_t red_zone_region_base = red_zone_base;
+ vm_size_t red_zone_region_size;
+ natural_t red_zone_depth = 0;
+ vm_prot_t red_zone_protection;
+ unsigned int red_zone_user_tag;
+ kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(),
+ &red_zone_region_base,
+ &red_zone_region_size,
+ &red_zone_depth,
+ &red_zone_protection,
+ &red_zone_user_tag);
+ if (kr != KERN_SUCCESS) {
+ *start_address = *region_base;
+ } else if (red_zone_region_base + red_zone_region_size == *region_base &&
+ (red_zone_protection & VM_PROT_READ) != 0 &&
+ red_zone_user_tag == user_tag) {
+ // The region containing the red zone is immediately below the region
+ // already found, it’s readable (not the guard region), and it has the
+ // same user tag as the region already found, so merge them.
+ red_zone_ok = true;
+ *region_base -= red_zone_region_size;
+ *region_size += red_zone_region_size;
+ }
+ }
+
+ if (red_zone_ok) {
+ // Begin capturing from the base of the red zone (but not the entire
+ // region that encompasses the red zone).
+ *start_address = red_zone_base;
+ } else {
+ // The red zone would go lower into another region in memory, but no
+ // region was found. Memory can only be captured to an address as low as
+ // the base address of the region already found.
+ *start_address = *region_base;
+ }
+}
+
+//! \brief Calculates the base address and size of the region used as a
+//! thread’s stack.
+//!
+//! The region returned by this method may be formed by merging multiple
+//! adjacent regions in a process’ memory map if appropriate. The base address
+//! of the returned region may be lower than the \a stack_pointer passed in
+//! when the ABI mandates a red zone below the stack pointer.
+//!
+//! \param[in] stack_pointer The stack pointer, referring to the top (lowest
+//! address) of a thread’s stack.
+//! \param[out] stack_region_size The size of the memory region used as the
+//! thread’s stack.
+//!
+//! \return The base address (lowest address) of the memory region used as the
+//! thread’s stack.
+vm_address_t CalculateStackRegion(vm_address_t stack_pointer,
+ vm_size_t* stack_region_size) {
+ // For pthreads, it may be possible to compute the stack region based on the
+ // internal _pthread::stackaddr and _pthread::stacksize. The _pthread struct
+ // for a thread can be located at TSD slot 0, or the known offsets of
+ // stackaddr and stacksize from the TSD area could be used.
+ vm_address_t region_base = stack_pointer;
+ vm_size_t region_size;
+ natural_t depth = 0;
+ vm_prot_t protection;
+ unsigned int user_tag;
+ kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(),
+ ®ion_base,
+ ®ion_size,
+ &depth,
+ &protection,
+ &user_tag);
+ if (kr != KERN_SUCCESS) {
+ CRASHPAD_RAW_LOG_ERROR(kr, "MachVMRegionRecurseDeepest");
+ *stack_region_size = 0;
+ return 0;
+ }
+
+ if (region_base > stack_pointer) {
+ // There’s nothing mapped at the stack pointer’s address. Something may have
+ // trashed the stack pointer. Note that this shouldn’t happen for a normal
+ // stack guard region violation because the guard region is mapped but has
+ // VM_PROT_NONE protection.
+ *stack_region_size = 0;
+ return 0;
+ }
+
+ vm_address_t start_address = stack_pointer;
+
+ if ((protection & VM_PROT_READ) == 0) {
+ // If the region isn’t readable, the stack pointer probably points to the
+ // guard region. Don’t include it as part of the stack, and don’t include
+ // anything at any lower memory address. The code below may still possibly
+ // find the real stack region at a memory address higher than this region.
+ start_address = region_base + region_size;
+ } else {
+ // If the ABI requires a red zone, adjust the region to include it if
+ // possible.
+ LocateRedZone(&start_address, ®ion_base, ®ion_size, user_tag);
+
+ // Regardless of whether the ABI requires a red zone, capture up to
+ // kExtraCaptureSize additional bytes of stack, but only if present in the
+ // region that was already found.
+ constexpr vm_size_t kExtraCaptureSize = 128;
+ start_address = std::max(start_address >= kExtraCaptureSize
+ ? start_address - kExtraCaptureSize
+ : start_address,
+ region_base);
+
+ // Align start_address to a 16-byte boundary, which can help readers by
+ // ensuring that data is aligned properly. This could page-align instead,
+ // but that might be wasteful.
+ constexpr vm_size_t kDesiredAlignment = 16;
+ start_address &= ~(kDesiredAlignment - 1);
+ DCHECK_GE(start_address, region_base);
+ }
+
+ region_size -= (start_address - region_base);
+ region_base = start_address;
+
+ vm_size_t total_region_size = region_size;
+
+ // The stack region may have gotten split up into multiple abutting regions.
+ // Try to coalesce them. This frequently happens for the main thread’s stack
+ // when setrlimit(RLIMIT_STACK, …) is called. It may also happen if a region
+ // is split up due to an mprotect() or vm_protect() call.
+ //
+ // Stack regions created by the kernel and the pthreads library will be marked
+ // with the VM_MEMORY_STACK user tag. Scanning for multiple adjacent regions
+ // with the same tag should find an entire stack region. Checking that the
+ // protection on individual regions is not VM_PROT_NONE should guarantee that
+ // this algorithm doesn’t collect map entries belonging to another thread’s
+ // stack: well-behaved stacks (such as those created by the kernel and the
+ // pthreads library) have VM_PROT_NONE guard regions at their low-address
+ // ends.
+ //
+ // Other stack regions may not be so well-behaved and thus if user_tag is not
+ // VM_MEMORY_STACK, the single region that was found is used as-is without
+ // trying to merge it with other adjacent regions.
+ if (user_tag == VM_MEMORY_STACK) {
+ vm_address_t try_address = region_base;
+ vm_address_t original_try_address;
+
+ while (try_address += region_size,
+ original_try_address = try_address,
+ (kr = MachVMRegionRecurseDeepest(mach_task_self(),
+ &try_address,
+ ®ion_size,
+ &depth,
+ &protection,
+ &user_tag) == KERN_SUCCESS) &&
+ try_address == original_try_address &&
+ (protection & VM_PROT_READ) != 0 &&
+ user_tag == VM_MEMORY_STACK) {
+ total_region_size += region_size;
+ }
+
+ if (kr != KERN_SUCCESS && kr != KERN_INVALID_ADDRESS) {
+ // Tolerate KERN_INVALID_ADDRESS because it will be returned when there
+ // are no more regions in the map at or above the specified |try_address|.
+ CRASHPAD_RAW_LOG_ERROR(kr, "MachVMRegionRecurseDeepest");
+ }
+ }
+
+ *stack_region_size = total_region_size;
+ return region_base;
+}
+
+//! \brief Write data around \a address to intermediate dump. Must be called
+//! from within a ScopedArray.
+void MaybeCaptureMemoryAround(IOSIntermediateDumpWriter* writer,
+ uint64_t address) {
+ constexpr uint64_t non_address_offset = 0x10000;
+ if (address < non_address_offset)
+ return;
+
+ constexpr uint64_t max_address = std::numeric_limits<uint64_t>::max();
+
+ if (address > max_address - non_address_offset)
+ return;
+
+ constexpr uint64_t kRegisterByteOffset = 128;
+ const uint64_t target = address - kRegisterByteOffset;
+ constexpr uint64_t size = 512;
+ static_assert(kRegisterByteOffset <= size / 2, "negative offset too large");
+
+ IOSIntermediateDumpWriter::ScopedArrayMap memory_region(writer);
+ WriteProperty(
+ writer, IntermediateDumpKey::kThreadContextMemoryRegionAddress, &address);
+ // Don't use WritePropertyBytes, this one will fail regularly if |target|
+ // cannot be read.
+ writer->AddPropertyBytes(IntermediateDumpKey::kThreadContextMemoryRegionData,
+ reinterpret_cast<const void*>(target),
+ size);
+}
+
+void CaptureMemoryPointedToByThreadState(IOSIntermediateDumpWriter* writer,
+ thread_state_type thread_state) {
+ IOSIntermediateDumpWriter::ScopedArray memory_regions(
+ writer, IntermediateDumpKey::kThreadContextMemoryRegions);
+
+#if defined(ARCH_CPU_X86_64)
+ MaybeCaptureMemoryAround(writer, thread_state.__rax);
+ MaybeCaptureMemoryAround(writer, thread_state.__rbx);
+ MaybeCaptureMemoryAround(writer, thread_state.__rcx);
+ MaybeCaptureMemoryAround(writer, thread_state.__rdx);
+ MaybeCaptureMemoryAround(writer, thread_state.__rdi);
+ MaybeCaptureMemoryAround(writer, thread_state.__rsi);
+ MaybeCaptureMemoryAround(writer, thread_state.__rbp);
+ MaybeCaptureMemoryAround(writer, thread_state.__r8);
+ MaybeCaptureMemoryAround(writer, thread_state.__r9);
+ MaybeCaptureMemoryAround(writer, thread_state.__r10);
+ MaybeCaptureMemoryAround(writer, thread_state.__r11);
+ MaybeCaptureMemoryAround(writer, thread_state.__r12);
+ MaybeCaptureMemoryAround(writer, thread_state.__r13);
+ MaybeCaptureMemoryAround(writer, thread_state.__r14);
+ MaybeCaptureMemoryAround(writer, thread_state.__r15);
+ MaybeCaptureMemoryAround(writer, thread_state.__rip);
+#elif defined(ARCH_CPU_ARM_FAMILY)
+ MaybeCaptureMemoryAround(writer, thread_state.__pc);
+ for (size_t i = 0; i < base::size(thread_state.__x); ++i) {
+ MaybeCaptureMemoryAround(writer, thread_state.__x[i]);
+ }
+#endif
+}
+
+void WriteCrashpadSimpleAnnotationsDictionary(IOSIntermediateDumpWriter* writer,
+ CrashpadInfo* crashpad_info) {
+ if (!crashpad_info->simple_annotations())
+ return;
+
+ ScopedVMRead<SimpleStringDictionary> simple_annotations;
+ if (!simple_annotations.Read(crashpad_info->simple_annotations())) {
+ CRASHPAD_RAW_LOG("Unable to read simple annotations.");
+ return;
+ }
+
+ const size_t count = simple_annotations->GetCount();
+ if (!count)
+ return;
+
+ IOSIntermediateDumpWriter::ScopedArray annotations_array(
+ writer, IntermediateDumpKey::kAnnotationsSimpleMap);
+
+ SimpleStringDictionary::Entry* entries =
+ reinterpret_cast<SimpleStringDictionary::Entry*>(
+ simple_annotations.get());
+ for (size_t index = 0; index < count; index++) {
+ IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
+ const auto& entry = entries[index];
+ size_t key_length = strnlen(entry.key, sizeof(entry.key));
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kAnnotationName,
+ reinterpret_cast<const void*>(entry.key),
+ key_length);
+ size_t value_length = strnlen(entry.value, sizeof(entry.value));
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kAnnotationValue,
+ reinterpret_cast<const void*>(entry.value),
+ value_length);
+ }
+}
+
+void WriteAppleCrashReporterAnnotations(
+ IOSIntermediateDumpWriter* writer,
+ crashreporter_annotations_t* crash_info) {
+ // This number was totally made up out of nowhere, but it seems prudent to
+ // enforce some limit.
+ constexpr size_t kMaxMessageSize = 1024;
+ IOSIntermediateDumpWriter::ScopedMap annotation_map(
+ writer, IntermediateDumpKey::kAnnotationsCrashInfo);
+ if (crash_info->message) {
+ const size_t message_len = strnlen(
+ reinterpret_cast<const char*>(crash_info->message), kMaxMessageSize);
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kAnnotationsCrashInfoMessage1,
+ reinterpret_cast<const void*>(crash_info->message),
+ message_len);
+ }
+ if (crash_info->message2) {
+ const size_t message_len = strnlen(
+ reinterpret_cast<const char*>(crash_info->message2), kMaxMessageSize);
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kAnnotationsCrashInfoMessage2,
+ reinterpret_cast<const void*>(crash_info->message2),
+ message_len);
+ }
+}
+
+void WriteDyldErrorStringAnnotation(
+ IOSIntermediateDumpWriter* writer,
+ const uint64_t address,
+ const symtab_command* symtab_command_ptr,
+ const dysymtab_command* dysymtab_command_ptr,
+ const segment_command_64* text_seg_ptr,
+ const segment_command_64* linkedit_seg_ptr,
+ vm_size_t slide) {
+ if (text_seg_ptr == nullptr || linkedit_seg_ptr == nullptr ||
+ symtab_command_ptr == nullptr) {
+ return;
+ }
+
+ ScopedVMRead<symtab_command> symtab_command;
+ ScopedVMRead<dysymtab_command> dysymtab_command;
+ ScopedVMRead<segment_command_64> text_seg;
+ ScopedVMRead<segment_command_64> linkedit_seg;
+ if (!symtab_command.Read(symtab_command_ptr) ||
+ !text_seg.Read(text_seg_ptr) || !linkedit_seg.Read(linkedit_seg_ptr) ||
+ (dysymtab_command_ptr && !dysymtab_command.Read(dysymtab_command_ptr))) {
+ CRASHPAD_RAW_LOG("Unable to load dyld symbol table.");
+ }
+
+ uint64_t file_slide =
+ (linkedit_seg->vmaddr - text_seg->vmaddr) - linkedit_seg->fileoff;
+ uint64_t strings = address + (symtab_command->stroff + file_slide);
+ nlist_64* symbol_ptr = reinterpret_cast<nlist_64*>(
+ address + (symtab_command->symoff + file_slide));
+
+ // If a dysymtab is present, use it to filter the symtab for just the
+ // portion used for extdefsym. If no dysymtab is present, the entire symtab
+ // will need to be consulted.
+ uint32_t symbol_count = symtab_command->nsyms;
+ if (dysymtab_command_ptr) {
+ symbol_ptr += dysymtab_command->iextdefsym;
+ symbol_count = dysymtab_command->nextdefsym;
+ }
+
+ for (uint32_t i = 0; i < symbol_count; i++, symbol_ptr++) {
+ ScopedVMRead<nlist_64> symbol;
+ if (!symbol.Read(symbol_ptr)) {
+ CRASHPAD_RAW_LOG("Unable to load dyld symbol table symbol.");
+ return;
+ }
+
+ if (!symbol->n_value)
+ continue;
+
+ ScopedVMRead<const char> symbol_name;
+ if (!symbol_name.Read(strings + symbol->n_un.n_strx)) {
+ CRASHPAD_RAW_LOG("Unable to load dyld symbol name.");
+ }
+
+ if (strcmp(symbol_name.get(), "_error_string") == 0) {
+ ScopedVMRead<const char> symbol_value;
+ if (!symbol_value.Read(symbol->n_value + slide)) {
+ CRASHPAD_RAW_LOG("Unable to load dyld symbol value.");
+ }
+ // 1024 here is distinct from kMaxMessageSize above, because it refers to
+ // a precisely-sized buffer inside dyld.
+ const size_t value_len = strnlen(symbol_value.get(), 1024);
+ if (value_len) {
+ WriteProperty(writer,
+ IntermediateDumpKey::kAnnotationsDyldErrorString,
+ symbol_value.get(),
+ value_len);
+ }
+ return;
+ }
+
+ continue;
+ }
+}
+
+} // namespace
+
+// static
+void InProcessIntermediateDumpHandler::WriteHeader(
+ IOSIntermediateDumpWriter* writer) {
+ static constexpr uint8_t version = 1;
+ WriteProperty(writer, IntermediateDumpKey::kVersion, &version);
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteProcessInfo(
+ IOSIntermediateDumpWriter* writer) {
+ IOSIntermediateDumpWriter::ScopedMap process_map(
+ writer, IntermediateDumpKey::kProcessInfo);
+
+ timeval snapshot_time;
+ if (gettimeofday(&snapshot_time, nullptr) == 0) {
+ WriteProperty(writer, IntermediateDumpKey::kSnapshotTime, &snapshot_time);
+ } else {
+ CRASHPAD_RAW_LOG("gettimeofday");
+ }
+
+ // Used by pid, parent pid and snapshot time.
+ kinfo_proc kern_proc_info;
+ int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
+ size_t len = sizeof(kern_proc_info);
+ if (sysctl(mib, base::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) {
+ WriteProperty(
+ writer, IntermediateDumpKey::kPID, &kern_proc_info.kp_proc.p_pid);
+ WriteProperty(writer,
+ IntermediateDumpKey::kParentPID,
+ &kern_proc_info.kp_eproc.e_ppid);
+ WriteProperty(writer,
+ IntermediateDumpKey::kStartTime,
+ &kern_proc_info.kp_proc.p_starttime);
+ } else {
+ CRASHPAD_RAW_LOG("sysctl kern_proc_info");
+ }
+
+ // Used by user time and system time.
+ mach_task_basic_info task_basic_info;
+ mach_msg_type_number_t task_basic_info_count = MACH_TASK_BASIC_INFO_COUNT;
+ kern_return_t kr = task_info(mach_task_self(),
+ MACH_TASK_BASIC_INFO,
+ reinterpret_cast<task_info_t>(&task_basic_info),
+ &task_basic_info_count);
+ if (kr == KERN_SUCCESS) {
+ IOSIntermediateDumpWriter::ScopedMap task_info(
+ writer, IntermediateDumpKey::kTaskBasicInfo);
+
+ WriteProperty(
+ writer, IntermediateDumpKey::kUserTime, &task_basic_info.user_time);
+ WriteProperty(
+ writer, IntermediateDumpKey::kSystemTime, &task_basic_info.system_time);
+ } else {
+ CRASHPAD_RAW_LOG("task_info task_basic_info");
+ }
+
+ task_thread_times_info_data_t task_thread_times;
+ mach_msg_type_number_t task_thread_times_count = TASK_THREAD_TIMES_INFO_COUNT;
+ kr = task_info(mach_task_self(),
+ TASK_THREAD_TIMES_INFO,
+ reinterpret_cast<task_info_t>(&task_thread_times),
+ &task_thread_times_count);
+ if (kr == KERN_SUCCESS) {
+ IOSIntermediateDumpWriter::ScopedMap task_thread_times_map(
+ writer, IntermediateDumpKey::kTaskThreadTimes);
+
+ WriteProperty(
+ writer, IntermediateDumpKey::kUserTime, &task_thread_times.user_time);
+ WriteProperty(writer,
+ IntermediateDumpKey::kSystemTime,
+ &task_thread_times.system_time);
+ } else {
+ CRASHPAD_RAW_LOG("task_info task_basic_info");
+ }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteSystemInfo(
+ IOSIntermediateDumpWriter* writer,
+ const IOSSystemDataCollector& system_data) {
+ IOSIntermediateDumpWriter::ScopedMap system_map(
+ writer, IntermediateDumpKey::kSystemInfo);
+
+ const std::string& machine_description = system_data.MachineDescription();
+ WriteProperty(writer,
+ IntermediateDumpKey::kMachineDescription,
+ machine_description.c_str(),
+ machine_description.length());
+ int os_version_major;
+ int os_version_minor;
+ int os_version_bugfix;
+ system_data.OSVersion(
+ &os_version_major, &os_version_minor, &os_version_bugfix);
+ WriteProperty(
+ writer, IntermediateDumpKey::kOSVersionMajor, &os_version_major);
+ WriteProperty(
+ writer, IntermediateDumpKey::kOSVersionMinor, &os_version_minor);
+ WriteProperty(
+ writer, IntermediateDumpKey::kOSVersionBugfix, &os_version_bugfix);
+ const std::string& os_version_build = system_data.Build();
+ WriteProperty(writer,
+ IntermediateDumpKey::kOSVersionBuild,
+ os_version_build.c_str(),
+ os_version_build.length());
+
+ int cpu_count = system_data.ProcessorCount();
+ WriteProperty(writer, IntermediateDumpKey::kCpuCount, &cpu_count);
+ const std::string& cpu_vendor = system_data.CPUVendor();
+ WriteProperty(writer,
+ IntermediateDumpKey::kCpuVendor,
+ cpu_vendor.c_str(),
+ cpu_vendor.length());
+
+ bool has_daylight_saving_time = system_data.HasDaylightSavingTime();
+ WriteProperty(writer,
+ IntermediateDumpKey::kHasDaylightSavingTime,
+ &has_daylight_saving_time);
+ bool is_daylight_saving_time = system_data.IsDaylightSavingTime();
+ WriteProperty(writer,
+ IntermediateDumpKey::kIsDaylightSavingTime,
+ &is_daylight_saving_time);
+ int standard_offset_seconds = system_data.StandardOffsetSeconds();
+ WriteProperty(writer,
+ IntermediateDumpKey::kStandardOffsetSeconds,
+ &standard_offset_seconds);
+ int daylight_offset_seconds = system_data.DaylightOffsetSeconds();
+ WriteProperty(writer,
+ IntermediateDumpKey::kDaylightOffsetSeconds,
+ &daylight_offset_seconds);
+ const std::string& standard_name = system_data.StandardName();
+ WriteProperty(writer,
+ IntermediateDumpKey::kStandardName,
+ standard_name.c_str(),
+ standard_name.length());
+ const std::string& daylight_name = system_data.DaylightName();
+ WriteProperty(writer,
+ IntermediateDumpKey::kDaylightName,
+ daylight_name.c_str(),
+ daylight_name.length());
+
+ vm_size_t page_size;
+ host_page_size(mach_host_self(), &page_size);
+ WriteProperty(writer, IntermediateDumpKey::kPageSize, &page_size);
+
+ mach_msg_type_number_t host_size =
+ sizeof(vm_statistics_data_t) / sizeof(integer_t);
+ vm_statistics_data_t vm_stat;
+ kern_return_t kr = host_statistics(mach_host_self(),
+ HOST_VM_INFO,
+ reinterpret_cast<host_info_t>(&vm_stat),
+ &host_size);
+ if (kr == KERN_SUCCESS) {
+ IOSIntermediateDumpWriter::ScopedMap vm_stat_map(
+ writer, IntermediateDumpKey::kVMStat);
+
+ WriteProperty(writer, IntermediateDumpKey::kActive, &vm_stat.active_count);
+ WriteProperty(
+ writer, IntermediateDumpKey::kInactive, &vm_stat.inactive_count);
+ WriteProperty(writer, IntermediateDumpKey::kWired, &vm_stat.wire_count);
+ WriteProperty(writer, IntermediateDumpKey::kFree, &vm_stat.free_count);
+ } else {
+ CRASHPAD_RAW_LOG("host_statistics");
+ }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteThreadInfo(
+ IOSIntermediateDumpWriter* writer,
+ const uint64_t* frames,
+ const size_t num_frames) {
+ IOSIntermediateDumpWriter::ScopedArray thread_array(
+ writer, IntermediateDumpKey::kThreads);
+
+ // Exception thread ID.
+ uint64_t exception_thread_id = 0;
+ thread_identifier_info identifier_info;
+ mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+ kern_return_t kr =
+ thread_info(mach_thread_self(),
+ THREAD_IDENTIFIER_INFO,
+ reinterpret_cast<thread_info_t>(&identifier_info),
+ &count);
+ if (kr == KERN_SUCCESS) {
+ exception_thread_id = identifier_info.thread_id;
+ } else {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_IDENTIFIER_INFO");
+ }
+
+ mach_msg_type_number_t thread_count = 0;
+ thread_act_array_t threads;
+ kr = task_threads(mach_task_self(), &threads, &thread_count);
+ if (kr != KERN_SUCCESS) {
+ CRASHPAD_RAW_LOG_ERROR(kr, "task_threads");
+ }
+ ScopedTaskThreads threads_vm_owner(threads, thread_count);
+
+ for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) {
+ IOSIntermediateDumpWriter::ScopedArrayMap thread_map(writer);
+ thread_t thread = threads[thread_index];
+
+ thread_basic_info basic_info;
+ mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
+ kr = thread_info(thread,
+ THREAD_BASIC_INFO,
+ reinterpret_cast<thread_info_t>(&basic_info),
+ &count);
+ if (kr == KERN_SUCCESS) {
+ WriteProperty(writer,
+ IntermediateDumpKey::kSuspendCount,
+ &basic_info.suspend_count);
+ } else {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_BASIC_INFO");
+ }
+
+ thread_precedence_policy precedence;
+ count = THREAD_PRECEDENCE_POLICY_COUNT;
+ boolean_t get_default = FALSE;
+ kr = thread_policy_get(thread,
+ THREAD_PRECEDENCE_POLICY,
+ reinterpret_cast<thread_policy_t>(&precedence),
+ &count,
+ &get_default);
+ if (kr == KERN_SUCCESS) {
+ WriteProperty(
+ writer, IntermediateDumpKey::kPriority, &precedence.importance);
+ } else {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_policy_get");
+ }
+
+ // Thread ID.
+ uint64_t thread_id;
+ thread_identifier_info identifier_info;
+ count = THREAD_IDENTIFIER_INFO_COUNT;
+ kr = thread_info(thread,
+ THREAD_IDENTIFIER_INFO,
+ reinterpret_cast<thread_info_t>(&identifier_info),
+ &count);
+ if (kr == KERN_SUCCESS) {
+ thread_id = identifier_info.thread_id;
+ WriteProperty(
+ writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+ WriteProperty(writer,
+ IntermediateDumpKey::kThreadDataAddress,
+ &identifier_info.thread_handle);
+ } else {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_IDENTIFIER_INFO");
+ }
+
+ // thread_snapshot_ios_intermediate_dump::GenerateStackMemoryFromFrames is
+ // only implemented for arm64, so no x86_64 block here.
+#if defined(ARCH_CPU_ARM64)
+ // For uncaught NSExceptions, use the frames passed from the system rather
+ // than the current thread state.
+ if (num_frames > 0 && exception_thread_id == thread_id) {
+ WriteProperty(writer,
+ IntermediateDumpKey::kThreadUncaughtNSExceptionFrames,
+ frames,
+ num_frames);
+ continue;
+ }
+#endif
+
+#if defined(ARCH_CPU_X86_64)
+ x86_thread_state64_t thread_state;
+ x86_float_state64_t float_state;
+ x86_debug_state64_t debug_state;
+ mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT;
+ mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT;
+ mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT;
+#elif defined(ARCH_CPU_ARM64)
+ arm_thread_state64_t thread_state;
+ arm_neon_state64_t float_state;
+ arm_debug_state64_t debug_state;
+ mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT;
+ mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT;
+ mach_msg_type_number_t debug_state_count = ARM_DEBUG_STATE64_COUNT;
+#endif
+
+ kern_return_t kr =
+ thread_get_state(thread,
+ kThreadStateFlavor,
+ reinterpret_cast<thread_state_t>(&thread_state),
+ &thread_state_count);
+ if (kr != KERN_SUCCESS) {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kThreadStateFlavor");
+ }
+ WriteProperty(writer, IntermediateDumpKey::kThreadState, &thread_state);
+
+ kr = thread_get_state(thread,
+ kFloatStateFlavor,
+ reinterpret_cast<thread_state_t>(&float_state),
+ &float_state_count);
+ if (kr != KERN_SUCCESS) {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kFloatStateFlavor");
+ }
+ WriteProperty(writer, IntermediateDumpKey::kFloatState, &float_state);
+
+ kr = thread_get_state(thread,
+ kDebugStateFlavor,
+ reinterpret_cast<thread_state_t>(&debug_state),
+ &debug_state_count);
+ if (kr != KERN_SUCCESS) {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_get_state::kDebugStateFlavor");
+ }
+ WriteProperty(writer, IntermediateDumpKey::kDebugState, &debug_state);
+
+#if defined(ARCH_CPU_X86_64)
+ vm_address_t stack_pointer = thread_state.__rsp;
+#elif defined(ARCH_CPU_ARM64)
+ vm_address_t stack_pointer = thread_state.__sp;
+#endif
+
+ vm_size_t stack_region_size;
+ const vm_address_t stack_region_address =
+ CalculateStackRegion(stack_pointer, &stack_region_size);
+ WriteProperty(writer,
+ IntermediateDumpKey::kStackRegionAddress,
+ &stack_region_address);
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kStackRegionData,
+ reinterpret_cast<const void*>(stack_region_address),
+ stack_region_size);
+
+ // Grab extra memory from context.
+ CaptureMemoryPointedToByThreadState(writer, thread_state);
+ }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteModuleInfo(
+ IOSIntermediateDumpWriter* writer) {
+#ifndef ARCH_CPU_64_BITS
+#error Only 64-bit Mach-O is supported
+#endif
+
+ IOSIntermediateDumpWriter::ScopedArray module_array(
+ writer, IntermediateDumpKey::kModules);
+
+ task_dyld_info_data_t dyld_info;
+ mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
+ kern_return_t kr = task_info(mach_task_self(),
+ TASK_DYLD_INFO,
+ reinterpret_cast<task_info_t>(&dyld_info),
+ &count);
+ if (kr != KERN_SUCCESS) {
+ CRASHPAD_RAW_LOG_ERROR(kr, "task_info");
+ }
+
+ ScopedVMRead<dyld_all_image_infos> image_infos;
+ if (!image_infos.Read(dyld_info.all_image_info_addr)) {
+ CRASHPAD_RAW_LOG("Unable to dyld_info.all_image_info_addr");
+ return;
+ }
+
+ uint32_t image_count = image_infos->infoArrayCount;
+ const dyld_image_info* image_array = image_infos->infoArray;
+ for (uint32_t image_index = 0; image_index < image_count; ++image_index) {
+ IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);
+ ScopedVMRead<dyld_image_info> image;
+ if (!image.Read(&image_array[image_index])) {
+ CRASHPAD_RAW_LOG("Unable to dyld_image_info");
+ return;
+ }
+
+ WriteProperty(writer,
+ IntermediateDumpKey::kName,
+ image->imageFilePath,
+ strlen(image->imageFilePath));
+ uint64_t address = FromPointerCast<uint64_t>(image->imageLoadAddress);
+ WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
+ WriteProperty(
+ writer, IntermediateDumpKey::kTimestamp, &image->imageFileModDate);
+ WriteModuleInfoAtAddress(writer, address, false /*is_dyld=false*/);
+ }
+
+ {
+ IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);
+ WriteProperty(writer, IntermediateDumpKey::kName, image_infos->dyldPath);
+ uint64_t address =
+ FromPointerCast<uint64_t>(image_infos->dyldImageLoadAddress);
+ WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
+ WriteModuleInfoAtAddress(writer, address, true /*is_dyld=true*/);
+ }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteExceptionFromSignal(
+ IOSIntermediateDumpWriter* writer,
+ const IOSSystemDataCollector& system_data,
+ siginfo_t* siginfo,
+ ucontext_t* context) {
+ IOSIntermediateDumpWriter::ScopedMap signal_exception_map(
+ writer, IntermediateDumpKey::kSignalException);
+
+ WriteProperty(writer, IntermediateDumpKey::kSignalNumber, &siginfo->si_signo);
+ WriteProperty(writer, IntermediateDumpKey::kSignalCode, &siginfo->si_code);
+ WriteProperty(writer, IntermediateDumpKey::kSignalAddress, &siginfo->si_addr);
+#if defined(ARCH_CPU_X86_64)
+ WriteProperty(
+ writer, IntermediateDumpKey::kThreadState, &context->uc_mcontext->__ss);
+ WriteProperty(
+ writer, IntermediateDumpKey::kFloatState, &context->uc_mcontext->__fs);
+#elif defined(ARCH_CPU_ARM64)
+ WriteProperty(
+ writer, IntermediateDumpKey::kThreadState, &context->uc_mcontext->__ss);
+ WriteProperty(
+ writer, IntermediateDumpKey::kFloatState, &context->uc_mcontext->__ns);
+#else
+#error Port to your CPU architecture
+#endif
+
+ // Thread ID.
+ thread_identifier_info identifier_info;
+ mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+ kern_return_t kr =
+ thread_info(mach_thread_self(),
+ THREAD_IDENTIFIER_INFO,
+ reinterpret_cast<thread_info_t>(&identifier_info),
+ &count);
+ if (kr == KERN_SUCCESS) {
+ WriteProperty(
+ writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+ } else {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::self");
+ }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
+ IOSIntermediateDumpWriter* writer,
+ exception_behavior_t behavior,
+ thread_t exception_thread,
+ exception_type_t exception,
+ const mach_exception_data_type_t* code,
+ mach_msg_type_number_t code_count,
+ thread_state_flavor_t flavor,
+ ConstThreadState state,
+ mach_msg_type_number_t state_count) {
+ IOSIntermediateDumpWriter::ScopedMap mach_exception_map(
+ writer, IntermediateDumpKey::kMachException);
+
+ WriteProperty(writer, IntermediateDumpKey::kException, &exception);
+ WriteProperty(writer, IntermediateDumpKey::kCodes, code, code_count);
+ WriteProperty(writer, IntermediateDumpKey::kFlavor, &flavor);
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kState,
+ state,
+ state_count * sizeof(uint32_t));
+
+ thread_identifier_info identifier_info;
+ mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+ kern_return_t kr =
+ thread_info(exception_thread,
+ THREAD_IDENTIFIER_INFO,
+ reinterpret_cast<thread_info_t>(&identifier_info),
+ &count);
+ if (kr == KERN_SUCCESS) {
+ WriteProperty(
+ writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+ } else {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_info");
+ }
+}
+
+// static
+void InProcessIntermediateDumpHandler::WriteExceptionFromNSException(
+ IOSIntermediateDumpWriter* writer) {
+ IOSIntermediateDumpWriter::ScopedMap nsexception_map(
+ writer, IntermediateDumpKey::kNSException);
+
+ thread_identifier_info identifier_info;
+ mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+ kern_return_t kr =
+ thread_info(mach_thread_self(),
+ THREAD_IDENTIFIER_INFO,
+ reinterpret_cast<thread_info_t>(&identifier_info),
+ &count);
+ if (kr == KERN_SUCCESS) {
+ WriteProperty(
+ writer, IntermediateDumpKey::kThreadID, &identifier_info.thread_id);
+ } else {
+ CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::self");
+ }
+}
+
+void InProcessIntermediateDumpHandler::WriteModuleInfoAtAddress(
+ IOSIntermediateDumpWriter* writer,
+ uint64_t address,
+ bool is_dyld) {
+ ScopedVMRead<mach_header_64> header;
+ if (!header.Read(address) || header->magic != MH_MAGIC_64) {
+ CRASHPAD_RAW_LOG("Invalid module header");
+ return;
+ }
+
+ const load_command* command_ptr = reinterpret_cast<const load_command*>(
+ reinterpret_cast<const mach_header_64*>(address) + 1);
+
+ ScopedVMRead<load_command> command;
+ if (!command.Read(command_ptr)) {
+ CRASHPAD_RAW_LOG("Invalid module command");
+ return;
+ }
+
+ // Make sure that the basic load command structure doesn’t overflow the
+ // space allotted for load commands, as well as iterating through ncmds.
+ vm_size_t slide = 0;
+ const symtab_command* symtab_command = nullptr;
+ const dysymtab_command* dysymtab_command = nullptr;
+ const segment_command_64* linkedit_seg = nullptr;
+ const segment_command_64* text_seg = nullptr;
+ for (uint32_t cmd_index = 0, cumulative_cmd_size = 0;
+ cmd_index <= header->ncmds && cumulative_cmd_size < header->sizeofcmds;
+ ++cmd_index, cumulative_cmd_size += command->cmdsize) {
+ if (command->cmd == LC_SEGMENT_64) {
+ ScopedVMRead<segment_command_64> segment;
+ if (!segment.Read(command_ptr)) {
+ CRASHPAD_RAW_LOG("Invalid LC_SEGMENT_64 segment");
+ return;
+ }
+ const segment_command_64* segment_ptr =
+ reinterpret_cast<const segment_command_64*>(command_ptr);
+ if (strcmp(segment->segname, SEG_TEXT) == 0) {
+ text_seg = segment_ptr;
+ WriteProperty(writer, IntermediateDumpKey::kSize, &segment->vmsize);
+ slide = address - segment->vmaddr;
+ } else if (strcmp(segment->segname, SEG_DATA) == 0) {
+ WriteDataSegmentAnnotations(writer, segment_ptr, slide);
+ } else if (strcmp(segment->segname, SEG_LINKEDIT) == 0) {
+ linkedit_seg = segment_ptr;
+ }
+ } else if (command->cmd == LC_SYMTAB) {
+ symtab_command =
+ reinterpret_cast<const struct symtab_command*>(command_ptr);
+ } else if (command->cmd == LC_DYSYMTAB) {
+ dysymtab_command =
+ reinterpret_cast<const struct dysymtab_command*>(command_ptr);
+ } else if (command->cmd == LC_ID_DYLIB) {
+ ScopedVMRead<dylib_command> dylib;
+ if (!dylib.Read(command_ptr)) {
+ CRASHPAD_RAW_LOG("Invalid LC_ID_DYLIB segment");
+ return;
+ }
+ WriteProperty(writer,
+ IntermediateDumpKey::kDylibCurrentVersion,
+ &dylib->dylib.current_version);
+ } else if (command->cmd == LC_SOURCE_VERSION) {
+ ScopedVMRead<source_version_command> source_version;
+ if (!source_version.Read(command_ptr)) {
+ CRASHPAD_RAW_LOG("Invalid LC_SOURCE_VERSION segment");
+ return;
+ }
+ WriteProperty(writer,
+ IntermediateDumpKey::kSourceVersion,
+ &source_version->version);
+ } else if (command->cmd == LC_UUID) {
+ ScopedVMRead<uuid_command> uuid;
+ if (!uuid.Read(command_ptr)) {
+ CRASHPAD_RAW_LOG("Invalid LC_UUID segment");
+ return;
+ }
+ WriteProperty(writer, IntermediateDumpKey::kUUID, &uuid->uuid);
+ }
+
+ command_ptr = reinterpret_cast<const load_command*>(
+ reinterpret_cast<const uint8_t*>(command_ptr) + command->cmdsize);
+ if (!command.Read(command_ptr)) {
+ CRASHPAD_RAW_LOG("Invalid module command");
+ return;
+ }
+ }
+
+ WriteProperty(writer, IntermediateDumpKey::kFileType, &header->filetype);
+
+ if (is_dyld && header->filetype == MH_DYLINKER) {
+ WriteDyldErrorStringAnnotation(writer,
+ address,
+ symtab_command,
+ dysymtab_command,
+ text_seg,
+ linkedit_seg,
+ slide);
+ }
+}
+
+void InProcessIntermediateDumpHandler::WriteDataSegmentAnnotations(
+ IOSIntermediateDumpWriter* writer,
+ const segment_command_64* segment_ptr,
+ vm_size_t slide) {
+ ScopedVMRead<segment_command_64> segment;
+ if (!segment.Read(segment_ptr)) {
+ CRASHPAD_RAW_LOG("Unable to read SEG_DATA.");
+ return;
+ }
+ const section_64* section_ptr = reinterpret_cast<const section_64*>(
+ reinterpret_cast<uint64_t>(segment_ptr) + sizeof(segment_command_64));
+ for (uint32_t sect_index = 0; sect_index <= segment->nsects; ++sect_index) {
+ ScopedVMRead<section_64> section;
+ if (!section.Read(section_ptr)) {
+ CRASHPAD_RAW_LOG("Unable to read SEG_DATA section.");
+ return;
+ }
+ if (strcmp(section->sectname, "crashpad_info") == 0) {
+ ScopedVMRead<CrashpadInfo> crashpad_info;
+ if (crashpad_info.Read(section->addr + slide) &&
+ crashpad_info->size() == sizeof(CrashpadInfo) &&
+ crashpad_info->signature() == CrashpadInfo::kSignature &&
+ crashpad_info->version() == 1) {
+ WriteCrashpadAnnotationsList(writer, crashpad_info.get());
+ WriteCrashpadSimpleAnnotationsDictionary(writer, crashpad_info.get());
+ }
+ } else if (strcmp(section->sectname, "__crash_info") == 0) {
+ ScopedVMRead<crashreporter_annotations_t> crash_info;
+ if (!crash_info.Read(section->addr + slide) ||
+ (crash_info->version != 4 && crash_info->version != 5)) {
+ continue;
+ }
+ WriteAppleCrashReporterAnnotations(writer, crash_info.get());
+ }
+ section_ptr = reinterpret_cast<const section_64*>(
+ reinterpret_cast<uint64_t>(section_ptr) + sizeof(section_64));
+ }
+}
+
+void InProcessIntermediateDumpHandler::WriteCrashpadAnnotationsList(
+ IOSIntermediateDumpWriter* writer,
+ CrashpadInfo* crashpad_info) {
+ if (!crashpad_info->annotations_list()) {
+ return;
+ }
+ ScopedVMRead<AnnotationList> annotation_list;
+ if (!annotation_list.Read(crashpad_info->annotations_list())) {
+ CRASHPAD_RAW_LOG("Unable to read annotations list object");
+ return;
+ }
+
+ IOSIntermediateDumpWriter::ScopedArray annotations_array(
+ writer, IntermediateDumpKey::kAnnotationObjects);
+ ScopedVMRead<Annotation> current;
+ if (!current.Read(annotation_list->head())) {
+ CRASHPAD_RAW_LOG("Unable to read annotation");
+ return;
+ }
+
+ for (size_t index = 0;
+ current->link_node() != annotation_list.get()->tail_pointer() &&
+ index < kMaxNumberOfAnnotations;
+ ++index) {
+ ScopedVMRead<Annotation> node;
+ if (!node.Read(current->link_node())) {
+ CRASHPAD_RAW_LOG("Unable to read annotation");
+ return;
+ }
+ current.Read(current->link_node());
+
+ if (node->size() == 0)
+ continue;
+
+ if (node->size() > Annotation::kValueMaxSize) {
+ CRASHPAD_RAW_LOG("Incorrect annotation length");
+ continue;
+ }
+
+ IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
+ const size_t name_len = strnlen(reinterpret_cast<const char*>(node->name()),
+ Annotation::kNameMaxLength);
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kAnnotationName,
+ reinterpret_cast<const void*>(node->name()),
+ name_len);
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kAnnotationValue,
+ reinterpret_cast<const void*>(node->value()),
+ node->size());
+ Annotation::Type type = node->type();
+ WritePropertyBytes(writer,
+ IntermediateDumpKey::kAnnotationType,
+ reinterpret_cast<const void*>(&type),
+ sizeof(type));
+ }
+}
+
+} // namespace internal
+} // namespace crashpad
diff --git a/client/ios_handler/in_process_intermediate_dump_handler.h b/client/ios_handler/in_process_intermediate_dump_handler.h
new file mode 100644
index 0000000..9a33ff4
--- /dev/null
+++ b/client/ios_handler/in_process_intermediate_dump_handler.h
@@ -0,0 +1,146 @@
+// Copyright 2021 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_
+#define CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_
+
+#include <mach-o/loader.h>
+#include <mach/mach.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#include "client/crashpad_info.h"
+#include "util/ios/ios_intermediate_dump_writer.h"
+#include "util/ios/ios_system_data_collector.h"
+#include "util/mach/mach_extensions.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Dump all in-process data to iOS intermediate dump.
+//! Note: All methods are `RUNS-DURING-CRASH`.
+class InProcessIntermediateDumpHandler final {
+ public:
+ //! \brief Set kVersion to 1.
+ //!
+ //! \param[in] writer The dump writer
+ static void WriteHeader(IOSIntermediateDumpWriter* writer);
+
+ //! \brief Write ProcessSnapshot data to the intermediate dump.
+ //!
+ //! \param[in] writer The dump writer
+ static void WriteProcessInfo(IOSIntermediateDumpWriter* writer);
+
+ //! \brief Write SystemSnapshot data to the intermediate dump.
+ //!
+ //! \param[in] writer The dump writer
+ static void WriteSystemInfo(IOSIntermediateDumpWriter* writer,
+ const IOSSystemDataCollector& system_data);
+
+ //! \brief Write ThreadSnapshot data to the intermediate dump.
+ //!
+ //! For uncaught NSExceptions, \a frames and \a num_frames will be added to
+ //! the intermediate dump for the exception thread. Otherwise, or for the
+ //! remaining threads, use `thread_get_state`.
+ //!
+ //! \param[in] writer The dump writer
+ //! \param[in] frames An array of callstack return addresses.
+ //! \param[in] num_frames The number of callstack return address in \a frames.
+ static void WriteThreadInfo(IOSIntermediateDumpWriter* writer,
+ const uint64_t* frames,
+ const size_t num_frames);
+
+ //! \brief Write ModuleSnapshot data to the intermediate dump.
+ //!
+ //! This includes both modules and annotations.
+ //!
+ //! \param[in] writer The dump writer
+ static void WriteModuleInfo(IOSIntermediateDumpWriter* writer);
+
+ //! \brief Write an ExceptionSnapshot from a signal to the intermediate dump.
+ //!
+ //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException
+ //! and WriteExceptionFromNSException should be called per intermediate dump.
+ //!
+ //! \param[in] writer The dump writer
+ //! \param[in] system_data An object containing various system data points.
+ //! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal
+ //! handler.
+ //! \param[in] context A pointer to a `ucontext_t` object received by a
+ //! signal.
+ static void WriteExceptionFromSignal(
+ IOSIntermediateDumpWriter* writer,
+ const IOSSystemDataCollector& system_data,
+ siginfo_t* siginfo,
+ ucontext_t* context);
+
+ //! \brief Write an ExceptionSnapshot from a mach exception to the
+ //! intermediate dump.
+ //!
+ //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException
+ //! and WriteExceptionFromNSException should be called per intermediate dump.
+ //!
+ //! \param[in] writer The dump writer
+ //! \param[in] system_data An object containing various system data points.
+ //! \param[in] behavior
+ //! \param[in] thread
+ //! \param[in] exception
+ //! \param[in] code
+ //! \param[in] code_count
+ //! \param[in] flavor
+ //! \param[in] old_state
+ //! \param[in] old_state_count
+ static void WriteExceptionFromMachException(
+ IOSIntermediateDumpWriter* writer,
+ exception_behavior_t behavior,
+ thread_t thread,
+ exception_type_t exception,
+ const mach_exception_data_type_t* code,
+ mach_msg_type_number_t code_count,
+ thread_state_flavor_t flavor,
+ ConstThreadState old_state,
+ mach_msg_type_number_t old_state_count);
+
+ //! \brief Write an ExceptionSnapshot from an NSException to the
+ //! intermediate dump.
+ //!
+ //! Only one of the WriteExceptionFromSignal, WriteExceptionFromMachException
+ //! and WriteExceptionFromNSException should be called per intermediate dump.
+ //!
+ //! \param[in] writer The dump writer
+ static void WriteExceptionFromNSException(IOSIntermediateDumpWriter* writer);
+
+ private:
+ //! \brief Parse and extract module and annotation information from header.
+ static void WriteModuleInfoAtAddress(IOSIntermediateDumpWriter* writer,
+ uint64_t address,
+ bool is_dyld);
+
+ //! \brief Extract and write Apple crashreporter_annotations_t data and
+ //! Crashpad annotations.
+ static void WriteDataSegmentAnnotations(IOSIntermediateDumpWriter* writer,
+ const segment_command_64* segment_ptr,
+ vm_size_t slide);
+
+ //! \brief Write Crashpad annotations list.
+ static void WriteCrashpadAnnotationsList(IOSIntermediateDumpWriter* writer,
+ CrashpadInfo* crashpad_info);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(InProcessIntermediateDumpHandler);
+};
+
+} // namespace internal
+} // namespace crashpad
+
+#endif // CRASHPAD_CLIENT_IOS_HANDLER_IN_PROCESS_INTERMEDIATE_DUMP_HANDLER_H_
diff --git a/client/ios_handler/in_process_intermediate_dump_handler_test.cc b/client/ios_handler/in_process_intermediate_dump_handler_test.cc
new file mode 100644
index 0000000..9616ce7
--- /dev/null
+++ b/client/ios_handler/in_process_intermediate_dump_handler_test.cc
@@ -0,0 +1,242 @@
+// Copyright 2021 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "client/ios_handler/in_process_intermediate_dump_handler.h"
+
+#include <sys/utsname.h>
+
+#include "base/cxx17_backports.h"
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "client/annotation.h"
+#include "client/annotation_list.h"
+#include "client/crashpad_info.h"
+#include "client/simple_string_dictionary.h"
+#include "gtest/gtest.h"
+#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"
+#include "test/scoped_temp_dir.h"
+#include "test/test_paths.h"
+#include "util/file/filesystem.h"
+#include "util/misc/capture_context.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using internal::InProcessIntermediateDumpHandler;
+
+class InProcessIntermediateDumpHandlerTest : public testing::Test {
+ protected:
+ // testing::Test:
+
+ void SetUp() override {
+ path_ = temp_dir_.path().Append("dump_file");
+ writer_ = std::make_unique<internal::IOSIntermediateDumpWriter>();
+ EXPECT_TRUE(writer_->Open(path_));
+ ASSERT_TRUE(IsRegularFile(path_));
+ }
+
+ void TearDown() override {
+ writer_.reset();
+ EXPECT_FALSE(IsRegularFile(path_));
+ }
+
+ void WriteReport() {
+ internal::IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer_.get());
+ InProcessIntermediateDumpHandler::WriteHeader(writer_.get());
+ InProcessIntermediateDumpHandler::WriteProcessInfo(writer_.get());
+ InProcessIntermediateDumpHandler::WriteSystemInfo(writer_.get(),
+ system_data_);
+ InProcessIntermediateDumpHandler::WriteThreadInfo(writer_.get(), 0, 0);
+ InProcessIntermediateDumpHandler::WriteModuleInfo(writer_.get());
+ }
+
+ void WriteMachException() {
+ crashpad::NativeCPUContext cpu_context;
+ crashpad::CaptureContext(&cpu_context);
+ const mach_exception_data_type_t code[2] = {};
+ static constexpr int kSimulatedException = -1;
+ InProcessIntermediateDumpHandler::WriteExceptionFromMachException(
+ writer_.get(),
+ MACH_EXCEPTION_CODES,
+ mach_thread_self(),
+ kSimulatedException,
+ code,
+ base::size(code),
+ MACHINE_THREAD_STATE,
+ reinterpret_cast<ConstThreadState>(&cpu_context),
+ MACHINE_THREAD_STATE_COUNT);
+ }
+
+ const auto& path() const { return path_; }
+ auto writer() const { return writer_.get(); }
+
+ private:
+ std::unique_ptr<internal::IOSIntermediateDumpWriter> writer_;
+ internal::IOSSystemDataCollector system_data_;
+ ScopedTempDir temp_dir_;
+ base::FilePath path_;
+};
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestSystem) {
+ WriteReport();
+ internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+ ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+
+ // Snpahot
+ const SystemSnapshot* system = process_snapshot.System();
+ ASSERT_NE(system, nullptr);
+#if defined(ARCH_CPU_X86_64)
+ EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureX86_64);
+ EXPECT_STREQ(system->CPUVendor().c_str(), "GenuineIntel");
+#elif defined(ARCH_CPU_ARM64)
+ EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureARM64);
+ utsname uts;
+ ASSERT_EQ(uname(&uts), 0);
+ EXPECT_STREQ(system->MachineDescription().c_str(), uts.machine);
+#else
+#error Port to your CPU architecture
+#endif
+ EXPECT_EQ(system->GetOperatingSystem(), SystemSnapshot::kOperatingSystemIOS);
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) {
+ // This is “leaked” to crashpad_info.
+ crashpad::SimpleStringDictionary* simple_annotations =
+ new crashpad::SimpleStringDictionary();
+ simple_annotations->SetKeyValue("#TEST# pad", "break");
+ simple_annotations->SetKeyValue("#TEST# key", "value");
+ simple_annotations->SetKeyValue("#TEST# pad", "crash");
+ simple_annotations->SetKeyValue("#TEST# x", "y");
+ simple_annotations->SetKeyValue("#TEST# longer", "shorter");
+ simple_annotations->SetKeyValue("#TEST# empty_value", "");
+
+ crashpad::CrashpadInfo* crashpad_info =
+ crashpad::CrashpadInfo::GetCrashpadInfo();
+
+ crashpad_info->set_simple_annotations(simple_annotations);
+
+ crashpad::AnnotationList::Register(); // This is “leaked” to crashpad_info.
+
+ static crashpad::StringAnnotation<32> test_annotation_one{"#TEST# one"};
+ static crashpad::StringAnnotation<32> test_annotation_two{"#TEST# two"};
+ static crashpad::StringAnnotation<32> test_annotation_three{
+ "#TEST# same-name"};
+ static crashpad::StringAnnotation<32> test_annotation_four{
+ "#TEST# same-name"};
+
+ test_annotation_one.Set("moocow");
+ test_annotation_two.Set("this will be cleared");
+ test_annotation_three.Set("same-name 3");
+ test_annotation_four.Set("same-name 4");
+ test_annotation_two.Clear();
+
+ WriteReport();
+ internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+ ASSERT_TRUE(process_snapshot.Initialize(path(), {{"after_dump", "post"}}));
+
+ auto process_map = process_snapshot.AnnotationsSimpleMap();
+ EXPECT_EQ(process_map.size(), 1u);
+ EXPECT_EQ(process_map["after_dump"], "post");
+
+ std::map<std::string, std::string> all_annotations_simple_map;
+ std::vector<AnnotationSnapshot> all_annotations;
+ for (const auto* module : process_snapshot.Modules()) {
+ std::map<std::string, std::string> module_annotations_simple_map =
+ module->AnnotationsSimpleMap();
+ all_annotations_simple_map.insert(module_annotations_simple_map.begin(),
+ module_annotations_simple_map.end());
+
+ std::vector<AnnotationSnapshot> annotations = module->AnnotationObjects();
+ all_annotations.insert(
+ all_annotations.end(), annotations.begin(), annotations.end());
+ }
+
+ EXPECT_EQ(all_annotations_simple_map.size(), 5u);
+ EXPECT_EQ(all_annotations_simple_map["#TEST# pad"], "crash");
+ EXPECT_EQ(all_annotations_simple_map["#TEST# key"], "value");
+ EXPECT_EQ(all_annotations_simple_map["#TEST# x"], "y");
+ EXPECT_EQ(all_annotations_simple_map["#TEST# longer"], "shorter");
+ EXPECT_EQ(all_annotations_simple_map["#TEST# empty_value"], "");
+
+ bool saw_same_name_3 = false, saw_same_name_4 = false;
+ for (const auto& annotation : all_annotations) {
+ EXPECT_EQ(annotation.type,
+ static_cast<uint16_t>(Annotation::Type::kString));
+ std::string value(reinterpret_cast<const char*>(annotation.value.data()),
+ annotation.value.size());
+ if (annotation.name == "#TEST# one") {
+ EXPECT_EQ(value, "moocow");
+ } else if (annotation.name == "#TEST# same-name") {
+ if (value == "same-name 3") {
+ EXPECT_FALSE(saw_same_name_3);
+ saw_same_name_3 = true;
+ } else if (value == "same-name 4") {
+ EXPECT_FALSE(saw_same_name_4);
+ saw_same_name_4 = true;
+ } else {
+ ADD_FAILURE() << "unexpected annotation value " << value;
+ }
+ } else {
+ ADD_FAILURE() << "unexpected annotation " << annotation.name;
+ }
+ }
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) {
+ WriteReport();
+ internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+ ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+
+ const auto& threads = process_snapshot.Threads();
+ ASSERT_GT(threads.size(), 0u);
+
+ thread_identifier_info identifier_info;
+ mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+ ASSERT_EQ(thread_info(mach_thread_self(),
+ THREAD_IDENTIFIER_INFO,
+ reinterpret_cast<thread_info_t>(&identifier_info),
+ &count),
+ 0);
+ EXPECT_EQ(threads[0]->ThreadID(), identifier_info.thread_id);
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestProcess) {
+ WriteReport();
+ internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+ ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+ EXPECT_EQ(process_snapshot.ProcessID(), getpid());
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestMachException) {
+ WriteReport();
+ internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+ ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestSignalException) {
+ WriteReport();
+ internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+ ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+}
+
+TEST_F(InProcessIntermediateDumpHandlerTest, TestNSException) {
+ WriteReport();
+ internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
+ ASSERT_TRUE(process_snapshot.Initialize(path(), {}));
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad
diff --git a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
index 0d24029..cd60451 100644
--- a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
+++ b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
@@ -156,20 +156,18 @@
exception_ = exception;
}
- mach_msg_type_number_t code_count;
- GetDataValueFromMap(exception_data, Key::kCodeCount, &code_count);
-
const IOSIntermediateDumpData* code_dump =
- GetDataFromMap(exception_data, Key::kCode);
+ GetDataFromMap(exception_data, Key::kCodes);
if (code_dump) {
const std::vector<uint8_t>& bytes = code_dump->bytes();
const mach_exception_data_type_t* code =
reinterpret_cast<const mach_exception_data_type_t*>(bytes.data());
- if (!code ||
- bytes.size() != sizeof(mach_exception_data_type_t) * code_count) {
+ if (bytes.size() == 0 || !code) {
LOG(ERROR) << "Invalid mach exception code.";
} else {
// TODO: rationalize with the macOS implementation.
+ mach_msg_type_number_t code_count =
+ bytes.size() / sizeof(mach_exception_data_type_t);
for (mach_msg_type_number_t code_index = 0; code_index < code_count;
++code_index) {
codes_.push_back(code[code_index]);
@@ -288,22 +286,24 @@
float_state_type float_state;
debug_state_type debug_state;
- mach_msg_type_number_t state_count = 0;
thread_state_flavor_t flavor = THREAD_STATE_NONE;
- if (GetDataValueFromMap(exception_data, Key::kStateCount, &state_count) &&
- GetDataValueFromMap(exception_data, Key::kFlavor, &flavor) &&
+ if (GetDataValueFromMap(exception_data, Key::kFlavor, &flavor) &&
GetDataValueFromMap(other_thread, Key::kThreadState, &thread_state) &&
GetDataValueFromMap(other_thread, Key::kFloatState, &float_state) &&
GetDataValueFromMap(other_thread, Key::kDebugState, &debug_state)) {
- size_t expected_length = ThreadStateLengthForFlavor(flavor);
const IOSIntermediateDumpData* state_dump =
GetDataFromMap(exception_data, Key::kState);
if (state_dump) {
const std::vector<uint8_t>& bytes = state_dump->bytes();
size_t actual_length = bytes.size();
+ size_t expected_length = ThreadStateLengthForFlavor(flavor);
+ // TODO(justincohen): Consider zero-ing out bytes if actual_length is
+ // shorter than expected_length, and tolerating actual_length longer than
+ // expected_length.
if (expected_length == actual_length) {
const ConstThreadState state =
reinterpret_cast<const ConstThreadState>(bytes.data());
+ mach_msg_type_number_t state_count = bytes.size() / sizeof(uint32_t);
#if defined(ARCH_CPU_X86_64)
InitializeCPUContextX86_64(&context_x86_64_,
flavor,
diff --git a/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc b/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
index 9710439..0bc9247 100644
--- a/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
+++ b/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
@@ -281,7 +281,6 @@
#if defined(ARCH_CPU_X86_64)
thread_state_flavor_t flavor = x86_THREAD_STATE;
- mach_msg_type_number_t state_count = x86_THREAD_STATE_COUNT;
x86_thread_state_t state = {};
state.tsh.flavor = x86_THREAD_STATE64;
state.tsh.count = x86_THREAD_STATE64_COUNT;
@@ -289,7 +288,6 @@
size_t state_length = sizeof(x86_thread_state_t);
#elif defined(ARCH_CPU_ARM64)
thread_state_flavor_t flavor = ARM_UNIFIED_THREAD_STATE;
- mach_msg_type_number_t state_count = ARM_UNIFIED_THREAD_STATE_COUNT;
arm_unified_thread_state_t state = {};
state.ash.flavor = ARM_THREAD_STATE64;
state.ash.count = ARM_THREAD_STATE64_COUNT;
@@ -297,12 +295,10 @@
size_t state_length = sizeof(arm_unified_thread_state_t);
#endif
EXPECT_TRUE(writer->AddProperty(Key::kException, &exception));
- EXPECT_TRUE(writer->AddProperty(Key::kCode, code, code_count));
- EXPECT_TRUE(writer->AddProperty(Key::kCodeCount, &code_count));
+ EXPECT_TRUE(writer->AddProperty(Key::kCodes, code, code_count));
EXPECT_TRUE(writer->AddProperty(Key::kFlavor, &flavor));
EXPECT_TRUE(writer->AddPropertyBytes(
Key::kState, reinterpret_cast<const void*>(&state), state_length));
- EXPECT_TRUE(writer->AddProperty(Key::kStateCount, &state_count));
uint64_t thread_id = 1;
EXPECT_TRUE(writer->AddProperty(Key::kThreadID, &thread_id));
}
diff --git a/snapshot/mac/process_reader_mac.cc b/snapshot/mac/process_reader_mac.cc
index 821fe3c..9b2a235 100644
--- a/snapshot/mac/process_reader_mac.cc
+++ b/snapshot/mac/process_reader_mac.cc
@@ -701,7 +701,7 @@
#if defined(ARCH_CPU_X86_FAMILY)
if (Is64Bit()) {
// x86_64 has a red zone. See AMD64 ABI 0.99.8,
- // https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/x86-64-psABI-r252.pdf#page=19,
+ // https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/uploads/01de35b2c8adc7545de52604cc45d942/x86-64-psABI-2021-05-20.pdf#page=23.
// section 3.2.2, “The Stack Frame”.
constexpr mach_vm_size_t kRedZoneSize = 128;
mach_vm_address_t red_zone_base =
diff --git a/snapshot/mac/process_types/crashreporterclient.proctype b/snapshot/mac/process_types/crashreporterclient.proctype
index 398bd63..226eaa2 100644
--- a/snapshot/mac/process_types/crashreporterclient.proctype
+++ b/snapshot/mac/process_types/crashreporterclient.proctype
@@ -23,7 +23,8 @@
//
// This file is included by snapshot/mac/process_types.h and
// snapshot/mac/process_types.cc to produce process type struct definitions and
-// accessors.
+// accessors. This file is also used by the iOS in process handler to read both
+// messages in client/ios_handler/in_process_intermediate_dump_handler.cc.
// Client Mach-O images will contain a __DATA,__crash_info section formatted
// according to this structure.
diff --git a/util/ios/ios_intermediate_dump_format.h b/util/ios/ios_intermediate_dump_format.h
index 1d7cad6..46d3f46 100644
--- a/util/ios/ios_intermediate_dump_format.h
+++ b/util/ios/ios_intermediate_dump_format.h
@@ -26,12 +26,10 @@
TD(kInvalid, 0) \
TD(kVersion, 1) \
TD(kMachException, 1000) \
- TD(kCode, 1001) \
- TD(kCodeCount, 1002) \
- TD(kException, 1003) \
- TD(kFlavor, 1004) \
- TD(kState, 1005) \
- TD(kStateCount, 1006) \
+ TD(kCodes, 1001) \
+ TD(kException, 1002) \
+ TD(kFlavor, 1003) \
+ TD(kState, 1004) \
TD(kSignalException, 2000) \
TD(kSignalNumber, 2001) \
TD(kSignalCode, 2002) \
diff --git a/util/ios/ios_system_data_collector.h b/util/ios/ios_system_data_collector.h
index 556d724..cac0c86 100644
--- a/util/ios/ios_system_data_collector.h
+++ b/util/ios/ios_system_data_collector.h
@@ -29,16 +29,16 @@
~IOSSystemDataCollector();
void OSVersion(int* major, int* minor, int* bugfix) const;
- std::string MachineDescription() const { return machine_description_; }
+ const std::string& MachineDescription() const { return machine_description_; }
int ProcessorCount() const { return processor_count_; }
- std::string Build() const { return build_; }
- std::string CPUVendor() const { return cpu_vendor_; }
+ const std::string& Build() const { return build_; }
+ const std::string& CPUVendor() const { return cpu_vendor_; }
bool HasDaylightSavingTime() const { return has_next_daylight_saving_time_; }
bool IsDaylightSavingTime() const { return is_daylight_saving_time_; }
int StandardOffsetSeconds() const { return standard_offset_seconds_; }
int DaylightOffsetSeconds() const { return daylight_offset_seconds_; }
- std::string StandardName() const { return standard_name_; }
- std::string DaylightName() const { return daylight_name_; }
+ const std::string& StandardName() const { return standard_name_; }
+ const std::string& DaylightName() const { return daylight_name_; }
// Currently unused by minidump.
int Orientation() const { return orientation_; }
diff --git a/util/ios/scoped_vm_read.cc b/util/ios/scoped_vm_read.cc
index 3bbc596..06cf434 100644
--- a/util/ios/scoped_vm_read.cc
+++ b/util/ios/scoped_vm_read.cc
@@ -53,7 +53,7 @@
data_ = vm_read_data_ + (data_address - page_region_address);
return true;
} else {
- CRASHPAD_RAW_LOG_ERROR(kr, "vm_read");
+ // It's expected that this will sometimes fail. Don't log here.
return false;
}
}
diff --git a/util/ios/scoped_vm_read.h b/util/ios/scoped_vm_read.h
index 86d8039..3f396d4 100644
--- a/util/ios/scoped_vm_read.h
+++ b/util/ios/scoped_vm_read.h
@@ -70,13 +70,22 @@
//! \param[in] data Memory to be read by vm_read.
//! \param[in] count Length of \a data.
//!
- //! \return `true` if all the data was read. Logs an error and returns false
- //! on failure
+ //! \return `true` if all \a data was read. Returns false on failure.
bool Read(const void* data, size_t count = 1) {
size_t data_length = count * sizeof(T);
return internal_.Read(data, data_length);
}
+ //! \brief Releases any previously read data and vm_reads address.
+ //!
+ //! \param[in] address Address of memory to be read by vm_read.
+ //! \param[in] count Length of \a data.
+ //!
+ //! \return `true` if all of \a address was read. Returns false on failure.
+ bool Read(vm_address_t address, size_t count = 1) {
+ return Read(reinterpret_cast<T*>(address), count);
+ }
+
//! \brief Returns the pointer to memory safe to read during the in-process
//! crash handler.
T* operator->() const { return get(); }