386 lines
9.6 KiB
C++
386 lines
9.6 KiB
C++
// Copyright 2017 The Crashpad Authors
|
|
//
|
|
// 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 <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <iterator>
|
|
#include <string>
|
|
|
|
#include "base/logging.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "util/file/file_io.h"
|
|
#include "util/linux/ptrace_broker.h"
|
|
#include "util/process/process_memory_linux.h"
|
|
|
|
namespace crashpad {
|
|
|
|
namespace {
|
|
|
|
bool ReceiveAndLogError(int sock, const std::string& operation) {
|
|
ExceptionHandlerProtocol::Errno error;
|
|
if (!LoggingReadFileExactly(sock, &error, sizeof(error))) {
|
|
return false;
|
|
}
|
|
errno = error;
|
|
PLOG(ERROR) << operation;
|
|
return true;
|
|
}
|
|
|
|
bool ReceiveAndLogReadError(int sock, const std::string& operation) {
|
|
PtraceBroker::ReadError err;
|
|
if (!LoggingReadFileExactly(sock, &err, sizeof(err))) {
|
|
return false;
|
|
}
|
|
switch (err) {
|
|
case PtraceBroker::kReadErrorAccessDenied:
|
|
LOG(ERROR) << operation << " access denied";
|
|
return true;
|
|
default:
|
|
if (err <= 0) {
|
|
LOG(ERROR) << operation << " invalid error " << err;
|
|
DCHECK(false);
|
|
return false;
|
|
}
|
|
errno = err;
|
|
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;
|
|
}
|
|
|
|
ExceptionHandlerProtocol::Bool success;
|
|
if (!LoggingReadFileExactly(sock, &success, sizeof(success))) {
|
|
return false;
|
|
}
|
|
|
|
if (success != ExceptionHandlerProtocol::kBoolTrue) {
|
|
ReceiveAndLogError(sock, "PtraceBroker Attach");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct Dirent64 {
|
|
ino64_t d_ino;
|
|
off64_t d_off;
|
|
unsigned short d_reclen;
|
|
unsigned char d_type;
|
|
char d_name[];
|
|
};
|
|
|
|
void ReadDentsAsThreadIDs(char* buffer,
|
|
size_t size,
|
|
std::vector<pid_t>* threads) {
|
|
while (size > offsetof(Dirent64, d_name)) {
|
|
auto dirent = reinterpret_cast<Dirent64*>(buffer);
|
|
if (size < dirent->d_reclen) {
|
|
LOG(ERROR) << "short dirent";
|
|
break;
|
|
}
|
|
buffer += dirent->d_reclen;
|
|
size -= dirent->d_reclen;
|
|
|
|
const size_t max_name_length =
|
|
dirent->d_reclen - offsetof(Dirent64, d_name);
|
|
size_t name_len = strnlen(dirent->d_name, max_name_length);
|
|
if (name_len >= max_name_length) {
|
|
LOG(ERROR) << "format error";
|
|
break;
|
|
}
|
|
|
|
if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
pid_t tid;
|
|
if (!base::StringToInt(dirent->d_name, &tid)) {
|
|
LOG(ERROR) << "format error";
|
|
continue;
|
|
}
|
|
threads->push_back(tid);
|
|
}
|
|
DCHECK_EQ(size, 0u);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
PtraceClient::PtraceClient()
|
|
: PtraceConnection(),
|
|
memory_(),
|
|
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;
|
|
}
|
|
|
|
ExceptionHandlerProtocol::Bool is_64_bit;
|
|
if (!LoggingReadFileExactly(sock_, &is_64_bit, sizeof(is_64_bit))) {
|
|
return false;
|
|
}
|
|
is_64_bit_ = is_64_bit == ExceptionHandlerProtocol::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 == ExceptionHandlerProtocol::kBoolTrue) {
|
|
*info = response.info;
|
|
return true;
|
|
}
|
|
|
|
ReceiveAndLogError(sock_, "PtraceBroker GetThreadInfo");
|
|
return false;
|
|
}
|
|
|
|
bool PtraceClient::ReadFileContents(const base::FilePath& path,
|
|
std::string* contents) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
PtraceBroker::Request request = {};
|
|
request.type = PtraceBroker::Request::kTypeReadFile;
|
|
request.path.path_length = path.value().size();
|
|
|
|
if (!LoggingWriteFile(sock_, &request, sizeof(request)) ||
|
|
!SendFilePath(path.value().c_str(), request.path.path_length)) {
|
|
return false;
|
|
}
|
|
|
|
std::string local_contents;
|
|
int32_t read_result;
|
|
do {
|
|
if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) {
|
|
return false;
|
|
}
|
|
|
|
if (read_result < 0) {
|
|
ReceiveAndLogReadError(sock_, "ReadFileContents");
|
|
return false;
|
|
}
|
|
|
|
if (read_result > 0) {
|
|
size_t old_length = local_contents.size();
|
|
local_contents.resize(old_length + read_result);
|
|
if (!LoggingReadFileExactly(
|
|
sock_, &local_contents[old_length], read_result)) {
|
|
return false;
|
|
}
|
|
}
|
|
} while (read_result > 0);
|
|
|
|
contents->swap(local_contents);
|
|
return true;
|
|
}
|
|
|
|
ProcessMemoryLinux* PtraceClient::Memory() {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
if (!memory_) {
|
|
memory_ = std::make_unique<ProcessMemoryLinux>(this);
|
|
}
|
|
return memory_.get();
|
|
}
|
|
|
|
bool PtraceClient::Threads(std::vector<pid_t>* threads) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
DCHECK(threads->empty());
|
|
|
|
// If the broker is unable to read thread IDs, fall-back to just the main
|
|
// thread's ID.
|
|
threads->push_back(pid_);
|
|
|
|
char path[32];
|
|
snprintf(path, std::size(path), "/proc/%d/task", pid_);
|
|
|
|
PtraceBroker::Request request = {};
|
|
request.type = PtraceBroker::Request::kTypeListDirectory;
|
|
request.path.path_length = strlen(path);
|
|
|
|
if (!LoggingWriteFile(sock_, &request, sizeof(request)) ||
|
|
!SendFilePath(path, request.path.path_length)) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<pid_t> local_threads;
|
|
int32_t read_result;
|
|
do {
|
|
if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) {
|
|
return false;
|
|
}
|
|
|
|
if (read_result < 0) {
|
|
return ReceiveAndLogReadError(sock_, "Threads");
|
|
}
|
|
|
|
if (read_result > 0) {
|
|
auto buffer = std::make_unique<char[]>(read_result);
|
|
if (!LoggingReadFileExactly(sock_, buffer.get(), read_result)) {
|
|
return false;
|
|
}
|
|
|
|
ReadDentsAsThreadIDs(buffer.get(), read_result, &local_threads);
|
|
}
|
|
} while (read_result > 0);
|
|
|
|
threads->swap(local_threads);
|
|
return true;
|
|
}
|
|
|
|
ssize_t PtraceClient::ReadUpTo(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;
|
|
}
|
|
|
|
ssize_t total_read = 0;
|
|
while (size > 0) {
|
|
int32_t bytes_read;
|
|
if (!LoggingReadFileExactly(sock_, &bytes_read, sizeof(bytes_read))) {
|
|
return -1;
|
|
}
|
|
|
|
if (bytes_read < 0) {
|
|
ReceiveAndLogReadError(sock_, "PtraceBroker ReadMemory");
|
|
return -1;
|
|
}
|
|
|
|
if (bytes_read == 0) {
|
|
return total_read;
|
|
}
|
|
|
|
if (static_cast<size_t>(bytes_read) > size) {
|
|
LOG(ERROR) << "invalid size " << bytes_read;
|
|
return -1;
|
|
}
|
|
|
|
if (!LoggingReadFileExactly(sock_, buffer_c, bytes_read)) {
|
|
return -1;
|
|
}
|
|
|
|
size -= bytes_read;
|
|
buffer_c += bytes_read;
|
|
total_read += bytes_read;
|
|
}
|
|
|
|
return total_read;
|
|
}
|
|
|
|
bool PtraceClient::SendFilePath(const char* path, size_t length) {
|
|
if (!LoggingWriteFile(sock_, path, length)) {
|
|
return false;
|
|
}
|
|
|
|
PtraceBroker::OpenResult result;
|
|
if (!LoggingReadFileExactly(sock_, &result, sizeof(result))) {
|
|
return false;
|
|
}
|
|
|
|
switch (result) {
|
|
case PtraceBroker::kOpenResultAccessDenied:
|
|
LOG(ERROR) << "Broker Open: access denied";
|
|
return false;
|
|
|
|
case PtraceBroker::kOpenResultTooLong:
|
|
LOG(ERROR) << "Broker Open: path too long";
|
|
return false;
|
|
|
|
case PtraceBroker::kOpenResultSuccess:
|
|
return true;
|
|
|
|
default:
|
|
if (result < 0) {
|
|
LOG(ERROR) << "Broker Open: invalid result " << result;
|
|
DCHECK(false);
|
|
} else {
|
|
errno = result;
|
|
PLOG(ERROR) << "Broker Open";
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace crashpad
|