be/src/common/symbol_index.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // This file is copied from |
18 | | // https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/SymbolIndex.cpp |
19 | | // and modified by Doris |
20 | | |
21 | | #if defined(__ELF__) && !defined(__FreeBSD__) |
22 | | |
23 | | #include "common/symbol_index.h" |
24 | | |
25 | | #include <link.h> |
26 | | #include <pdqsort.h> |
27 | | |
28 | | #include <algorithm> |
29 | | #include <array> |
30 | | #include <cassert> |
31 | | #include <cstring> |
32 | | #include <filesystem> |
33 | | #include <mutex> |
34 | | #include <optional> |
35 | | |
36 | | #include "common/logging.h" |
37 | | #include "common/stack_trace.h" |
38 | | #include "exec/common/hex.h" |
39 | | |
40 | | /** |
41 | | |
42 | | ELF object can contain three different places with symbol names and addresses: |
43 | | |
44 | | 1. Symbol table in section headers. It is used for static linking and usually left in executable. |
45 | | It is not loaded in memory and they are not necessary for program to run. |
46 | | It does not relate to debug info and present regardless to -g flag. |
47 | | You can use strip to get rid of this symbol table. |
48 | | If you have this symbol table in your binary, you can manually read it and get symbol names, even for symbols from anonymous namespaces. |
49 | | |
50 | | 2. Hashes in program headers such as DT_HASH and DT_GNU_HASH. |
51 | | It is necessary for dynamic object (.so libraries and any dynamically linked executable that depend on .so libraries) |
52 | | because it is used for dynamic linking that happens in runtime and performed by dynamic loader. |
53 | | Only exported symbols will be presented in that hash tables. Symbols from anonymous namespaces are not. |
54 | | This part of executable binary is loaded in memory and accessible via 'dl_iterate_phdr', 'dladdr' and 'backtrace_symbols' functions from libc. |
55 | | ClickHouse versions prior to 19.13 has used just these symbol names to symbolize stack traces |
56 | | and stack traces may be incomplete due to lack of symbols with internal linkage. |
57 | | But because ClickHouse is linked with most of the symbols exported (-rdynamic flag) it can still provide good enough stack traces. |
58 | | |
59 | | 3. DWARF debug info. It contains the most detailed information about symbols and everything else. |
60 | | It allows to get source file names and line numbers from addresses. Only available if you use -g option for compiler. |
61 | | It is also used by default for ClickHouse builds, but because of its weight (about two gigabytes) |
62 | | it is split to separate binary and provided in clickhouse-common-static-dbg package. |
63 | | This separate binary is placed in /usr/lib/debug/usr/bin/clickhouse.debug and is loaded automatically by tools like gdb, addr2line. |
64 | | When you build ClickHouse by yourself, debug info is not split and present in a single huge binary. |
65 | | |
66 | | What ClickHouse is using to provide good stack traces? |
67 | | |
68 | | In versions prior to 19.13, only "program headers" (2) was used. |
69 | | |
70 | | In version 19.13, ClickHouse will read program headers (2) and cache them, |
71 | | also it will read itself as ELF binary and extract symbol tables from section headers (1) |
72 | | to also symbolize functions that are not exported for dynamic linking. |
73 | | And finally, it will read DWARF info (3) if available to display file names and line numbers. |
74 | | |
75 | | What detail can you obtain depending on your binary? |
76 | | |
77 | | If you have debug info (you build ClickHouse by yourself or install clickhouse-common-static-dbg package), you will get source file names and line numbers. |
78 | | Otherwise you will get only symbol names. If your binary contains symbol table in section headers (the default, unless stripped), you will get all symbol names. |
79 | | Otherwise you will get only exported symbols from program headers. |
80 | | |
81 | | */ |
82 | | |
83 | | #if defined(__clang__) |
84 | | #pragma clang diagnostic ignored "-Wreserved-id-macro" |
85 | | #pragma clang diagnostic ignored "-Wunused-macros" |
86 | | #endif |
87 | | |
88 | | #define __msan_unpoison(X, Y) // NOLINT |
89 | | #define __msan_unpoison_string(X) // NOLINT |
90 | | #if defined(__clang__) && defined(__has_feature) |
91 | | #if __has_feature(memory_sanitizer) |
92 | | #undef __msan_unpoison |
93 | | #undef __msan_unpoison_string |
94 | | #include <sanitizer/msan_interface.h> |
95 | | #endif |
96 | | #endif |
97 | | |
98 | | namespace doris { |
99 | | |
100 | | namespace { |
101 | | |
102 | | constexpr size_t MAX_SYMBOL_INDEX_LOADED_OBJECTS = 4096; |
103 | | constexpr size_t MAX_SYMBOL_INDEX_OBJECT_NAME = 4096; |
104 | | constexpr size_t MAX_SYMBOL_INDEX_BUILD_ID = 128; |
105 | | |
106 | 6 | std::mutex& symbolIndexReloadMutex() { |
107 | 6 | static std::mutex lock; |
108 | 6 | return lock; |
109 | 6 | } |
110 | | |
111 | | struct LoadedObject { |
112 | | ElfW(Addr) base_address = 0; |
113 | | std::array<char, MAX_SYMBOL_INDEX_OBJECT_NAME> name {}; |
114 | | size_t name_size = 0; |
115 | | bool name_truncated = false; |
116 | | std::array<char, MAX_SYMBOL_INDEX_BUILD_ID> build_id {}; |
117 | | size_t build_id_size = 0; |
118 | | bool build_id_truncated = false; |
119 | | |
120 | 256 | std::string nameString() const { return {name.data(), name_size}; } |
121 | 256 | std::string buildIDString() const { return {build_id.data(), build_id_size}; } |
122 | | }; |
123 | | |
124 | | struct LoadedObjectsSnapshot { |
125 | | std::vector<LoadedObject> objects; |
126 | | bool overflow = false; |
127 | | bool name_truncated = false; |
128 | | bool build_id_truncated = false; |
129 | | }; |
130 | | |
131 | | /// Notes: "PHDR" is "Program Headers". |
132 | | /// To look at program headers, run: |
133 | | /// readelf -l ./clickhouse-server |
134 | | /// To look at section headers, run: |
135 | | /// readelf -S ./clickhouse-server |
136 | | /// Also look at: https://wiki.osdev.org/ELF |
137 | | /// Also look at: man elf |
138 | | /// http://www.linker-aliens.org/blogs/ali/entry/inside_elf_symbol_tables/ |
139 | | /// https://stackoverflow.com/questions/32088140/multiple-string-tables-in-elf-object |
140 | | |
141 | | template <size_t N> |
142 | 256 | bool copyCString(const char* src, std::array<char, N>& dst, size_t& dst_size) { |
143 | 256 | dst_size = 0; |
144 | 256 | if (src == nullptr) { |
145 | 0 | dst[0] = '\0'; |
146 | 0 | return false; |
147 | 0 | } |
148 | | |
149 | 9.72k | while (dst_size + 1 < N && src[dst_size] != '\0') { |
150 | 9.46k | dst[dst_size] = src[dst_size]; |
151 | 9.46k | ++dst_size; |
152 | 9.46k | } |
153 | 256 | const bool truncated = src[dst_size] != '\0'; |
154 | 256 | dst[dst_size] = '\0'; |
155 | 256 | return truncated; |
156 | 256 | } |
157 | | |
158 | 693 | const char* alignELFNote(const char* ptr) { |
159 | 693 | const auto value = reinterpret_cast<uintptr_t>(ptr); |
160 | 693 | return reinterpret_cast<const char*>((value + 3) & ~uintptr_t {3}); |
161 | 693 | } |
162 | | |
163 | 443 | bool copyBuildIDFromNotes(const char* note_begin, size_t size, LoadedObject& object) { |
164 | 443 | const char* pos = note_begin; |
165 | 443 | const char* end = note_begin + size; |
166 | | |
167 | 669 | while (pos + sizeof(ElfNhdr) <= end) { |
168 | 467 | ElfNhdr nhdr; |
169 | 467 | memcpy(&nhdr, pos, sizeof(nhdr)); |
170 | | |
171 | 467 | const char* name_begin = pos + sizeof(ElfNhdr); |
172 | 467 | if (name_begin > end || static_cast<size_t>(end - name_begin) < nhdr.n_namesz) { |
173 | 0 | return false; |
174 | 0 | } |
175 | | |
176 | 467 | const char* desc_begin = alignELFNote(name_begin + nhdr.n_namesz); |
177 | 467 | if (desc_begin > end || static_cast<size_t>(end - desc_begin) < nhdr.n_descsz) { |
178 | 0 | return false; |
179 | 0 | } |
180 | | |
181 | 467 | if (nhdr.n_type == NT_GNU_BUILD_ID) { |
182 | 241 | const size_t copied = std::min<size_t>(nhdr.n_descsz, object.build_id.size()); |
183 | 241 | memcpy(object.build_id.data(), desc_begin, copied); |
184 | 241 | object.build_id_size = copied; |
185 | 241 | object.build_id_truncated = nhdr.n_descsz > object.build_id.size(); |
186 | 241 | return true; |
187 | 241 | } |
188 | | |
189 | 226 | pos = alignELFNote(desc_begin + nhdr.n_descsz); |
190 | 226 | } |
191 | 202 | return false; |
192 | 443 | } |
193 | | |
194 | 256 | void copyBuildIDFromProgramHeaders(dl_phdr_info* info, LoadedObject& object) { |
195 | 1.79k | for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) { |
196 | 1.77k | const ElfPhdr& phdr = info->dlpi_phdr[header_index]; |
197 | 1.77k | if (phdr.p_type != PT_NOTE) { |
198 | 1.33k | continue; |
199 | 1.33k | } |
200 | | |
201 | 443 | if (copyBuildIDFromNotes(reinterpret_cast<const char*>(info->dlpi_addr + phdr.p_vaddr), |
202 | 443 | phdr.p_memsz, object)) { |
203 | 241 | return; |
204 | 241 | } |
205 | 443 | } |
206 | 256 | } |
207 | | |
208 | | void updateResources(ElfW(Addr) base_address, std::string_view object_name, std::string_view name, |
209 | 37.3M | const void* address, SymbolIndex::Resources& resources) { |
210 | 37.3M | const char* char_address = static_cast<const char*>(address); |
211 | | |
212 | 37.3M | if (name.starts_with("_binary_") || name.starts_with("binary_")) { |
213 | 118 | if (name.ends_with("_start")) { |
214 | 0 | name = name.substr((name[0] == '_') + strlen("binary_")); |
215 | 0 | name = name.substr(0, name.size() - strlen("_start")); |
216 | |
|
217 | 0 | auto& resource = resources[name]; |
218 | 0 | if (!resource.base_address || resource.base_address == base_address) { |
219 | 0 | resource.base_address = base_address; |
220 | 0 | resource.start = |
221 | 0 | std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor) |
222 | 0 | resource.object_name = object_name; |
223 | 0 | } |
224 | 0 | } |
225 | 118 | if (name.ends_with("_end")) { |
226 | 0 | name = name.substr((name[0] == '_') + strlen("binary_")); |
227 | 0 | name = name.substr(0, name.size() - strlen("_end")); |
228 | |
|
229 | 0 | auto& resource = resources[name]; |
230 | 0 | if (!resource.base_address || resource.base_address == base_address) { |
231 | 0 | resource.base_address = base_address; |
232 | 0 | resource.end = |
233 | 0 | std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor) |
234 | 0 | resource.object_name = object_name; |
235 | 0 | } |
236 | 0 | } |
237 | 118 | } |
238 | 37.3M | } |
239 | | |
240 | | /// Based on the code of musl-libc and the answer of Kanalpiroge on |
241 | | /// https://stackoverflow.com/questions/15779185/list-all-the-functions-symbols-on-the-fly-in-c-code-on-a-linux-architecture |
242 | | /// It does not extract all the symbols (but only public - exported and used for dynamic linking), |
243 | | /// but will work if we cannot find or parse ELF files. |
244 | | [[maybe_unused]] void collectSymbolsFromProgramHeaders(dl_phdr_info* info, |
245 | | std::vector<SymbolIndex::Symbol>& symbols, |
246 | 0 | SymbolIndex::Resources& resources) { |
247 | 0 | /* Iterate over all headers of the current shared lib |
248 | 0 | * (first call is for the executable itself) |
249 | 0 | */ |
250 | 0 | for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) { |
251 | 0 | /* Further processing is only needed if the dynamic section is reached |
252 | 0 | */ |
253 | 0 | if (info->dlpi_phdr[header_index].p_type != PT_DYNAMIC) { |
254 | 0 | continue; |
255 | 0 | } |
256 | 0 |
|
257 | 0 | /* Get a pointer to the first entry of the dynamic section. |
258 | 0 | * It's address is the shared lib's address + the virtual address |
259 | 0 | */ |
260 | 0 | const ElfW(Dyn)* dyn_begin = reinterpret_cast<const ElfW(Dyn)*>( |
261 | 0 | info->dlpi_addr + info->dlpi_phdr[header_index].p_vaddr); |
262 | 0 |
|
263 | 0 | /// For unknown reason, addresses are sometimes relative sometimes absolute. |
264 | 0 | auto correct_address = [](ElfW(Addr) base, ElfW(Addr) ptr) { |
265 | 0 | return ptr > base ? ptr : base + ptr; |
266 | 0 | }; |
267 | 0 |
|
268 | 0 | /* Iterate over all entries of the dynamic section until the |
269 | 0 | * end of the symbol table is reached. This is indicated by |
270 | 0 | * an entry with d_tag == DT_NULL. |
271 | 0 | */ |
272 | 0 |
|
273 | 0 | size_t sym_cnt = 0; |
274 | 0 | for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) { |
275 | 0 | ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr); |
276 | 0 |
|
277 | 0 | // TODO: this branch leads to invalid address of the hash table. Need further investigation. |
278 | 0 | // if (it->d_tag == DT_HASH) |
279 | 0 | // { |
280 | 0 | // const ElfW(Word) * hash = reinterpret_cast<const ElfW(Word) *>(base_address); |
281 | 0 | // sym_cnt = hash[1]; |
282 | 0 | // break; |
283 | 0 | // } |
284 | 0 | if (it->d_tag == DT_GNU_HASH) { |
285 | 0 | /// This code based on Musl-libc. |
286 | 0 |
|
287 | 0 | const uint32_t* buckets = nullptr; |
288 | 0 | const uint32_t* hashval = nullptr; |
289 | 0 |
|
290 | 0 | const ElfW(Word)* hash = reinterpret_cast<const ElfW(Word)*>(base_address); |
291 | 0 |
|
292 | 0 | buckets = hash + 4 + (hash[2] * sizeof(size_t) / 4); |
293 | 0 |
|
294 | 0 | for (ElfW(Word) i = 0; i < hash[0]; ++i) { |
295 | 0 | if (buckets[i] > sym_cnt) { |
296 | 0 | sym_cnt = buckets[i]; |
297 | 0 | } |
298 | 0 | } |
299 | 0 |
|
300 | 0 | if (sym_cnt) { |
301 | 0 | sym_cnt -= hash[1]; |
302 | 0 | hashval = buckets + hash[0] + sym_cnt; |
303 | 0 | do { |
304 | 0 | ++sym_cnt; |
305 | 0 | } while (!(*hashval++ & 1)); |
306 | 0 | } |
307 | 0 |
|
308 | 0 | break; |
309 | 0 | } |
310 | 0 | } |
311 | 0 |
|
312 | 0 | if (!sym_cnt) { |
313 | 0 | continue; |
314 | 0 | } |
315 | 0 |
|
316 | 0 | const char* strtab = nullptr; |
317 | 0 | for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) { |
318 | 0 | ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr); |
319 | 0 |
|
320 | 0 | if (it->d_tag == DT_STRTAB) { |
321 | 0 | strtab = reinterpret_cast<const char*>(base_address); |
322 | 0 | break; |
323 | 0 | } |
324 | 0 | } |
325 | 0 |
|
326 | 0 | if (!strtab) { |
327 | 0 | continue; |
328 | 0 | } |
329 | 0 |
|
330 | 0 | for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) { |
331 | 0 | ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr); |
332 | 0 |
|
333 | 0 | if (it->d_tag == DT_SYMTAB) { |
334 | 0 | /* Get the pointer to the first entry of the symbol table */ |
335 | 0 | const ElfW(Sym)* elf_sym = reinterpret_cast<const ElfW(Sym)*>(base_address); |
336 | 0 |
|
337 | 0 | /* Iterate over the symbol table */ |
338 | 0 | for (ElfW(Word) sym_index = 0; sym_index < ElfW(Word)(sym_cnt); ++sym_index) { |
339 | 0 | /* Get the name of the sym_index-th symbol. |
340 | 0 | * This is located at the address of st_name relative to the beginning of the string table. |
341 | 0 | */ |
342 | 0 | const char* sym_name = &strtab[elf_sym[sym_index].st_name]; |
343 | 0 |
|
344 | 0 | if (!sym_name) { |
345 | 0 | continue; |
346 | 0 | } |
347 | 0 |
|
348 | 0 | SymbolIndex::Symbol symbol; |
349 | 0 | symbol.address_begin = reinterpret_cast<const void*>( |
350 | 0 | info->dlpi_addr + elf_sym[sym_index].st_value); |
351 | 0 | symbol.address_end = reinterpret_cast<const void*>(info->dlpi_addr + |
352 | 0 | elf_sym[sym_index].st_value + |
353 | 0 | elf_sym[sym_index].st_size); |
354 | 0 | symbol.name = sym_name; |
355 | 0 |
|
356 | 0 | /// We are not interested in empty symbols. |
357 | 0 | if (elf_sym[sym_index].st_size) { |
358 | 0 | symbols.push_back(symbol); |
359 | 0 | } |
360 | 0 |
|
361 | 0 | /// But resources can be represented by a pair of empty symbols (indicating their boundaries). |
362 | 0 | updateResources(base_address, info->dlpi_name, symbol.name, |
363 | 0 | symbol.address_begin, resources); |
364 | 0 | } |
365 | 0 |
|
366 | 0 | break; |
367 | 0 | } |
368 | 0 | } |
369 | 0 | } |
370 | 0 | } |
371 | | |
372 | | #if !defined USE_MUSL |
373 | 0 | [[maybe_unused]] std::string getBuildIDFromProgramHeaders(dl_phdr_info* info) { |
374 | 0 | for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) { |
375 | 0 | const ElfPhdr& phdr = info->dlpi_phdr[header_index]; |
376 | 0 | if (phdr.p_type != PT_NOTE) { |
377 | 0 | continue; |
378 | 0 | } |
379 | 0 |
|
380 | 0 | return Elf::getBuildID(reinterpret_cast<const char*>(info->dlpi_addr + phdr.p_vaddr), |
381 | 0 | phdr.p_memsz); |
382 | 0 | } |
383 | 0 | return {}; |
384 | 0 | } |
385 | | #endif |
386 | | |
387 | | void collectSymbolsFromELFSymbolTable(ElfW(Addr) base_address, std::string_view object_name, |
388 | | const Elf& elf, const Elf::Section& symbol_table, |
389 | | const Elf::Section& string_table, |
390 | | std::vector<SymbolIndex::Symbol>& symbols, |
391 | 375 | SymbolIndex::Resources& resources) { |
392 | | /// Iterate symbol table. |
393 | 375 | const ElfSym* symbol_table_entry = reinterpret_cast<const ElfSym*>(symbol_table.begin()); |
394 | 375 | const ElfSym* symbol_table_end = reinterpret_cast<const ElfSym*>(symbol_table.end()); |
395 | | |
396 | 375 | const char* strings = string_table.begin(); |
397 | | |
398 | 37.5M | for (; symbol_table_entry < symbol_table_end; ++symbol_table_entry) { |
399 | 37.5M | if (!symbol_table_entry->st_name || !symbol_table_entry->st_value || |
400 | 37.5M | strings + symbol_table_entry->st_name >= elf.end()) { |
401 | 194k | continue; |
402 | 194k | } |
403 | | |
404 | | /// Find the name in strings table. |
405 | 37.3M | const char* symbol_name = strings + symbol_table_entry->st_name; |
406 | | |
407 | 37.3M | if (!symbol_name) { |
408 | 0 | continue; |
409 | 0 | } |
410 | | |
411 | 37.3M | SymbolIndex::Symbol symbol; |
412 | 37.3M | symbol.address_begin = |
413 | 37.3M | reinterpret_cast<const void*>(base_address + symbol_table_entry->st_value); |
414 | 37.3M | symbol.address_end = reinterpret_cast<const void*>( |
415 | 37.3M | base_address + symbol_table_entry->st_value + symbol_table_entry->st_size); |
416 | 37.3M | symbol.name = symbol_name; |
417 | | |
418 | 37.3M | if (symbol_table_entry->st_size) { |
419 | 34.8M | symbols.push_back(symbol); |
420 | 34.8M | } |
421 | | |
422 | 37.3M | updateResources(base_address, object_name, symbol.name, symbol.address_begin, resources); |
423 | 37.3M | } |
424 | 375 | } |
425 | | |
426 | | bool searchAndCollectSymbolsFromELFSymbolTable(ElfW(Addr) base_address, |
427 | | std::string_view object_name, const Elf& elf, |
428 | | unsigned section_header_type, |
429 | | const char* string_table_name, |
430 | | std::vector<SymbolIndex::Symbol>& symbols, |
431 | 488 | SymbolIndex::Resources& resources) { |
432 | 488 | std::optional<Elf::Section> symbol_table; |
433 | 488 | std::optional<Elf::Section> string_table; |
434 | | |
435 | 13.4k | if (!elf.iterateSections([&](const Elf::Section& section, size_t) { |
436 | 13.4k | if (section.header.sh_type == section_header_type) { |
437 | 375 | symbol_table.emplace(section); |
438 | 13.0k | } else if (section.header.sh_type == SHT_STRTAB && |
439 | 13.0k | 0 == strcmp(section.name(), string_table_name)) { |
440 | 375 | string_table.emplace(section); |
441 | 375 | } |
442 | | |
443 | 13.4k | return (symbol_table && string_table); |
444 | 13.4k | })) { |
445 | 113 | return false; |
446 | 113 | } |
447 | | |
448 | 375 | collectSymbolsFromELFSymbolTable(base_address, object_name, elf, *symbol_table, *string_table, |
449 | 375 | symbols, resources); |
450 | 375 | return true; |
451 | 488 | } |
452 | | |
453 | | void collectSymbolsFromELF(const LoadedObject& loaded_object, |
454 | | std::vector<SymbolIndex::Symbol>& symbols, |
455 | | std::vector<SymbolIndex::Object>& objects, |
456 | 256 | SymbolIndex::Resources& resources, std::string& build_id) { |
457 | 256 | std::string object_name; |
458 | 256 | std::string object_build_id = loaded_object.buildIDString(); |
459 | | #if defined(USE_MUSL) |
460 | | object_name = "/proc/self/exe"; |
461 | | object_build_id = Elf(object_name).getBuildID(); |
462 | | build_id = object_build_id; |
463 | | #else |
464 | 256 | object_name = loaded_object.nameString(); |
465 | | |
466 | | /// If the name is empty and there is a non-empty build-id - it's main executable. |
467 | | /// Find a elf file for the main executable and set the build-id. |
468 | 256 | if (object_name.empty()) { |
469 | 14 | object_name = "/proc/self/exe"; |
470 | | |
471 | 14 | if (object_build_id.empty()) { |
472 | 12 | object_build_id = Elf(object_name).getBuildID(); |
473 | 12 | } |
474 | | |
475 | 14 | if (build_id.empty()) { |
476 | 14 | build_id = object_build_id; |
477 | 14 | } |
478 | 14 | } |
479 | 256 | #endif |
480 | | |
481 | 256 | std::error_code ec; |
482 | 256 | std::filesystem::path canonical_path = std::filesystem::canonical(object_name, ec); |
483 | 256 | if (ec) { |
484 | 10 | return; |
485 | 10 | } |
486 | | |
487 | | /// Debug info and symbol table sections may be split to separate binary. |
488 | 246 | std::filesystem::path local_debug_info_path = |
489 | 246 | canonical_path.parent_path() / canonical_path.stem(); |
490 | 246 | local_debug_info_path += ".debug"; |
491 | 246 | std::filesystem::path debug_info_path = |
492 | 246 | std::filesystem::path("/usr/lib/debug") / canonical_path.relative_path(); |
493 | 246 | debug_info_path += ".debug"; |
494 | | |
495 | | /// NOTE: This is a workaround for current package system. |
496 | | /// |
497 | | /// Since nfpm cannot copy file only if it exists, |
498 | | /// and so in cmake empty .debug file is created instead, |
499 | | /// but if we will try to load empty Elf file, then the CANNOT_PARSE_ELF |
500 | | /// exception will be thrown from the Elf::Elf. |
501 | 723 | auto exists_not_empty = [](const std::filesystem::path& path) { |
502 | 723 | return std::filesystem::exists(path) && !std::filesystem::is_empty(path); |
503 | 723 | }; |
504 | | |
505 | 246 | if (exists_not_empty(local_debug_info_path)) { |
506 | 0 | object_name = local_debug_info_path; |
507 | 246 | } else if (exists_not_empty(debug_info_path)) { |
508 | 0 | object_name = debug_info_path; |
509 | 246 | } else if (object_build_id.size() >= 2) { |
510 | | // Check if there is a .debug file in .build-id folder. For example: |
511 | | // /usr/lib/debug/.build-id/e4/0526a12e9a8f3819a18694f6b798f10c624d5c.debug |
512 | 231 | std::string build_id_hex; |
513 | 231 | build_id_hex.resize(object_build_id.size() * 2); |
514 | | |
515 | 231 | char* pos = build_id_hex.data(); |
516 | 4.62k | for (auto c : object_build_id) { |
517 | 4.62k | write_hex_byte_lowercase(c, pos); |
518 | 4.62k | pos += 2; |
519 | 4.62k | } |
520 | | |
521 | 231 | std::filesystem::path build_id_debug_info_path( |
522 | 231 | fmt::format("/usr/lib/debug/.build-id/{}/{}.debug", build_id_hex.substr(0, 2), |
523 | 231 | build_id_hex.substr(2))); |
524 | 231 | if (exists_not_empty(build_id_debug_info_path)) { |
525 | 80 | object_name = build_id_debug_info_path; |
526 | 151 | } else { |
527 | 151 | object_name = canonical_path; |
528 | 151 | } |
529 | 231 | } else { |
530 | 15 | object_name = canonical_path; |
531 | 15 | } |
532 | | /// But we have to compare Build ID to check that debug info corresponds to the same executable. |
533 | | |
534 | 246 | SymbolIndex::Object object; |
535 | 246 | object.elf = std::make_unique<Elf>(object_name); |
536 | | |
537 | 246 | std::string file_build_id = object.elf->getBuildID(); |
538 | | |
539 | 246 | if (!object_build_id.empty() && object_build_id != file_build_id) { |
540 | | /// If debug info doesn't correspond to our binary, fallback to the info in our binary. |
541 | 2 | if (object_name != canonical_path) { |
542 | 0 | object_name = canonical_path; |
543 | 0 | object.elf = std::make_unique<Elf>(object_name); |
544 | | |
545 | | /// But it can still be outdated, for example, if executable file was deleted from filesystem and replaced by another file. |
546 | 0 | file_build_id = object.elf->getBuildID(); |
547 | 0 | if (object_build_id != file_build_id) { |
548 | 0 | return; |
549 | 0 | } |
550 | 2 | } else { |
551 | 2 | return; |
552 | 2 | } |
553 | 2 | } |
554 | | |
555 | 244 | object.address_begin = reinterpret_cast<const void*>(loaded_object.base_address); |
556 | 244 | object.address_end = |
557 | 244 | reinterpret_cast<const void*>(loaded_object.base_address + object.elf->size()); |
558 | 244 | object.name = object_name; |
559 | 244 | objects.push_back(std::move(object)); |
560 | 244 | const auto& indexed_object = objects.back(); |
561 | | |
562 | 244 | searchAndCollectSymbolsFromELFSymbolTable(loaded_object.base_address, indexed_object.name, |
563 | 244 | *indexed_object.elf, SHT_SYMTAB, ".strtab", symbols, |
564 | 244 | resources); |
565 | 244 | searchAndCollectSymbolsFromELFSymbolTable(loaded_object.base_address, indexed_object.name, |
566 | 244 | *indexed_object.elf, SHT_DYNSYM, ".dynstr", symbols, |
567 | 244 | resources); |
568 | 244 | } |
569 | | |
570 | | /* Callback for dl_iterate_phdr. |
571 | | * Is called by dl_iterate_phdr for every loaded shared lib until something |
572 | | * else than 0 is returned by one call of this function. |
573 | | */ |
574 | 256 | int collectLoadedObject(dl_phdr_info* info, size_t, void* data_ptr) { |
575 | 256 | __msan_unpoison(info, sizeof(*info)); |
576 | 256 | __msan_unpoison_string(info->dlpi_name); |
577 | 256 | auto& snapshot = *reinterpret_cast<LoadedObjectsSnapshot*>(data_ptr); |
578 | 256 | if (snapshot.objects.size() == snapshot.objects.capacity()) { |
579 | 0 | snapshot.overflow = true; |
580 | 0 | return 0; |
581 | 0 | } |
582 | | |
583 | 256 | LoadedObject object; |
584 | 256 | object.base_address = info->dlpi_addr; |
585 | 256 | object.name_truncated = copyCString(info->dlpi_name, object.name, object.name_size); |
586 | 256 | copyBuildIDFromProgramHeaders(info, object); |
587 | 256 | snapshot.name_truncated |= object.name_truncated; |
588 | 256 | snapshot.build_id_truncated |= object.build_id_truncated; |
589 | 256 | snapshot.objects.push_back(object); |
590 | 256 | return 0; |
591 | 256 | } |
592 | | |
593 | | template <typename T> |
594 | 9.97M | const T* find(const void* address, const std::vector<T>& vec) { |
595 | | /// First range that has left boundary greater than address. |
596 | | |
597 | 9.97M | auto it = std::lower_bound( |
598 | 9.97M | vec.begin(), vec.end(), address, |
599 | 129M | [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });symbol_index.cpp:_ZZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6SymbolEEEPKT_PKvRKSt6vectorIS4_SaIS4_EEENKUlRKS3_S8_E_clESF_S8_ Line | Count | Source | 599 | 104M | [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; }); |
symbol_index.cpp:_ZZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6ObjectEEEPKT_PKvRKSt6vectorIS4_SaIS4_EEENKUlRKS3_S8_E_clESF_S8_ Line | Count | Source | 599 | 24.9M | [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; }); |
|
600 | | |
601 | 9.97M | if (it == vec.begin()) { |
602 | 0 | return nullptr; |
603 | 9.97M | } else { |
604 | 9.97M | --it; /// Last range that has left boundary less or equals than address. |
605 | 9.97M | } |
606 | | |
607 | 9.97M | if (address >= it->address_begin && address < it->address_end) { |
608 | 9.97M | return &*it; |
609 | 9.97M | } else { |
610 | 2.75k | return nullptr; |
611 | 2.75k | } |
612 | 9.97M | } symbol_index.cpp:_ZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6SymbolEEEPKT_PKvRKSt6vectorIS4_SaIS4_EE Line | Count | Source | 594 | 4.98M | const T* find(const void* address, const std::vector<T>& vec) { | 595 | | /// First range that has left boundary greater than address. | 596 | | | 597 | 4.98M | auto it = std::lower_bound( | 598 | 4.98M | vec.begin(), vec.end(), address, | 599 | 4.98M | [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; }); | 600 | | | 601 | 4.98M | if (it == vec.begin()) { | 602 | 0 | return nullptr; | 603 | 4.98M | } else { | 604 | 4.98M | --it; /// Last range that has left boundary less or equals than address. | 605 | 4.98M | } | 606 | | | 607 | 4.98M | if (address >= it->address_begin && address < it->address_end) { | 608 | 4.98M | return &*it; | 609 | 4.98M | } else { | 610 | 2.75k | return nullptr; | 611 | 2.75k | } | 612 | 4.98M | } |
symbol_index.cpp:_ZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6ObjectEEEPKT_PKvRKSt6vectorIS4_SaIS4_EE Line | Count | Source | 594 | 4.98M | const T* find(const void* address, const std::vector<T>& vec) { | 595 | | /// First range that has left boundary greater than address. | 596 | | | 597 | 4.98M | auto it = std::lower_bound( | 598 | 4.98M | vec.begin(), vec.end(), address, | 599 | 4.98M | [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; }); | 600 | | | 601 | 4.98M | if (it == vec.begin()) { | 602 | 0 | return nullptr; | 603 | 4.98M | } else { | 604 | 4.98M | --it; /// Last range that has left boundary less or equals than address. | 605 | 4.98M | } | 606 | | | 607 | 4.98M | if (address >= it->address_begin && address < it->address_end) { | 608 | 4.98M | return &*it; | 609 | 4.98M | } else { | 610 | 0 | return nullptr; | 611 | 0 | } | 612 | 4.98M | } |
|
613 | | |
614 | | } // namespace |
615 | | |
616 | 12 | void SymbolIndex::update() { |
617 | 12 | LoadedObjectsSnapshot snapshot; |
618 | 12 | snapshot.objects.reserve(MAX_SYMBOL_INDEX_LOADED_OBJECTS); |
619 | | |
620 | | // glibc holds the loader lock while running dl_iterate_phdr callbacks. The callback only copies |
621 | | // fixed-size metadata into pre-reserved storage; ELF parsing and symbol vector growth happen |
622 | | // after the loader lock is released so jemalloc profiling cannot re-enter libunwind from here. |
623 | 12 | dl_iterate_phdr(collectLoadedObject, &snapshot); |
624 | | |
625 | 256 | for (const auto& object : snapshot.objects) { |
626 | 256 | collectSymbolsFromELF(object, data.symbols, data.objects, data.resources, data.build_id); |
627 | 256 | } |
628 | | |
629 | 12 | if (snapshot.overflow) { |
630 | 0 | LOG(WARNING) << "SymbolIndex skipped loaded objects after " |
631 | 0 | << MAX_SYMBOL_INDEX_LOADED_OBJECTS |
632 | 0 | << " entries; stack symbolization may be incomplete"; |
633 | 0 | } |
634 | 12 | if (snapshot.name_truncated) { |
635 | 0 | LOG(WARNING) << "SymbolIndex skipped at least one loaded object name longer than " |
636 | 0 | << MAX_SYMBOL_INDEX_OBJECT_NAME - 1 |
637 | 0 | << " bytes; stack symbolization may be incomplete"; |
638 | 0 | } |
639 | 12 | if (snapshot.build_id_truncated) { |
640 | 0 | LOG(WARNING) << "SymbolIndex truncated a loaded object build id longer than " |
641 | 0 | << MAX_SYMBOL_INDEX_BUILD_ID |
642 | 0 | << " bytes; stack symbolization may be incomplete"; |
643 | 0 | } |
644 | | |
645 | 12 | ::pdqsort(data.objects.begin(), data.objects.end(), |
646 | 2.31k | [](const Object& a, const Object& b) { return a.address_begin < b.address_begin; }); |
647 | 12 | ::pdqsort(data.symbols.begin(), data.symbols.end(), |
648 | 849M | [](const Symbol& a, const Symbol& b) { return a.address_begin < b.address_begin; }); |
649 | | /// We found symbols both from loaded program headers and from ELF symbol tables. |
650 | 12 | data.symbols.erase(std::unique(data.symbols.begin(), data.symbols.end(), |
651 | 34.8M | [](const Symbol& a, const Symbol& b) { |
652 | 34.8M | return a.address_begin == b.address_begin && |
653 | 34.8M | a.address_end == b.address_end; |
654 | 34.8M | }), |
655 | 12 | data.symbols.end()); |
656 | 12 | } |
657 | | |
658 | 4.98M | const SymbolIndex::Symbol* SymbolIndex::findSymbol(const void* address) const { |
659 | 4.98M | return find(address, data.symbols); |
660 | 4.98M | } |
661 | | |
662 | 4.98M | const SymbolIndex::Object* SymbolIndex::findObject(const void* address) const { |
663 | 4.98M | return find(address, data.objects); |
664 | 4.98M | } |
665 | | |
666 | 0 | std::string SymbolIndex::getBuildIDHex() const { |
667 | 0 | std::string build_id_binary = getBuildID(); |
668 | 0 | std::string build_id_hex; |
669 | 0 | build_id_hex.resize(build_id_binary.size() * 2); |
670 | |
|
671 | 0 | char* pos = build_id_hex.data(); |
672 | 0 | for (auto c : build_id_binary) { |
673 | 0 | write_hex_byte_uppercase(c, pos); |
674 | 0 | pos += 2; |
675 | 0 | } |
676 | |
|
677 | 0 | return build_id_hex; |
678 | 0 | } |
679 | | |
680 | 197k | MultiVersion<SymbolIndex>& SymbolIndex::instanceImpl() { |
681 | 197k | static MultiVersion<SymbolIndex> instance(std::unique_ptr<SymbolIndex>(new SymbolIndex)); |
682 | 197k | return instance; |
683 | 197k | } |
684 | | |
685 | 197k | MultiVersion<SymbolIndex>::Version SymbolIndex::instance() { |
686 | 197k | return instanceImpl().get(); |
687 | 197k | } |
688 | | |
689 | 6 | void SymbolIndex::reload() { |
690 | 6 | std::lock_guard<std::mutex> lock(symbolIndexReloadMutex()); |
691 | 6 | instanceImpl().set(std::unique_ptr<SymbolIndex>(new SymbolIndex)); |
692 | | /// Also drop stacktrace cache. |
693 | 6 | StackTrace::dropCache(); |
694 | 6 | } |
695 | | |
696 | | } // namespace doris |
697 | | |
698 | | #endif |