123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- // Copyright 2017 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 "util/linux/ptracer.h"
- #include <errno.h>
- #include <linux/elf.h>
- #include <string.h>
- #include <sys/ptrace.h>
- #include <sys/uio.h>
- #include "base/logging.h"
- #include "build/build_config.h"
- #include "util/misc/from_pointer_cast.h"
- #if defined(ARCH_CPU_X86_FAMILY)
- #include <asm/ldt.h>
- #endif
- namespace crashpad {
- namespace {
- #if defined(ARCH_CPU_X86_FAMILY)
- template <typename Destination>
- bool GetRegisterSet(pid_t tid, int set, Destination* dest, bool can_log) {
- iovec iov;
- iov.iov_base = dest;
- iov.iov_len = sizeof(*dest);
- if (ptrace(PTRACE_GETREGSET, tid, reinterpret_cast<void*>(set), &iov) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- if (iov.iov_len != sizeof(*dest)) {
- LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
- << " != " << sizeof(*dest);
- return false;
- }
- return true;
- }
- bool GetFloatingPointRegisters32(pid_t tid,
- FloatContext* context,
- bool can_log) {
- return GetRegisterSet(tid, NT_PRXFPREG, &context->f32.fxsave, can_log);
- }
- bool GetFloatingPointRegisters64(pid_t tid,
- FloatContext* context,
- bool can_log) {
- return GetRegisterSet(tid, NT_PRFPREG, &context->f64.fxsave, can_log);
- }
- bool GetThreadArea32(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- size_t index = (context.t32.xgs & 0xffff) >> 3;
- user_desc desc;
- if (ptrace(
- PTRACE_GET_THREAD_AREA, tid, reinterpret_cast<void*>(index), &desc) !=
- 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- *address = desc.base_addr;
- return true;
- }
- bool GetThreadArea64(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- *address = context.t64.fs_base;
- return true;
- }
- #elif defined(ARCH_CPU_ARM_FAMILY)
- #if defined(ARCH_CPU_ARMEL)
- // PTRACE_GETREGSET, introduced in Linux 2.6.34 (2225a122ae26), requires kernel
- // support enabled by HAVE_ARCH_TRACEHOOK. This has been set for x86 (including
- // x86_64) since Linux 2.6.28 (99bbc4b1e677a), but for ARM only since
- // Linux 3.5.0 (0693bf68148c4). Older Linux kernels support PTRACE_GETREGS,
- // PTRACE_GETFPREGS, and PTRACE_GETVFPREGS instead, which don't allow checking
- // the size of data copied.
- //
- // Fortunately, 64-bit ARM support only appeared in Linux 3.7.0, so if
- // PTRACE_GETREGSET fails on ARM with EIO, indicating that the request is not
- // supported, the kernel must be old enough that 64-bit ARM isn’t supported
- // either.
- //
- // TODO(mark): Once helpers to interpret the kernel version are available, add
- // a DCHECK to ensure that the kernel is older than 3.5.
- bool GetGeneralPurposeRegistersLegacy(pid_t tid,
- ThreadContext* context,
- bool can_log) {
- if (ptrace(PTRACE_GETREGS, tid, nullptr, &context->t32) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- return true;
- }
- bool GetFloatingPointRegistersLegacy(pid_t tid,
- FloatContext* context,
- bool can_log) {
- if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- context->f32.have_fpregs = true;
- if (ptrace(PTRACE_GETVFPREGS, tid, nullptr, &context->f32.vfp) != 0) {
- switch (errno) {
- case EINVAL:
- // These registers are optional on 32-bit ARM cpus
- break;
- default:
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- } else {
- context->f32.have_vfp = true;
- }
- return true;
- }
- #endif // ARCH_CPU_ARMEL
- // Normally, the Linux kernel will copy out register sets according to the size
- // of the struct that contains them. Tracing a 32-bit ARM process running in
- // compatibility mode on a 64-bit ARM cpu will only copy data for the number of
- // registers times the size of the register, which won't include any possible
- // trailing padding in the struct. These are the sizes of the register data, not
- // including any possible padding.
- constexpr size_t kArmVfpSize = 32 * 8 + 4;
- // Target is 32-bit
- bool GetFloatingPointRegisters32(pid_t tid,
- FloatContext* context,
- bool can_log) {
- context->f32.have_fpregs = false;
- context->f32.have_vfp = false;
- iovec iov;
- iov.iov_base = &context->f32.fpregs;
- iov.iov_len = sizeof(context->f32.fpregs);
- if (ptrace(
- PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRFPREG), &iov) !=
- 0) {
- switch (errno) {
- #if defined(ARCH_CPU_ARMEL)
- case EIO:
- return GetFloatingPointRegistersLegacy(tid, context, can_log);
- #endif // ARCH_CPU_ARMEL
- case EINVAL:
- // A 32-bit process running on a 64-bit CPU doesn't have this register
- // set. It should have a VFP register set instead.
- break;
- default:
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- } else {
- if (iov.iov_len != sizeof(context->f32.fpregs)) {
- LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
- << " != " << sizeof(context->f32.fpregs);
- return false;
- }
- context->f32.have_fpregs = true;
- }
- iov.iov_base = &context->f32.vfp;
- iov.iov_len = sizeof(context->f32.vfp);
- if (ptrace(
- PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_ARM_VFP), &iov) !=
- 0) {
- switch (errno) {
- case EINVAL:
- // VFP may not be present on 32-bit ARM cpus.
- break;
- default:
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- } else {
- if (iov.iov_len != kArmVfpSize && iov.iov_len != sizeof(context->f32.vfp)) {
- LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
- << " != " << sizeof(context->f32.vfp);
- return false;
- }
- context->f32.have_vfp = true;
- }
- if (!(context->f32.have_fpregs || context->f32.have_vfp)) {
- LOG_IF(ERROR, can_log) << "Unable to collect registers";
- return false;
- }
- return true;
- }
- bool GetFloatingPointRegisters64(pid_t tid,
- FloatContext* context,
- bool can_log) {
- iovec iov;
- iov.iov_base = context;
- iov.iov_len = sizeof(*context);
- if (ptrace(
- PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRFPREG), &iov) !=
- 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- if (iov.iov_len != sizeof(context->f64)) {
- LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
- << " != " << sizeof(context->f64);
- return false;
- }
- return true;
- }
- bool GetThreadArea32(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- #if defined(ARCH_CPU_ARMEL)
- void* result;
- if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- *address = FromPointerCast<LinuxVMAddress>(result);
- return true;
- #else
- // TODO(jperaza): it doesn't look like there is a way for a 64-bit ARM process
- // to get the thread area for a 32-bit ARM process with ptrace.
- LOG_IF(WARNING, can_log)
- << "64-bit ARM cannot trace TLS area for a 32-bit process";
- return false;
- #endif // ARCH_CPU_ARMEL
- }
- bool GetThreadArea64(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- iovec iov;
- iov.iov_base = address;
- iov.iov_len = sizeof(*address);
- if (ptrace(
- PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_ARM_TLS), &iov) !=
- 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- if (iov.iov_len != 8) {
- LOG_IF(ERROR, can_log) << "address size mismatch";
- return false;
- }
- return true;
- }
- #elif defined(ARCH_CPU_MIPS_FAMILY)
- // PTRACE_GETREGSET, introduced in Linux 2.6.34 (2225a122ae26), requires kernel
- // support enabled by HAVE_ARCH_TRACEHOOK. This has been set for x86 (including
- // x86_64) since Linux 2.6.28 (99bbc4b1e677a), but for MIPS only since
- // Linux 3.13 (c0ff3c53d4f99). Older Linux kernels support PTRACE_GETREGS,
- // and PTRACE_GETFPREGS instead, which don't allow checking the size of data
- // copied. Also, PTRACE_GETREGS assumes register size of 64 bits even for 32 bit
- // MIPS CPU (contrary to PTRACE_GETREGSET behavior), so we need buffer
- // structure here.
- bool GetGeneralPurposeRegistersLegacy(pid_t tid,
- ThreadContext* context,
- bool can_log) {
- ThreadContext context_buffer;
- if (ptrace(PTRACE_GETREGS, tid, nullptr, &context_buffer.t64) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- // Bitness of target process can't be determined through ptrace here, so we
- // assume target process has the same as current process, making cross-bit
- // ptrace unsupported on MIPS for kernels older than 3.13
- #if defined(ARCH_CPU_MIPSEL)
- #define THREAD_CONTEXT_FIELD t32
- #elif defined(ARCH_CPU_MIPS64EL)
- #define THREAD_CONTEXT_FIELD t64
- #endif
- for (size_t reg = 0; reg < 32; ++reg) {
- context->THREAD_CONTEXT_FIELD.regs[reg] = context_buffer.t64.regs[reg];
- }
- context->THREAD_CONTEXT_FIELD.lo = context_buffer.t64.lo;
- context->THREAD_CONTEXT_FIELD.hi = context_buffer.t64.hi;
- context->THREAD_CONTEXT_FIELD.cp0_epc = context_buffer.t64.cp0_epc;
- context->THREAD_CONTEXT_FIELD.cp0_badvaddr = context_buffer.t64.cp0_badvaddr;
- context->THREAD_CONTEXT_FIELD.cp0_status = context_buffer.t64.cp0_status;
- context->THREAD_CONTEXT_FIELD.cp0_cause = context_buffer.t64.cp0_cause;
- #undef THREAD_CONTEXT_FIELD
- return true;
- }
- bool GetFloatingPointRegistersLegacy(pid_t tid,
- FloatContext* context,
- bool can_log) {
- if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- return true;
- }
- bool GetFloatingPointRegisters32(pid_t tid,
- FloatContext* context,
- bool can_log) {
- iovec iov;
- iov.iov_base = &context->f32.fpregs;
- iov.iov_len = sizeof(context->f32.fpregs);
- if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) {
- switch (errno) {
- case EINVAL:
- // fp may not be present
- break;
- case EIO:
- return GetFloatingPointRegistersLegacy(tid, context, can_log);
- default:
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- }
- return true;
- }
- bool GetFloatingPointRegisters64(pid_t tid,
- FloatContext* context,
- bool can_log) {
- iovec iov;
- iov.iov_base = &context->f64.fpregs;
- iov.iov_len = sizeof(context->f64.fpregs);
- if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f64.fpregs) != 0) {
- switch (errno) {
- case EINVAL:
- // fp may not be present
- break;
- case EIO:
- return GetFloatingPointRegistersLegacy(tid, context, can_log);
- default:
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- }
- return true;
- }
- bool GetThreadArea32(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- #if defined(ARCH_CPU_MIPSEL)
- void* result;
- if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- *address = FromPointerCast<LinuxVMAddress>(result);
- return true;
- #else
- return false;
- #endif
- }
- bool GetThreadArea64(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- void* result;
- #if defined(ARCH_CPU_MIPSEL)
- if (ptrace(PTRACE_GET_THREAD_AREA_3264, tid, nullptr, &result) != 0) {
- #else
- if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) {
- #endif
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- *address = FromPointerCast<LinuxVMAddress>(result);
- return true;
- }
- #elif defined(ARCH_CPU_RISCV_FAMILY)
- template <typename Destination>
- bool GetRegisterSet(pid_t tid, int set, Destination* dest, bool can_log) {
- iovec iov;
- iov.iov_base = dest;
- iov.iov_len = sizeof(*dest);
- if (ptrace(PTRACE_GETREGSET, tid, reinterpret_cast<void*>(set), &iov) != 0) {
- PLOG_IF(ERROR, can_log) << "ptrace";
- return false;
- }
- if (iov.iov_len != sizeof(*dest)) {
- LOG_IF(ERROR, can_log) << "Unexpected registers size";
- return false;
- }
- return true;
- }
- bool GetFloatingPointRegisters32(pid_t tid,
- FloatContext* context,
- bool can_log) {
- return false;
- }
- bool GetFloatingPointRegisters64(pid_t tid,
- FloatContext* context,
- bool can_log) {
- return GetRegisterSet(tid, NT_PRFPREG, &context->f64.f, can_log);
- }
- bool GetThreadArea32(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- return false;
- }
- bool GetThreadArea64(pid_t tid,
- const ThreadContext& context,
- LinuxVMAddress* address,
- bool can_log) {
- *address = context.t64.tp;
- return true;
- }
- #else
- #error Port.
- #endif // ARCH_CPU_X86_FAMILY
- size_t GetGeneralPurposeRegistersAndLength(pid_t tid,
- ThreadContext* context,
- bool can_log) {
- iovec iov;
- iov.iov_base = context;
- iov.iov_len = sizeof(*context);
- if (ptrace(
- PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRSTATUS), &iov) !=
- 0) {
- switch (errno) {
- #if defined(ARCH_CPU_ARMEL) || defined(ARCH_CPU_MIPS_FAMILY)
- case EIO:
- return GetGeneralPurposeRegistersLegacy(tid, context, can_log)
- ? sizeof(context->t32)
- : 0;
- #endif // ARCH_CPU_ARMEL
- default:
- PLOG_IF(ERROR, can_log) << "ptrace";
- return 0;
- }
- }
- return iov.iov_len;
- }
- bool GetGeneralPurposeRegisters32(pid_t tid,
- ThreadContext* context,
- bool can_log) {
- size_t length = GetGeneralPurposeRegistersAndLength(tid, context, can_log);
- if (length != sizeof(context->t32)) {
- LOG_IF(ERROR, can_log) << "Unexpected registers size " << length
- << " != " << sizeof(context->t32);
- return false;
- }
- return true;
- }
- bool GetGeneralPurposeRegisters64(pid_t tid,
- ThreadContext* context,
- bool can_log) {
- size_t length = GetGeneralPurposeRegistersAndLength(tid, context, can_log);
- if (length != sizeof(context->t64)) {
- LOG_IF(ERROR, can_log) << "Unexpected registers size " << length
- << " != " << sizeof(context->t64);
- return false;
- }
- return true;
- }
- } // namespace
- Ptracer::Ptracer(bool can_log)
- : is_64_bit_(false), can_log_(can_log), initialized_() {}
- Ptracer::Ptracer(bool is_64_bit, bool can_log)
- : is_64_bit_(is_64_bit), can_log_(can_log), initialized_() {
- INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
- INITIALIZATION_STATE_SET_VALID(initialized_);
- }
- Ptracer::~Ptracer() {}
- bool Ptracer::Initialize(pid_t pid) {
- INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
- ThreadContext context;
- size_t length = GetGeneralPurposeRegistersAndLength(pid, &context, can_log_);
- if (length == sizeof(context.t64)) {
- is_64_bit_ = true;
- } else if (length == sizeof(context.t32)) {
- is_64_bit_ = false;
- } else {
- LOG_IF(ERROR, can_log_)
- << "Unexpected registers size " << length
- << " != " << sizeof(context.t64) << ", " << sizeof(context.t32);
- return false;
- }
- INITIALIZATION_STATE_SET_VALID(initialized_);
- return true;
- }
- bool Ptracer::Is64Bit() {
- INITIALIZATION_STATE_DCHECK_VALID(initialized_);
- return is_64_bit_;
- }
- bool Ptracer::GetThreadInfo(pid_t tid, ThreadInfo* info) {
- INITIALIZATION_STATE_DCHECK_VALID(initialized_);
- if (is_64_bit_) {
- return GetGeneralPurposeRegisters64(tid, &info->thread_context, can_log_) &&
- GetFloatingPointRegisters64(tid, &info->float_context, can_log_) &&
- GetThreadArea64(tid,
- info->thread_context,
- &info->thread_specific_data_address,
- can_log_);
- }
- return GetGeneralPurposeRegisters32(tid, &info->thread_context, can_log_) &&
- GetFloatingPointRegisters32(tid, &info->float_context, can_log_) &&
- GetThreadArea32(tid,
- info->thread_context,
- &info->thread_specific_data_address,
- can_log_);
- }
- ssize_t Ptracer::ReadUpTo(pid_t pid,
- LinuxVMAddress address,
- size_t size,
- char* buffer) {
- size_t bytes_read = 0;
- while (size > 0) {
- errno = 0;
- if (size >= sizeof(long)) {
- *reinterpret_cast<long*>(buffer) =
- ptrace(PTRACE_PEEKDATA, pid, address, nullptr);
- if (errno == EIO) {
- ssize_t last_bytes = ReadLastBytes(pid, address, size, buffer);
- return last_bytes >= 0 ? bytes_read + last_bytes : -1;
- }
- if (errno != 0) {
- PLOG_IF(ERROR, can_log_) << "ptrace";
- return -1;
- }
- size -= sizeof(long);
- buffer += sizeof(long);
- address += sizeof(long);
- bytes_read += sizeof(long);
- } else {
- long word = ptrace(PTRACE_PEEKDATA, pid, address, nullptr);
- if (errno == 0) {
- memcpy(buffer, reinterpret_cast<char*>(&word), size);
- return bytes_read + size;
- }
- if (errno == EIO) {
- ssize_t last_bytes = ReadLastBytes(pid, address, size, buffer);
- return last_bytes >= 0 ? bytes_read + last_bytes : -1;
- }
- PLOG_IF(ERROR, can_log_);
- return -1;
- }
- }
- return bytes_read;
- }
- // Handles an EIO by reading at most size bytes from address into buffer if
- // address was within a word of a possible page boundary, by aligning to read
- // the last word of the page and extracting the desired bytes.
- ssize_t Ptracer::ReadLastBytes(pid_t pid,
- LinuxVMAddress address,
- size_t size,
- char* buffer) {
- LinuxVMAddress aligned = ((address + 4095) & ~4095) - sizeof(long);
- if (aligned >= address || aligned == address - sizeof(long)) {
- PLOG_IF(ERROR, can_log_) << "ptrace";
- return -1;
- }
- DCHECK_GT(aligned, address - sizeof(long));
- errno = 0;
- long word = ptrace(PTRACE_PEEKDATA, pid, aligned, nullptr);
- if (errno != 0) {
- PLOG_IF(ERROR, can_log_) << "ptrace";
- return -1;
- }
- size_t bytes_read = address - aligned;
- size_t last_bytes = std::min(sizeof(long) - bytes_read, size);
- memcpy(buffer, reinterpret_cast<char*>(&word) + bytes_read, last_bytes);
- return last_bytes;
- }
- } // namespace crashpad
|