Coverage Report

Created: 2026-07-08 16:10

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