Coverage Report

Created: 2026-07-07 19:03

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