mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-26 23:01:05 +08:00
4f5dd67229
Only RV64GC is supported. Bug: fuchsia:127655 Tested: `python build/run_tests.py` on RISC-V emulator Tested: Created minidump via self-induced crash on RISC-V emulator, ran through Breakpad stackwalker Change-Id: I713797cd623b0a758269048e01696cbce502ca6c Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4581050 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
565 lines
17 KiB
C++
565 lines
17 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 "snapshot/linux/process_reader_linux.h"
|
|
|
|
#include <elf.h>
|
|
#include <errno.h>
|
|
#include <sched.h>
|
|
#include <string.h>
|
|
#include <sys/resource.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include "base/logging.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "build/build_config.h"
|
|
#include "snapshot/linux/debug_rendezvous.h"
|
|
#include "util/linux/auxiliary_vector.h"
|
|
#include "util/linux/proc_stat_reader.h"
|
|
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
#include <android/api-level.h>
|
|
#endif
|
|
|
|
namespace crashpad {
|
|
|
|
namespace {
|
|
|
|
bool ShouldMergeStackMappings(const MemoryMap::Mapping& stack_mapping,
|
|
const MemoryMap::Mapping& adj_mapping) {
|
|
DCHECK(stack_mapping.readable);
|
|
return adj_mapping.readable && stack_mapping.device == adj_mapping.device &&
|
|
stack_mapping.inode == adj_mapping.inode &&
|
|
(stack_mapping.name == adj_mapping.name ||
|
|
stack_mapping.name.empty() || adj_mapping.name.empty());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ProcessReaderLinux::Thread::Thread()
|
|
: thread_info(),
|
|
stack_region_address(0),
|
|
stack_region_size(0),
|
|
name(),
|
|
tid(-1),
|
|
static_priority(-1),
|
|
nice_value(-1) {}
|
|
|
|
ProcessReaderLinux::Thread::~Thread() {}
|
|
|
|
bool ProcessReaderLinux::Thread::InitializePtrace(
|
|
PtraceConnection* connection) {
|
|
if (!connection->GetThreadInfo(tid, &thread_info)) {
|
|
return false;
|
|
}
|
|
|
|
// From man proc(5):
|
|
//
|
|
// /proc/[pid]/comm (since Linux 2.6.33)
|
|
//
|
|
// Different threads in the same process may have different comm values,
|
|
// accessible via /proc/[pid]/task/[tid]/comm.
|
|
const std::string path = base::StringPrintf(
|
|
"/proc/%d/task/%d/comm", connection->GetProcessID(), tid);
|
|
if (connection->ReadFileContents(base::FilePath(path), &name)) {
|
|
if (!name.empty() && name.back() == '\n') {
|
|
// Remove the final newline character.
|
|
name.pop_back();
|
|
}
|
|
} else {
|
|
// Continue on without the thread name.
|
|
}
|
|
|
|
// TODO(jperaza): Collect scheduling priorities via the broker when they can't
|
|
// be collected directly.
|
|
have_priorities = false;
|
|
|
|
// TODO(jperaza): Starting with Linux 3.14, scheduling policy, static
|
|
// priority, and nice value can be collected all in one call with
|
|
// sched_getattr().
|
|
int res = sched_getscheduler(tid);
|
|
if (res < 0) {
|
|
PLOG(WARNING) << "sched_getscheduler";
|
|
return true;
|
|
}
|
|
sched_policy = res;
|
|
|
|
sched_param param;
|
|
if (sched_getparam(tid, ¶m) != 0) {
|
|
PLOG(WARNING) << "sched_getparam";
|
|
return true;
|
|
}
|
|
static_priority = param.sched_priority;
|
|
|
|
errno = 0;
|
|
res = getpriority(PRIO_PROCESS, tid);
|
|
if (res == -1 && errno) {
|
|
PLOG(WARNING) << "getpriority";
|
|
return true;
|
|
}
|
|
nice_value = res;
|
|
|
|
have_priorities = true;
|
|
return true;
|
|
}
|
|
|
|
void ProcessReaderLinux::Thread::InitializeStack(ProcessReaderLinux* reader) {
|
|
LinuxVMAddress stack_pointer;
|
|
#if defined(ARCH_CPU_X86_FAMILY)
|
|
stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.rsp
|
|
: thread_info.thread_context.t32.esp;
|
|
#elif defined(ARCH_CPU_ARM_FAMILY)
|
|
stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.sp
|
|
: thread_info.thread_context.t32.sp;
|
|
#elif defined(ARCH_CPU_MIPS_FAMILY)
|
|
stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.regs[29]
|
|
: thread_info.thread_context.t32.regs[29];
|
|
#elif defined(ARCH_CPU_RISCV64)
|
|
stack_pointer = thread_info.thread_context.t64.regs[1];
|
|
#else
|
|
#error Port.
|
|
#endif
|
|
InitializeStackFromSP(reader, stack_pointer);
|
|
}
|
|
|
|
void ProcessReaderLinux::Thread::InitializeStackFromSP(
|
|
ProcessReaderLinux* reader,
|
|
LinuxVMAddress stack_pointer) {
|
|
const MemoryMap* memory_map = reader->GetMemoryMap();
|
|
|
|
// If we can't find the mapping, it's probably a bad stack pointer
|
|
const MemoryMap::Mapping* mapping = memory_map->FindMapping(stack_pointer);
|
|
if (!mapping) {
|
|
LOG(WARNING) << "no stack mapping";
|
|
return;
|
|
}
|
|
LinuxVMAddress stack_region_start =
|
|
reader->Memory()->PointerToAddress(stack_pointer);
|
|
|
|
// We've hit what looks like a guard page; skip to the end and check for a
|
|
// mapped stack region.
|
|
if (!mapping->readable) {
|
|
stack_region_start = mapping->range.End();
|
|
mapping = memory_map->FindMapping(stack_region_start);
|
|
if (!mapping) {
|
|
LOG(WARNING) << "no stack mapping";
|
|
return;
|
|
}
|
|
} else {
|
|
#if defined(ARCH_CPU_X86_FAMILY)
|
|
// Adjust start address to include the red zone
|
|
if (reader->Is64Bit()) {
|
|
constexpr LinuxVMSize kRedZoneSize = 128;
|
|
LinuxVMAddress red_zone_base =
|
|
stack_region_start - std::min(kRedZoneSize, stack_region_start);
|
|
|
|
// Only include the red zone if it is part of a valid mapping
|
|
if (red_zone_base >= mapping->range.Base()) {
|
|
stack_region_start = red_zone_base;
|
|
} else {
|
|
const MemoryMap::Mapping* rz_mapping =
|
|
memory_map->FindMapping(red_zone_base);
|
|
if (rz_mapping && ShouldMergeStackMappings(*mapping, *rz_mapping)) {
|
|
stack_region_start = red_zone_base;
|
|
} else {
|
|
stack_region_start = mapping->range.Base();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
stack_region_address = stack_region_start;
|
|
|
|
// If there are more mappings at the end of this one, they may be a
|
|
// continuation of the stack.
|
|
LinuxVMAddress stack_end = mapping->range.End();
|
|
const MemoryMap::Mapping* next_mapping;
|
|
while ((next_mapping = memory_map->FindMapping(stack_end)) &&
|
|
ShouldMergeStackMappings(*mapping, *next_mapping)) {
|
|
stack_end = next_mapping->range.End();
|
|
}
|
|
|
|
// The main thread should have an entry in the maps file just for its stack,
|
|
// so we'll assume the base of the stack is at the end of the region. Other
|
|
// threads' stacks may not have their own entries in the maps file if they
|
|
// were user-allocated within a larger mapping, but pthreads places the TLS
|
|
// at the high-address end of the stack so we can try using that to shrink
|
|
// the stack region.
|
|
stack_region_size = stack_end - stack_region_address;
|
|
VMAddress tls_address = reader->Memory()->PointerToAddress(
|
|
thread_info.thread_specific_data_address);
|
|
if (tid != reader->ProcessID() && tls_address > stack_region_address &&
|
|
tls_address < stack_end) {
|
|
stack_region_size = tls_address - stack_region_address;
|
|
}
|
|
}
|
|
|
|
ProcessReaderLinux::Module::Module()
|
|
: name(), elf_reader(nullptr), type(ModuleSnapshot::kModuleTypeUnknown) {}
|
|
|
|
ProcessReaderLinux::Module::~Module() = default;
|
|
|
|
ProcessReaderLinux::ProcessReaderLinux()
|
|
: connection_(),
|
|
process_info_(),
|
|
memory_map_(),
|
|
threads_(),
|
|
modules_(),
|
|
elf_readers_(),
|
|
is_64_bit_(false),
|
|
initialized_threads_(false),
|
|
initialized_modules_(false),
|
|
initialized_() {}
|
|
|
|
ProcessReaderLinux::~ProcessReaderLinux() {}
|
|
|
|
bool ProcessReaderLinux::Initialize(PtraceConnection* connection) {
|
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
|
DCHECK(connection);
|
|
connection_ = connection;
|
|
|
|
if (!process_info_.InitializeWithPtrace(connection_)) {
|
|
return false;
|
|
}
|
|
|
|
if (!memory_map_.Initialize(connection_)) {
|
|
return false;
|
|
}
|
|
|
|
is_64_bit_ = process_info_.Is64Bit();
|
|
|
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
|
return true;
|
|
}
|
|
|
|
bool ProcessReaderLinux::StartTime(timeval* start_time) const {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
return process_info_.StartTime(start_time);
|
|
}
|
|
|
|
bool ProcessReaderLinux::CPUTimes(timeval* user_time,
|
|
timeval* system_time) const {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
timerclear(user_time);
|
|
timerclear(system_time);
|
|
|
|
timeval local_user_time;
|
|
timerclear(&local_user_time);
|
|
timeval local_system_time;
|
|
timerclear(&local_system_time);
|
|
|
|
for (const Thread& thread : threads_) {
|
|
ProcStatReader stat;
|
|
if (!stat.Initialize(connection_, thread.tid)) {
|
|
return false;
|
|
}
|
|
|
|
timeval thread_user_time;
|
|
if (!stat.UserCPUTime(&thread_user_time)) {
|
|
return false;
|
|
}
|
|
|
|
timeval thread_system_time;
|
|
if (!stat.SystemCPUTime(&thread_system_time)) {
|
|
return false;
|
|
}
|
|
|
|
timeradd(&local_user_time, &thread_user_time, &local_user_time);
|
|
timeradd(&local_system_time, &thread_system_time, &local_system_time);
|
|
}
|
|
|
|
*user_time = local_user_time;
|
|
*system_time = local_system_time;
|
|
return true;
|
|
}
|
|
|
|
const std::vector<ProcessReaderLinux::Thread>& ProcessReaderLinux::Threads() {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
if (!initialized_threads_) {
|
|
InitializeThreads();
|
|
}
|
|
return threads_;
|
|
}
|
|
|
|
const std::vector<ProcessReaderLinux::Module>& ProcessReaderLinux::Modules() {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
if (!initialized_modules_) {
|
|
InitializeModules();
|
|
}
|
|
return modules_;
|
|
}
|
|
|
|
void ProcessReaderLinux::InitializeAbortMessage() {
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
const MemoryMap::Mapping* mapping =
|
|
memory_map_.FindMappingWithName("[anon:abort message]");
|
|
if (!mapping) {
|
|
return;
|
|
}
|
|
|
|
if (is_64_bit_) {
|
|
ReadAbortMessage<true>(mapping);
|
|
} else {
|
|
ReadAbortMessage<false>(mapping);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
|
|
// These structure definitions and the magic numbers below were copied from
|
|
// bionic/libc/bionic/android_set_abort_message.cpp
|
|
|
|
template <bool is64Bit>
|
|
struct abort_msg_t {
|
|
uint32_t size;
|
|
char msg[0];
|
|
};
|
|
|
|
template <>
|
|
struct abort_msg_t<true> {
|
|
uint64_t size;
|
|
char msg[0];
|
|
};
|
|
|
|
template <bool is64Bit>
|
|
struct magic_abort_msg_t {
|
|
uint64_t magic1;
|
|
uint64_t magic2;
|
|
abort_msg_t<is64Bit> msg;
|
|
};
|
|
|
|
template <bool is64Bit>
|
|
void ProcessReaderLinux::ReadAbortMessage(const MemoryMap::Mapping* mapping) {
|
|
magic_abort_msg_t<is64Bit> header;
|
|
if (!Memory()->Read(
|
|
mapping->range.Base(), sizeof(magic_abort_msg_t<is64Bit>), &header)) {
|
|
return;
|
|
}
|
|
|
|
size_t size = header.msg.size - sizeof(magic_abort_msg_t<is64Bit>) - 1;
|
|
if (header.magic1 != 0xb18e40886ac388f0ULL ||
|
|
header.magic2 != 0xc6dfba755a1de0b5ULL ||
|
|
mapping->range.Size() <
|
|
offsetof(magic_abort_msg_t<is64Bit>, msg.msg) + size) {
|
|
return;
|
|
}
|
|
|
|
abort_message_.resize(size);
|
|
if (!Memory()->Read(
|
|
mapping->range.Base() + offsetof(magic_abort_msg_t<is64Bit>, msg.msg),
|
|
size,
|
|
&abort_message_[0])) {
|
|
abort_message_.clear();
|
|
}
|
|
}
|
|
|
|
#endif // BUILDFLAG(IS_ANDROID)
|
|
|
|
const std::string& ProcessReaderLinux::AbortMessage() {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
if (abort_message_.empty()) {
|
|
InitializeAbortMessage();
|
|
}
|
|
return abort_message_;
|
|
}
|
|
|
|
void ProcessReaderLinux::InitializeThreads() {
|
|
DCHECK(threads_.empty());
|
|
initialized_threads_ = true;
|
|
|
|
pid_t pid = ProcessID();
|
|
if (pid == getpid()) {
|
|
// TODO(jperaza): ptrace can't be used on threads in the same thread group.
|
|
// Using clone to create a new thread in it's own thread group doesn't work
|
|
// because glibc doesn't support threads it didn't create via pthreads.
|
|
// Fork a new process to snapshot us and copy the data back?
|
|
LOG(ERROR) << "not implemented";
|
|
return;
|
|
}
|
|
|
|
Thread main_thread;
|
|
main_thread.tid = pid;
|
|
if (main_thread.InitializePtrace(connection_)) {
|
|
main_thread.InitializeStack(this);
|
|
threads_.push_back(main_thread);
|
|
} else {
|
|
LOG(WARNING) << "Couldn't initialize main thread.";
|
|
}
|
|
|
|
bool main_thread_found = false;
|
|
std::vector<pid_t> thread_ids;
|
|
bool result = connection_->Threads(&thread_ids);
|
|
DCHECK(result);
|
|
for (pid_t tid : thread_ids) {
|
|
if (tid == pid) {
|
|
DCHECK(!main_thread_found);
|
|
main_thread_found = true;
|
|
continue;
|
|
}
|
|
|
|
Thread thread;
|
|
thread.tid = tid;
|
|
if (connection_->Attach(tid) && thread.InitializePtrace(connection_)) {
|
|
thread.InitializeStack(this);
|
|
threads_.push_back(thread);
|
|
}
|
|
}
|
|
DCHECK(main_thread_found);
|
|
}
|
|
|
|
void ProcessReaderLinux::InitializeModules() {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
initialized_modules_ = true;
|
|
|
|
AuxiliaryVector aux;
|
|
if (!aux.Initialize(connection_)) {
|
|
return;
|
|
}
|
|
|
|
LinuxVMAddress phdrs;
|
|
if (!aux.GetValue(AT_PHDR, &phdrs)) {
|
|
return;
|
|
}
|
|
|
|
ProcessMemoryRange range;
|
|
if (!range.Initialize(Memory(), is_64_bit_)) {
|
|
return;
|
|
}
|
|
|
|
// The strategy used for identifying loaded modules depends on ELF files
|
|
// conventionally loading their header and program headers into memory.
|
|
// Locating the correct module could fail if the headers aren't mapped, are
|
|
// mapped at an unexpected location, or if there are other mappings
|
|
// constructed to look like the ELF module being searched for.
|
|
const MemoryMap::Mapping* exe_mapping = nullptr;
|
|
std::unique_ptr<ElfImageReader> exe_reader;
|
|
{
|
|
const MemoryMap::Mapping* phdr_mapping = memory_map_.FindMapping(phdrs);
|
|
if (!phdr_mapping) {
|
|
return;
|
|
}
|
|
|
|
auto possible_mappings =
|
|
memory_map_.FindFilePossibleMmapStarts(*phdr_mapping);
|
|
const MemoryMap::Mapping* mapping = nullptr;
|
|
while ((mapping = possible_mappings->Next())) {
|
|
auto parsed_exe = std::make_unique<ElfImageReader>();
|
|
if (parsed_exe->Initialize(
|
|
range,
|
|
mapping->range.Base(),
|
|
/* verbose= */ possible_mappings->Count() == 0) &&
|
|
parsed_exe->GetProgramHeaderTableAddress() == phdrs) {
|
|
exe_mapping = mapping;
|
|
exe_reader = std::move(parsed_exe);
|
|
break;
|
|
}
|
|
}
|
|
if (!exe_mapping) {
|
|
LOG(ERROR) << "no exe mappings 0x" << std::hex
|
|
<< phdr_mapping->range.Base();
|
|
return;
|
|
}
|
|
}
|
|
|
|
LinuxVMAddress debug_address;
|
|
if (!exe_reader->GetDebugAddress(&debug_address)) {
|
|
return;
|
|
}
|
|
|
|
DebugRendezvous debug;
|
|
if (!debug.Initialize(range, debug_address)) {
|
|
return;
|
|
}
|
|
|
|
Module exe = {};
|
|
exe.name = !debug.Executable()->name.empty() ? debug.Executable()->name
|
|
: exe_mapping->name;
|
|
exe.elf_reader = exe_reader.get();
|
|
exe.type = ModuleSnapshot::ModuleType::kModuleTypeExecutable;
|
|
|
|
modules_.push_back(exe);
|
|
elf_readers_.push_back(std::move(exe_reader));
|
|
|
|
LinuxVMAddress loader_base = 0;
|
|
aux.GetValue(AT_BASE, &loader_base);
|
|
|
|
for (const DebugRendezvous::LinkEntry& entry : debug.Modules()) {
|
|
const MemoryMap::Mapping* module_mapping = nullptr;
|
|
std::unique_ptr<ElfImageReader> elf_reader;
|
|
{
|
|
const MemoryMap::Mapping* dyn_mapping =
|
|
memory_map_.FindMapping(entry.dynamic_array);
|
|
if (!dyn_mapping) {
|
|
continue;
|
|
}
|
|
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
// Beginning at API 21, Bionic provides android_dlopen_ext() which allows
|
|
// passing a file descriptor with an existing relro segment to the loader.
|
|
// This means that the mapping attributes of dyn_mapping may be unrelated
|
|
// to the attributes of the other mappings for the module. In this case,
|
|
// search all mappings in reverse order from dyn_mapping until a module is
|
|
// parsed whose dynamic address matches the value in the debug link.
|
|
static int api_level = android_get_device_api_level();
|
|
auto possible_mappings =
|
|
(api_level >= 21 || api_level < 0)
|
|
? memory_map_.ReverseIteratorFrom(*dyn_mapping)
|
|
: memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
|
|
#else
|
|
auto possible_mappings =
|
|
memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
|
|
#endif
|
|
const MemoryMap::Mapping* mapping = nullptr;
|
|
while ((mapping = possible_mappings->Next())) {
|
|
auto parsed_module = std::make_unique<ElfImageReader>();
|
|
VMAddress dynamic_address;
|
|
if (parsed_module->Initialize(
|
|
range,
|
|
mapping->range.Base(),
|
|
/* verbose= */ possible_mappings->Count() == 0) &&
|
|
parsed_module->GetDynamicArrayAddress(&dynamic_address) &&
|
|
dynamic_address == entry.dynamic_array) {
|
|
module_mapping = mapping;
|
|
elf_reader = std::move(parsed_module);
|
|
break;
|
|
}
|
|
}
|
|
if (!module_mapping) {
|
|
LOG(ERROR) << "no module mappings 0x" << std::hex
|
|
<< dyn_mapping->range.Base();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Module module = {};
|
|
std::string soname;
|
|
if (elf_reader->SoName(&soname) && !soname.empty()) {
|
|
module.name = soname;
|
|
} else {
|
|
module.name = !entry.name.empty() ? entry.name : module_mapping->name;
|
|
}
|
|
module.elf_reader = elf_reader.get();
|
|
module.type = loader_base && elf_reader->Address() == loader_base
|
|
? ModuleSnapshot::kModuleTypeDynamicLoader
|
|
: ModuleSnapshot::kModuleTypeSharedLibrary;
|
|
modules_.push_back(module);
|
|
elf_readers_.push_back(std::move(elf_reader));
|
|
}
|
|
}
|
|
|
|
} // namespace crashpad
|