From 0924e567514fc577e725b56cd602808467b317a9 Mon Sep 17 00:00:00 2001 From: Joshua Peraza Date: Mon, 11 Dec 2017 10:23:17 -0800 Subject: [PATCH] linux: Add PtraceBroker and PtraceClient A PtraceBroker/Client pair implement a PtraceConnection over a socket. The broker runs in a process with `ptrace` capabilities for the target process and serves requests for the client over a socket. Bug: crashpad:30 Change-Id: Ied19bcedf84b46c8f68440fd1c284b2126470e5e Reviewed-on: https://chromium-review.googlesource.com/780397 Commit-Queue: Joshua Peraza Reviewed-by: Mark Mentovai --- snapshot/linux/process_reader_test.cc | 23 +-- test/linux/get_tls.cc | 45 +++++ test/linux/get_tls.h | 29 ++++ test/test.gyp | 2 + util/linux/direct_ptrace_connection.cc | 2 +- util/linux/ptrace_broker.cc | 187 +++++++++++++++++++++ util/linux/ptrace_broker.h | 146 ++++++++++++++++ util/linux/ptrace_broker_test.cc | 222 +++++++++++++++++++++++++ util/linux/ptrace_client.cc | 178 ++++++++++++++++++++ util/linux/ptrace_client.h | 85 ++++++++++ util/linux/ptracer.cc | 187 +++++++++++++++------ util/linux/ptracer.h | 28 +++- util/linux/ptracer_test.cc | 23 +-- util/util.gyp | 4 + util/util_test.gyp | 1 + 15 files changed, 1063 insertions(+), 99 deletions(-) create mode 100644 test/linux/get_tls.cc create mode 100644 test/linux/get_tls.h create mode 100644 util/linux/ptrace_broker.cc create mode 100644 util/linux/ptrace_broker.h create mode 100644 util/linux/ptrace_broker_test.cc create mode 100644 util/linux/ptrace_client.cc create mode 100644 util/linux/ptrace_client.h diff --git a/snapshot/linux/process_reader_test.cc b/snapshot/linux/process_reader_test.cc index 08edf02a..4bdee743 100644 --- a/snapshot/linux/process_reader_test.cc +++ b/snapshot/linux/process_reader_test.cc @@ -35,6 +35,7 @@ #include "gtest/gtest.h" #include "test/errors.h" #include "test/linux/fake_ptrace_connection.h" +#include "test/linux/get_tls.h" #include "test/multiprocess.h" #include "util/file/file_io.h" #include "util/linux/direct_ptrace_connection.h" @@ -49,28 +50,6 @@ pid_t gettid() { return syscall(SYS_gettid); } -LinuxVMAddress GetTLS() { - LinuxVMAddress tls; -#if defined(ARCH_CPU_ARMEL) - // 0xffff0fe0 is the address of the kernel user helper __kuser_get_tls(). - auto kuser_get_tls = reinterpret_cast(0xffff0fe0); - tls = FromPointerCast(kuser_get_tls()); -#elif defined(ARCH_CPU_ARM64) - // Linux/aarch64 places the tls address in system register tpidr_el0. - asm("mrs %0, tpidr_el0" : "=r"(tls)); -#elif defined(ARCH_CPU_X86) - uint32_t tls_32; - asm("movl %%gs:0x0, %0" : "=r"(tls_32)); - tls = tls_32; -#elif defined(ARCH_CPU_X86_64) - asm("movq %%fs:0x0, %0" : "=r"(tls)); -#else -#error Port. -#endif // ARCH_CPU_ARMEL - - return tls; -} - TEST(ProcessReader, SelfBasic) { FakePtraceConnection connection; connection.Initialize(getpid()); diff --git a/test/linux/get_tls.cc b/test/linux/get_tls.cc new file mode 100644 index 00000000..47d041e4 --- /dev/null +++ b/test/linux/get_tls.cc @@ -0,0 +1,45 @@ +// 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/get_tls.h" + +#include "build/build_config.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { + +LinuxVMAddress GetTLS() { + LinuxVMAddress tls; +#if defined(ARCH_CPU_ARMEL) + // 0xffff0fe0 is the address of the kernel user helper __kuser_get_tls(). + auto kuser_get_tls = reinterpret_cast(0xffff0fe0); + tls = FromPointerCast(kuser_get_tls()); +#elif defined(ARCH_CPU_ARM64) + // Linux/aarch64 places the tls address in system register tpidr_el0. + asm("mrs %0, tpidr_el0" : "=r"(tls)); +#elif defined(ARCH_CPU_X86) + uint32_t tls_32; + asm("movl %%gs:0x0, %0" : "=r"(tls_32)); + tls = tls_32; +#elif defined(ARCH_CPU_X86_64) + asm("movq %%fs:0x0, %0" : "=r"(tls)); +#else +#error Port. +#endif // ARCH_CPU_ARMEL + return tls; +} + +} // namespace test +} // namespace crashpad diff --git a/test/linux/get_tls.h b/test/linux/get_tls.h new file mode 100644 index 00000000..5d59144e --- /dev/null +++ b/test/linux/get_tls.h @@ -0,0 +1,29 @@ +// 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_GET_TLS_H_ +#define CRASHPAD_TEST_LINUX_GET_TLS_H_ + +#include "util/linux/address_types.h" + +namespace crashpad { +namespace test { + +//! \brief Return the thread-local storage address for the current thread. +LinuxVMAddress GetTLS(); + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_TEST_LINUX_GET_TLS_H_ diff --git a/test/test.gyp b/test/test.gyp index ce0c817e..ce2ba7d5 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -43,6 +43,8 @@ 'hex_string.h', 'linux/fake_ptrace_connection.cc', 'linux/fake_ptrace_connection.h', + 'linux/get_tls.cc', + 'linux/get_tls.h', 'mac/dyld.cc', 'mac/dyld.h', 'mac/exception_swallower.cc', diff --git a/util/linux/direct_ptrace_connection.cc b/util/linux/direct_ptrace_connection.cc index 78092bb9..27ad5b11 100644 --- a/util/linux/direct_ptrace_connection.cc +++ b/util/linux/direct_ptrace_connection.cc @@ -22,7 +22,7 @@ DirectPtraceConnection::DirectPtraceConnection() : PtraceConnection(), attachments_(), pid_(-1), - ptracer_(), + ptracer_(/* can_log= */ true), initialized_() {} DirectPtraceConnection::~DirectPtraceConnection() {} diff --git a/util/linux/ptrace_broker.cc b/util/linux/ptrace_broker.cc new file mode 100644 index 00000000..8810a48f --- /dev/null +++ b/util/linux/ptrace_broker.cc @@ -0,0 +1,187 @@ +// 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/ptrace_broker.h" + +#include + +#include "base/logging.h" +#include "util/file/file_io.h" + +namespace crashpad { + +PtraceBroker::PtraceBroker(int sock, bool is_64_bit) + : ptracer_(is_64_bit, /* can_log= */ false), + attachments_(nullptr), + attach_count_(0), + attach_capacity_(0), + sock_(sock) { + AllocateAttachments(); +} + +PtraceBroker::~PtraceBroker() = default; + +int PtraceBroker::Run() { + int result = RunImpl(); + ReleaseAttachments(); + return result; +} + +int PtraceBroker::RunImpl() { + while (true) { + Request request = {}; + if (!ReadFileExactly(sock_, &request, sizeof(request))) { + return errno; + } + + if (request.version != Request::kVersion) { + return EINVAL; + } + + switch (request.type) { + case Request::kTypeAttach: { + ScopedPtraceAttach* attach; + ScopedPtraceAttach stack_attach; + bool attach_on_stack = false; + + if (attach_capacity_ > attach_count_ || AllocateAttachments()) { + attach = new (&attachments_[attach_count_]) ScopedPtraceAttach; + } else { + attach = &stack_attach; + attach_on_stack = true; + } + + Bool status = kBoolFalse; + if (attach->ResetAttach(request.tid)) { + status = kBoolTrue; + if (!attach_on_stack) { + ++attach_count_; + } + } + + if (!WriteFile(sock_, &status, sizeof(status))) { + return errno; + } + + if (status == kBoolFalse) { + Errno error = errno; + if (!WriteFile(sock_, &error, sizeof(error))) { + return errno; + } + } + + if (attach_on_stack && status == kBoolTrue) { + return RunImpl(); + } + continue; + } + + case Request::kTypeIs64Bit: { + Bool is_64_bit = ptracer_.Is64Bit() ? kBoolTrue : kBoolFalse; + if (!WriteFile(sock_, &is_64_bit, sizeof(is_64_bit))) { + return errno; + } + continue; + } + + case Request::kTypeGetThreadInfo: { + GetThreadInfoResponse response; + response.success = ptracer_.GetThreadInfo(request.tid, &response.info) + ? kBoolTrue + : kBoolFalse; + + if (!WriteFile(sock_, &response, sizeof(response))) { + return errno; + } + + if (response.success == kBoolFalse) { + Errno error = errno; + if (!WriteFile(sock_, &error, sizeof(error))) { + return errno; + } + } + continue; + } + + case Request::kTypeReadMemory: { + int result = + SendMemory(request.tid, request.iov.base, request.iov.size); + if (result != 0) { + return result; + } + continue; + } + + case Request::kTypeExit: + return 0; + } + + DCHECK(false); + return EINVAL; + } +} + +int PtraceBroker::SendMemory(pid_t pid, VMAddress address, VMSize size) { + char buffer[4096]; + while (size > 0) { + VMSize bytes_read = std::min(size, VMSize{sizeof(buffer)}); + + if (!ptracer_.ReadMemory(pid, address, bytes_read, buffer)) { + bytes_read = 0; + Errno error = errno; + if (!WriteFile(sock_, &bytes_read, sizeof(bytes_read)) || + !WriteFile(sock_, &error, sizeof(error))) { + return errno; + } + return 0; + } + + if (!WriteFile(sock_, &bytes_read, sizeof(bytes_read))) { + return errno; + } + + if (!WriteFile(sock_, buffer, bytes_read)) { + return errno; + } + + size -= bytes_read; + address += bytes_read; + } + return 0; +} + +bool PtraceBroker::AllocateAttachments() { + constexpr size_t page_size = 4096; + constexpr size_t alloc_size = + (sizeof(ScopedPtraceAttach) + page_size - 1) & ~(page_size - 1); + void* alloc = sbrk(alloc_size); + if (reinterpret_cast(alloc) == -1) { + return false; + } + + if (attachments_ == nullptr) { + attachments_ = reinterpret_cast(alloc); + } + + attach_capacity_ += alloc_size / sizeof(ScopedPtraceAttach); + return true; +} + +void PtraceBroker::ReleaseAttachments() { + for (size_t index = 0; index < attach_count_; ++index) { + attachments_[index].Reset(); + } +} + +} // namespace crashpad diff --git a/util/linux/ptrace_broker.h b/util/linux/ptrace_broker.h new file mode 100644 index 00000000..d725cb7b --- /dev/null +++ b/util/linux/ptrace_broker.h @@ -0,0 +1,146 @@ +// 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_BROKER_H_ +#define CRASHPAD_UTIL_LINUX_PTRACE_BROKER_H_ + +#include +#include +#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/linux/thread_info.h" +#include "util/misc/address_types.h" + +namespace crashpad { + +//! \brief Implements a PtraceConnection over a socket. +//! +//! This class is the server half of the connection. The broker should be run +//! in a process with `ptrace` capabilities for the target process and may run +//! in a compromised context. +class PtraceBroker { + public: +#pragma pack(push, 1) + //! \brief A request sent to a PtraceBroker from a PtraceClient. + struct Request { + static constexpr uint16_t kVersion = 1; + + //! \brief The version number for this Request. + uint16_t version = kVersion; + + //! \brief The type of request to serve. + enum Type : uint16_t { + //! \brief `ptrace`-attach the specified thread ID. Responds with + //! kBoolTrue on success, otherwise kBoolFalse, followed by an Errno. + kTypeAttach, + + //! \brief Responds with kBoolTrue if the target process is 64-bit. + //! Otherwise, kBoolFalse. + kTypeIs64Bit, + + //! \brief Responds with a GetThreadInfoResponse containing a ThreadInfo + //! for the specified thread ID. If an error occurs, + //! GetThreadInfoResponse::success is set to kBoolFalse and is + //! followed by an Errno. + kTypeGetThreadInfo, + + //! \brief Reads memory from the attached process. The data is returned in + //! a series of messages. Each message begins with a VMSize indicating + //! the number of bytes being returned in this message, followed by + //! the requested bytes. The broker continues to send messages until + //! either all of the requested memory has been sent or an error + //! occurs, in which case it sends a message containing a VMSize equal + //! to zero, followed by an Errno. + kTypeReadMemory, + + //! \brief Causes the broker to return from Run(), detaching all attached + //! threads. Does not respond. + kTypeExit + } type; + + //! \brief The thread ID associated with this request. Valid for kTypeAttach, + //! kTypeGetThreadInfo, and kTypeReadMemory. + pid_t tid; + + //! \brief Specifies the memory region to read for a kTypeReadMemory request. + struct { + //! \brief The base address of the memory region. + VMAddress base; + + //! \brief The size of the memory region. + VMSize size; + } iov; + }; + + //! \brief The type used for error reporting. + using Errno = int32_t; + static_assert(sizeof(Errno) >= sizeof(errno), "Errno type is too small"); + + //! \brief A boolean status suitable for communication between processes. + enum Bool : char { kBoolFalse, kBoolTrue }; + + //! \brief The response sent for a Request with type kTypeGetThreadInfo. + struct GetThreadInfoResponse { + //! \brief Information about the specified thread. Only valid if #success + //! is kBoolTrue. + ThreadInfo info; + + //! \brief Specifies the success or failure of this call. + Bool success; + }; +#pragma pack(pop) + + //! \brief Constructs this object. + //! + //! \param[in] sock A socket on which to read requests from a connected + //! PtraceClient. Does not take ownership of the socket. + //! \param[in] is_64_bit Whether this broker should be configured to trace a + //! 64-bit process. + PtraceBroker(int sock, bool is_64_bit); + + ~PtraceBroker(); + + //! \brief Begin serving requests on the configured socket. + //! + //! This method returns when a PtraceBrokerRequest with type kTypeExit is + //! received or an error is encountered on the socket. + //! + //! This method calls `sbrk`, which may break other memory management tools, + //! such as `malloc`. + //! + //! \return 0 if Run() exited due to an exit request. Otherwise an error code. + int Run(); + + private: + int RunImpl(); + int SendMemory(pid_t pid, VMAddress address, VMSize size); + bool AllocateAttachments(); + void ReleaseAttachments(); + + Ptracer ptracer_; + ScopedPtraceAttach* attachments_; + size_t attach_count_; + size_t attach_capacity_; + int sock_; + + DISALLOW_COPY_AND_ASSIGN(PtraceBroker); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_PTRACE_BROKER_H_ diff --git a/util/linux/ptrace_broker_test.cc b/util/linux/ptrace_broker_test.cc new file mode 100644 index 00000000..749df759 --- /dev/null +++ b/util/linux/ptrace_broker_test.cc @@ -0,0 +1,222 @@ +// 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/ptrace_broker.h" + +#include +#include +#include +#include + +#include + +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "test/linux/get_tls.h" +#include "test/multiprocess.h" +#include "util/file/file_io.h" +#include "util/linux/ptrace_client.h" +#include "util/posix/scoped_mmap.h" +#include "util/synchronization/semaphore.h" +#include "util/thread/thread.h" + +namespace crashpad { +namespace test { +namespace { + +class ScopedTimeoutThread : public Thread { + public: + ScopedTimeoutThread() : join_sem_(0) {} + ~ScopedTimeoutThread() { EXPECT_TRUE(JoinWithTimeout(5.0)); } + + protected: + void ThreadMain() override { join_sem_.Signal(); } + + private: + bool JoinWithTimeout(double timeout) { + if (!join_sem_.TimedWait(timeout)) { + return false; + } + Join(); + return true; + } + + Semaphore join_sem_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTimeoutThread); +}; + +class RunBrokerThread : public ScopedTimeoutThread { + public: + RunBrokerThread(PtraceBroker* broker) + : ScopedTimeoutThread(), broker_(broker) {} + + ~RunBrokerThread() {} + + private: + void ThreadMain() override { + EXPECT_EQ(broker_->Run(), 0); + ScopedTimeoutThread::ThreadMain(); + } + + PtraceBroker* broker_; + + DISALLOW_COPY_AND_ASSIGN(RunBrokerThread); +}; + +class BlockOnReadThread : public ScopedTimeoutThread { + public: + BlockOnReadThread(int readfd, int writefd) + : ScopedTimeoutThread(), readfd_(readfd), writefd_(writefd) {} + + ~BlockOnReadThread() {} + + private: + void ThreadMain() override { + pid_t pid = syscall(SYS_gettid); + LoggingWriteFile(writefd_, &pid, sizeof(pid)); + + LinuxVMAddress tls = GetTLS(); + LoggingWriteFile(writefd_, &tls, sizeof(tls)); + + CheckedReadFileAtEOF(readfd_); + ScopedTimeoutThread::ThreadMain(); + } + + int readfd_; + int writefd_; + + DISALLOW_COPY_AND_ASSIGN(BlockOnReadThread); +}; + +class SameBitnessTest : public Multiprocess { + public: + SameBitnessTest() : Multiprocess(), mapping_() {} + ~SameBitnessTest() {} + + protected: + void PreFork() override { + ASSERT_NO_FATAL_FAILURE(Multiprocess::PreFork()); + + size_t page_size = getpagesize(); + ASSERT_TRUE(mapping_.ResetMmap(nullptr, + page_size * 3, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + ASSERT_TRUE(mapping_.ResetAddrLen(mapping_.addr(), page_size * 2)); + + auto buffer = mapping_.addr_as(); + for (size_t index = 0; index < mapping_.len(); ++index) { + buffer[index] = index % 256; + } + } + + private: + void MultiprocessParent() override { + LinuxVMAddress child1_tls; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &child1_tls, sizeof(child1_tls))); + + pid_t child2_tid; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &child2_tid, sizeof(child2_tid))); + + LinuxVMAddress child2_tls; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &child2_tls, sizeof(child2_tls))); + + int socks[2]; + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0); + ScopedFileHandle broker_sock(socks[0]); + ScopedFileHandle client_sock(socks[1]); + +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif // ARCH_CPU_64_BITS + PtraceBroker broker(broker_sock.get(), am_64_bit); + RunBrokerThread broker_thread(&broker); + broker_thread.Start(); + + { + PtraceClient client; + ASSERT_TRUE(client.Initialize(client_sock.get(), ChildPID())); + + EXPECT_EQ(client.GetProcessID(), ChildPID()); + EXPECT_TRUE(client.Attach(child2_tid)); + EXPECT_EQ(client.Is64Bit(), am_64_bit); + + ThreadInfo info1; + ASSERT_TRUE(client.GetThreadInfo(ChildPID(), &info1)); + EXPECT_EQ(info1.thread_specific_data_address, child1_tls); + + ThreadInfo info2; + ASSERT_TRUE(client.GetThreadInfo(child2_tid, &info2)); + EXPECT_EQ(info2.thread_specific_data_address, child2_tls); + + auto buffer = std::make_unique(mapping_.len()); + ASSERT_TRUE(client.Read( + mapping_.addr_as(), mapping_.len(), buffer.get())); + auto expected_buffer = mapping_.addr_as(); + for (size_t index = 0; index < mapping_.len(); ++index) { + EXPECT_EQ(buffer[index], expected_buffer[index]); + } + + char first; + ASSERT_TRUE( + client.Read(mapping_.addr_as(), sizeof(first), &first)); + EXPECT_EQ(first, expected_buffer[0]); + + char last; + ASSERT_TRUE( + client.Read(mapping_.addr_as() + mapping_.len() - 1, + sizeof(last), + &last)); + EXPECT_EQ(last, expected_buffer[mapping_.len() - 1]); + + char unmapped; + EXPECT_FALSE(client.Read(mapping_.addr_as() + mapping_.len(), + sizeof(unmapped), + &unmapped)); + } + } + + void MultiprocessChild() override { + LinuxVMAddress tls = GetTLS(); + ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &tls, sizeof(tls))); + + BlockOnReadThread thread(ReadPipeHandle(), WritePipeHandle()); + thread.Start(); + + CheckedReadFileAtEOF(ReadPipeHandle()); + } + + ScopedMmap mapping_; + + DISALLOW_COPY_AND_ASSIGN(SameBitnessTest); +}; + +TEST(PtraceBroker, SameBitness) { + SameBitnessTest test; + test.Run(); +} + +// TODO(jperaza): Test against a process with different bitness. + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/linux/ptrace_client.cc b/util/linux/ptrace_client.cc new file mode 100644 index 00000000..dabda12e --- /dev/null +++ b/util/linux/ptrace_client.cc @@ -0,0 +1,178 @@ +// 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/ptrace_client.h" + +#include + +#include + +#include "base/logging.h" +#include "util/file/file_io.h" +#include "util/linux/ptrace_broker.h" + +namespace crashpad { + +namespace { + +bool ReceiveAndLogError(int sock, const std::string& operation) { + PtraceBroker::Errno error; + if (!LoggingReadFileExactly(sock, &error, sizeof(error))) { + return false; + } + errno = error; + PLOG(ERROR) << operation; + return true; +} + +bool AttachImpl(int sock, pid_t tid) { + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeAttach; + request.tid = tid; + if (!LoggingWriteFile(sock, &request, sizeof(request))) { + return false; + } + + PtraceBroker::Bool success; + if (!LoggingReadFileExactly(sock, &success, sizeof(success))) { + return false; + } + + if (success != PtraceBroker::kBoolTrue) { + ReceiveAndLogError(sock, "PtraceBroker Attach"); + } + + return true; +} + +} // namespace + +PtraceClient::PtraceClient() + : PtraceConnection(), + sock_(kInvalidFileHandle), + pid_(-1), + is_64_bit_(false), + initialized_() {} + +PtraceClient::~PtraceClient() { + if (sock_ != kInvalidFileHandle) { + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeExit; + LoggingWriteFile(sock_, &request, sizeof(request)); + } +} + +bool PtraceClient::Initialize(int sock, pid_t pid) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + sock_ = sock; + pid_ = pid; + + if (!AttachImpl(sock_, pid_)) { + return false; + } + + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeIs64Bit; + request.tid = pid_; + + if (!LoggingWriteFile(sock_, &request, sizeof(request))) { + return false; + } + + PtraceBroker::Bool is_64_bit; + if (!LoggingReadFileExactly(sock_, &is_64_bit, sizeof(is_64_bit))) { + return false; + } + is_64_bit_ = is_64_bit == PtraceBroker::kBoolTrue; + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +pid_t PtraceClient::GetProcessID() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return pid_; +} + +bool PtraceClient::Attach(pid_t tid) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return AttachImpl(sock_, tid); +} + +bool PtraceClient::Is64Bit() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return is_64_bit_; +} + +bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeGetThreadInfo; + request.tid = tid; + if (!LoggingWriteFile(sock_, &request, sizeof(request))) { + return false; + } + + PtraceBroker::GetThreadInfoResponse response; + if (!LoggingReadFileExactly(sock_, &response, sizeof(response))) { + return false; + } + + if (response.success == PtraceBroker::kBoolTrue) { + *info = response.info; + return true; + } + + ReceiveAndLogError(sock_, "PtraceBroker GetThreadInfo"); + return false; +} + +bool PtraceClient::Read(VMAddress address, size_t size, void* buffer) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + char* buffer_c = reinterpret_cast(buffer); + + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeReadMemory; + request.tid = pid_; + request.iov.base = address; + request.iov.size = size; + + if (!LoggingWriteFile(sock_, &request, sizeof(request))) { + return false; + } + + while (size > 0) { + VMSize bytes_read; + if (!LoggingReadFileExactly(sock_, &bytes_read, sizeof(bytes_read))) { + return false; + } + + if (!bytes_read) { + ReceiveAndLogError(sock_, "PtraceBroker ReadMemory"); + return false; + } + + if (!LoggingReadFileExactly(sock_, buffer_c, bytes_read)) { + return false; + } + + size -= bytes_read; + buffer_c += bytes_read; + } + + return true; +} + +} // namespace crashpad diff --git a/util/linux/ptrace_client.h b/util/linux/ptrace_client.h new file mode 100644 index 00000000..397c891f --- /dev/null +++ b/util/linux/ptrace_client.h @@ -0,0 +1,85 @@ +// 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_CLIENT_H_ +#define CRASHPAD_UTIL_LINUX_PTRACE_CLIENT_H_ + +#include + +#include "base/macros.h" +#include "util/linux/ptrace_connection.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +//! \brief Implements a PtraceConnection over a socket. +//! +//! This class forms the client half of the connection and is typically used +//! when the current process does not have `ptrace` capabilities on the target +//! process. It should be created with a socket connected to a PtraceBroker. +class PtraceClient : public PtraceConnection { + public: + PtraceClient(); + ~PtraceClient(); + + //! \brief Initializes this object. + //! + //! This method must be successfully called before any other method in this + //! class. + //! + //! \param[in] sock A socket connected to a PtraceBroker. Does not take + //! ownership of the socket. + //! \param[in] pid The process ID of the process to form a PtraceConnection + //! with. + //! \return `true` on success. `false` on failure with a message logged. + bool Initialize(int sock, pid_t pid); + + //! \brief Copies memory from the target process into a caller-provided buffer + //! in the current process. + //! + //! TODO(jperaza): In order for this to be usable, PtraceConnection will need + //! to surface it, possibly by inheriting from ProcessMemory, or providing a + //! method to return a ProcessMemory*. + //! + //! \param[in] address The address, in the target process' address space, of + //! the memory region to copy. + //! \param[in] size The size, in bytes, of the memory region to copy. + //! \a buffer must be at least this size. + //! \param[out] buffer The buffer into which the contents of the other + //! process' memory will be copied. + //! + //! \return `true` on success, with \a buffer filled appropriately. `false` on + //! failure, with a message logged. + bool Read(VMAddress address, size_t size, void* buffer); + + // PtraceConnection: + + pid_t GetProcessID() override; + bool Attach(pid_t tid) override; + bool Is64Bit() override; + bool GetThreadInfo(pid_t tid, ThreadInfo* info) override; + + private: + int sock_; + pid_t pid_; + bool is_64_bit_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(PtraceClient); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_PTRACE_CLIENT_H_ diff --git a/util/linux/ptracer.cc b/util/linux/ptracer.cc index fcbaedc0..20115819 100644 --- a/util/linux/ptracer.cc +++ b/util/linux/ptracer.cc @@ -14,6 +14,7 @@ #include "util/linux/ptracer.h" +#include #include #include #include @@ -34,38 +35,43 @@ namespace { #if defined(ARCH_CPU_X86_FAMILY) template -bool GetRegisterSet(pid_t tid, int set, Destination* dest) { +bool GetRegisterSet(pid_t tid, int set, Destination* dest, bool can_log) { iovec iov; iov.iov_base = dest; iov.iov_len = sizeof(*dest); if (ptrace(PTRACE_GETREGSET, tid, reinterpret_cast(set), &iov) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } if (iov.iov_len != sizeof(*dest)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; } -bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { - return GetRegisterSet(tid, NT_PRXFPREG, &context->f32.fxsave); +bool GetFloatingPointRegisters32(pid_t tid, + FloatContext* context, + bool can_log) { + return GetRegisterSet(tid, NT_PRXFPREG, &context->f32.fxsave, can_log); } -bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) { - return GetRegisterSet(tid, NT_PRFPREG, &context->f64.fxsave); +bool GetFloatingPointRegisters64(pid_t tid, + FloatContext* context, + bool can_log) { + return GetRegisterSet(tid, NT_PRFPREG, &context->f64.fxsave, can_log); } bool GetThreadArea32(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { size_t index = (context.t32.xgs & 0xffff) >> 3; user_desc desc; if (ptrace( PTRACE_GET_THREAD_AREA, tid, reinterpret_cast(index), &desc) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } @@ -75,7 +81,8 @@ bool GetThreadArea32(pid_t tid, bool GetThreadArea64(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { *address = context.t64.fs_base; return true; } @@ -98,17 +105,21 @@ bool GetThreadArea64(pid_t tid, // TODO(mark): Once helpers to interpret the kernel version are available, add // a DCHECK to ensure that the kernel is older than 3.5. -bool GetGeneralPurposeRegistersLegacy(pid_t tid, ThreadContext* context) { +bool GetGeneralPurposeRegistersLegacy(pid_t tid, + ThreadContext* context, + bool can_log) { if (ptrace(PTRACE_GETREGS, tid, nullptr, &context->t32) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } return true; } -bool GetFloatingPointRegistersLegacy(pid_t tid, FloatContext* context) { +bool GetFloatingPointRegistersLegacy(pid_t tid, + FloatContext* context, + bool can_log) { if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } context->f32.have_fpregs = true; @@ -119,7 +130,7 @@ bool GetFloatingPointRegistersLegacy(pid_t tid, FloatContext* context) { // These registers are optional on 32-bit ARM cpus break; default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } } else { @@ -138,7 +149,9 @@ bool GetFloatingPointRegistersLegacy(pid_t tid, FloatContext* context) { constexpr size_t kArmVfpSize = 32 * 8 + 4; // Target is 32-bit -bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { +bool GetFloatingPointRegisters32(pid_t tid, + FloatContext* context, + bool can_log) { context->f32.have_fpregs = false; context->f32.have_vfp = false; @@ -151,19 +164,19 @@ bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { switch (errno) { #if defined(ARCH_CPU_ARMEL) case EIO: - return GetFloatingPointRegistersLegacy(tid, context); + return GetFloatingPointRegistersLegacy(tid, context, can_log); #endif // ARCH_CPU_ARMEL case EINVAL: // A 32-bit process running on a 64-bit CPU doesn't have this register // set. It should have a VFP register set instead. break; default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } } else { if (iov.iov_len != sizeof(context->f32.fpregs)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } context->f32.have_fpregs = true; @@ -179,36 +192,38 @@ bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { // VFP may not be present on 32-bit ARM cpus. break; default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } } else { if (iov.iov_len != kArmVfpSize && iov.iov_len != sizeof(context->f32.vfp)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "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"; + LOG_IF(ERROR, can_log) << "Unable to collect registers"; return false; } return true; } -bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) { +bool GetFloatingPointRegisters64(pid_t tid, + FloatContext* context, + bool can_log) { iovec iov; iov.iov_base = context; iov.iov_len = sizeof(*context); if (ptrace( PTRACE_GETREGSET, tid, reinterpret_cast(NT_PRFPREG), &iov) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } if (iov.iov_len != sizeof(context->f64)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; @@ -216,11 +231,12 @@ bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) { bool GetThreadArea32(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { #if defined(ARCH_CPU_ARMEL) void* result; if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } *address = FromPointerCast(result); @@ -228,25 +244,27 @@ bool GetThreadArea32(pid_t tid, #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"; + LOG_IF(WARNING, can_log) + << "64-bit ARM cannot trace TLS area for a 32-bit process"; return false; #endif // ARCH_CPU_ARMEL } bool GetThreadArea64(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { 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"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } if (iov.iov_len != 8) { - LOG(ERROR) << "address size mismatch"; + LOG_IF(ERROR, can_log) << "address size mismatch"; return false; } return true; @@ -255,7 +273,9 @@ bool GetThreadArea64(pid_t tid, #error Port. #endif // ARCH_CPU_X86_FAMILY -size_t GetGeneralPurposeRegistersAndLength(pid_t tid, ThreadContext* context) { +size_t GetGeneralPurposeRegistersAndLength(pid_t tid, + ThreadContext* context, + bool can_log) { iovec iov; iov.iov_base = context; iov.iov_len = sizeof(*context); @@ -265,31 +285,35 @@ size_t GetGeneralPurposeRegistersAndLength(pid_t tid, ThreadContext* context) { switch (errno) { #if defined(ARCH_CPU_ARMEL) case EIO: - if (GetGeneralPurposeRegistersLegacy(tid, context)) { + if (GetGeneralPurposeRegistersLegacy(tid, context, can_log)) { return sizeof(context->t32); } #endif // ARCH_CPU_ARMEL default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return 0; } } return iov.iov_len; } -bool GetGeneralPurposeRegisters32(pid_t tid, ThreadContext* context) { - if (GetGeneralPurposeRegistersAndLength(tid, context) != +bool GetGeneralPurposeRegisters32(pid_t tid, + ThreadContext* context, + bool can_log) { + if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) != sizeof(context->t32)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; } -bool GetGeneralPurposeRegisters64(pid_t tid, ThreadContext* context) { - if (GetGeneralPurposeRegistersAndLength(tid, context) != +bool GetGeneralPurposeRegisters64(pid_t tid, + ThreadContext* context, + bool can_log) { + if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) != sizeof(context->t64)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; @@ -297,9 +321,11 @@ bool GetGeneralPurposeRegisters64(pid_t tid, ThreadContext* context) { } // namespace -Ptracer::Ptracer() : is_64_bit_(false), initialized_() {} +Ptracer::Ptracer(bool can_log) + : is_64_bit_(false), can_log_(can_log), initialized_() {} -Ptracer::Ptracer(bool is_64_bit) : is_64_bit_(is_64_bit) { +Ptracer::Ptracer(bool is_64_bit, bool can_log) + : is_64_bit_(is_64_bit), can_log_(can_log), initialized_() { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); INITIALIZATION_STATE_SET_VALID(initialized_); } @@ -310,13 +336,13 @@ bool Ptracer::Initialize(pid_t pid) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); ThreadContext context; - size_t length = GetGeneralPurposeRegistersAndLength(pid, &context); + size_t length = GetGeneralPurposeRegistersAndLength(pid, &context, can_log_); if (length == sizeof(context.t64)) { is_64_bit_ = true; } else if (length == sizeof(context.t32)) { is_64_bit_ = false; } else { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log_) << "Unexpected registers size"; return false; } @@ -333,16 +359,73 @@ 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 GetGeneralPurposeRegisters64(tid, &info->thread_context, can_log_) && + GetFloatingPointRegisters64(tid, &info->float_context, can_log_) && + GetThreadArea64(tid, + info->thread_context, + &info->thread_specific_data_address, + can_log_); } - return GetGeneralPurposeRegisters32(tid, &info->thread_context) && - GetFloatingPointRegisters32(tid, &info->float_context) && - GetThreadArea32( - tid, info->thread_context, &info->thread_specific_data_address); + return GetGeneralPurposeRegisters32(tid, &info->thread_context, can_log_) && + GetFloatingPointRegisters32(tid, &info->float_context, can_log_) && + GetThreadArea32(tid, + info->thread_context, + &info->thread_specific_data_address, + can_log_); +} + +bool Ptracer::ReadMemory(pid_t pid, + LinuxVMAddress address, + size_t size, + char* buffer) { + while (size > 0) { + errno = 0; + + if (size >= sizeof(long)) { + *reinterpret_cast(buffer) = + ptrace(PTRACE_PEEKDATA, pid, address, nullptr); + + if (errno != 0) { + PLOG_IF(ERROR, can_log_) << "ptrace"; + return false; + } + + size -= sizeof(long); + buffer += sizeof(long); + address += sizeof(long); + } else { + long word = ptrace(PTRACE_PEEKDATA, pid, address, nullptr); + + if (errno == 0) { + memcpy(buffer, reinterpret_cast(&word), size); + return true; + } + + if (errno != EIO) { + PLOG_IF(ERROR, can_log_); + return false; + } + + // A read smaller than a word at the end of a mapping might spill over + // into unmapped memory. Try aligning the read so that the requested + // data is at the end of the word instead. + errno = 0; + word = + ptrace(PTRACE_PEEKDATA, pid, address - sizeof(word) + size, nullptr); + + if (errno == 0) { + memcpy( + buffer, reinterpret_cast(&word) + sizeof(word) - size, size); + return true; + } + + PLOG_IF(ERROR, can_log_); + return false; + } + } + + return true; } } // namespace crashpad diff --git a/util/linux/ptracer.h b/util/linux/ptracer.h index e47f6577..daa7a01f 100644 --- a/util/linux/ptracer.h +++ b/util/linux/ptracer.h @@ -35,13 +35,16 @@ class Ptracer { //! \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); + //! \param[in] can_log Whether methods in this class can log error messages. + Ptracer(bool is_64_bit, bool can_log); //! \brief Constructs this object without a pre-determined bitness. //! //! Initialize() must be successfully called before making any other calls on //! this object. - Ptracer(); + //! + //! \param[in] can_log Whether methods in this class can log error messages. + explicit Ptracer(bool can_log); ~Ptracer(); @@ -49,7 +52,8 @@ class Ptracer { //! 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. + //! \return `true` on success. `false` on failure with a message logged, if + //! enabled. bool Initialize(pid_t pid); //! \brief Return `true` if this object is configured for 64-bit. @@ -63,11 +67,27 @@ class Ptracer { //! //! \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. + //! \return `true` on success. `false` on failure with a message logged, if + //! enabled. bool GetThreadInfo(pid_t tid, ThreadInfo* info); + //! \brief Uses `ptrace` to read memory from the process with process ID \a + //! pid. + //! + //! The target process should already be attached before calling this method. + //! \see ScopedPtraceAttach + //! + //! \param[in] pid The process ID whose memory to read. + //! \param[in] address The base address of the region to read. + //! \param[in] size The size of the memory region to read. + //! \param[out] buffer The buffer to fill with the data read. + //! \return `true` on success. `false` on failure with a message logged, if + //! enabled. + bool ReadMemory(pid_t pid, LinuxVMAddress address, size_t size, char* buffer); + private: bool is_64_bit_; + bool can_log_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(Ptracer); diff --git a/util/linux/ptracer_test.cc b/util/linux/ptracer_test.cc index b32e258a..9a759850 100644 --- a/util/linux/ptracer_test.cc +++ b/util/linux/ptracer_test.cc @@ -16,10 +16,10 @@ #include "build/build_config.h" #include "gtest/gtest.h" +#include "test/linux/get_tls.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 { namespace test { @@ -45,7 +45,7 @@ class SameBitnessTest : public Multiprocess { ScopedPtraceAttach attach; ASSERT_TRUE(attach.ResetAttach(ChildPID())); - Ptracer ptracer(am_64_bit); + Ptracer ptracer(am_64_bit, /* can_log= */ true); EXPECT_EQ(ptracer.Is64Bit(), am_64_bit); @@ -60,24 +60,7 @@ class SameBitnessTest : public Multiprocess { } void MultiprocessChild() override { - LinuxVMAddress expected_tls; -#if defined(ARCH_CPU_ARMEL) - // 0xffff0fe0 is the address of the kernel user helper __kuser_get_tls(). - auto kuser_get_tls = reinterpret_cast(0xffff0fe0); - expected_tls = FromPointerCast(kuser_get_tls()); -#elif defined(ARCH_CPU_ARM64) - // Linux/aarch64 places the tls address in system register tpidr_el0. - asm("mrs %0, tpidr_el0" : "=r"(expected_tls)); -#elif defined(ARCH_CPU_X86) - uint32_t expected_tls_32; - asm("movl %%gs:0x0, %0" : "=r"(expected_tls_32)); - expected_tls = expected_tls_32; -#elif defined(ARCH_CPU_X86_64) - asm("movq %%fs:0x0, %0" : "=r"(expected_tls)); -#else -#error Port. -#endif // ARCH_CPU_ARMEL - + LinuxVMAddress expected_tls = GetTLS(); CheckedWriteFile(WritePipeHandle(), &expected_tls, sizeof(expected_tls)); CheckedReadFileAtEOF(ReadPipeHandle()); diff --git a/util/util.gyp b/util/util.gyp index 5aeb4503..a7bed771 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -62,6 +62,10 @@ 'linux/memory_map.h', 'linux/proc_stat_reader.cc', 'linux/proc_stat_reader.h', + 'linux/ptrace_broker.cc', + 'linux/ptrace_broker.h', + 'linux/ptrace_client.cc', + 'linux/ptrace_client.h' 'linux/ptrace_connection.h', 'linux/ptracer.cc', 'linux/ptracer.h', diff --git a/util/util_test.gyp b/util/util_test.gyp index f9c114db..a2873270 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -44,6 +44,7 @@ 'linux/auxiliary_vector_test.cc', 'linux/memory_map_test.cc', 'linux/proc_stat_reader_test.cc', + 'linux/ptrace_broker_test.cc', 'linux/ptracer_test.cc', 'linux/scoped_ptrace_attach_test.cc', 'mac/launchd_test.mm',