Coverage Report

Created: 2026-07-12 14:58

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