linux: Refactor ptrace usage.

1) Add PtraceConnection which serves as the base class for specific
types of connections Crashpad uses to trace processes.
2) Add DirectPtraceConnection which is used when the handler process
has `ptrace` capabilities for the target process.
3) Move `ptrace` logic into Ptracer. This class isolates `ptrace` call
logic for use by various PtraceConnection implementations.

Bug: crashpad:30
Change-Id: I98083134a9f7d9f085e4cc816d2b85ffd6d73162
Reviewed-on: https://chromium-review.googlesource.com/671659
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Reviewed-by: Leonard Mosescu <mosescu@chromium.org>
This commit is contained in:
Joshua Peraza 2017-09-22 09:14:52 -07:00
parent be7b8a509c
commit 59c5d848e5
26 changed files with 944 additions and 502 deletions

View File

@ -28,6 +28,7 @@
#include "snapshot/linux/process_reader.h"
#include "sys/syscall.h"
#include "test/errors.h"
#include "test/linux/fake_ptrace_connection.h"
#include "util/linux/address_types.h"
#include "util/misc/clock.h"
#include "util/misc/from_pointer_cast.h"
@ -96,8 +97,11 @@ void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) {
#endif
TEST(ExceptionSnapshotLinux, SelfBasic) {
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(getpid()));
ASSERT_TRUE(process_reader.Initialize(&connection));
siginfo_t siginfo;
siginfo.si_signo = SIGSEGV;
@ -170,8 +174,11 @@ class RaiseTest {
private:
static void HandleRaisedSignal(int signo, siginfo_t* siginfo, void* context) {
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(getpid()));
ASSERT_TRUE(process_reader.Initialize(&connection));
internal::ExceptionSnapshotLinux exception;
ASSERT_TRUE(exception.Initialize(&process_reader,
@ -230,8 +237,11 @@ class TimerTest {
private:
static void HandleTimer(int signo, siginfo_t* siginfo, void* context) {
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(getpid()));
ASSERT_TRUE(process_reader.Initialize(&connection));
internal::ExceptionSnapshotLinux exception;
ASSERT_TRUE(exception.Initialize(&process_reader,

View File

@ -20,6 +20,7 @@
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
#include <algorithm>
@ -43,9 +44,7 @@ bool ShouldMergeStackMappings(const MemoryMap::Mapping& stack_mapping,
} // namespace
ProcessReader::Thread::Thread()
: thread_context(),
float_context(),
thread_specific_data_address(0),
: thread_info(),
stack_region_address(0),
stack_region_size(0),
tid(-1),
@ -54,19 +53,8 @@ ProcessReader::Thread::Thread()
ProcessReader::Thread::~Thread() {}
bool ProcessReader::Thread::InitializePtrace() {
ThreadInfo thread_info;
if (!thread_info.Initialize(tid)) {
return false;
}
thread_info.GetGeneralPurposeRegisters(&thread_context);
if (!thread_info.GetFloatingPointRegisters(&float_context)) {
return false;
}
if (!thread_info.GetThreadArea(&thread_specific_data_address)) {
bool ProcessReader::Thread::InitializePtrace(PtraceConnection* connection) {
if (!connection->GetThreadInfo(tid, &thread_info)) {
return false;
}
@ -101,11 +89,11 @@ bool ProcessReader::Thread::InitializePtrace() {
void ProcessReader::Thread::InitializeStack(ProcessReader* reader) {
LinuxVMAddress stack_pointer;
#if defined(ARCH_CPU_X86_FAMILY)
stack_pointer =
reader->Is64Bit() ? thread_context.t64.rsp : thread_context.t32.esp;
stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.rsp
: thread_info.thread_context.t32.esp;
#elif defined(ARCH_CPU_ARM_FAMILY)
stack_pointer =
reader->Is64Bit() ? thread_context.t64.sp : thread_context.t32.sp;
stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.sp
: thread_info.thread_context.t32.sp;
#else
#error Port.
#endif
@ -171,14 +159,16 @@ void ProcessReader::Thread::InitializeStack(ProcessReader* reader) {
// the stack region.
stack_region_size = stack_end - stack_region_address;
if (tid != reader->ProcessID() &&
thread_specific_data_address > stack_region_address &&
thread_specific_data_address < stack_end) {
stack_region_size = thread_specific_data_address - stack_region_address;
thread_info.thread_specific_data_address > stack_region_address &&
thread_info.thread_specific_data_address < stack_end) {
stack_region_size =
thread_info.thread_specific_data_address - stack_region_address;
}
}
ProcessReader::ProcessReader()
: process_info_(),
: connection_(),
process_info_(),
memory_map_(),
threads_(),
process_memory_(),
@ -188,12 +178,16 @@ ProcessReader::ProcessReader()
ProcessReader::~ProcessReader() {}
bool ProcessReader::Initialize(pid_t pid) {
bool ProcessReader::Initialize(PtraceConnection* connection) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
if (!process_info_.Initialize(pid)) {
DCHECK(connection);
connection_ = connection;
if (!process_info_.InitializeWithPtrace(connection_)) {
return false;
}
pid_t pid = connection->GetProcessID();
if (!memory_map_.Initialize(pid)) {
return false;
}
@ -203,9 +197,7 @@ bool ProcessReader::Initialize(pid_t pid) {
return false;
}
if (!process_info_.Is64Bit(&is_64_bit_)) {
return false;
}
is_64_bit_ = process_info_.Is64Bit();
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
@ -283,7 +275,7 @@ void ProcessReader::InitializeThreads() {
Thread main_thread;
main_thread.tid = pid;
if (main_thread.InitializePtrace()) {
if (main_thread.InitializePtrace(connection_)) {
main_thread.InitializeStack(this);
threads_.push_back(main_thread);
} else {
@ -311,7 +303,7 @@ void ProcessReader::InitializeThreads() {
Thread thread;
thread.tid = tid;
if (thread.InitializePtrace()) {
if (connection_->Attach(tid) && thread.InitializePtrace(connection_)) {
thread.InitializeStack(this);
threads_.push_back(thread);
}

View File

@ -25,6 +25,7 @@
#include "util/linux/address_types.h"
#include "util/linux/memory_map.h"
#include "util/linux/process_memory.h"
#include "util/linux/ptrace_connection.h"
#include "util/linux/thread_info.h"
#include "util/posix/process_info.h"
#include "util/misc/initialization_state_dcheck.h"
@ -40,9 +41,7 @@ class ProcessReader {
Thread();
~Thread();
ThreadContext thread_context;
FloatContext float_context;
LinuxVMAddress thread_specific_data_address;
ThreadInfo thread_info;
LinuxVMAddress stack_region_address;
LinuxVMSize stack_region_size;
pid_t tid;
@ -53,7 +52,7 @@ class ProcessReader {
private:
friend class ProcessReader;
bool InitializePtrace();
bool InitializePtrace(PtraceConnection* connection);
void InitializeStack(ProcessReader* reader);
};
@ -63,11 +62,11 @@ class ProcessReader {
//! \brief Initializes this object.
//!
//! This method must be successfully called before calling any other method in
//! this class.
//! this class and may only be called once.
//!
//! \param[in] pid The process ID of the target process.
//! \param[in] connection A PtraceConnection to the target process.
//! \return `true` on success. `false` on failure with a message logged.
bool Initialize(pid_t pid);
bool Initialize(PtraceConnection* connection);
//! \brief Return `true` if the target task is a 64-bit process.
bool Is64Bit() const { return is_64_bit_; }
@ -111,6 +110,7 @@ class ProcessReader {
private:
void InitializeThreads();
PtraceConnection* connection_; // weak
ProcessInfo process_info_;
class MemoryMap memory_map_;
std::vector<Thread> threads_;

View File

@ -32,8 +32,10 @@
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/linux/fake_ptrace_connection.h"
#include "test/multiprocess.h"
#include "util/file/file_io.h"
#include "util/linux/direct_ptrace_connection.h"
#include "util/misc/from_pointer_cast.h"
#include "util/stdlib/pointer_container.h"
#include "util/synchronization/semaphore.h"
@ -69,13 +71,16 @@ LinuxVMAddress GetTLS() {
}
TEST(ProcessReader, SelfBasic) {
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(getpid()));
FakePtraceConnection connection;
connection.Initialize(getpid());
#if !defined(ARCH_CPU_64_BITS)
EXPECT_FALSE(process_reader.Is64Bit());
#else
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(&connection));
#if defined(ARCH_CPU_64_BITS)
EXPECT_TRUE(process_reader.Is64Bit());
#else
EXPECT_FALSE(process_reader.Is64Bit());
#endif
EXPECT_EQ(process_reader.ProcessID(), getpid());
@ -99,8 +104,11 @@ class BasicChildTest : public Multiprocess {
private:
void MultiprocessParent() override {
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(ChildPID()));
ASSERT_TRUE(process_reader.Initialize(&connection));
#if !defined(ARCH_CPU_64_BITS)
EXPECT_FALSE(process_reader.Is64Bit());
@ -255,18 +263,19 @@ void ExpectThreads(const ThreadMap& thread_map,
ASSERT_TRUE(memory_map.Initialize(pid));
for (const auto& thread : threads) {
SCOPED_TRACE(base::StringPrintf("Thread id %d, tls 0x%" PRIx64
", stack addr 0x%" PRIx64
", stack size 0x%" PRIx64,
SCOPED_TRACE(
base::StringPrintf("Thread id %d, tls 0x%" PRIx64
", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64,
thread.tid,
thread.thread_specific_data_address,
thread.thread_info.thread_specific_data_address,
thread.stack_region_address,
thread.stack_region_size));
const auto& iterator = thread_map.find(thread.tid);
ASSERT_NE(iterator, thread_map.end());
EXPECT_EQ(thread.thread_specific_data_address, iterator->second.tls);
EXPECT_EQ(thread.thread_info.thread_specific_data_address,
iterator->second.tls);
ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address));
EXPECT_LE(thread.stack_region_address, iterator->second.stack_address);
@ -305,8 +314,11 @@ class ChildThreadTest : public Multiprocess {
thread_map[tid] = expectation;
}
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(ChildPID()));
ASSERT_TRUE(process_reader.Initialize(&connection));
const std::vector<ProcessReader::Thread>& threads =
process_reader.Threads();
ExpectThreads(thread_map, threads, ChildPID());
@ -379,8 +391,11 @@ class ChildWithSplitStackTest : public Multiprocess {
CheckedReadFileExactly(ReadPipeHandle(), &stack_addr2, sizeof(stack_addr2));
CheckedReadFileExactly(ReadPipeHandle(), &stack_addr3, sizeof(stack_addr3));
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(ChildPID()));
ASSERT_TRUE(process_reader.Initialize(&connection));
const std::vector<ProcessReader::Thread>& threads =
process_reader.Threads();

View File

@ -15,6 +15,7 @@
#include "snapshot/linux/system_snapshot_linux.h"
#include <sys/time.h>
#include <unistd.h>
#include <string>
@ -22,14 +23,18 @@
#include "gtest/gtest.h"
#include "snapshot/linux/process_reader.h"
#include "test/errors.h"
#include "test/linux/fake_ptrace_connection.h"
namespace crashpad {
namespace test {
namespace {
TEST(SystemSnapshotLinux, Basic) {
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(getpid()));
ASSERT_TRUE(process_reader.Initialize(&connection));
timeval snapshot_time;
ASSERT_EQ(gettimeofday(&snapshot_time, nullptr), 0)

View File

@ -46,14 +46,14 @@ bool ThreadSnapshotLinux::Initialize(
if (process_reader->Is64Bit()) {
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
InitializeCPUContextX86_64(thread.thread_context.t64,
thread.float_context.f64,
InitializeCPUContextX86_64(thread.thread_info.thread_context.t64,
thread.thread_info.float_context.f64,
context_.x86_64);
} else {
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeCPUContextX86(thread.thread_context.t32,
thread.float_context.f32,
InitializeCPUContextX86(thread.thread_info.thread_context.t32,
thread.thread_info.float_context.f32,
context_.x86);
}
#else
@ -65,7 +65,7 @@ bool ThreadSnapshotLinux::Initialize(
thread.stack_region_size);
thread_specific_data_address_ =
thread.thread_specific_data_address;
thread.thread_info.thread_specific_data_address;
thread_id_ = thread.tid;

View File

@ -112,13 +112,11 @@ ProcessReader::~ProcessReader() {
bool ProcessReader::Initialize(task_t task) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
if (!process_info_.InitializeFromTask(task)) {
if (!process_info_.InitializeWithTask(task)) {
return false;
}
if (!process_info_.Is64Bit(&is_64_bit_)) {
return false;
}
is_64_bit_ = process_info_.Is64Bit();
task_memory_.reset(new TaskMemory(task));
task_ = task;

View File

@ -0,0 +1,74 @@
// 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 "test/linux/fake_ptrace_connection.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
namespace crashpad {
namespace test {
FakePtraceConnection::FakePtraceConnection()
: PtraceConnection(),
attachments_(),
pid_(-1),
is_64_bit_(false),
initialized_() {}
FakePtraceConnection::~FakePtraceConnection() {}
bool FakePtraceConnection::Initialize(pid_t pid) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
if (!Attach(pid)) {
return false;
}
pid_ = pid;
#if defined(ARCH_CPU_64_BITS)
is_64_bit_ = true;
#else
is_64_bit_ = false;
#endif
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
pid_t FakePtraceConnection::GetProcessID() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return pid_;
}
bool FakePtraceConnection::Attach(pid_t tid) {
bool inserted = attachments_.insert(tid).second;
EXPECT_TRUE(inserted);
return inserted;
}
bool FakePtraceConnection::Is64Bit() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return is_64_bit_;
}
bool FakePtraceConnection::GetThreadInfo(pid_t tid, ThreadInfo* info) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
bool attached = attachments_.find(tid) != attachments_.end();
EXPECT_TRUE(attached);
return attached;
}
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,68 @@
// 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_TEST_LINUX_FAKE_PTRACE_CONNECTION_H_
#define CRASHPAD_TEST_LINUX_FAKE_PTRACE_CONNECTION_H_
#include <sys/types.h>
#include <set>
#include "base/macros.h"
#include "util/linux/ptrace_connection.h"
#include "util/misc/initialization_state_dcheck.h"
namespace crashpad {
namespace test {
//! \brief Stands-in where real PtraceConnections aren't available.
//!
//! This class performs basic EXPECTs that it is used correctly, but does not
//! execute any real `ptrace` calls or attachments.
class FakePtraceConnection : public PtraceConnection {
public:
FakePtraceConnection();
~FakePtraceConnection();
//! \brief Initializes this connection for the process whose process ID is
//! \a pid.
//!
//! \param[in] pid The process ID of the process to connect to.
//! \return `true` on success. `false` on failure with a message logged.
bool Initialize(pid_t pid);
// PtraceConnection:
pid_t GetProcessID() override;
bool Attach(pid_t tid) override;
//! \brief Returns `true` if the current process is 64-bit.
bool Is64Bit() override;
//! \brief Does not modify \a info.
bool GetThreadInfo(pid_t tid, ThreadInfo* info) override;
private:
std::set<pid_t> attachments_;
pid_t pid_;
bool is_64_bit_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(FakePtraceConnection);
};
} // namespace test
} // namespace crashpad
#endif // CRASHPAD_TEST_LINUX_FAKE_PTRACE_CONNECTION_H_

View File

@ -38,6 +38,8 @@
'gtest_death_check.h',
'hex_string.cc',
'hex_string.h',
'linux/fake_ptrace_connection.cc',
'linux/fake_ptrace_connection.h',
'mac/dyld.cc',
'mac/dyld.h',
'mac/mach_errors.cc',

View File

@ -0,0 +1,66 @@
// 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/direct_ptrace_connection.h"
#include <memory>
namespace crashpad {
DirectPtraceConnection::DirectPtraceConnection()
: PtraceConnection(),
attachments_(),
pid_(-1),
ptracer_(),
initialized_() {}
DirectPtraceConnection::~DirectPtraceConnection() {}
bool DirectPtraceConnection::Initialize(pid_t pid) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
if (!Attach(pid) || !ptracer_.Initialize(pid)) {
return false;
}
pid_ = pid;
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
pid_t DirectPtraceConnection::GetProcessID() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return pid_;
}
bool DirectPtraceConnection::Attach(pid_t tid) {
std::unique_ptr<ScopedPtraceAttach> attach(new ScopedPtraceAttach);
if (!attach->ResetAttach(tid)) {
return false;
}
attachments_.push_back(attach.release());
return true;
}
bool DirectPtraceConnection::Is64Bit() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return ptracer_.Is64Bit();
}
bool DirectPtraceConnection::GetThreadInfo(pid_t tid, ThreadInfo* info) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return ptracer_.GetThreadInfo(tid, info);
}
} // namespace crashpad

View File

@ -0,0 +1,65 @@
// 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_DIRECT_PTRACE_CONNECTION_H_
#define CRASHPAD_UTIL_LINUX_DIRECT_PTRACE_CONNECTION_H_
#include <sys/types.h>
#include "base/macros.h"
#include "util/linux/ptrace_connection.h"
#include "util/linux/ptracer.h"
#include "util/linux/scoped_ptrace_attach.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/stdlib/pointer_container.h"
namespace crashpad {
//! \brief Manages a direct `ptrace` connection to a process.
//!
//! This class is used when the current process has `ptrace` capabilities for
//! the target process.
class DirectPtraceConnection : public PtraceConnection {
public:
DirectPtraceConnection();
~DirectPtraceConnection();
//! \brief Initializes this connection for the process whose process ID is
//! \a pid.
//!
//! The main thread of the process is automatically attached by this call.
//!
//! \param[in] pid The process ID of the process to connect to.
//! \return `true` on success. `false` on failure with a message logged.
bool Initialize(pid_t pid);
// PtraceConnection:
pid_t GetProcessID() override;
bool Attach(pid_t tid) override;
bool Is64Bit() override;
bool GetThreadInfo(pid_t tid, ThreadInfo* info) override;
private:
PointerVector<ScopedPtraceAttach> attachments_;
pid_t pid_;
Ptracer ptracer_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(DirectPtraceConnection);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_LINUX_DIRECT_PTRACE_CONNECTION_H_

View File

@ -0,0 +1,52 @@
// 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_PTRACE_CONNECTION_H_
#define CRASHPAD_UTIL_LINUX_PTRACE_CONNECTION_H_
#include <sys/types.h>
#include "util/linux/thread_info.h"
namespace crashpad {
//! \brief Provides an interface for making `ptrace` requests against a process
//! and its threads.
class PtraceConnection {
public:
virtual ~PtraceConnection() {}
//! \brief Returns the process ID of the connected process.
virtual pid_t GetProcessID() = 0;
//! \brief Adds a new thread to this connection.
//!
//! \param[in] tid The thread ID of the thread to attach.
//! \return `true` on success. `false` on failure with a message logged.
virtual bool Attach(pid_t tid) = 0;
//! \brief Returns `true` if connected to a 64-bit process.
virtual bool Is64Bit() = 0;
//! \brief Retrieves a ThreadInfo for a target thread.
//!
//! \param[in] tid The thread ID of the target thread.
//! \param[out] info Information about the thread.
//! \return `true` on success. `false` on failure with a message logged.
virtual bool GetThreadInfo(pid_t tid, ThreadInfo* info) = 0;
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_LINUX_PTRACE_CONNECTION_H_

347
util/linux/ptracer.cc Normal file
View File

@ -0,0 +1,347 @@
// 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 <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);
}
bool GetThreadArea32(pid_t tid,
const ThreadContext& context,
LinuxVMAddress* address) {
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;
}
bool GetThreadArea64(pid_t tid,
const ThreadContext& context,
LinuxVMAddress* address) {
*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 isnt 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;
}
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;
}
bool GetThreadArea32(pid_t tid,
const ThreadContext& context,
LinuxVMAddress* address) {
#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
}
bool GetThreadArea64(pid_t tid,
const ThreadContext& context,
LinuxVMAddress* address) {
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;
}
#else
#error Port.
#endif // ARCH_CPU_X86_FAMILY
size_t GetGeneralPurposeRegistersAndLength(pid_t tid, 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 GetGeneralPurposeRegisters32(pid_t tid, ThreadContext* context) {
if (GetGeneralPurposeRegistersAndLength(tid, context) !=
sizeof(context->t32)) {
LOG(ERROR) << "Unexpected registers size";
return false;
}
return true;
}
bool GetGeneralPurposeRegisters64(pid_t tid, ThreadContext* context) {
if (GetGeneralPurposeRegistersAndLength(tid, context) !=
sizeof(context->t64)) {
LOG(ERROR) << "Unexpected registers size";
return false;
}
return true;
}
} // namespace
Ptracer::Ptracer() : is_64_bit_(false), initialized_() {}
Ptracer::Ptracer(bool is_64_bit) : is_64_bit_(is_64_bit) {
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);
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 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) &&
GetFloatingPointRegisters64(tid, &info->float_context) &&
GetThreadArea64(
tid, info->thread_context, &info->thread_specific_data_address);
}
return GetGeneralPurposeRegisters32(tid, &info->thread_context) &&
GetFloatingPointRegisters32(tid, &info->float_context) &&
GetThreadArea32(
tid, info->thread_context, &info->thread_specific_data_address);
}
} // namespace crashpad

78
util/linux/ptracer.h Normal file
View File

@ -0,0 +1,78 @@
// 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_PTRACER_H_
#define CRASHPAD_UTIL_LINUX_PTRACER_H_
#include <sys/types.h>
#include "base/macros.h"
#include "util/linux/address_types.h"
#include "util/linux/thread_info.h"
#include "util/misc/initialization_state_dcheck.h"
namespace crashpad {
//! \brief Provides an architecturally agnostic interface for collecting
//! information with `ptrace`.
//!
//! A ptracer is configured for a particular bitness. It is an error to make any
//! calls via this object against a thread whose bitness does not match the
//! bitness this object was initialized with.
class Ptracer {
public:
//! \brief Constructs this object with a pre-determined bitness.
//!
//! \param[in] is_64_bit `true` if this object is to be configured for 64-bit.
explicit Ptracer(bool is_64_bit);
//! \brief Constructs this object without a pre-determined bitness.
//!
//! Initialize() must be successfully called before making any other calls on
//! this object.
Ptracer();
~Ptracer();
//! \brief Initializes this object to the bitness of the process whose process
//! ID is \a pid.
//!
//! \param[in] pid The process ID of the process to initialize with.
//! \return `true` on success. `false` on failure with a message logged.
bool Initialize(pid_t pid);
//! \brief Return `true` if this object is configured for 64-bit.
bool Is64Bit();
//! \brief Uses `ptrace` to collect information about the thread with thread
//! ID \a tid.
//!
//! The target thread should be attached before calling this method.
//! \see ScopedPtraceAttach
//!
//! \param[in] tid The thread ID of the thread to collect information for.
//! \param[out] info A ThreadInfo for the thread.
//! \return `true` on success. `false` on failure with a message logged.
bool GetThreadInfo(pid_t tid, ThreadInfo* info);
private:
bool is_64_bit_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(Ptracer);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_LINUX_PTRACER_H_

View File

@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/linux/thread_info.h"
#include "util/linux/ptracer.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/multiprocess.h"
#include "util/file/file_io.h"
#include "util/linux/scoped_ptrace_attach.h"
#include "util/misc/from_pointer_cast.h"
namespace crashpad {
@ -35,30 +36,27 @@ class SameBitnessTest : public Multiprocess {
CheckedReadFileExactly(
ReadPipeHandle(), &expected_tls, sizeof(expected_tls));
ThreadInfo thread_info;
ASSERT_TRUE(thread_info.Initialize(ChildPID()));
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif // ARCH_CPU_64_BITS
EXPECT_EQ(thread_info.Is64Bit(), am_64_bit);
ScopedPtraceAttach attach;
ASSERT_TRUE(attach.ResetAttach(ChildPID()));
ThreadContext thread_context;
thread_info.GetGeneralPurposeRegisters(&thread_context);
Ptracer ptracer(am_64_bit);
EXPECT_EQ(ptracer.Is64Bit(), am_64_bit);
ThreadInfo thread_info;
ASSERT_TRUE(ptracer.GetThreadInfo(ChildPID(), &thread_info));
#if defined(ARCH_CPU_X86_64)
EXPECT_EQ(thread_context.t64.fs_base, expected_tls);
EXPECT_EQ(thread_info.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);
EXPECT_EQ(thread_info.thread_specific_data_address, expected_tls);
}
void MultiprocessChild() override {
@ -88,7 +86,7 @@ class SameBitnessTest : public Multiprocess {
DISALLOW_COPY_AND_ASSIGN(SameBitnessTest);
};
TEST(ThreadInfo, SameBitness) {
TEST(Ptracer, SameBitness) {
SameBitnessTest test;
test.Run();
}

View File

@ -14,188 +14,10 @@
#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 isnt 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));
}
@ -209,129 +31,8 @@ FloatContext::FloatContext() {
FloatContext::~FloatContext() {}
ThreadInfo::ThreadInfo()
: context_(), attachment_(), tid_(-1), initialized_(), is_64_bit_(false) {}
: thread_context(), float_context(), thread_specific_data_address(0) {}
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

View File

@ -16,15 +16,12 @@
#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"
#if defined(OS_ANDROID)
@ -245,62 +242,19 @@ union FloatContext {
static_assert(std::is_standard_layout<FloatContext>::value,
"Not standard layout");
class ThreadInfo {
public:
//! \brief A collection of `ptrace`-able information about a thread.
struct ThreadInfo {
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 The general purpose registers for the thread.
ThreadContext thread_context;
//! \brief Determines the target threads bitness.
//!
//! \return `true` if the target is 64-bit.
bool Is64Bit();
//! \brief The floating point registers for the thread.
FloatContext float_context;
//! \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_;
//! \brief The thread-local storage address for the thread.
LinuxVMAddress thread_specific_data_address;
};
} // namespace crashpad

View File

@ -38,7 +38,7 @@ namespace {
// requiring that its argv[argc - 1] compare equal to last_arg.
void ExpectProcessIsRunning(pid_t pid, std::string& last_arg) {
ProcessInfo process_info;
ASSERT_TRUE(process_info.Initialize(pid));
ASSERT_TRUE(process_info.InitializeWithPid(pid));
// The process may not have called exec yet, so loop with a small delay while
// looking for the cookie.
@ -85,7 +85,8 @@ void ExpectProcessIsNotRunning(pid_t pid, std::string& last_arg) {
std::vector<std::string> job_argv;
while (tries--) {
ProcessInfo process_info;
if (!process_info.Initialize(pid) || !process_info.Arguments(&job_argv)) {
if (!process_info.InitializeWithPid(pid) ||
!process_info.Arguments(&job_argv)) {
// The PID was not found.
return;
}

View File

@ -38,7 +38,7 @@ bool TaskForPIDGroupCheck(const ProcessInfo& process_info) {
std::set<gid_t> groups = process_info.AllGroups();
ProcessInfo process_info_self;
if (!process_info_self.Initialize(getpid())) {
if (!process_info_self.InitializeWithPid(getpid())) {
return false;
}
@ -114,7 +114,7 @@ bool TaskForPIDCheck(task_t task) {
// interface.
ProcessInfo process_info;
if (!process_info.InitializeFromTask(task)) {
if (!process_info.InitializeWithTask(task)) {
return false;
}

View File

@ -33,6 +33,10 @@
#include <sys/sysctl.h>
#endif
#if defined(OS_LINUX) || defined(OS_ANDROID)
#include "util/linux/ptrace_connection.h"
#endif
namespace crashpad {
class ProcessInfo {
@ -40,6 +44,24 @@ class ProcessInfo {
ProcessInfo();
~ProcessInfo();
#if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
//! \brief Initializes this object with information about the process whose ID
//! is \a pid using a PtraceConnection \a connection.
//!
//! 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] connection A connection to the remote process.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool InitializeWithPtrace(PtraceConnection* connection);
#endif // OS_LINUX || OS_ANDROID || DOXYGEN
#if defined(OS_MACOSX) || DOXYGEN
//! \brief Initializes this object with information about the process whose ID
//! is \a pid.
//!
@ -53,19 +75,18 @@ class ProcessInfo {
//! \param[in] pid The process ID to obtain information for.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool Initialize(pid_t pid);
bool InitializeWithPid(pid_t pid);
#if defined(OS_MACOSX) || DOXYGEN
//! \brief Initializes this object with information about a process based on
//! its Mach task.
//!
//! This method serves as a stand-in for Initialize() and may be called in its
//! place with the same restrictions and considerations.
//! This method serves as a stand-in for InitializeWithPid() and may be called
//! in its place with the same restrictions and considerations.
//!
//! \param[in] task The Mach task to obtain information for.
//!
//! \return `true` on success, `false` on failure with an message logged.
bool InitializeFromTask(task_t task);
bool InitializeWithTask(task_t task);
#endif
//! \return The target tasks process ID.
@ -116,11 +137,8 @@ class ProcessInfo {
//! \brief Determines the target process bitness.
//!
//! \param[out] is_64_bit `true` if the target task is a 64-bit process.
//!
//! \return `true` on success, with \a is_64_bit set. Otherwise, `false` with
//! a message logged.
bool Is64Bit(bool* is_64_bit) const;
//! \return `true` if the target task is a 64-bit process.
bool Is64Bit() const;
//! \brief Determines the target process start time.
//!
@ -166,9 +184,8 @@ class ProcessInfo {
gid_t gid_;
gid_t egid_;
gid_t sgid_;
bool is_64_bit_;
mutable InitializationState start_time_initialized_;
mutable InitializationState is_64_bit_initialized_;
mutable bool is_64_bit_;
#endif
InitializationStateDcheck initialized_;

View File

@ -21,7 +21,6 @@
#include "util/file/delimited_file_reader.h"
#include "util/file/file_reader.h"
#include "util/linux/proc_stat_reader.h"
#include "util/linux/thread_info.h"
#include "util/misc/lexing.h"
namespace crashpad {
@ -38,16 +37,16 @@ ProcessInfo::ProcessInfo()
egid_(-1),
sgid_(-1),
start_time_initialized_(),
is_64_bit_initialized_(),
is_64_bit_(false),
initialized_() {}
ProcessInfo::~ProcessInfo() {}
bool ProcessInfo::Initialize(pid_t pid) {
bool ProcessInfo::InitializeWithPtrace(PtraceConnection* connection) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
DCHECK(connection);
pid_ = pid;
pid_ = connection->GetProcessID();
is_64_bit_ = connection->Is64Bit();
{
char path[32];
@ -220,37 +219,9 @@ bool ProcessInfo::DidChangePrivileges() const {
return false;
}
bool ProcessInfo::Is64Bit(bool* is_64_bit) const {
bool ProcessInfo::Is64Bit() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (is_64_bit_initialized_.is_uninitialized()) {
is_64_bit_initialized_.set_invalid();
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif
if (pid_ == getpid()) {
is_64_bit_ = am_64_bit;
} else {
ThreadInfo thread_info;
if (!thread_info.Initialize(pid_)) {
return false;
}
is_64_bit_ = thread_info.Is64Bit();
}
is_64_bit_initialized_.set_valid();
}
if (!is_64_bit_initialized_.is_valid()) {
return false;
}
*is_64_bit = is_64_bit_;
return true;
return is_64_bit_;
}
bool ProcessInfo::StartTime(timeval* start_time) const {

View File

@ -27,7 +27,7 @@ ProcessInfo::ProcessInfo() : kern_proc_info_(), initialized_() {
ProcessInfo::~ProcessInfo() {
}
bool ProcessInfo::Initialize(pid_t pid) {
bool ProcessInfo::InitializeWithPid(pid_t pid) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
@ -54,7 +54,7 @@ bool ProcessInfo::Initialize(pid_t pid) {
return true;
}
bool ProcessInfo::InitializeFromTask(task_t task) {
bool ProcessInfo::InitializeWithTask(task_t task) {
pid_t pid;
kern_return_t kr = pid_for_task(task, &pid);
if (kr != KERN_SUCCESS) {
@ -62,7 +62,7 @@ bool ProcessInfo::InitializeFromTask(task_t task) {
return false;
}
return Initialize(pid);
return InitializeWithPid(pid);
}
pid_t ProcessInfo::ProcessID() const {
@ -132,10 +132,9 @@ bool ProcessInfo::DidChangePrivileges() const {
return kern_proc_info_.kp_proc.p_flag & P_SUGID;
}
bool ProcessInfo::Is64Bit(bool* is_64_bit) const {
bool ProcessInfo::Is64Bit() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
*is_64_bit = kern_proc_info_.kp_proc.p_flag & P_LP64;
return true;
return kern_proc_info_.kp_proc.p_flag & P_LP64;
}
bool ProcessInfo::StartTime(timeval* start_time) const {

View File

@ -30,6 +30,11 @@
#include "util/file/file_io.h"
#include "util/misc/implicit_cast.h"
#if defined(OS_LINUX) || defined(OS_ANDROID)
#include "util/linux/direct_ptrace_connection.h"
#include "test/linux/fake_ptrace_connection.h"
#endif
namespace crashpad {
namespace test {
namespace {
@ -74,12 +79,10 @@ void TestProcessSelfOrClone(const ProcessInfo& process_info) {
// The test executable isnt expected to change privileges.
EXPECT_FALSE(process_info.DidChangePrivileges());
bool is_64_bit;
ASSERT_TRUE(process_info.Is64Bit(&is_64_bit));
#if defined(ARCH_CPU_64_BITS)
EXPECT_TRUE(is_64_bit);
EXPECT_TRUE(process_info.Is64Bit());
#else
EXPECT_FALSE(is_64_bit);
EXPECT_FALSE(process_info.Is64Bit());
#endif
// Test StartTime(). This program must have started at some time in the past.
@ -127,14 +130,21 @@ void TestSelfProcess(const ProcessInfo& process_info) {
TEST(ProcessInfo, Self) {
ProcessInfo process_info;
ASSERT_TRUE(process_info.Initialize(getpid()));
#if defined(OS_LINUX) || defined(OS_ANDROID)
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
ASSERT_TRUE(process_info.InitializeWithPtrace(&connection));
#else
ASSERT_TRUE(process_info.InitializeWithPid(getpid()));
#endif // OS_LINUX || OS_ANDROID
TestSelfProcess(process_info);
}
#if defined(OS_MACOSX)
TEST(ProcessInfo, SelfTask) {
ProcessInfo process_info;
ASSERT_TRUE(process_info.InitializeFromTask(mach_task_self()));
ASSERT_TRUE(process_info.InitializeWithTask(mach_task_self()));
TestSelfProcess(process_info);
}
#endif
@ -143,7 +153,13 @@ TEST(ProcessInfo, Pid1) {
// PID 1 is expected to be init or the systems equivalent. This tests reading
// information about another process.
ProcessInfo process_info;
ASSERT_TRUE(process_info.Initialize(1));
#if defined(OS_LINUX) || defined(OS_ANDROID)
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(1));
ASSERT_TRUE(process_info.InitializeWithPtrace(&connection));
#else
ASSERT_TRUE(process_info.InitializeWithPid(1));
#endif
EXPECT_EQ(process_info.ProcessID(), implicit_cast<pid_t>(1));
EXPECT_EQ(process_info.ParentProcessID(), implicit_cast<pid_t>(0));
@ -165,8 +181,16 @@ class ProcessInfoForkedTest : public Multiprocess {
void MultiprocessParent() override {
const pid_t pid = ChildPID();
#if defined(OS_LINUX) || defined(OS_ANDROID)
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(pid));
ProcessInfo process_info;
ASSERT_TRUE(process_info.Initialize(pid));
ASSERT_TRUE(process_info.InitializeWithPtrace(&connection));
#else
ProcessInfo process_info;
ASSERT_TRUE(process_info.InitializeWithPid(pid));
#endif // OS_LINUX || OS_ANDROID
EXPECT_EQ(process_info.ProcessID(), pid);
EXPECT_EQ(process_info.ParentProcessID(), getpid());

View File

@ -48,6 +48,8 @@
'linux/auxiliary_vector.cc',
'linux/auxiliary_vector.h',
'linux/checked_address_range.h',
'linux/direct_ptrace_connection.cc',
'linux/direct_ptrace_connection.h',
'linux/memory_map.cc',
'linux/memory_map.h',
'linux/proc_stat_reader.cc',
@ -56,6 +58,9 @@
'linux/process_memory.h',
'linux/process_memory_range.cc',
'linux/process_memory_range.h',
'linux/ptrace_connection.h',
'linux/ptracer.cc',
'linux/ptracer.h',
'linux/scoped_ptrace_attach.cc',
'linux/scoped_ptrace_attach.h',
'linux/thread_info.cc',

View File

@ -44,8 +44,8 @@
'linux/proc_stat_reader_test.cc',
'linux/process_memory_range_test.cc',
'linux/process_memory_test.cc',
'linux/ptracer_test.cc',
'linux/scoped_ptrace_attach_test.cc',
'linux/thread_info_test.cc',
'mac/launchd_test.mm',
'mac/mac_util_test.mm',
'mac/service_management_test.mm',