| // 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 |