Coverage Report

Created: 2026-07-08 18:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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
7
std::mutex& symbolIndexReloadMutex() {
107
7
    static std::mutex lock;
108
7
    return lock;
109
7
}
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
302
    std::string nameString() const { return {name.data(), name_size}; }
121
302
    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
302
bool copyCString(const char* src, std::array<char, N>& dst, size_t& dst_size) {
143
302
    dst_size = 0;
144
302
    if (src == nullptr) {
145
0
        dst[0] = '\0';
146
0
        return false;
147
0
    }
148
149
11.5k
    while (dst_size + 1 < N && src[dst_size] != '\0') {
150
11.2k
        dst[dst_size] = src[dst_size];
151
11.2k
        ++dst_size;
152
11.2k
    }
153
302
    const bool truncated = src[dst_size] != '\0';
154
302
    dst[dst_size] = '\0';
155
302
    return truncated;
156
302
}
157
158
825
const char* alignELFNote(const char* ptr) {
159
825
    const auto value = reinterpret_cast<uintptr_t>(ptr);
160
825
    return reinterpret_cast<const char*>((value + 3) & ~uintptr_t {3});
161
825
}
162
163
527
bool copyBuildIDFromNotes(const char* note_begin, size_t size, LoadedObject& object) {
164
527
    const char* pos = note_begin;
165
527
    const char* end = note_begin + size;
166
167
797
    while (pos + sizeof(ElfNhdr) <= end) {
168
555
        ElfNhdr nhdr;
169
555
        memcpy(&nhdr, pos, sizeof(nhdr));
170
171
555
        const char* name_begin = pos + sizeof(ElfNhdr);
172
555
        if (name_begin > end || static_cast<size_t>(end - name_begin) < nhdr.n_namesz) {
173
0
            return false;
174
0
        }
175
176
555
        const char* desc_begin = alignELFNote(name_begin + nhdr.n_namesz);
177
555
        if (desc_begin > end || static_cast<size_t>(end - desc_begin) < nhdr.n_descsz) {
178
0
            return false;
179
0
        }
180
181
555
        if (nhdr.n_type == NT_GNU_BUILD_ID) {
182
285
            const size_t copied = std::min<size_t>(nhdr.n_descsz, object.build_id.size());
183
285
            memcpy(object.build_id.data(), desc_begin, copied);
184
285
            object.build_id_size = copied;
185
285
            object.build_id_truncated = nhdr.n_descsz > object.build_id.size();
186
285
            return true;
187
285
        }
188
189
270
        pos = alignELFNote(desc_begin + nhdr.n_descsz);
190
270
    }
191
242
    return false;
192
527
}
193
194
302
void copyBuildIDFromProgramHeaders(dl_phdr_info* info, LoadedObject& object) {
195
2.12k
    for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) {
196
2.10k
        const ElfPhdr& phdr = info->dlpi_phdr[header_index];
197
2.10k
        if (phdr.p_type != PT_NOTE) {
198
1.58k
            continue;
199
1.58k
        }
200
201
527
        if (copyBuildIDFromNotes(reinterpret_cast<const char*>(info->dlpi_addr + phdr.p_vaddr),
202
527
                                 phdr.p_memsz, object)) {
203
285
            return;
204
285
        }
205
527
    }
206
302
}
207
208
void updateResources(ElfW(Addr) base_address, std::string_view object_name, std::string_view name,
209
43.5M
                     const void* address, SymbolIndex::Resources& resources) {
210
43.5M
    const char* char_address = static_cast<const char*>(address);
211
212
43.5M
    if (name.starts_with("_binary_") || name.starts_with("binary_")) {
213
138
        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
138
        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
138
    }
238
43.5M
}
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
441
                                      SymbolIndex::Resources& resources) {
392
    /// Iterate symbol table.
393
441
    const ElfSym* symbol_table_entry = reinterpret_cast<const ElfSym*>(symbol_table.begin());
394
441
    const ElfSym* symbol_table_end = reinterpret_cast<const ElfSym*>(symbol_table.end());
395
396
441
    const char* strings = string_table.begin();
397
398
43.8M
    for (; symbol_table_entry < symbol_table_end; ++symbol_table_entry) {
399
43.8M
        if (!symbol_table_entry->st_name || !symbol_table_entry->st_value ||
400
43.8M
            strings + symbol_table_entry->st_name >= elf.end()) {
401
227k
            continue;
402
227k
        }
403
404
        /// Find the name in strings table.
405
43.5M
        const char* symbol_name = strings + symbol_table_entry->st_name;
406
407
43.5M
        if (!symbol_name) {
408
0
            continue;
409
0
        }
410
411
43.5M
        SymbolIndex::Symbol symbol;
412
43.5M
        symbol.address_begin =
413
43.5M
                reinterpret_cast<const void*>(base_address + symbol_table_entry->st_value);
414
43.5M
        symbol.address_end = reinterpret_cast<const void*>(
415
43.5M
                base_address + symbol_table_entry->st_value + symbol_table_entry->st_size);
416
43.5M
        symbol.name = symbol_name;
417
418
43.5M
        if (symbol_table_entry->st_size) {
419
40.7M
            symbols.push_back(symbol);
420
40.7M
        }
421
422
43.5M
        updateResources(base_address, object_name, symbol.name, symbol.address_begin, resources);
423
43.5M
    }
424
441
}
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
576
                                               SymbolIndex::Resources& resources) {
432
576
    std::optional<Elf::Section> symbol_table;
433
576
    std::optional<Elf::Section> string_table;
434
435
15.9k
    if (!elf.iterateSections([&](const Elf::Section& section, size_t) {
436
15.9k
            if (section.header.sh_type == section_header_type) {
437
441
                symbol_table.emplace(section);
438
15.5k
            } else if (section.header.sh_type == SHT_STRTAB &&
439
15.5k
                       0 == strcmp(section.name(), string_table_name)) {
440
441
                string_table.emplace(section);
441
441
            }
442
443
15.9k
            return (symbol_table && string_table);
444
15.9k
        })) {
445
135
        return false;
446
135
    }
447
448
441
    collectSymbolsFromELFSymbolTable(base_address, object_name, elf, *symbol_table, *string_table,
449
441
                                     symbols, resources);
450
441
    return true;
451
576
}
452
453
void collectSymbolsFromELF(const LoadedObject& loaded_object,
454
                           std::vector<SymbolIndex::Symbol>& symbols,
455
                           std::vector<SymbolIndex::Object>& objects,
456
302
                           SymbolIndex::Resources& resources, std::string& build_id) {
457
302
    std::string object_name;
458
302
    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
302
    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
302
    if (object_name.empty()) {
469
16
        object_name = "/proc/self/exe";
470
471
16
        if (object_build_id.empty()) {
472
14
            object_build_id = Elf(object_name).getBuildID();
473
14
        }
474
475
16
        if (build_id.empty()) {
476
16
            build_id = object_build_id;
477
16
        }
478
16
    }
479
302
#endif
480
481
302
    std::error_code ec;
482
302
    std::filesystem::path canonical_path = std::filesystem::canonical(object_name, ec);
483
302
    if (ec) {
484
12
        return;
485
12
    }
486
487
    /// Debug info and symbol table sections may be split to separate binary.
488
290
    std::filesystem::path local_debug_info_path =
489
290
            canonical_path.parent_path() / canonical_path.stem();
490
290
    local_debug_info_path += ".debug";
491
290
    std::filesystem::path debug_info_path =
492
290
            std::filesystem::path("/usr/lib/debug") / canonical_path.relative_path();
493
290
    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
853
    auto exists_not_empty = [](const std::filesystem::path& path) {
502
853
        return std::filesystem::exists(path) && !std::filesystem::is_empty(path);
503
853
    };
504
505
290
    if (exists_not_empty(local_debug_info_path)) {
506
0
        object_name = local_debug_info_path;
507
290
    } else if (exists_not_empty(debug_info_path)) {
508
0
        object_name = debug_info_path;
509
290
    } 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
273
        std::string build_id_hex;
513
273
        build_id_hex.resize(object_build_id.size() * 2);
514
515
273
        char* pos = build_id_hex.data();
516
5.46k
        for (auto c : object_build_id) {
517
5.46k
            write_hex_byte_lowercase(c, pos);
518
5.46k
            pos += 2;
519
5.46k
        }
520
521
273
        std::filesystem::path build_id_debug_info_path(
522
273
                fmt::format("/usr/lib/debug/.build-id/{}/{}.debug", build_id_hex.substr(0, 2),
523
273
                            build_id_hex.substr(2)));
524
273
        if (exists_not_empty(build_id_debug_info_path)) {
525
96
            object_name = build_id_debug_info_path;
526
177
        } else {
527
177
            object_name = canonical_path;
528
177
        }
529
273
    } else {
530
17
        object_name = canonical_path;
531
17
    }
532
    /// But we have to compare Build ID to check that debug info corresponds to the same executable.
533
534
290
    SymbolIndex::Object object;
535
290
    object.elf = std::make_unique<Elf>(object_name);
536
537
290
    std::string file_build_id = object.elf->getBuildID();
538
539
290
    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
288
    object.address_begin = reinterpret_cast<const void*>(loaded_object.base_address);
556
288
    object.address_end =
557
288
            reinterpret_cast<const void*>(loaded_object.base_address + object.elf->size());
558
288
    object.name = object_name;
559
288
    objects.push_back(std::move(object));
560
288
    const auto& indexed_object = objects.back();
561
562
288
    searchAndCollectSymbolsFromELFSymbolTable(loaded_object.base_address, indexed_object.name,
563
288
                                              *indexed_object.elf, SHT_SYMTAB, ".strtab", symbols,
564
288
                                              resources);
565
288
    searchAndCollectSymbolsFromELFSymbolTable(loaded_object.base_address, indexed_object.name,
566
288
                                              *indexed_object.elf, SHT_DYNSYM, ".dynstr", symbols,
567
288
                                              resources);
568
288
}
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
302
int collectLoadedObject(dl_phdr_info* info, size_t, void* data_ptr) {
575
302
    __msan_unpoison(info, sizeof(*info));
576
302
    __msan_unpoison_string(info->dlpi_name);
577
302
    auto& snapshot = *reinterpret_cast<LoadedObjectsSnapshot*>(data_ptr);
578
302
    if (snapshot.objects.size() == snapshot.objects.capacity()) {
579
0
        snapshot.overflow = true;
580
0
        return 0;
581
0
    }
582
583
302
    LoadedObject object;
584
302
    object.base_address = info->dlpi_addr;
585
302
    object.name_truncated = copyCString(info->dlpi_name, object.name, object.name_size);
586
302
    copyBuildIDFromProgramHeaders(info, object);
587
302
    snapshot.name_truncated |= object.name_truncated;
588
302
    snapshot.build_id_truncated |= object.build_id_truncated;
589
302
    snapshot.objects.push_back(object);
590
302
    return 0;
591
302
}
592
593
template <typename T>
594
7.10M
const T* find(const void* address, const std::vector<T>& vec) {
595
    /// First range that has left boundary greater than address.
596
597
7.10M
    auto it = std::lower_bound(
598
7.10M
            vec.begin(), vec.end(), address,
599
92.5M
            [](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
74.7M
            [](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
17.7M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
600
601
7.10M
    if (it == vec.begin()) {
602
0
        return nullptr;
603
7.10M
    } else {
604
7.10M
        --it; /// Last range that has left boundary less or equals than address.
605
7.10M
    }
606
607
7.10M
    if (address >= it->address_begin && address < it->address_end) {
608
7.10M
        return &*it;
609
7.10M
    } else {
610
589
        return nullptr;
611
589
    }
612
7.10M
}
symbol_index.cpp:_ZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6SymbolEEEPKT_PKvRKSt6vectorIS4_SaIS4_EE
Line
Count
Source
594
3.55M
const T* find(const void* address, const std::vector<T>& vec) {
595
    /// First range that has left boundary greater than address.
596
597
3.55M
    auto it = std::lower_bound(
598
3.55M
            vec.begin(), vec.end(), address,
599
3.55M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
600
601
3.55M
    if (it == vec.begin()) {
602
0
        return nullptr;
603
3.55M
    } else {
604
3.55M
        --it; /// Last range that has left boundary less or equals than address.
605
3.55M
    }
606
607
3.55M
    if (address >= it->address_begin && address < it->address_end) {
608
3.55M
        return &*it;
609
3.55M
    } else {
610
589
        return nullptr;
611
589
    }
612
3.55M
}
symbol_index.cpp:_ZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6ObjectEEEPKT_PKvRKSt6vectorIS4_SaIS4_EE
Line
Count
Source
594
3.55M
const T* find(const void* address, const std::vector<T>& vec) {
595
    /// First range that has left boundary greater than address.
596
597
3.55M
    auto it = std::lower_bound(
598
3.55M
            vec.begin(), vec.end(), address,
599
3.55M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
600
601
3.55M
    if (it == vec.begin()) {
602
0
        return nullptr;
603
3.55M
    } else {
604
3.55M
        --it; /// Last range that has left boundary less or equals than address.
605
3.55M
    }
606
607
3.55M
    if (address >= it->address_begin && address < it->address_end) {
608
3.55M
        return &*it;
609
3.55M
    } else {
610
0
        return nullptr;
611
0
    }
612
3.55M
}
613
614
} // namespace
615
616
14
void SymbolIndex::update() {
617
14
    LoadedObjectsSnapshot snapshot;
618
14
    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
14
    dl_iterate_phdr(collectLoadedObject, &snapshot);
624
625
302
    for (const auto& object : snapshot.objects) {
626
302
        collectSymbolsFromELF(object, data.symbols, data.objects, data.resources, data.build_id);
627
302
    }
628
629
14
    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
14
    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
14
    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
14
    ::pdqsort(data.objects.begin(), data.objects.end(),
646
2.73k
              [](const Object& a, const Object& b) { return a.address_begin < b.address_begin; });
647
14
    ::pdqsort(data.symbols.begin(), data.symbols.end(),
648
984M
              [](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
14
    data.symbols.erase(std::unique(data.symbols.begin(), data.symbols.end(),
651
40.7M
                                   [](const Symbol& a, const Symbol& b) {
652
40.7M
                                       return a.address_begin == b.address_begin &&
653
40.7M
                                              a.address_end == b.address_end;
654
40.7M
                                   }),
655
14
                       data.symbols.end());
656
14
}
657
658
3.55M
const SymbolIndex::Symbol* SymbolIndex::findSymbol(const void* address) const {
659
3.55M
    return find(address, data.symbols);
660
3.55M
}
661
662
3.55M
const SymbolIndex::Object* SymbolIndex::findObject(const void* address) const {
663
3.55M
    return find(address, data.objects);
664
3.55M
}
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
144k
MultiVersion<SymbolIndex>& SymbolIndex::instanceImpl() {
681
144k
    static MultiVersion<SymbolIndex> instance(std::unique_ptr<SymbolIndex>(new SymbolIndex));
682
144k
    return instance;
683
144k
}
684
685
144k
MultiVersion<SymbolIndex>::Version SymbolIndex::instance() {
686
144k
    return instanceImpl().get();
687
144k
}
688
689
7
void SymbolIndex::reload() {
690
7
    std::lock_guard<std::mutex> lock(symbolIndexReloadMutex());
691
7
    instanceImpl().set(std::unique_ptr<SymbolIndex>(new SymbolIndex));
692
    /// Also drop stacktrace cache.
693
7
    StackTrace::dropCache();
694
7
}
695
696
} // namespace doris
697
698
#endif