mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
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 <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
13d0defbfb
commit
0924e56751
@ -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<void* (*)()>(0xffff0fe0);
|
||||
tls = FromPointerCast<LinuxVMAddress>(kuser_get_tls());
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
// Linux/aarch64 places the tls address in system register tpidr_el0.
|
||||
asm("mrs %0, tpidr_el0" : "=r"(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());
|
||||
|
45
test/linux/get_tls.cc
Normal file
45
test/linux/get_tls.cc
Normal file
@ -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<void* (*)()>(0xffff0fe0);
|
||||
tls = FromPointerCast<LinuxVMAddress>(kuser_get_tls());
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
// Linux/aarch64 places the tls address in system register tpidr_el0.
|
||||
asm("mrs %0, tpidr_el0" : "=r"(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
|
29
test/linux/get_tls.h
Normal file
29
test/linux/get_tls.h
Normal file
@ -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_
|
@ -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',
|
||||
|
@ -22,7 +22,7 @@ DirectPtraceConnection::DirectPtraceConnection()
|
||||
: PtraceConnection(),
|
||||
attachments_(),
|
||||
pid_(-1),
|
||||
ptracer_(),
|
||||
ptracer_(/* can_log= */ true),
|
||||
initialized_() {}
|
||||
|
||||
DirectPtraceConnection::~DirectPtraceConnection() {}
|
||||
|
187
util/linux/ptrace_broker.cc
Normal file
187
util/linux/ptrace_broker.cc
Normal file
@ -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 <unistd.h>
|
||||
|
||||
#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<intptr_t>(alloc) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attachments_ == nullptr) {
|
||||
attachments_ = reinterpret_cast<ScopedPtraceAttach*>(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
|
146
util/linux/ptrace_broker.h
Normal file
146
util/linux/ptrace_broker.h
Normal file
@ -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 <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "util/linux/ptrace_connection.h"
|
||||
#include "util/linux/ptracer.h"
|
||||
#include "util/linux/scoped_ptrace_attach.h"
|
||||
#include "util/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_
|
222
util/linux/ptrace_broker_test.cc
Normal file
222
util/linux/ptrace_broker_test.cc
Normal file
@ -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 <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#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<char*>();
|
||||
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<char[]>(mapping_.len());
|
||||
ASSERT_TRUE(client.Read(
|
||||
mapping_.addr_as<VMAddress>(), mapping_.len(), buffer.get()));
|
||||
auto expected_buffer = mapping_.addr_as<char*>();
|
||||
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<VMAddress>(), sizeof(first), &first));
|
||||
EXPECT_EQ(first, expected_buffer[0]);
|
||||
|
||||
char last;
|
||||
ASSERT_TRUE(
|
||||
client.Read(mapping_.addr_as<VMAddress>() + mapping_.len() - 1,
|
||||
sizeof(last),
|
||||
&last));
|
||||
EXPECT_EQ(last, expected_buffer[mapping_.len() - 1]);
|
||||
|
||||
char unmapped;
|
||||
EXPECT_FALSE(client.Read(mapping_.addr_as<VMAddress>() + 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
|
178
util/linux/ptrace_client.cc
Normal file
178
util/linux/ptrace_client.cc
Normal file
@ -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 <errno.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#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<char*>(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
|
85
util/linux/ptrace_client.h
Normal file
85
util/linux/ptrace_client.h
Normal file
@ -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 <sys/types.h>
|
||||
|
||||
#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_
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "util/linux/ptracer.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <linux/elf.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
@ -34,38 +35,43 @@ namespace {
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
|
||||
template <typename Destination>
|
||||
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<void*>(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<void*>(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<void*>(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<LinuxVMAddress>(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<void*>(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<long*>(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<char*>(&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<char*>(&word) + sizeof(word) - size, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
PLOG_IF(ERROR, can_log_);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
@ -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);
|
||||
|
@ -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<void* (*)()>(0xffff0fe0);
|
||||
expected_tls = FromPointerCast<LinuxVMAddress>(kuser_get_tls());
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
// Linux/aarch64 places the tls address in system register tpidr_el0.
|
||||
asm("mrs %0, tpidr_el0" : "=r"(expected_tls));
|
||||
#elif defined(ARCH_CPU_X86)
|
||||
uint32_t expected_tls_32;
|
||||
asm("movl %%gs:0x0, %0" : "=r"(expected_tls_32));
|
||||
expected_tls = expected_tls_32;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
asm("movq %%fs:0x0, %0" : "=r"(expected_tls));
|
||||
#else
|
||||
#error Port.
|
||||
#endif // ARCH_CPU_ARMEL
|
||||
|
||||
LinuxVMAddress expected_tls = GetTLS();
|
||||
CheckedWriteFile(WritePipeHandle(), &expected_tls, sizeof(expected_tls));
|
||||
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user