| // Copyright 2020 The Crashpad Authors |
| // |
| // 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 "snapshot/ios/exception_snapshot_ios_intermediate_dump.h" |
| |
| #include "base/apple/mach_logging.h" |
| #include "base/check_op.h" |
| #include "base/logging.h" |
| #include "snapshot/cpu_context.h" |
| #include "snapshot/ios/intermediate_dump_reader_util.h" |
| #include "snapshot/mac/cpu_context_mac.h" |
| #include "util/ios/ios_intermediate_dump_data.h" |
| #include "util/ios/ios_intermediate_dump_list.h" |
| #include "util/ios/ios_intermediate_dump_writer.h" |
| #include "util/misc/from_pointer_cast.h" |
| |
| namespace crashpad { |
| |
| namespace internal { |
| |
| size_t ThreadStateLengthForFlavor(thread_state_flavor_t flavor) { |
| #if defined(ARCH_CPU_X86_64) |
| switch (flavor) { |
| case x86_THREAD_STATE: |
| return sizeof(x86_thread_state_t); |
| case x86_FLOAT_STATE: |
| return sizeof(x86_float_state_t); |
| case x86_DEBUG_STATE: |
| return sizeof(x86_debug_state_t); |
| case x86_THREAD_STATE64: |
| return sizeof(x86_thread_state64_t); |
| case x86_FLOAT_STATE64: |
| return sizeof(x86_float_state64_t); |
| case x86_DEBUG_STATE64: |
| return sizeof(x86_debug_state64_t); |
| default: |
| return 0; |
| } |
| #elif defined(ARCH_CPU_ARM64) |
| switch (flavor) { |
| case ARM_UNIFIED_THREAD_STATE: |
| return sizeof(arm_unified_thread_state_t); |
| case ARM_THREAD_STATE64: |
| return sizeof(arm_thread_state64_t); |
| case ARM_NEON_STATE64: |
| return sizeof(arm_neon_state64_t); |
| case ARM_DEBUG_STATE64: |
| return sizeof(arm_debug_state64_t); |
| default: |
| return 0; |
| } |
| #endif |
| } |
| |
| using Key = IntermediateDumpKey; |
| |
| ExceptionSnapshotIOSIntermediateDump::ExceptionSnapshotIOSIntermediateDump() |
| : ExceptionSnapshot(), |
| #if defined(ARCH_CPU_X86_64) |
| context_x86_64_(), |
| #elif defined(ARCH_CPU_ARM64) |
| context_arm64_(), |
| #else |
| #error Port to your CPU architecture |
| #endif |
| context_(), |
| codes_(), |
| thread_id_(0), |
| exception_address_(0), |
| exception_(0), |
| exception_info_(0), |
| initialized_() { |
| #if defined(ARCH_CPU_X86_64) |
| context_.architecture = kCPUArchitectureX86_64; |
| context_.x86_64 = &context_x86_64_; |
| #elif defined(ARCH_CPU_ARM64) |
| context_.architecture = kCPUArchitectureARM64; |
| context_.arm64 = &context_arm64_; |
| #else |
| #error Port to your CPU architecture |
| #endif |
| } |
| |
| ExceptionSnapshotIOSIntermediateDump::~ExceptionSnapshotIOSIntermediateDump() {} |
| |
| bool ExceptionSnapshotIOSIntermediateDump::InitializeFromSignal( |
| const IOSIntermediateDumpMap* exception_data) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| DCHECK(exception_data); |
| |
| if (!GetDataValueFromMap(exception_data, Key::kThreadID, &thread_id_)) { |
| LOG(ERROR) << "Exceptions require a thread id."; |
| return false; |
| } |
| |
| #if defined(ARCH_CPU_X86_64) |
| typedef x86_thread_state64_t thread_state_type; |
| typedef x86_float_state64_t float_state_type; |
| #elif defined(ARCH_CPU_ARM64) |
| typedef arm_thread_state64_t thread_state_type; |
| typedef arm_neon_state64_t float_state_type; |
| #endif |
| |
| thread_state_type thread_state; |
| float_state_type float_state; |
| if (GetDataValueFromMap(exception_data, Key::kThreadState, &thread_state) && |
| GetDataValueFromMap(exception_data, Key::kFloatState, &float_state)) { |
| #if defined(ARCH_CPU_X86_64) |
| x86_debug_state64_t empty_debug_state = {}; |
| InitializeCPUContextX86_64(&context_x86_64_, |
| THREAD_STATE_NONE, |
| nullptr, |
| 0, |
| &thread_state, |
| &float_state, |
| &empty_debug_state); |
| #elif defined(ARCH_CPU_ARM64) |
| arm_debug_state64_t empty_debug_state = {}; |
| InitializeCPUContextARM64(&context_arm64_, |
| THREAD_STATE_NONE, |
| nullptr, |
| 0, |
| &thread_state, |
| &float_state, |
| &empty_debug_state); |
| #else |
| #error Port to your CPU architecture |
| #endif |
| } |
| |
| exception_ = EXC_SOFT_SIGNAL; |
| GetDataValueFromMap(exception_data, Key::kSignalNumber, &exception_info_); |
| GetDataValueFromMap(exception_data, Key::kSignalAddress, &exception_address_); |
| |
| codes_.push_back(exception_); |
| codes_.push_back(exception_info_); |
| uint32_t code; |
| GetDataValueFromMap(exception_data, Key::kSignalCode, &code); |
| codes_.push_back(code); |
| |
| const IOSIntermediateDumpList* thread_context_memory_regions = |
| GetListFromMap(exception_data, Key::kThreadContextMemoryRegions); |
| if (thread_context_memory_regions) { |
| for (auto& region : *thread_context_memory_regions) { |
| vm_address_t address; |
| const IOSIntermediateDumpData* region_data = |
| region->GetAsData(Key::kThreadContextMemoryRegionData); |
| if (!region_data) |
| continue; |
| if (GetDataValueFromMap( |
| region.get(), Key::kThreadContextMemoryRegionAddress, &address)) { |
| const std::vector<uint8_t>& bytes = region_data->bytes(); |
| vm_size_t data_size = bytes.size(); |
| if (data_size == 0) |
| continue; |
| |
| const vm_address_t data = |
| reinterpret_cast<const vm_address_t>(bytes.data()); |
| |
| auto memory = |
| std::make_unique<internal::MemorySnapshotIOSIntermediateDump>(); |
| memory->Initialize(address, data, data_size); |
| extra_memory_.push_back(std::move(memory)); |
| } |
| } |
| } |
| |
| INITIALIZATION_STATE_SET_VALID(initialized_); |
| return true; |
| } |
| |
| bool ExceptionSnapshotIOSIntermediateDump::InitializeFromMachException( |
| const IOSIntermediateDumpMap* exception_data, |
| const IOSIntermediateDumpList* thread_list) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| DCHECK(exception_data); |
| |
| if (!GetDataValueFromMap(exception_data, Key::kThreadID, &thread_id_)) { |
| LOG(ERROR) << "Exceptions require a thread id."; |
| return false; |
| } |
| |
| exception_type_t exception; |
| if (GetDataValueFromMap(exception_data, Key::kException, &exception)) { |
| codes_.push_back(exception); |
| exception_ = exception; |
| } |
| |
| const IOSIntermediateDumpData* code_dump = |
| 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 (bytes.size() == 0 || |
| bytes.size() % sizeof(mach_exception_data_type_t) != 0 || !code) { |
| LOG(ERROR) << "Invalid mach exception code."; |
| } else { |
| 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]); |
| } |
| DCHECK_GE(code_count, 1u); |
| exception_info_ = code[0]; |
| if (code_count >= 2) { |
| exception_address_ = code[1]; |
| } |
| } |
| } |
| |
| if (thread_list) { |
| for (const auto& other_thread : *thread_list) { |
| uint64_t other_thread_id; |
| if (GetDataValueFromMap( |
| other_thread.get(), Key::kThreadID, &other_thread_id)) { |
| if (thread_id_ == other_thread_id) { |
| LoadContextFromThread(exception_data, other_thread.get()); |
| break; |
| } |
| } |
| } |
| } |
| |
| INITIALIZATION_STATE_SET_VALID(initialized_); |
| return true; |
| } |
| |
| bool ExceptionSnapshotIOSIntermediateDump::InitializeFromNSException( |
| const IOSIntermediateDumpMap* exception_data, |
| const IOSIntermediateDumpList* thread_list) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| DCHECK(exception_data); |
| |
| exception_ = kMachExceptionFromNSException; |
| |
| if (!GetDataValueFromMap(exception_data, Key::kThreadID, &thread_id_)) { |
| LOG(ERROR) << "Exceptions require a thread id."; |
| return false; |
| } |
| |
| if (thread_list) { |
| for (const auto& other_thread : *thread_list) { |
| uint64_t other_thread_id; |
| if (GetDataValueFromMap( |
| other_thread.get(), Key::kThreadID, &other_thread_id)) { |
| if (thread_id_ == other_thread_id) { |
| const IOSIntermediateDumpData* uncaught_exceptions = |
| other_thread->GetAsData(Key::kThreadUncaughtNSExceptionFrames); |
| if (uncaught_exceptions) { |
| LoadContextFromUncaughtNSExceptionFrames(uncaught_exceptions, |
| other_thread.get()); |
| } else { |
| LoadContextFromThread(exception_data, other_thread.get()); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| INITIALIZATION_STATE_SET_VALID(initialized_); |
| return true; |
| } |
| |
| const CPUContext* ExceptionSnapshotIOSIntermediateDump::Context() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return &context_; |
| } |
| |
| uint64_t ExceptionSnapshotIOSIntermediateDump::ThreadID() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return thread_id_; |
| } |
| |
| uint32_t ExceptionSnapshotIOSIntermediateDump::Exception() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return exception_; |
| } |
| |
| uint32_t ExceptionSnapshotIOSIntermediateDump::ExceptionInfo() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return exception_info_; |
| } |
| |
| uint64_t ExceptionSnapshotIOSIntermediateDump::ExceptionAddress() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return exception_address_; |
| } |
| |
| const std::vector<uint64_t>& ExceptionSnapshotIOSIntermediateDump::Codes() |
| const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return codes_; |
| } |
| |
| std::vector<const MemorySnapshot*> |
| ExceptionSnapshotIOSIntermediateDump::ExtraMemory() const { |
| std::vector<const MemorySnapshot*> extra_memory; |
| for (const auto& memory : extra_memory_) { |
| extra_memory.push_back(memory.get()); |
| } |
| return extra_memory; |
| } |
| |
| void ExceptionSnapshotIOSIntermediateDump::LoadContextFromThread( |
| const IOSIntermediateDumpMap* exception_data, |
| const IOSIntermediateDumpMap* other_thread) { |
| #if defined(ARCH_CPU_X86_64) |
| typedef x86_thread_state64_t thread_state_type; |
| typedef x86_float_state64_t float_state_type; |
| typedef x86_debug_state64_t debug_state_type; |
| #elif defined(ARCH_CPU_ARM64) |
| typedef arm_thread_state64_t thread_state_type; |
| typedef arm_neon_state64_t float_state_type; |
| typedef arm_debug_state64_t debug_state_type; |
| #endif |
| |
| thread_state_type thread_state; |
| float_state_type float_state; |
| debug_state_type debug_state; |
| |
| thread_state_flavor_t flavor = THREAD_STATE_NONE; |
| 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)) { |
| const IOSIntermediateDumpData* state_dump = |
| GetDataFromMap(exception_data, Key::kState); |
| if (state_dump) { |
| std::vector<uint8_t> bytes = state_dump->bytes(); |
| size_t actual_length = bytes.size(); |
| size_t expected_length = ThreadStateLengthForFlavor(flavor); |
| if (actual_length < expected_length) { |
| // Zero out bytes if actual_length is shorter than expected_length. |
| bytes.resize(expected_length, 0); |
| actual_length = bytes.size(); |
| LOG(WARNING) << "Exception context length " << actual_length |
| << " shorter than expected length " << expected_length; |
| } |
| const ConstThreadState state = |
| reinterpret_cast<const ConstThreadState>(bytes.data()); |
| // Tolerating actual_length longer than expected_length by setting |
| // state_count based on expected_length, not bytes.size(). |
| mach_msg_type_number_t state_count = expected_length / sizeof(uint32_t); |
| #if defined(ARCH_CPU_X86_64) |
| InitializeCPUContextX86_64(&context_x86_64_, |
| flavor, |
| state, |
| state_count, |
| &thread_state, |
| &float_state, |
| &debug_state); |
| #elif defined(ARCH_CPU_ARM64) |
| InitializeCPUContextARM64(&context_arm64_, |
| flavor, |
| state, |
| state_count, |
| &thread_state, |
| &float_state, |
| &debug_state); |
| #else |
| #error Port to your CPU architecture |
| #endif |
| } |
| } |
| |
| // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present |
| // in code[1]. It may or may not be the instruction pointer address (usually |
| // it’s not). code[1] may carry the exception address for other exception |
| // types too, but it’s not guaranteed. But for all other exception types, the |
| // instruction pointer will be the exception address, and in fact will be |
| // equal to codes[1] when it’s carrying the exception address. In those cases, |
| // just use the instruction pointer directly. |
| bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS; |
| |
| #if defined(ARCH_CPU_X86_64) |
| // For x86 and x86_64 EXC_BAD_ACCESS exceptions, some code[0] values |
| // indicate that code[1] does not (or may not) carry the exception address: |
| // EXC_I386_GPFLT (10.9.5 xnu-2422.115.4/osfmk/i386/trap.c user_trap() for |
| // T_GENERAL_PROTECTION) and the oddball (VM_PROT_READ | VM_PROT_EXECUTE) |
| // which collides with EXC_I386_BOUNDFLT (10.9.5 |
| // xnu-2422.115.4/osfmk/i386/fpu.c fpextovrflt()). Other EXC_BAD_ACCESS |
| // exceptions come through 10.9.5 xnu-2422.115.4/osfmk/i386/trap.c |
| // user_page_fault_continue() and do contain the exception address in |
| // code[1]. |
| if (exception_ == EXC_BAD_ACCESS && |
| (exception_info_ == EXC_I386_GPFLT || |
| exception_info_ == (VM_PROT_READ | VM_PROT_EXECUTE))) { |
| code_1_is_exception_address = false; |
| } |
| #endif |
| |
| if (!code_1_is_exception_address) { |
| exception_address_ = context_.InstructionPointer(); |
| } |
| } |
| |
| void ExceptionSnapshotIOSIntermediateDump:: |
| LoadContextFromUncaughtNSExceptionFrames( |
| const IOSIntermediateDumpData* frames_dump, |
| const IOSIntermediateDumpMap* other_thread) { |
| const std::vector<uint8_t>& bytes = frames_dump->bytes(); |
| const uint64_t* frames = reinterpret_cast<const uint64_t*>(bytes.data()); |
| size_t num_frames = bytes.size() / sizeof(uint64_t); |
| if (num_frames < 2) { |
| return; |
| } |
| |
| #if defined(ARCH_CPU_X86_64) |
| context_x86_64_.rip = frames[0]; // instruction pointer |
| context_x86_64_.rsp = frames[1]; |
| #elif defined(ARCH_CPU_ARM64) |
| context_arm64_.sp = 0; |
| context_arm64_.pc = frames[0]; |
| context_arm64_.regs[30] = frames[1]; // link register |
| context_arm64_.regs[29] = sizeof(uintptr_t); // function pointers |
| #else |
| #error Port to your CPU architecture |
| #endif |
| |
| exception_address_ = frames[0]; |
| } |
| |
| } // namespace internal |
| } // namespace crashpad |