mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
linux: Provide ThreadInfo to collect register sets with ptrace
ThreadInfo provides a uniform interface to collect register sets or the thread-local storage address across bitness for x86 and ARM family architectures. Additionally, ThreadInfo.h defines context structs which mirror those provided in sys/user.h. This allows tracing across bitness as the structs in sys/user.h are only provided for a single target architecture. Bug: crashpad:30 Change-Id: I91d0d788927bdac5fb630a6ad3c6ea6d3645ef8a Reviewed-on: https://chromium-review.googlesource.com/494075 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
5ebd24e96e
commit
8fb23f2acc
@ -12,14 +12,27 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_COMPAT_ANDROID_ELF_H_
|
||||
#define CRASHPAD_COMPAT_ANDROID_ELF_H_
|
||||
#ifndef CRASHPAD_COMPAT_ANDROID_LINUX_ELF_H_
|
||||
#define CRASHPAD_COMPAT_ANDROID_LINUX_ELF_H_
|
||||
|
||||
#include_next <elf.h>
|
||||
#include_next <linux/elf.h>
|
||||
|
||||
// Android 5.0.0 (API 21) NDK
|
||||
#if !defined(NT_PRSTATUS)
|
||||
#define NT_PRSTATUS 1
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
#if !defined(NT_386_TLS)
|
||||
#define NT_386_TLS 0x200
|
||||
#endif
|
||||
#endif // __i386__ || __x86_64__
|
||||
|
||||
#if defined(__ARMEL__) || defined(__aarch64__)
|
||||
#if !defined(NT_ARM_VFP)
|
||||
#define NT_ARM_VFP 0x400
|
||||
#endif
|
||||
|
||||
#endif // CRASHPAD_COMPAT_ANDROID_ELF_H_
|
||||
#if !defined(NT_ARM_TLS)
|
||||
#define NT_ARM_TLS 0x401
|
||||
#endif
|
||||
#endif // __ARMEL__ || __aarch64__
|
||||
|
||||
#endif // CRASHPAD_COMPAT_ANDROID_LINUX_ELF_H_
|
332
util/linux/thread_info.cc
Normal file
332
util/linux/thread_info.cc
Normal file
@ -0,0 +1,332 @@
|
||||
// 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/thread_info.h"
|
||||
|
||||
#include <linux/elf.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "base/logging.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_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) {
|
||||
if (ptrace(PTRACE_GETREGS, tid, nullptr, &context->t32) != 0) {
|
||||
PLOG(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetFloatingPointRegistersLegacy(pid_t tid, FloatContext* context) {
|
||||
if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) {
|
||||
PLOG(ERROR) << "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(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
context->f32.have_vfp = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // ARCH_CPU_ARMEL
|
||||
|
||||
#if defined(ARCH_CPU_ARM_FAMILY)
|
||||
// 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) {
|
||||
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);
|
||||
#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(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (iov.iov_len != sizeof(context->f32.fpregs)) {
|
||||
LOG(ERROR) << "Unexpected registers size";
|
||||
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(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (iov.iov_len != kArmVfpSize && iov.iov_len != sizeof(context->f32.vfp)) {
|
||||
LOG(ERROR) << "Unexpected registers size";
|
||||
return false;
|
||||
}
|
||||
context->f32.have_vfp = true;
|
||||
}
|
||||
|
||||
if (!(context->f32.have_fpregs || context->f32.have_vfp)) {
|
||||
LOG(ERROR) << "Unable to collect registers";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Target is 64-bit
|
||||
bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) {
|
||||
iovec iov;
|
||||
iov.iov_base = context;
|
||||
iov.iov_len = sizeof(*context);
|
||||
if (ptrace(
|
||||
PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRFPREG), &iov) !=
|
||||
0) {
|
||||
PLOG(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
if (iov.iov_len != sizeof(context->f64)) {
|
||||
LOG(ERROR) << "Unexpected registers size";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // ARCH_CPU_ARM_FAMILY
|
||||
|
||||
} // namespace
|
||||
|
||||
ThreadContext::ThreadContext() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
ThreadContext::~ThreadContext() {}
|
||||
|
||||
FloatContext::FloatContext() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
FloatContext::~FloatContext() {}
|
||||
|
||||
ThreadInfo::ThreadInfo()
|
||||
: context_(), attachment_(), tid_(-1), initialized_(), is_64_bit_(false) {}
|
||||
|
||||
ThreadInfo::~ThreadInfo() {}
|
||||
|
||||
bool ThreadInfo::Initialize(pid_t tid) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
|
||||
if (!attachment_.ResetAttach(tid)) {
|
||||
return false;
|
||||
}
|
||||
tid_ = tid;
|
||||
|
||||
size_t length = GetGeneralPurposeRegistersAndLength(&context_);
|
||||
if (length == sizeof(context_.t64)) {
|
||||
is_64_bit_ = true;
|
||||
} else if (length == sizeof(context_.t32)) {
|
||||
is_64_bit_ = false;
|
||||
} else {
|
||||
LOG(ERROR) << "Unexpected registers size";
|
||||
return false;
|
||||
}
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThreadInfo::Is64Bit() {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return is_64_bit_;
|
||||
}
|
||||
|
||||
void ThreadInfo::GetGeneralPurposeRegisters(ThreadContext* context) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
*context = context_;
|
||||
}
|
||||
|
||||
size_t ThreadInfo::GetGeneralPurposeRegistersAndLength(ThreadContext* context) {
|
||||
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)
|
||||
case EIO:
|
||||
if (GetGeneralPurposeRegistersLegacy(tid_, context)) {
|
||||
return sizeof(context->t32);
|
||||
}
|
||||
#endif // ARCH_CPU_ARMEL
|
||||
default:
|
||||
PLOG(ERROR) << "ptrace";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return iov.iov_len;
|
||||
}
|
||||
|
||||
bool ThreadInfo::GetFloatingPointRegisters(FloatContext* context) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
iovec iov;
|
||||
iov.iov_base = context;
|
||||
iov.iov_len = sizeof(*context);
|
||||
if (ptrace(
|
||||
PTRACE_GETREGSET, tid_, reinterpret_cast<void*>(NT_PRFPREG), &iov) !=
|
||||
0) {
|
||||
PLOG(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
if (is_64_bit_ && iov.iov_len == sizeof(context->f64)) {
|
||||
return true;
|
||||
}
|
||||
if (!is_64_bit_ && iov.iov_len == sizeof(context->f32)) {
|
||||
return true;
|
||||
}
|
||||
LOG(ERROR) << "Unexpected registers size";
|
||||
return false;
|
||||
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
return is_64_bit_ ? GetFloatingPointRegisters64(tid_, context)
|
||||
: GetFloatingPointRegisters32(tid_, context);
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86_FAMILY
|
||||
}
|
||||
|
||||
bool ThreadInfo::GetThreadArea(LinuxVMAddress* address) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
if (is_64_bit_) {
|
||||
*address = context_.t64.fs_base;
|
||||
return true;
|
||||
}
|
||||
|
||||
user_desc desc;
|
||||
iovec iov;
|
||||
iov.iov_base = &desc;
|
||||
iov.iov_len = sizeof(desc);
|
||||
*address = 0;
|
||||
if (ptrace(
|
||||
PTRACE_GETREGSET, tid_, reinterpret_cast<void*>(NT_386_TLS), &iov) !=
|
||||
0) {
|
||||
PLOG(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
*address = desc.base_addr;
|
||||
return true;
|
||||
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
if (is_64_bit_) {
|
||||
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(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
if (iov.iov_len != 8) {
|
||||
LOG(ERROR) << "address size mismatch";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(ARCH_CPU_ARMEL)
|
||||
void* result;
|
||||
if (ptrace(PTRACE_GET_THREAD_AREA, tid_, nullptr, &result) != 0) {
|
||||
PLOG(ERROR) << "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(WARNING) << "64-bit ARM cannot trace TLS area for a 32-bit process";
|
||||
return false;
|
||||
#endif // ARCH_CPU_ARMEL
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86_FAMILY
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
284
util/linux/thread_info.h
Normal file
284
util/linux/thread_info.h
Normal file
@ -0,0 +1,284 @@
|
||||
// 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.
|
||||
|
||||
#ifndef CRASHPAD_UTIL_LINUX_THREAD_INFO_H_
|
||||
#define CRASHPAD_UTIL_LINUX_THREAD_INFO_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "util/linux/address_types.h"
|
||||
#include "util/linux/scoped_ptrace_attach.h"
|
||||
#include "util/misc/initialization_state_dcheck.h"
|
||||
#include "util/numeric/int128.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief The set of general purpose registers for an architecture family.
|
||||
union ThreadContext {
|
||||
ThreadContext();
|
||||
~ThreadContext();
|
||||
|
||||
//! \brief The general purpose registers used by the 32-bit variant of the
|
||||
//! architecture.
|
||||
struct t32 {
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
// Reflects user_regs_struct in sys/user.h.
|
||||
uint32_t ebx;
|
||||
uint32_t ecx;
|
||||
uint32_t edx;
|
||||
uint32_t esi;
|
||||
uint32_t edi;
|
||||
uint32_t ebp;
|
||||
uint32_t eax;
|
||||
uint32_t xds;
|
||||
uint32_t xes;
|
||||
uint32_t xfs;
|
||||
uint32_t xgs;
|
||||
uint32_t orig_eax;
|
||||
uint32_t eip;
|
||||
uint32_t xcs;
|
||||
uint32_t eflags;
|
||||
uint32_t esp;
|
||||
uint32_t xss;
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
// Reflects user_regs in sys/user.h.
|
||||
uint32_t regs[11];
|
||||
uint32_t fp;
|
||||
uint32_t ip;
|
||||
uint32_t sp;
|
||||
uint32_t lr;
|
||||
uint32_t pc;
|
||||
uint32_t cpsr;
|
||||
uint32_t orig_r0;
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86_FAMILY
|
||||
} t32;
|
||||
|
||||
//! \brief The general purpose registers used by the 64-bit variant of the
|
||||
//! architecture.
|
||||
struct t64 {
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
// Reflects user_regs_struct in sys/user.h.
|
||||
uint64_t r15;
|
||||
uint64_t r14;
|
||||
uint64_t r13;
|
||||
uint64_t r12;
|
||||
uint64_t rbp;
|
||||
uint64_t rbx;
|
||||
uint64_t r11;
|
||||
uint64_t r10;
|
||||
uint64_t r9;
|
||||
uint64_t r8;
|
||||
uint64_t rax;
|
||||
uint64_t rcx;
|
||||
uint64_t rdx;
|
||||
uint64_t rsi;
|
||||
uint64_t rdi;
|
||||
uint64_t orig_rax;
|
||||
uint64_t rip;
|
||||
uint64_t cs;
|
||||
uint64_t eflags;
|
||||
uint64_t rsp;
|
||||
uint64_t ss;
|
||||
uint64_t fs_base;
|
||||
uint64_t gs_base;
|
||||
uint64_t ds;
|
||||
uint64_t es;
|
||||
uint64_t fs;
|
||||
uint64_t gs;
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
// Reflects user_regs_struct in sys/user.h.
|
||||
uint64_t regs[31];
|
||||
uint64_t sp;
|
||||
uint64_t pc;
|
||||
uint64_t pstate;
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86_FAMILY
|
||||
} t64;
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
using NativeThreadContext = user_regs_struct;
|
||||
#elif defined(ARCH_CPU_ARMEL)
|
||||
using NativeThreadContext = user_regs;
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86_FAMILY || ARCH_CPU_ARM64
|
||||
|
||||
#if defined(ARCH_CPU_32_BITS)
|
||||
static_assert(sizeof(t32) == sizeof(NativeThreadContext), "Size mismatch");
|
||||
#else // ARCH_CPU_64_BITS
|
||||
static_assert(sizeof(t64) == sizeof(NativeThreadContext), "Size mismatch");
|
||||
#endif // ARCH_CPU_32_BITS
|
||||
};
|
||||
static_assert(std::is_standard_layout<ThreadContext>::value,
|
||||
"Not standard layout");
|
||||
|
||||
//! \brief The floating point registers used for an architecture family.
|
||||
union FloatContext {
|
||||
FloatContext();
|
||||
~FloatContext();
|
||||
|
||||
//! \brief The floating point registers used by the 32-bit variant of the
|
||||
//! architecture.
|
||||
struct f32 {
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
// Reflects user_fpregs_struct in sys/user.h.
|
||||
uint32_t cwd;
|
||||
uint32_t swd;
|
||||
uint32_t twd;
|
||||
uint32_t fip;
|
||||
uint32_t fcs;
|
||||
uint32_t foo;
|
||||
uint32_t fos;
|
||||
uint32_t st_space[20];
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
// Reflects user_fpregs in sys/user.h.
|
||||
struct fpregs {
|
||||
struct fp_reg {
|
||||
uint32_t sign1 : 1;
|
||||
uint32_t unused : 15;
|
||||
uint32_t sign2 : 1;
|
||||
uint32_t exponent : 14;
|
||||
uint32_t j : 1;
|
||||
uint32_t mantissa1 : 31;
|
||||
uint32_t mantisss0 : 32;
|
||||
} fpregs[8];
|
||||
uint32_t fpsr : 32;
|
||||
uint32_t fpcr : 32;
|
||||
uint8_t type[8];
|
||||
uint32_t init_flag;
|
||||
} fpregs;
|
||||
|
||||
// Reflects user_vfp in sys/user.h.
|
||||
struct vfp {
|
||||
uint64_t fpregs[32];
|
||||
uint32_t fpscr;
|
||||
} vfp;
|
||||
|
||||
bool have_fpregs;
|
||||
bool have_vfp;
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86_FAMILY
|
||||
} f32;
|
||||
|
||||
//! \brief The floating point registers used by the 64-bit variant of the
|
||||
//! architecture.
|
||||
struct f64 {
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
uint16_t cwd;
|
||||
uint16_t swd;
|
||||
uint16_t ftw;
|
||||
uint16_t fop;
|
||||
uint64_t rip;
|
||||
uint64_t rdp;
|
||||
uint32_t mxcsr;
|
||||
uint32_t mxcr_mask;
|
||||
uint32_t st_space[32];
|
||||
uint32_t xmm_space[64];
|
||||
uint32_t padding[24];
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
uint128_struct vregs[32];
|
||||
uint32_t fpsr;
|
||||
uint32_t fpcr;
|
||||
uint8_t padding[8];
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86_FAMILY
|
||||
} f64;
|
||||
|
||||
#if defined(ARCH_CPU_X86)
|
||||
static_assert(sizeof(f32) == sizeof(user_fpregs_struct), "Size mismatch");
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
static_assert(sizeof(f64) == sizeof(user_fpregs_struct), "Size mismatch");
|
||||
#elif defined(ARCH_CPU_ARMEL)
|
||||
static_assert(sizeof(f32::fpregs) == sizeof(user_fpregs), "Size mismatch");
|
||||
static_assert(sizeof(f32::vfp) == sizeof(user_vfp), "Size mismatch");
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
static_assert(sizeof(f64) == sizeof(user_fpsimd_struct), "Size mismatch");
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_X86
|
||||
};
|
||||
static_assert(std::is_standard_layout<FloatContext>::value,
|
||||
"Not standard layout");
|
||||
|
||||
class ThreadInfo {
|
||||
public:
|
||||
ThreadInfo();
|
||||
~ThreadInfo();
|
||||
|
||||
//! \brief Initializes this object with information about the thread whose ID
|
||||
//! is \a tid.
|
||||
//!
|
||||
//! This method must be called successfully prior to calling any other method
|
||||
//! in this class. This method may only be called once.
|
||||
//!
|
||||
//! It is unspecified whether the information that an object of this class
|
||||
//! returns is loaded at the time Initialize() is called or subsequently, and
|
||||
//! whether this information is cached in the object or not.
|
||||
//!
|
||||
//! \param[in] tid The thread ID to obtain information for.
|
||||
//!
|
||||
//! \return `true` on success, `false` on failure with a message logged.
|
||||
bool Initialize(pid_t tid);
|
||||
|
||||
//! \brief Determines the target thread’s bitness.
|
||||
//!
|
||||
//! \return `true` if the target is 64-bit.
|
||||
bool Is64Bit();
|
||||
|
||||
//! \brief Uses `ptrace` to collect general purpose registers from the target
|
||||
//! thread and places the result in \a context.
|
||||
//!
|
||||
//! \param[out] context The registers read from the target thread.
|
||||
void GetGeneralPurposeRegisters(ThreadContext* context);
|
||||
|
||||
//! \brief Uses `ptrace` to collect floating point registers from the target
|
||||
//! thread and places the result in \a context.
|
||||
//!
|
||||
//! \param[out] context The registers read from the target thread.
|
||||
//!
|
||||
//! \return `true` on success, with \a context set. Otherwise, `false` with a
|
||||
//! message logged.
|
||||
bool GetFloatingPointRegisters(FloatContext* context);
|
||||
|
||||
//! \brief Uses `ptrace` to determine the thread-local storage address for the
|
||||
//! target thread and places the result in \a address.
|
||||
//!
|
||||
//! \param[out] address The address of the TLS area.
|
||||
//!
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
bool GetThreadArea(LinuxVMAddress* address);
|
||||
|
||||
private:
|
||||
size_t GetGeneralPurposeRegistersAndLength(ThreadContext* context);
|
||||
|
||||
ThreadContext context_;
|
||||
ScopedPtraceAttach attachment_;
|
||||
pid_t tid_;
|
||||
InitializationStateDcheck initialized_;
|
||||
bool is_64_bit_;
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_LINUX_THREAD_INFO_H_
|
100
util/linux/thread_info_test.cc
Normal file
100
util/linux/thread_info_test.cc
Normal file
@ -0,0 +1,100 @@
|
||||
// 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/thread_info.h"
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
class SameBitnessTest : public Multiprocess {
|
||||
public:
|
||||
SameBitnessTest() : Multiprocess() {}
|
||||
~SameBitnessTest() {}
|
||||
|
||||
private:
|
||||
void MultiprocessParent() override {
|
||||
LinuxVMAddress expected_tls;
|
||||
CheckedReadFileExactly(
|
||||
ReadPipeHandle(), &expected_tls, sizeof(expected_tls));
|
||||
|
||||
ThreadInfo thread_info;
|
||||
ASSERT_TRUE(thread_info.Initialize(ChildPID()));
|
||||
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
const bool am_64_bit = true;
|
||||
#else
|
||||
const bool am_64_bit = false;
|
||||
#endif // ARCH_CPU_64_BITS
|
||||
|
||||
EXPECT_EQ(thread_info.Is64Bit(), am_64_bit);
|
||||
|
||||
ThreadContext thread_context;
|
||||
thread_info.GetGeneralPurposeRegisters(&thread_context);
|
||||
|
||||
#if defined(ARCH_CPU_X86_64)
|
||||
EXPECT_EQ(thread_context.t64.fs_base, expected_tls);
|
||||
#endif // ARCH_CPU_X86_64
|
||||
|
||||
FloatContext float_context;
|
||||
EXPECT_TRUE(thread_info.GetFloatingPointRegisters(&float_context));
|
||||
|
||||
LinuxVMAddress tls_address;
|
||||
ASSERT_TRUE(thread_info.GetThreadArea(&tls_address));
|
||||
EXPECT_EQ(tls_address, expected_tls);
|
||||
}
|
||||
|
||||
void MultiprocessChild() override {
|
||||
LinuxVMAddress expected_tls;
|
||||
#if defined(ARCH_CPU_ARMEL)
|
||||
// 0xffff0fe0 is the address of the kernel user helper __kuser_get_tls().
|
||||
auto kuser_get_tls = reinterpret_cast<void* (*)()>(0xffff0fe0);
|
||||
expected_tls = FromPointerCast<LinuxVMAddress>(kuser_get_tls());
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
// Linux/aarch64 places the tls address in system register tpidr_el0.
|
||||
asm("mrs %0, tpidr_el0" : "=r"(expected_tls));
|
||||
#elif defined(ARCH_CPU_X86)
|
||||
uint32_t expected_tls_32;
|
||||
asm("movl %%gs:0x0, %0" : "=r"(expected_tls_32));
|
||||
expected_tls = expected_tls_32;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
asm("movq %%fs:0x0, %0" : "=r"(expected_tls));
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_ARMEL
|
||||
|
||||
CheckedWriteFile(WritePipeHandle(), &expected_tls, sizeof(expected_tls));
|
||||
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SameBitnessTest);
|
||||
};
|
||||
|
||||
TEST(ThreadInfo, SameBitness) {
|
||||
SameBitnessTest test;
|
||||
test.Run();
|
||||
}
|
||||
|
||||
// TODO(jperaza): Test against a process with different bitness.
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -15,12 +15,8 @@
|
||||
#include "util/posix/process_info.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/user.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@ -33,7 +29,7 @@
|
||||
#include "util/file/delimited_file_reader.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/file/file_reader.h"
|
||||
#include "util/linux/scoped_ptrace_attach.h"
|
||||
#include "util/linux/thread_info.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
@ -305,57 +301,11 @@ bool ProcessInfo::Is64Bit(bool* is_64_bit) const {
|
||||
if (pid_ == getpid()) {
|
||||
is_64_bit_ = am_64_bit;
|
||||
} else {
|
||||
ScopedPtraceAttach ptrace_attach;
|
||||
if (!ptrace_attach.ResetAttach(pid_)) {
|
||||
ThreadInfo thread_info;
|
||||
if (!thread_info.Initialize(pid_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate more buffer space than is required to hold registers for this
|
||||
// process. If the kernel fills the extra space, the target process uses
|
||||
// more/larger registers than this process. If the kernel fills less space
|
||||
// than sizeof(regs) then the target process uses smaller/fewer registers.
|
||||
struct {
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
using PrStatusType = user_regs_struct;
|
||||
#elif defined(ARCH_CPU_ARMEL)
|
||||
using PrStatusType = user_regs;
|
||||
#endif
|
||||
PrStatusType regs;
|
||||
char extra;
|
||||
} regbuf;
|
||||
|
||||
iovec iov;
|
||||
iov.iov_base = ®buf;
|
||||
iov.iov_len = sizeof(regbuf);
|
||||
if (ptrace(PTRACE_GETREGSET,
|
||||
pid_,
|
||||
reinterpret_cast<void*>(NT_PRSTATUS),
|
||||
&iov) != 0) {
|
||||
switch (errno) {
|
||||
#if defined(ARCH_CPU_ARMEL)
|
||||
case EIO:
|
||||
// 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). 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.
|
||||
is_64_bit_ = false;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
PLOG(ERROR) << "ptrace";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
is_64_bit_ = am_64_bit == (iov.iov_len == sizeof(regbuf.regs));
|
||||
}
|
||||
is_64_bit_ = thread_info.Is64Bit();
|
||||
}
|
||||
|
||||
is_64_bit_initialized_.set_valid();
|
||||
|
@ -50,6 +50,8 @@
|
||||
'linux/memory_map.h',
|
||||
'linux/process_memory.cc',
|
||||
'linux/process_memory.h',
|
||||
'linux/thread_info.cc',
|
||||
'linux/thread_info.h',
|
||||
'linux/scoped_ptrace_attach.cc',
|
||||
'linux/scoped_ptrace_attach.h',
|
||||
'mac/checked_mach_address_range.h',
|
||||
|
@ -41,6 +41,7 @@
|
||||
'file/string_file_test.cc',
|
||||
'linux/memory_map_test.cc',
|
||||
'linux/process_memory_test.cc',
|
||||
'linux/thread_info_test.cc',
|
||||
'linux/scoped_ptrace_attach_test.cc',
|
||||
'mac/launchd_test.mm',
|
||||
'mac/mac_util_test.mm',
|
||||
|
Loading…
x
Reference in New Issue
Block a user