/* * Copyright (c) 2021, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include namespace Symbolication { struct CachedELF { NonnullRefPtr mapped_file; NonnullOwnPtr debug_info; NonnullOwnPtr image; }; static HashMap> s_cache; enum class KernelBaseState { Uninitialized, Valid, Invalid, }; static FlatPtr s_kernel_base; static KernelBaseState s_kernel_base_state = KernelBaseState::Uninitialized; Optional kernel_base() { if (s_kernel_base_state == KernelBaseState::Uninitialized) { auto file = Core::File::open("/proc/kernel_base", Core::OpenMode::ReadOnly); if (file.is_error()) { s_kernel_base_state = KernelBaseState::Invalid; return {}; } auto kernel_base_str = String { file.value()->read_all(), NoChomp }; #if ARCH(I386) using AddressType = u32; #else using AddressType = u64; #endif auto maybe_kernel_base = kernel_base_str.to_uint(); if (!maybe_kernel_base.has_value()) { s_kernel_base_state = KernelBaseState::Invalid; return {}; } s_kernel_base = maybe_kernel_base.value(); s_kernel_base_state = KernelBaseState::Valid; } if (s_kernel_base_state == KernelBaseState::Invalid) return {}; return s_kernel_base; } Optional symbolicate(String const& path, FlatPtr address, IncludeSourcePosition include_source_positions) { String full_path = path; if (!path.starts_with('/')) { Array search_paths { "/usr/lib"sv, "/usr/local/lib"sv }; bool found = false; for (auto& search_path : search_paths) { full_path = LexicalPath::join(search_path, path).string(); if (Core::File::exists(full_path)) { found = true; break; } } if (!found) { dbgln("Failed to find candidate for {}", path); s_cache.set(path, {}); return {}; } } if (!s_cache.contains(full_path)) { auto mapped_file = Core::MappedFile::map(full_path); if (mapped_file.is_error()) { dbgln("Failed to map {}: {}", full_path, mapped_file.error()); s_cache.set(full_path, {}); return {}; } auto elf = make(mapped_file.value()->bytes()); if (!elf->is_valid()) { dbgln("ELF not valid: {}", full_path); s_cache.set(full_path, {}); return {}; } auto cached_elf = make(mapped_file.release_value(), make(*elf), move(elf)); s_cache.set(full_path, move(cached_elf)); } auto it = s_cache.find(full_path); VERIFY(it != s_cache.end()); auto& cached_elf = it->value; if (!cached_elf) return {}; u32 offset = 0; auto symbol = cached_elf->debug_info->elf().symbolicate(address, &offset); Vector positions; if (include_source_positions == IncludeSourcePosition::Yes) { auto source_position_with_inlines = cached_elf->debug_info->get_source_position_with_inlines(address); for (auto& position : source_position_with_inlines.inline_chain) { if (!positions.contains_slow(position)) positions.append(position); } if (source_position_with_inlines.source_position.has_value() && !positions.contains_slow(source_position_with_inlines.source_position.value())) { positions.insert(0, source_position_with_inlines.source_position.value()); } } return Symbol { .address = address, .name = move(symbol), .object = LexicalPath::basename(path), .offset = offset, .source_positions = move(positions), }; } Vector symbolicate_thread(pid_t pid, pid_t tid, IncludeSourcePosition include_source_positions) { struct RegionWithSymbols { FlatPtr base { 0 }; size_t size { 0 }; String path; }; Vector stack; Vector regions; if (auto maybe_kernel_base = kernel_base(); maybe_kernel_base.has_value()) { regions.append(RegionWithSymbols { .base = maybe_kernel_base.value(), .size = 0x3fffffff, .path = "/boot/Kernel.debug", }); } { auto stack_path = String::formatted("/proc/{}/stacks/{}", pid, tid); auto file_or_error = Core::File::open(stack_path, Core::OpenMode::ReadOnly); if (file_or_error.is_error()) { warnln("Could not open {}: {}", stack_path, file_or_error.error()); return {}; } auto json = JsonValue::from_string(file_or_error.value()->read_all()); if (json.is_error() || !json.value().is_array()) { warnln("Invalid contents in {}", stack_path); return {}; } stack.ensure_capacity(json.value().as_array().size()); for (auto& value : json.value().as_array().values()) { stack.append(value.to_addr()); } } { auto vm_path = String::formatted("/proc/{}/vm", pid); auto file_or_error = Core::File::open(vm_path, Core::OpenMode::ReadOnly); if (file_or_error.is_error()) { warnln("Could not open {}: {}", vm_path, file_or_error.error()); return {}; } auto json = JsonValue::from_string(file_or_error.value()->read_all()); if (json.is_error() || !json.value().is_array()) { warnln("Invalid contents in {}", vm_path); return {}; } for (auto& region_value : json.value().as_array().values()) { auto& region = region_value.as_object(); auto name = region.get("name"sv).to_string(); auto address = region.get("address"sv).to_addr(); auto size = region.get("size"sv).to_addr(); String path; if (name == "/usr/lib/Loader.so") { path = name; } else if (name.ends_with(": .text"sv) || name.ends_with(": .rodata"sv)) { auto parts = name.split_view(':'); path = parts[0]; } else { continue; } RegionWithSymbols r; r.base = address; r.size = size; r.path = path; regions.append(move(r)); } } Vector symbols; bool first_frame = true; for (auto address : stack) { RegionWithSymbols const* found_region = nullptr; for (auto& region : regions) { FlatPtr region_end; if (Checked::addition_would_overflow(region.base, region.size)) region_end = NumericLimits::max(); else region_end = region.base + region.size; if (address >= region.base && address < region_end) { found_region = ®ion; break; } } if (!found_region) { outln("{:p} ??", address); continue; } // We found an address inside of a region, but the base of that region // may not be the base of the ELF image. For example, there could be an // .rodata mapping at a lower address than the first .text mapping from // the same image. look for the lowest address region with the same path. RegionWithSymbols const* base_region = nullptr; for (auto& region : regions) { if (region.path != found_region->path) continue; if (!base_region || region.base <= base_region->base) base_region = ®ion; } FlatPtr adjusted_address = address - base_region->base; // We're subtracting 1 from the address because this is the return address, // i.e. it is one instruction past the call instruction. // However, because the first frame represents the current // instruction pointer rather than the return address we don't // subtract 1 for that. auto result = symbolicate(found_region->path, adjusted_address - (first_frame ? 0 : 1), include_source_positions); first_frame = false; if (!result.has_value()) { symbols.append(Symbol { .address = address, .source_positions = {}, }); continue; } symbols.append(result.value()); } return symbols; } }