blob: 2869c2174f44ba89037b2471a5e179d871d13fa7 [file] [log] [blame]
// 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 <iterator>
#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(const ScopedTaskThreads&) = delete;
ScopedTaskThreads& operator=(const ScopedTaskThreads&) = delete;
~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_;
};
//! \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(),
&region_base,
&region_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, &region_base, &region_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,
&region_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 < std::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,
const std::map<std::string, std::string>& annotations) {
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, std::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 thread_times_info");
}
if (!annotations.empty()) {
IOSIntermediateDumpWriter::ScopedArray simple_annotations_array(
writer, IntermediateDumpKey::kAnnotationsSimpleMap);
for (const auto& annotation_pair : annotations) {
const std::string& key = annotation_pair.first;
const std::string& value = annotation_pair.second;
IOSIntermediateDumpWriter::ScopedArrayMap annotation_map(writer);
WriteProperty(writer,
IntermediateDumpKey::kAnnotationName,
key.c_str(),
key.length());
WriteProperty(writer,
IntermediateDumpKey::kAnnotationValue,
value.c_str(),
value.length());
}
}
}
// 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.
#if defined(ARCH_CPU_ARM64)
uint64_t exception_thread_id = 0;
#endif
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) {
#if defined(ARCH_CPU_ARM64)
exception_thread_id = identifier_info.thread_id;
#endif
} 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;
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.
#if defined(ARCH_CPU_ARM64)
uint64_t thread_id;
#endif
count = THREAD_IDENTIFIER_INFO_COUNT;
kr = thread_info(thread,
THREAD_IDENTIFIER_INFO,
reinterpret_cast<thread_info_t>(&identifier_info),
&count);
if (kr == KERN_SUCCESS) {
#if defined(ARCH_CPU_ARM64)
thread_id = identifier_info.thread_id;
#endif
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
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;
}
if (image->imageFilePath) {
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);
if (image_infos->dyldPath) {
WriteProperty(writer,
IntermediateDumpKey::kName,
image_infos->dyldPath,
strlen(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