From 59c5d848e5c5988f04454c63df3e407fb1d66ae9 Mon Sep 17 00:00:00 2001 From: Joshua Peraza Date: Fri, 22 Sep 2017 09:14:52 -0700 Subject: [PATCH] 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 Reviewed-by: Mark Mentovai Reviewed-by: Leonard Mosescu --- .../linux/exception_snapshot_linux_test.cc | 16 +- snapshot/linux/process_reader.cc | 54 ++- snapshot/linux/process_reader.h | 14 +- snapshot/linux/process_reader_test.cc | 47 ++- snapshot/linux/system_snapshot_linux_test.cc | 7 +- snapshot/linux/thread_snapshot_linux.cc | 10 +- snapshot/mac/process_reader.cc | 6 +- test/linux/fake_ptrace_connection.cc | 74 ++++ test/linux/fake_ptrace_connection.h | 68 ++++ test/test.gyp | 2 + util/linux/direct_ptrace_connection.cc | 66 ++++ util/linux/direct_ptrace_connection.h | 65 ++++ util/linux/ptrace_connection.h | 52 +++ util/linux/ptracer.cc | 347 ++++++++++++++++++ util/linux/ptracer.h | 78 ++++ .../{thread_info_test.cc => ptracer_test.cc} | 28 +- util/linux/thread_info.cc | 301 +-------------- util/linux/thread_info.h | 62 +--- util/mac/service_management_test.mm | 5 +- util/mach/task_for_pid.cc | 4 +- util/posix/process_info.h | 41 ++- util/posix/process_info_linux.cc | 41 +-- util/posix/process_info_mac.cc | 11 +- util/posix/process_info_test.cc | 40 +- util/util.gyp | 5 + util/util_test.gyp | 2 +- 26 files changed, 944 insertions(+), 502 deletions(-) create mode 100644 test/linux/fake_ptrace_connection.cc create mode 100644 test/linux/fake_ptrace_connection.h create mode 100644 util/linux/direct_ptrace_connection.cc create mode 100644 util/linux/direct_ptrace_connection.h create mode 100644 util/linux/ptrace_connection.h create mode 100644 util/linux/ptracer.cc create mode 100644 util/linux/ptracer.h rename util/linux/{thread_info_test.cc => ptracer_test.cc} (81%) diff --git a/snapshot/linux/exception_snapshot_linux_test.cc b/snapshot/linux/exception_snapshot_linux_test.cc index fbfbcdec..bbccf7d8 100644 --- a/snapshot/linux/exception_snapshot_linux_test.cc +++ b/snapshot/linux/exception_snapshot_linux_test.cc @@ -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, diff --git a/snapshot/linux/process_reader.cc b/snapshot/linux/process_reader.cc index d126cfa7..115c259f 100644 --- a/snapshot/linux/process_reader.cc +++ b/snapshot/linux/process_reader.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -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); } diff --git a/snapshot/linux/process_reader.h b/snapshot/linux/process_reader.h index 9f398f34..4b23de69 100644 --- a/snapshot/linux/process_reader.h +++ b/snapshot/linux/process_reader.h @@ -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 threads_; diff --git a/snapshot/linux/process_reader_test.cc b/snapshot/linux/process_reader_test.cc index e3743bed..7adb3ab5 100644 --- a/snapshot/linux/process_reader_test.cc +++ b/snapshot/linux/process_reader_test.cc @@ -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, - thread.tid, - thread.thread_specific_data_address, - thread.stack_region_address, - thread.stack_region_size)); + SCOPED_TRACE( + base::StringPrintf("Thread id %d, tls 0x%" PRIx64 + ", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64, + thread.tid, + 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& 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& threads = process_reader.Threads(); diff --git a/snapshot/linux/system_snapshot_linux_test.cc b/snapshot/linux/system_snapshot_linux_test.cc index f55036eb..a91dfa41 100644 --- a/snapshot/linux/system_snapshot_linux_test.cc +++ b/snapshot/linux/system_snapshot_linux_test.cc @@ -15,6 +15,7 @@ #include "snapshot/linux/system_snapshot_linux.h" #include +#include #include @@ -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) diff --git a/snapshot/linux/thread_snapshot_linux.cc b/snapshot/linux/thread_snapshot_linux.cc index dd902905..dcfe5c40 100644 --- a/snapshot/linux/thread_snapshot_linux.cc +++ b/snapshot/linux/thread_snapshot_linux.cc @@ -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; diff --git a/snapshot/mac/process_reader.cc b/snapshot/mac/process_reader.cc index 43957977..c24b45c3 100644 --- a/snapshot/mac/process_reader.cc +++ b/snapshot/mac/process_reader.cc @@ -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; diff --git a/test/linux/fake_ptrace_connection.cc b/test/linux/fake_ptrace_connection.cc new file mode 100644 index 00000000..26d03711 --- /dev/null +++ b/test/linux/fake_ptrace_connection.cc @@ -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 diff --git a/test/linux/fake_ptrace_connection.h b/test/linux/fake_ptrace_connection.h new file mode 100644 index 00000000..e24bfa58 --- /dev/null +++ b/test/linux/fake_ptrace_connection.h @@ -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 + +#include + +#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 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_ diff --git a/test/test.gyp b/test/test.gyp index f2682c02..e0882885 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -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', diff --git a/util/linux/direct_ptrace_connection.cc b/util/linux/direct_ptrace_connection.cc new file mode 100644 index 00000000..1fead9cd --- /dev/null +++ b/util/linux/direct_ptrace_connection.cc @@ -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 + +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 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 diff --git a/util/linux/direct_ptrace_connection.h b/util/linux/direct_ptrace_connection.h new file mode 100644 index 00000000..5f9f4f93 --- /dev/null +++ b/util/linux/direct_ptrace_connection.h @@ -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 + +#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 attachments_; + pid_t pid_; + Ptracer ptracer_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(DirectPtraceConnection); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_DIRECT_PTRACE_CONNECTION_H_ diff --git a/util/linux/ptrace_connection.h b/util/linux/ptrace_connection.h new file mode 100644 index 00000000..d09cd20d --- /dev/null +++ b/util/linux/ptrace_connection.h @@ -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 + +#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_ diff --git a/util/linux/ptracer.cc b/util/linux/ptracer.cc new file mode 100644 index 00000000..11c1a656 --- /dev/null +++ b/util/linux/ptracer.cc @@ -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 +#include +#include +#include + +#include "base/logging.h" +#include "util/misc/from_pointer_cast.h" + +#if defined(ARCH_CPU_X86_FAMILY) +#include +#endif + +namespace crashpad { + +namespace { + +#if defined(ARCH_CPU_X86_FAMILY) + +template +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(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(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 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(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(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(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(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(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(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 diff --git a/util/linux/ptracer.h b/util/linux/ptracer.h new file mode 100644 index 00000000..e47f6577 --- /dev/null +++ b/util/linux/ptracer.h @@ -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 + +#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_ diff --git a/util/linux/thread_info_test.cc b/util/linux/ptracer_test.cc similarity index 81% rename from util/linux/thread_info_test.cc rename to util/linux/ptracer_test.cc index a4bf6baf..b32e258a 100644 --- a/util/linux/thread_info_test.cc +++ b/util/linux/ptracer_test.cc @@ -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(); } diff --git a/util/linux/thread_info.cc b/util/linux/thread_info.cc index f63106a6..ec84f536 100644 --- a/util/linux/thread_info.cc +++ b/util/linux/thread_info.cc @@ -14,188 +14,10 @@ #include "util/linux/thread_info.h" -#include #include -#include -#include - -#include "base/logging.h" -#include "util/misc/from_pointer_cast.h" - -#if defined(ARCH_CPU_X86_FAMILY) -#include -#endif namespace crashpad { -namespace { - -#if defined(ARCH_CPU_X86_FAMILY) - -template -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(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(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(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(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(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(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(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(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 diff --git a/util/linux/thread_info.h b/util/linux/thread_info.h index f8b97514..4208be67 100644 --- a/util/linux/thread_info.h +++ b/util/linux/thread_info.h @@ -16,15 +16,12 @@ #define CRASHPAD_UTIL_LINUX_THREAD_INFO_H_ #include -#include #include #include #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::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 thread’s 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 diff --git a/util/mac/service_management_test.mm b/util/mac/service_management_test.mm index 55019df0..0bec3366 100644 --- a/util/mac/service_management_test.mm +++ b/util/mac/service_management_test.mm @@ -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 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; } diff --git a/util/mach/task_for_pid.cc b/util/mach/task_for_pid.cc index 39ac2f96..4d0dfeba 100644 --- a/util/mach/task_for_pid.cc +++ b/util/mach/task_for_pid.cc @@ -38,7 +38,7 @@ bool TaskForPIDGroupCheck(const ProcessInfo& process_info) { std::set 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; } diff --git a/util/posix/process_info.h b/util/posix/process_info.h index 5e1a6108..18a9cd93 100644 --- a/util/posix/process_info.h +++ b/util/posix/process_info.h @@ -33,6 +33,10 @@ #include #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 task’s 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_; diff --git a/util/posix/process_info_linux.cc b/util/posix/process_info_linux.cc index 05896d27..10c94b36 100644 --- a/util/posix/process_info_linux.cc +++ b/util/posix/process_info_linux.cc @@ -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 { diff --git a/util/posix/process_info_mac.cc b/util/posix/process_info_mac.cc index 4a1fc58a..fe9fb654 100644 --- a/util/posix/process_info_mac.cc +++ b/util/posix/process_info_mac.cc @@ -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 { diff --git a/util/posix/process_info_test.cc b/util/posix/process_info_test.cc index 819bb549..6154ef70 100644 --- a/util/posix/process_info_test.cc +++ b/util/posix/process_info_test.cc @@ -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 isn’t 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 system’s 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(1)); EXPECT_EQ(process_info.ParentProcessID(), implicit_cast(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()); diff --git a/util/util.gyp b/util/util.gyp index 8b0b8fc0..31d4847e 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -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', diff --git a/util/util_test.gyp b/util/util_test.gyp index 23be3600..329ada05 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -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',