// 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 #include #include #include #include #include #include #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 #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_RISCV_FAMILY) stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.sp : thread_info.thread_context.t32.sp; #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::Threads() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (!initialized_threads_) { InitializeThreads(); } return threads_; } const std::vector& 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(mapping); } else { ReadAbortMessage(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 struct abort_msg_t { uint32_t size; char msg[0]; }; template <> struct abort_msg_t { uint64_t size; char msg[0]; }; template struct magic_abort_msg_t { uint64_t magic1; uint64_t magic2; abort_msg_t msg; }; template void ProcessReaderLinux::ReadAbortMessage(const MemoryMap::Mapping* mapping) { magic_abort_msg_t header; if (!Memory()->Read( mapping->range.Base(), sizeof(magic_abort_msg_t), &header)) { return; } size_t size = header.msg.size - sizeof(magic_abort_msg_t) - 1; if (header.magic1 != 0xb18e40886ac388f0ULL || header.magic2 != 0xc6dfba755a1de0b5ULL || mapping->range.Size() < offsetof(magic_abort_msg_t, msg.msg) + size) { return; } abort_message_.resize(size); if (!Memory()->Read( mapping->range.Base() + offsetof(magic_abort_msg_t, 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 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 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(); 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 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(); 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