Serenity Operating System
at master 262 lines 9.1 kB view raw
1/* 2 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Array.h> 8#include <AK/Checked.h> 9#include <AK/JsonArray.h> 10#include <AK/JsonObject.h> 11#include <AK/JsonValue.h> 12#include <AK/LexicalPath.h> 13#include <LibCore/DeprecatedFile.h> 14#include <LibCore/MappedFile.h> 15#include <LibDebug/DebugInfo.h> 16#include <LibSymbolication/Symbolication.h> 17 18namespace Symbolication { 19 20struct CachedELF { 21 NonnullRefPtr<Core::MappedFile> mapped_file; 22 NonnullOwnPtr<Debug::DebugInfo> debug_info; 23 NonnullOwnPtr<ELF::Image> image; 24}; 25 26static HashMap<DeprecatedString, OwnPtr<CachedELF>> s_cache; 27 28enum class KernelBaseState { 29 Uninitialized, 30 Valid, 31 Invalid, 32}; 33 34static FlatPtr s_kernel_base; 35static KernelBaseState s_kernel_base_state = KernelBaseState::Uninitialized; 36 37Optional<FlatPtr> kernel_base() 38{ 39 if (s_kernel_base_state == KernelBaseState::Uninitialized) { 40 auto file = Core::DeprecatedFile::open("/sys/kernel/constants/load_base", Core::OpenMode::ReadOnly); 41 if (file.is_error()) { 42 s_kernel_base_state = KernelBaseState::Invalid; 43 return {}; 44 } 45 auto kernel_base_str = DeprecatedString { file.value()->read_all(), NoChomp }; 46 using AddressType = u64; 47 auto maybe_kernel_base = kernel_base_str.to_uint<AddressType>(); 48 if (!maybe_kernel_base.has_value()) { 49 s_kernel_base_state = KernelBaseState::Invalid; 50 return {}; 51 } 52 s_kernel_base = maybe_kernel_base.value(); 53 s_kernel_base_state = KernelBaseState::Valid; 54 } 55 if (s_kernel_base_state == KernelBaseState::Invalid) 56 return {}; 57 return s_kernel_base; 58} 59 60Optional<Symbol> symbolicate(DeprecatedString const& path, FlatPtr address, IncludeSourcePosition include_source_positions) 61{ 62 DeprecatedString full_path = path; 63 if (!path.starts_with('/')) { 64 Array<StringView, 2> search_paths { "/usr/lib"sv, "/usr/local/lib"sv }; 65 bool found = false; 66 for (auto& search_path : search_paths) { 67 full_path = LexicalPath::join(search_path, path).string(); 68 if (Core::DeprecatedFile::exists(full_path)) { 69 found = true; 70 break; 71 } 72 } 73 if (!found) { 74 dbgln("Failed to find candidate for {}", path); 75 s_cache.set(path, {}); 76 return {}; 77 } 78 } 79 if (!s_cache.contains(full_path)) { 80 auto mapped_file = Core::MappedFile::map(full_path); 81 if (mapped_file.is_error()) { 82 dbgln("Failed to map {}: {}", full_path, mapped_file.error()); 83 s_cache.set(full_path, {}); 84 return {}; 85 } 86 auto elf = make<ELF::Image>(mapped_file.value()->bytes()); 87 if (!elf->is_valid()) { 88 dbgln("ELF not valid: {}", full_path); 89 s_cache.set(full_path, {}); 90 return {}; 91 } 92 auto cached_elf = make<CachedELF>(mapped_file.release_value(), make<Debug::DebugInfo>(*elf), move(elf)); 93 s_cache.set(full_path, move(cached_elf)); 94 } 95 96 auto it = s_cache.find(full_path); 97 VERIFY(it != s_cache.end()); 98 auto& cached_elf = it->value; 99 100 if (!cached_elf) 101 return {}; 102 103 u32 offset = 0; 104 auto symbol = cached_elf->debug_info->elf().symbolicate(address, &offset); 105 106 Vector<Debug::DebugInfo::SourcePosition> positions; 107 if (include_source_positions == IncludeSourcePosition::Yes) { 108 auto source_position_with_inlines = cached_elf->debug_info->get_source_position_with_inlines(address).release_value_but_fixme_should_propagate_errors(); 109 110 for (auto& position : source_position_with_inlines.inline_chain) { 111 if (!positions.contains_slow(position)) 112 positions.append(position); 113 } 114 115 if (source_position_with_inlines.source_position.has_value() && !positions.contains_slow(source_position_with_inlines.source_position.value())) { 116 positions.insert(0, source_position_with_inlines.source_position.value()); 117 } 118 } 119 120 return Symbol { 121 .address = address, 122 .name = move(symbol), 123 .object = LexicalPath::basename(path), 124 .offset = offset, 125 .source_positions = move(positions), 126 }; 127} 128 129Vector<Symbol> symbolicate_thread(pid_t pid, pid_t tid, IncludeSourcePosition include_source_positions) 130{ 131 struct RegionWithSymbols { 132 FlatPtr base { 0 }; 133 size_t size { 0 }; 134 DeprecatedString path; 135 }; 136 137 Vector<FlatPtr> stack; 138 Vector<RegionWithSymbols> regions; 139 140 if (auto maybe_kernel_base = kernel_base(); maybe_kernel_base.has_value()) { 141 regions.append(RegionWithSymbols { 142 .base = maybe_kernel_base.value(), 143 .size = 0x3fffffff, 144 .path = "/boot/Kernel.debug", 145 }); 146 } 147 148 { 149 auto stack_path = DeprecatedString::formatted("/proc/{}/stacks/{}", pid, tid); 150 auto file_or_error = Core::DeprecatedFile::open(stack_path, Core::OpenMode::ReadOnly); 151 if (file_or_error.is_error()) { 152 warnln("Could not open {}: {}", stack_path, file_or_error.error()); 153 return {}; 154 } 155 156 auto json = JsonValue::from_string(file_or_error.value()->read_all()); 157 if (json.is_error() || !json.value().is_array()) { 158 warnln("Invalid contents in {}", stack_path); 159 return {}; 160 } 161 162 stack.ensure_capacity(json.value().as_array().size()); 163 for (auto& value : json.value().as_array().values()) { 164 stack.append(value.to_addr()); 165 } 166 } 167 168 { 169 auto vm_path = DeprecatedString::formatted("/proc/{}/vm", pid); 170 auto file_or_error = Core::DeprecatedFile::open(vm_path, Core::OpenMode::ReadOnly); 171 if (file_or_error.is_error()) { 172 warnln("Could not open {}: {}", vm_path, file_or_error.error()); 173 return {}; 174 } 175 176 auto json = JsonValue::from_string(file_or_error.value()->read_all()); 177 if (json.is_error() || !json.value().is_array()) { 178 warnln("Invalid contents in {}", vm_path); 179 return {}; 180 } 181 182 for (auto& region_value : json.value().as_array().values()) { 183 auto& region = region_value.as_object(); 184 auto name = region.get_deprecated_string("name"sv).value_or({}); 185 auto address = region.get_addr("address"sv).value_or(0); 186 auto size = region.get_addr("size"sv).value_or(0); 187 188 DeprecatedString path; 189 if (name == "/usr/lib/Loader.so") { 190 path = name; 191 } else if (name.ends_with(": .text"sv) || name.ends_with(": .rodata"sv)) { 192 auto parts = name.split_view(':'); 193 path = parts[0]; 194 } else { 195 continue; 196 } 197 198 RegionWithSymbols r; 199 r.base = address; 200 r.size = size; 201 r.path = path; 202 regions.append(move(r)); 203 } 204 } 205 206 Vector<Symbol> symbols; 207 bool first_frame = true; 208 209 for (auto address : stack) { 210 RegionWithSymbols const* found_region = nullptr; 211 for (auto& region : regions) { 212 FlatPtr region_end; 213 if (Checked<FlatPtr>::addition_would_overflow(region.base, region.size)) 214 region_end = NumericLimits<FlatPtr>::max(); 215 else 216 region_end = region.base + region.size; 217 if (address >= region.base && address < region_end) { 218 found_region = &region; 219 break; 220 } 221 } 222 223 if (!found_region) { 224 outln("{:p} ??", address); 225 continue; 226 } 227 228 // We found an address inside of a region, but the base of that region 229 // may not be the base of the ELF image. For example, there could be an 230 // .rodata mapping at a lower address than the first .text mapping from 231 // the same image. look for the lowest address region with the same path. 232 RegionWithSymbols const* base_region = nullptr; 233 for (auto& region : regions) { 234 if (region.path != found_region->path) 235 continue; 236 if (!base_region || region.base <= base_region->base) 237 base_region = &region; 238 } 239 240 FlatPtr adjusted_address = address - base_region->base; 241 242 // We're subtracting 1 from the address because this is the return address, 243 // i.e. it is one instruction past the call instruction. 244 // However, because the first frame represents the current 245 // instruction pointer rather than the return address we don't 246 // subtract 1 for that. 247 auto result = symbolicate(found_region->path, adjusted_address - (first_frame ? 0 : 1), include_source_positions); 248 first_frame = false; 249 if (!result.has_value()) { 250 symbols.append(Symbol { 251 .address = address, 252 .source_positions = {}, 253 }); 254 continue; 255 } 256 257 symbols.append(result.value()); 258 } 259 return symbols; 260} 261 262}