| // Copyright 2017 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/linux/exception_snapshot_linux.h" |
| |
| #include <signal.h> |
| |
| #include "base/logging.h" |
| #include "snapshot/linux/capture_memory_delegate_linux.h" |
| #include "snapshot/linux/cpu_context_linux.h" |
| #include "snapshot/linux/process_reader_linux.h" |
| #include "snapshot/linux/signal_context.h" |
| #include "util/linux/traits.h" |
| #include "util/misc/reinterpret_bytes.h" |
| #include "util/numeric/safe_assignment.h" |
| #include "util/posix/signals.h" |
| |
| namespace crashpad { |
| namespace internal { |
| |
| ExceptionSnapshotLinux::ExceptionSnapshotLinux() |
| : ExceptionSnapshot(), |
| context_union_(), |
| context_(), |
| codes_(), |
| thread_id_(0), |
| exception_address_(0), |
| signal_number_(0), |
| signal_code_(0), |
| initialized_() {} |
| |
| ExceptionSnapshotLinux::~ExceptionSnapshotLinux() {} |
| |
| #if defined(ARCH_CPU_X86_FAMILY) |
| |
| template <> |
| bool ExceptionSnapshotLinux::ReadContext<ContextTraits32>( |
| ProcessReaderLinux* reader, |
| LinuxVMAddress context_address) { |
| UContext<ContextTraits32> ucontext; |
| if (!reader->Memory()->Read(context_address, sizeof(ucontext), &ucontext)) { |
| LOG(ERROR) << "Couldn't read ucontext"; |
| return false; |
| } |
| |
| context_.architecture = kCPUArchitectureX86; |
| context_.x86 = &context_union_.x86; |
| |
| if (!ucontext.mcontext.fpptr) { |
| InitializeCPUContextX86_NoFloatingPoint(ucontext.mcontext.gprs, |
| context_.x86); |
| return true; |
| } |
| |
| SignalFloatContext32 fprs; |
| if (!reader->Memory()->Read(ucontext.mcontext.fpptr, sizeof(fprs), &fprs)) { |
| LOG(ERROR) << "Couldn't read float context"; |
| return false; |
| } |
| |
| if (fprs.magic == X86_FXSR_MAGIC) { |
| InitializeCPUContextX86_NoFloatingPoint(ucontext.mcontext.gprs, |
| context_.x86); |
| if (!reader->Memory()->Read( |
| ucontext.mcontext.fpptr + offsetof(SignalFloatContext32, fxsave), |
| sizeof(CPUContextX86::Fxsave), |
| &context_.x86->fxsave)) { |
| LOG(ERROR) << "Couldn't read fxsave"; |
| return false; |
| } |
| } else if (fprs.magic == 0xffff) { |
| InitializeCPUContextX86(ucontext.mcontext.gprs, fprs, context_.x86); |
| } else { |
| LOG(ERROR) << "unexpected magic 0x" << std::hex << fprs.magic; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <> |
| bool ExceptionSnapshotLinux::ReadContext<ContextTraits64>( |
| ProcessReaderLinux* reader, |
| LinuxVMAddress context_address) { |
| UContext<ContextTraits64> ucontext; |
| if (!reader->Memory()->Read(context_address, sizeof(ucontext), &ucontext)) { |
| LOG(ERROR) << "Couldn't read ucontext"; |
| return false; |
| } |
| |
| context_.architecture = kCPUArchitectureX86_64; |
| context_.x86_64 = &context_union_.x86_64; |
| |
| if (!ucontext.mcontext.fpptr) { |
| InitializeCPUContextX86_64_NoFloatingPoint(ucontext.mcontext.gprs, |
| context_.x86_64); |
| return true; |
| } |
| |
| SignalFloatContext64 fprs; |
| if (!reader->Memory()->Read(ucontext.mcontext.fpptr, sizeof(fprs), &fprs)) { |
| LOG(ERROR) << "Couldn't read float context"; |
| return false; |
| } |
| |
| InitializeCPUContextX86_64(ucontext.mcontext.gprs, fprs, context_.x86_64); |
| return true; |
| } |
| |
| #elif defined(ARCH_CPU_ARM_FAMILY) |
| |
| template <> |
| bool ExceptionSnapshotLinux::ReadContext<ContextTraits32>( |
| ProcessReaderLinux* reader, |
| LinuxVMAddress context_address) { |
| context_.architecture = kCPUArchitectureARM; |
| context_.arm = &context_union_.arm; |
| |
| CPUContextARM* dest_context = context_.arm; |
| const ProcessMemory* memory = reader->Memory(); |
| |
| LinuxVMAddress gprs_address = |
| context_address + offsetof(UContext<ContextTraits32>, mcontext32) + |
| offsetof(ContextTraits32::MContext32, gprs); |
| |
| SignalThreadContext32 thread_context; |
| if (!memory->Read(gprs_address, sizeof(thread_context), &thread_context)) { |
| LOG(ERROR) << "Couldn't read gprs"; |
| return false; |
| } |
| InitializeCPUContextARM_NoFloatingPoint(thread_context, dest_context); |
| |
| LinuxVMAddress reserved_address = |
| context_address + offsetof(UContext<ContextTraits32>, reserved); |
| if ((reserved_address & 7) != 0) { |
| LOG(ERROR) << "invalid alignment 0x" << std::hex << reserved_address; |
| return false; |
| } |
| |
| constexpr VMSize kMaxContextSpace = 1024; |
| |
| ProcessMemoryRange range; |
| if (!range.Initialize(memory, false, reserved_address, kMaxContextSpace)) { |
| return false; |
| } |
| |
| do { |
| CoprocessorContextHead head; |
| if (!range.Read(reserved_address, sizeof(head), &head)) { |
| LOG(ERROR) << "missing context terminator"; |
| return false; |
| } |
| reserved_address += sizeof(head); |
| |
| switch (head.magic) { |
| case VFP_MAGIC: |
| if (head.size != sizeof(SignalVFPContext) + sizeof(head)) { |
| LOG(ERROR) << "unexpected vfp context size " << head.size; |
| return false; |
| } |
| static_assert( |
| sizeof(SignalVFPContext::vfp) == sizeof(dest_context->vfp_regs), |
| "vfp context size mismatch"); |
| if (!range.Read(reserved_address + offsetof(SignalVFPContext, vfp), |
| sizeof(dest_context->vfp_regs), |
| &dest_context->vfp_regs)) { |
| LOG(ERROR) << "Couldn't read vfp"; |
| return false; |
| } |
| dest_context->have_vfp_regs = true; |
| return true; |
| |
| case CRUNCH_MAGIC: |
| case IWMMXT_MAGIC: |
| case DUMMY_MAGIC: |
| reserved_address += head.size - sizeof(head); |
| continue; |
| |
| case 0: |
| return true; |
| |
| default: |
| LOG(ERROR) << "invalid magic number 0x" << std::hex << head.magic; |
| return false; |
| } |
| } while (true); |
| } |
| |
| template <> |
| bool ExceptionSnapshotLinux::ReadContext<ContextTraits64>( |
| ProcessReaderLinux* reader, |
| LinuxVMAddress context_address) { |
| context_.architecture = kCPUArchitectureARM64; |
| context_.arm64 = &context_union_.arm64; |
| |
| CPUContextARM64* dest_context = context_.arm64; |
| const ProcessMemory* memory = reader->Memory(); |
| |
| LinuxVMAddress gprs_address = |
| context_address + offsetof(UContext<ContextTraits64>, mcontext64) + |
| offsetof(ContextTraits64::MContext64, gprs); |
| |
| ThreadContext::t64_t thread_context; |
| if (!memory->Read(gprs_address, sizeof(thread_context), &thread_context)) { |
| LOG(ERROR) << "Couldn't read gprs"; |
| return false; |
| } |
| InitializeCPUContextARM64_NoFloatingPoint(thread_context, dest_context); |
| |
| LinuxVMAddress reserved_address = |
| context_address + offsetof(UContext<ContextTraits64>, reserved); |
| if ((reserved_address & 15) != 0) { |
| LOG(ERROR) << "invalid alignment 0x" << std::hex << reserved_address; |
| return false; |
| } |
| |
| constexpr VMSize kMaxContextSpace = 4096; |
| |
| ProcessMemoryRange range; |
| if (!range.Initialize(memory, true, reserved_address, kMaxContextSpace)) { |
| return false; |
| } |
| |
| do { |
| CoprocessorContextHead head; |
| if (!range.Read(reserved_address, sizeof(head), &head)) { |
| LOG(ERROR) << "missing context terminator"; |
| return false; |
| } |
| reserved_address += sizeof(head); |
| |
| switch (head.magic) { |
| case FPSIMD_MAGIC: |
| if (head.size != sizeof(SignalFPSIMDContext) + sizeof(head)) { |
| LOG(ERROR) << "unexpected fpsimd context size " << head.size; |
| return false; |
| } |
| SignalFPSIMDContext fpsimd; |
| if (!range.Read(reserved_address, sizeof(fpsimd), &fpsimd)) { |
| LOG(ERROR) << "Couldn't read fpsimd " << head.size; |
| return false; |
| } |
| InitializeCPUContextARM64_OnlyFPSIMD(fpsimd, dest_context); |
| return true; |
| |
| case ESR_MAGIC: |
| case EXTRA_MAGIC: |
| reserved_address += head.size - sizeof(head); |
| continue; |
| |
| case 0: |
| LOG(WARNING) << "fpsimd not found"; |
| return true; |
| |
| default: |
| LOG(ERROR) << "invalid magic number 0x" << std::hex << head.magic; |
| return false; |
| } |
| } while (true); |
| } |
| |
| #elif defined(ARCH_CPU_MIPS_FAMILY) |
| |
| template <typename Traits> |
| static bool ReadContext(ProcessReaderLinux* reader, |
| LinuxVMAddress context_address, |
| typename Traits::CPUContext* dest_context) { |
| const ProcessMemory* memory = reader->Memory(); |
| |
| LinuxVMAddress gregs_address = context_address + |
| offsetof(UContext<Traits>, mcontext) + |
| offsetof(typename Traits::MContext, gregs); |
| |
| typename Traits::SignalThreadContext thread_context; |
| if (!memory->Read(gregs_address, sizeof(thread_context), &thread_context)) { |
| LOG(ERROR) << "Couldn't read gregs"; |
| return false; |
| } |
| |
| LinuxVMAddress fpregs_address = context_address + |
| offsetof(UContext<Traits>, mcontext) + |
| offsetof(typename Traits::MContext, fpregs); |
| |
| typename Traits::SignalFloatContext fp_context; |
| if (!memory->Read(fpregs_address, sizeof(fp_context), &fp_context)) { |
| LOG(ERROR) << "Couldn't read fpregs"; |
| return false; |
| } |
| |
| InitializeCPUContextMIPS<Traits>(thread_context, fp_context, dest_context); |
| |
| return true; |
| } |
| |
| template <> |
| bool ExceptionSnapshotLinux::ReadContext<ContextTraits32>( |
| ProcessReaderLinux* reader, |
| LinuxVMAddress context_address) { |
| context_.architecture = kCPUArchitectureMIPSEL; |
| context_.mipsel = &context_union_.mipsel; |
| |
| return internal::ReadContext<ContextTraits32>( |
| reader, context_address, context_.mipsel); |
| } |
| |
| template <> |
| bool ExceptionSnapshotLinux::ReadContext<ContextTraits64>( |
| ProcessReaderLinux* reader, |
| LinuxVMAddress context_address) { |
| context_.architecture = kCPUArchitectureMIPS64EL; |
| context_.mips64 = &context_union_.mips64; |
| |
| return internal::ReadContext<ContextTraits64>( |
| reader, context_address, context_.mips64); |
| } |
| |
| #elif defined(ARCH_CPU_RISCV64) |
| |
| static bool ReadContext(ProcessReaderLinux* reader, |
| LinuxVMAddress context_address, |
| typename ContextTraits64::CPUContext* dest_context) { |
| const ProcessMemory* memory = reader->Memory(); |
| |
| LinuxVMAddress gregs_address = context_address + |
| offsetof(UContext<ContextTraits64>, mcontext) + |
| offsetof(MContext64, regs); |
| |
| typename ContextTraits64::SignalThreadContext thread_context; |
| if (!memory->Read(gregs_address, sizeof(thread_context), &thread_context)) { |
| LOG(ERROR) << "Couldn't read gregs"; |
| return false; |
| } |
| |
| LinuxVMAddress fpregs_address = |
| context_address + offsetof(UContext<ContextTraits64>, mcontext) + |
| offsetof(MContext64, fpregs); |
| |
| typename ContextTraits64::SignalFloatContext fp_context; |
| if (!memory->Read(fpregs_address, sizeof(fp_context), &fp_context)) { |
| LOG(ERROR) << "Couldn't read fpregs"; |
| return false; |
| } |
| |
| InitializeCPUContextRISCV64(thread_context, fp_context, dest_context); |
| |
| return true; |
| } |
| |
| template <> |
| bool ExceptionSnapshotLinux::ReadContext<ContextTraits64>( |
| ProcessReaderLinux* reader, |
| LinuxVMAddress context_address) { |
| context_.architecture = kCPUArchitectureRISCV64; |
| context_.riscv64 = &context_union_.riscv64; |
| |
| return internal::ReadContext(reader, context_address, context_.riscv64); |
| } |
| |
| #endif // ARCH_CPU_X86_FAMILY |
| |
| bool ExceptionSnapshotLinux::Initialize( |
| ProcessReaderLinux* process_reader, |
| LinuxVMAddress siginfo_address, |
| LinuxVMAddress context_address, |
| pid_t thread_id, |
| uint32_t* gather_indirectly_referenced_memory_cap) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| |
| thread_id_ = thread_id; |
| const ProcessReaderLinux::Thread* thread = nullptr; |
| for (const auto& loop_thread : process_reader->Threads()) { |
| if (thread_id == loop_thread.tid) { |
| thread = &loop_thread; |
| break; |
| } |
| } |
| if (!thread) { |
| // This is allowed until {ProcessReaderLinux::InitializeThreads()} is |
| // improved to support target threads in the same thread group. |
| LOG(WARNING) << "thread ID " << thread_id << " not found in process"; |
| } |
| |
| if (process_reader->Is64Bit()) { |
| if (!ReadContext<ContextTraits64>(process_reader, context_address) || |
| !ReadSiginfo<Traits64>(process_reader, siginfo_address)) { |
| return false; |
| } |
| } else { |
| #if !defined(ARCH_CPU_RISCV64) |
| if (!ReadContext<ContextTraits32>(process_reader, context_address) || |
| !ReadSiginfo<Traits32>(process_reader, siginfo_address)) { |
| return false; |
| } |
| #endif |
| } |
| |
| CaptureMemoryDelegateLinux capture_memory_delegate( |
| process_reader, |
| thread, |
| &extra_memory_, |
| gather_indirectly_referenced_memory_cap); |
| CaptureMemory::PointedToByContext(context_, &capture_memory_delegate); |
| |
| INITIALIZATION_STATE_SET_VALID(initialized_); |
| return true; |
| } |
| |
| template <typename Traits> |
| bool ExceptionSnapshotLinux::ReadSiginfo(ProcessReaderLinux* reader, |
| LinuxVMAddress siginfo_address) { |
| Siginfo<Traits> siginfo; |
| if (!reader->Memory()->Read(siginfo_address, sizeof(siginfo), &siginfo)) { |
| LOG(ERROR) << "Couldn't read siginfo"; |
| return false; |
| } |
| |
| signal_number_ = siginfo.signo; |
| signal_code_ = siginfo.code; |
| |
| uint64_t extra_code; |
| #define PUSH_CODE(value) \ |
| do { \ |
| if (!ReinterpretBytes(value, &extra_code)) { \ |
| LOG(ERROR) << "bad code"; \ |
| return false; \ |
| } \ |
| codes_.push_back(extra_code); \ |
| } while (false) |
| |
| switch (siginfo.signo) { |
| case SIGILL: |
| case SIGFPE: |
| case SIGSEGV: |
| case SIGBUS: |
| case SIGTRAP: |
| exception_address_ = siginfo.address; |
| break; |
| |
| case SIGPOLL: // SIGIO |
| PUSH_CODE(siginfo.band); |
| PUSH_CODE(siginfo.fd); |
| break; |
| |
| case SIGSYS: |
| exception_address_ = siginfo.call_address; |
| PUSH_CODE(siginfo.syscall); |
| PUSH_CODE(siginfo.arch); |
| break; |
| |
| case SIGALRM: |
| case SIGVTALRM: |
| case SIGPROF: |
| PUSH_CODE(siginfo.timerid); |
| PUSH_CODE(siginfo.overrun); |
| PUSH_CODE(siginfo.sigval.sigval); |
| break; |
| |
| case SIGABRT: |
| case SIGQUIT: |
| case SIGXCPU: |
| case SIGXFSZ: |
| case SIGHUP: |
| case SIGINT: |
| case SIGPIPE: |
| case SIGTERM: |
| case SIGUSR1: |
| case SIGUSR2: |
| #if defined(SIGEMT) |
| case SIGEMT: |
| #endif // SIGEMT |
| #if defined(SIGPWR) |
| case SIGPWR: |
| #endif // SIGPWR |
| #if defined(SIGSTKFLT) |
| case SIGSTKFLT: |
| #endif // SIGSTKFLT |
| PUSH_CODE(siginfo.pid); |
| PUSH_CODE(siginfo.uid); |
| PUSH_CODE(siginfo.sigval.sigval); |
| break; |
| |
| case Signals::kSimulatedSigno: |
| break; |
| |
| default: |
| LOG(WARNING) << "Unhandled signal " << siginfo.signo; |
| } |
| |
| return true; |
| } |
| |
| const CPUContext* ExceptionSnapshotLinux::Context() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return &context_; |
| } |
| |
| uint64_t ExceptionSnapshotLinux::ThreadID() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return thread_id_; |
| } |
| |
| uint32_t ExceptionSnapshotLinux::Exception() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return signal_number_; |
| } |
| |
| uint32_t ExceptionSnapshotLinux::ExceptionInfo() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return signal_code_; |
| } |
| |
| uint64_t ExceptionSnapshotLinux::ExceptionAddress() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return exception_address_; |
| } |
| |
| const std::vector<uint64_t>& ExceptionSnapshotLinux::Codes() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return codes_; |
| } |
| |
| std::vector<const MemorySnapshot*> ExceptionSnapshotLinux::ExtraMemory() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const MemorySnapshot*> result; |
| result.reserve(extra_memory_.size()); |
| for (const auto& em : extra_memory_) { |
| result.push_back(em.get()); |
| } |
| return result; |
| } |
| |
| } // namespace internal |
| } // namespace crashpad |