mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-28 15:50:26 +08:00
7a70b0f151
On Android P, Bionic mistakenly places the vdso first in the list where the executable should be. Also correctly set the section size in the section headers for test module string tables. Bug: chromium:1050178 Change-Id: I83581d05c5ed3e25a237d1ce4a27c45755a3ab3c Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2231525 Reviewed-by: Mark Mentovai <mark@chromium.org> Commit-Queue: Joshua Peraza <jperaza@chromium.org>
543 lines
16 KiB
C++
543 lines
16 KiB
C++
// 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 "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 "build/build_config.h"
|
|
#include "snapshot/linux/debug_rendezvous.h"
|
|
#include "util/linux/auxiliary_vector.h"
|
|
#include "util/linux/proc_stat_reader.h"
|
|
|
|
#if defined(OS_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),
|
|
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;
|
|
}
|
|
|
|
// 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];
|
|
#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 = 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;
|
|
if (tid != reader->ProcessID() &&
|
|
thread_info.thread_specific_data_address > stack_region_address &&
|
|
thread_info.thread_specific_data_address < stack_end) {
|
|
stack_region_size =
|
|
thread_info.thread_specific_data_address - stack_region_address;
|
|
}
|
|
}
|
|
|
|
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 defined(OS_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 defined(OS_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 // OS_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 defined(OS_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
|