mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-29 08:39:44 +08:00
5536baff13
Linux supports TLS on x86 by allocating slots in the GDT, accessible via the system calls get/set_thread_area. This allows segment registers (%gs on x86) to be used to quickly access the TLS. Previously, we used PTRACE_GETREGSET with the NT_386_TLS regset. This "register set" provides access to the subarray of the GDT used for TLS. However, there are multiple slots provided and we don't know which one is being used by the threading library for the current thread's TLS. Previously, we were just using the first one, which worked for x86 on 64-bit kernels, but not 32-bit kernels. On 32-bit kernels, the first slot ended up pointing to the TLS of the main thread. The authoritative index of the current thread's TLS in the GDT is given by bits 3-15 of %gs. However, this index cannot be used with PTRACE_GETREGSET+NT386_TLS because we don't know the location of the TLS slots in the GDT. PTRACE_GET_THREAD_AREA, however, accepts an index from the start of the GDT similarly to get/set_thread_area. Bug: crashpad:30 Change-Id: Ie6dfbdd088c6816fad409812a1a97037d4b38fd7 Reviewed-on: https://chromium-review.googlesource.com/575318 Reviewed-by: Mark Mentovai <mark@chromium.org>
338 lines
9.2 KiB
C++
338 lines
9.2 KiB
C++
// 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_X86_FAMILY)
|
||
|
||
template <typename Destination>
|
||
bool GetRegisterSet(pid_t tid, int set, Destination* dest) {
|
||
iovec iov;
|
||
iov.iov_base = dest;
|
||
iov.iov_len = sizeof(*dest);
|
||
if (ptrace(PTRACE_GETREGSET, tid, reinterpret_cast<void*>(set), &iov) != 0) {
|
||
PLOG(ERROR) << "ptrace";
|
||
return false;
|
||
}
|
||
if (iov.iov_len != sizeof(*dest)) {
|
||
LOG(ERROR) << "Unexpected registers size";
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) {
|
||
return GetRegisterSet(tid, NT_PRXFPREG, &context->f32.fxsave);
|
||
}
|
||
|
||
bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) {
|
||
return GetRegisterSet(tid, NT_PRFPREG, &context->f64.fxsave);
|
||
}
|
||
|
||
#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) {
|
||
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
|
||
|
||
// 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;
|
||
}
|
||
#else
|
||
#error Port.
|
||
#endif // ARCH_CPU_X86_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_);
|
||
|
||
return is_64_bit_ ? GetFloatingPointRegisters64(tid_, context)
|
||
: GetFloatingPointRegisters32(tid_, context);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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(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
|