Serenity Operating System
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 = ®ion;
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 = ®ion;
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}