Coverage Report

Created: 2026-07-06 08:54

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
6
std::mutex& symbolIndexReloadMutex() {
107
6
    static std::mutex lock;
108
6
    return lock;
109
6
}
110
111
struct LoadedObject {
112
    ElfW(Addr) base_address = 0;
113
    std::array<char, MAX_SYMBOL_INDEX_OBJECT_NAME> name {};
114
    size_t name_size = 0;
115
    bool name_truncated = false;
116
    std::array<char, MAX_SYMBOL_INDEX_BUILD_ID> build_id {};
117
    size_t build_id_size = 0;
118
    bool build_id_truncated = false;
119
120
256
    std::string nameString() const { return {name.data(), name_size}; }
121
256
    std::string buildIDString() const { return {build_id.data(), build_id_size}; }
122
};
123
124
struct LoadedObjectsSnapshot {
125
    std::vector<LoadedObject> objects;
126
    bool overflow = false;
127
    bool name_truncated = false;
128
    bool build_id_truncated = false;
129
};
130
131
/// Notes: "PHDR" is "Program Headers".
132
/// To look at program headers, run:
133
///  readelf -l ./clickhouse-server
134
/// To look at section headers, run:
135
///  readelf -S ./clickhouse-server
136
/// Also look at: https://wiki.osdev.org/ELF
137
/// Also look at: man elf
138
/// http://www.linker-aliens.org/blogs/ali/entry/inside_elf_symbol_tables/
139
/// https://stackoverflow.com/questions/32088140/multiple-string-tables-in-elf-object
140
141
template <size_t N>
142
256
bool copyCString(const char* src, std::array<char, N>& dst, size_t& dst_size) {
143
256
    dst_size = 0;
144
256
    if (src == nullptr) {
145
0
        dst[0] = '\0';
146
0
        return false;
147
0
    }
148
149
9.72k
    while (dst_size + 1 < N && src[dst_size] != '\0') {
150
9.46k
        dst[dst_size] = src[dst_size];
151
9.46k
        ++dst_size;
152
9.46k
    }
153
256
    const bool truncated = src[dst_size] != '\0';
154
256
    dst[dst_size] = '\0';
155
256
    return truncated;
156
256
}
157
158
693
const char* alignELFNote(const char* ptr) {
159
693
    const auto value = reinterpret_cast<uintptr_t>(ptr);
160
693
    return reinterpret_cast<const char*>((value + 3) & ~uintptr_t {3});
161
693
}
162
163
443
bool copyBuildIDFromNotes(const char* note_begin, size_t size, LoadedObject& object) {
164
443
    const char* pos = note_begin;
165
443
    const char* end = note_begin + size;
166
167
669
    while (pos + sizeof(ElfNhdr) <= end) {
168
467
        ElfNhdr nhdr;
169
467
        memcpy(&nhdr, pos, sizeof(nhdr));
170
171
467
        const char* name_begin = pos + sizeof(ElfNhdr);
172
467
        if (name_begin > end || static_cast<size_t>(end - name_begin) < nhdr.n_namesz) {
173
0
            return false;
174
0
        }
175
176
467
        const char* desc_begin = alignELFNote(name_begin + nhdr.n_namesz);
177
467
        if (desc_begin > end || static_cast<size_t>(end - desc_begin) < nhdr.n_descsz) {
178
0
            return false;
179
0
        }
180
181
467
        if (nhdr.n_type == NT_GNU_BUILD_ID) {
182
241
            const size_t copied = std::min<size_t>(nhdr.n_descsz, object.build_id.size());
183
241
            memcpy(object.build_id.data(), desc_begin, copied);
184
241
            object.build_id_size = copied;
185
241
            object.build_id_truncated = nhdr.n_descsz > object.build_id.size();
186
241
            return true;
187
241
        }
188
189
226
        pos = alignELFNote(desc_begin + nhdr.n_descsz);
190
226
    }
191
202
    return false;
192
443
}
193
194
256
void copyBuildIDFromProgramHeaders(dl_phdr_info* info, LoadedObject& object) {
195
1.79k
    for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) {
196
1.77k
        const ElfPhdr& phdr = info->dlpi_phdr[header_index];
197
1.77k
        if (phdr.p_type != PT_NOTE) {
198
1.33k
            continue;
199
1.33k
        }
200
201
443
        if (copyBuildIDFromNotes(reinterpret_cast<const char*>(info->dlpi_addr + phdr.p_vaddr),
202
443
                                 phdr.p_memsz, object)) {
203
241
            return;
204
241
        }
205
443
    }
206
256
}
207
208
void updateResources(ElfW(Addr) base_address, std::string_view object_name, std::string_view name,
209
37.3M
                     const void* address, SymbolIndex::Resources& resources) {
210
37.3M
    const char* char_address = static_cast<const char*>(address);
211
212
37.3M
    if (name.starts_with("_binary_") || name.starts_with("binary_")) {
213
118
        if (name.ends_with("_start")) {
214
0
            name = name.substr((name[0] == '_') + strlen("binary_"));
215
0
            name = name.substr(0, name.size() - strlen("_start"));
216
217
0
            auto& resource = resources[name];
218
0
            if (!resource.base_address || resource.base_address == base_address) {
219
0
                resource.base_address = base_address;
220
0
                resource.start =
221
0
                        std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor)
222
0
                resource.object_name = object_name;
223
0
            }
224
0
        }
225
118
        if (name.ends_with("_end")) {
226
0
            name = name.substr((name[0] == '_') + strlen("binary_"));
227
0
            name = name.substr(0, name.size() - strlen("_end"));
228
229
0
            auto& resource = resources[name];
230
0
            if (!resource.base_address || resource.base_address == base_address) {
231
0
                resource.base_address = base_address;
232
0
                resource.end =
233
0
                        std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor)
234
0
                resource.object_name = object_name;
235
0
            }
236
0
        }
237
118
    }
238
37.3M
}
239
240
/// Based on the code of musl-libc and the answer of Kanalpiroge on
241
/// https://stackoverflow.com/questions/15779185/list-all-the-functions-symbols-on-the-fly-in-c-code-on-a-linux-architecture
242
/// It does not extract all the symbols (but only public - exported and used for dynamic linking),
243
/// but will work if we cannot find or parse ELF files.
244
[[maybe_unused]] void collectSymbolsFromProgramHeaders(dl_phdr_info* info,
245
                                                       std::vector<SymbolIndex::Symbol>& symbols,
246
0
                                                       SymbolIndex::Resources& resources) {
247
0
    /* Iterate over all headers of the current shared lib
248
0
     * (first call is for the executable itself)
249
0
     */
250
0
    for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) {
251
0
        /* Further processing is only needed if the dynamic section is reached
252
0
         */
253
0
        if (info->dlpi_phdr[header_index].p_type != PT_DYNAMIC) {
254
0
            continue;
255
0
        }
256
0
257
0
        /* Get a pointer to the first entry of the dynamic section.
258
0
         * It's address is the shared lib's address + the virtual address
259
0
         */
260
0
        const ElfW(Dyn)* dyn_begin = reinterpret_cast<const ElfW(Dyn)*>(
261
0
                info->dlpi_addr + info->dlpi_phdr[header_index].p_vaddr);
262
0
263
0
        /// For unknown reason, addresses are sometimes relative sometimes absolute.
264
0
        auto correct_address = [](ElfW(Addr) base, ElfW(Addr) ptr) {
265
0
            return ptr > base ? ptr : base + ptr;
266
0
        };
267
0
268
0
        /* Iterate over all entries of the dynamic section until the
269
0
         * end of the symbol table is reached. This is indicated by
270
0
         * an entry with d_tag == DT_NULL.
271
0
         */
272
0
273
0
        size_t sym_cnt = 0;
274
0
        for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) {
275
0
            ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr);
276
0
277
0
            // TODO: this branch leads to invalid address of the hash table. Need further investigation.
278
0
            // if (it->d_tag == DT_HASH)
279
0
            // {
280
0
            //     const ElfW(Word) * hash = reinterpret_cast<const ElfW(Word) *>(base_address);
281
0
            //     sym_cnt = hash[1];
282
0
            //     break;
283
0
            // }
284
0
            if (it->d_tag == DT_GNU_HASH) {
285
0
                /// This code based on Musl-libc.
286
0
287
0
                const uint32_t* buckets = nullptr;
288
0
                const uint32_t* hashval = nullptr;
289
0
290
0
                const ElfW(Word)* hash = reinterpret_cast<const ElfW(Word)*>(base_address);
291
0
292
0
                buckets = hash + 4 + (hash[2] * sizeof(size_t) / 4);
293
0
294
0
                for (ElfW(Word) i = 0; i < hash[0]; ++i) {
295
0
                    if (buckets[i] > sym_cnt) {
296
0
                        sym_cnt = buckets[i];
297
0
                    }
298
0
                }
299
0
300
0
                if (sym_cnt) {
301
0
                    sym_cnt -= hash[1];
302
0
                    hashval = buckets + hash[0] + sym_cnt;
303
0
                    do {
304
0
                        ++sym_cnt;
305
0
                    } while (!(*hashval++ & 1));
306
0
                }
307
0
308
0
                break;
309
0
            }
310
0
        }
311
0
312
0
        if (!sym_cnt) {
313
0
            continue;
314
0
        }
315
0
316
0
        const char* strtab = nullptr;
317
0
        for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) {
318
0
            ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr);
319
0
320
0
            if (it->d_tag == DT_STRTAB) {
321
0
                strtab = reinterpret_cast<const char*>(base_address);
322
0
                break;
323
0
            }
324
0
        }
325
0
326
0
        if (!strtab) {
327
0
            continue;
328
0
        }
329
0
330
0
        for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) {
331
0
            ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr);
332
0
333
0
            if (it->d_tag == DT_SYMTAB) {
334
0
                /* Get the pointer to the first entry of the symbol table */
335
0
                const ElfW(Sym)* elf_sym = reinterpret_cast<const ElfW(Sym)*>(base_address);
336
0
337
0
                /* Iterate over the symbol table */
338
0
                for (ElfW(Word) sym_index = 0; sym_index < ElfW(Word)(sym_cnt); ++sym_index) {
339
0
                    /* Get the name of the sym_index-th symbol.
340
0
                     * This is located at the address of st_name relative to the beginning of the string table.
341
0
                     */
342
0
                    const char* sym_name = &strtab[elf_sym[sym_index].st_name];
343
0
344
0
                    if (!sym_name) {
345
0
                        continue;
346
0
                    }
347
0
348
0
                    SymbolIndex::Symbol symbol;
349
0
                    symbol.address_begin = reinterpret_cast<const void*>(
350
0
                            info->dlpi_addr + elf_sym[sym_index].st_value);
351
0
                    symbol.address_end = reinterpret_cast<const void*>(info->dlpi_addr +
352
0
                                                                       elf_sym[sym_index].st_value +
353
0
                                                                       elf_sym[sym_index].st_size);
354
0
                    symbol.name = sym_name;
355
0
356
0
                    /// We are not interested in empty symbols.
357
0
                    if (elf_sym[sym_index].st_size) {
358
0
                        symbols.push_back(symbol);
359
0
                    }
360
0
361
0
                    /// But resources can be represented by a pair of empty symbols (indicating their boundaries).
362
0
                    updateResources(base_address, info->dlpi_name, symbol.name,
363
0
                                    symbol.address_begin, resources);
364
0
                }
365
0
366
0
                break;
367
0
            }
368
0
        }
369
0
    }
370
0
}
371
372
#if !defined USE_MUSL
373
0
[[maybe_unused]] std::string getBuildIDFromProgramHeaders(dl_phdr_info* info) {
374
0
    for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) {
375
0
        const ElfPhdr& phdr = info->dlpi_phdr[header_index];
376
0
        if (phdr.p_type != PT_NOTE) {
377
0
            continue;
378
0
        }
379
0
380
0
        return Elf::getBuildID(reinterpret_cast<const char*>(info->dlpi_addr + phdr.p_vaddr),
381
0
                               phdr.p_memsz);
382
0
    }
383
0
    return {};
384
0
}
385
#endif
386
387
void collectSymbolsFromELFSymbolTable(ElfW(Addr) base_address, std::string_view object_name,
388
                                      const Elf& elf, const Elf::Section& symbol_table,
389
                                      const Elf::Section& string_table,
390
                                      std::vector<SymbolIndex::Symbol>& symbols,
391
375
                                      SymbolIndex::Resources& resources) {
392
    /// Iterate symbol table.
393
375
    const ElfSym* symbol_table_entry = reinterpret_cast<const ElfSym*>(symbol_table.begin());
394
375
    const ElfSym* symbol_table_end = reinterpret_cast<const ElfSym*>(symbol_table.end());
395
396
375
    const char* strings = string_table.begin();
397
398
37.5M
    for (; symbol_table_entry < symbol_table_end; ++symbol_table_entry) {
399
37.5M
        if (!symbol_table_entry->st_name || !symbol_table_entry->st_value ||
400
37.5M
            strings + symbol_table_entry->st_name >= elf.end()) {
401
194k
            continue;
402
194k
        }
403
404
        /// Find the name in strings table.
405
37.3M
        const char* symbol_name = strings + symbol_table_entry->st_name;
406
407
37.3M
        if (!symbol_name) {
408
0
            continue;
409
0
        }
410
411
37.3M
        SymbolIndex::Symbol symbol;
412
37.3M
        symbol.address_begin =
413
37.3M
                reinterpret_cast<const void*>(base_address + symbol_table_entry->st_value);
414
37.3M
        symbol.address_end = reinterpret_cast<const void*>(
415
37.3M
                base_address + symbol_table_entry->st_value + symbol_table_entry->st_size);
416
37.3M
        symbol.name = symbol_name;
417
418
37.3M
        if (symbol_table_entry->st_size) {
419
34.8M
            symbols.push_back(symbol);
420
34.8M
        }
421
422
37.3M
        updateResources(base_address, object_name, symbol.name, symbol.address_begin, resources);
423
37.3M
    }
424
375
}
425
426
bool searchAndCollectSymbolsFromELFSymbolTable(ElfW(Addr) base_address,
427
                                               std::string_view object_name, const Elf& elf,
428
                                               unsigned section_header_type,
429
                                               const char* string_table_name,
430
                                               std::vector<SymbolIndex::Symbol>& symbols,
431
488
                                               SymbolIndex::Resources& resources) {
432
488
    std::optional<Elf::Section> symbol_table;
433
488
    std::optional<Elf::Section> string_table;
434
435
13.4k
    if (!elf.iterateSections([&](const Elf::Section& section, size_t) {
436
13.4k
            if (section.header.sh_type == section_header_type) {
437
375
                symbol_table.emplace(section);
438
13.0k
            } else if (section.header.sh_type == SHT_STRTAB &&
439
13.0k
                       0 == strcmp(section.name(), string_table_name)) {
440
375
                string_table.emplace(section);
441
375
            }
442
443
13.4k
            return (symbol_table && string_table);
444
13.4k
        })) {
445
113
        return false;
446
113
    }
447
448
375
    collectSymbolsFromELFSymbolTable(base_address, object_name, elf, *symbol_table, *string_table,
449
375
                                     symbols, resources);
450
375
    return true;
451
488
}
452
453
void collectSymbolsFromELF(const LoadedObject& loaded_object,
454
                           std::vector<SymbolIndex::Symbol>& symbols,
455
                           std::vector<SymbolIndex::Object>& objects,
456
256
                           SymbolIndex::Resources& resources, std::string& build_id) {
457
256
    std::string object_name;
458
256
    std::string object_build_id = loaded_object.buildIDString();
459
#if defined(USE_MUSL)
460
    object_name = "/proc/self/exe";
461
    object_build_id = Elf(object_name).getBuildID();
462
    build_id = object_build_id;
463
#else
464
256
    object_name = loaded_object.nameString();
465
466
    /// If the name is empty and there is a non-empty build-id - it's main executable.
467
    /// Find a elf file for the main executable and set the build-id.
468
256
    if (object_name.empty()) {
469
14
        object_name = "/proc/self/exe";
470
471
14
        if (object_build_id.empty()) {
472
12
            object_build_id = Elf(object_name).getBuildID();
473
12
        }
474
475
14
        if (build_id.empty()) {
476
14
            build_id = object_build_id;
477
14
        }
478
14
    }
479
256
#endif
480
481
256
    std::error_code ec;
482
256
    std::filesystem::path canonical_path = std::filesystem::canonical(object_name, ec);
483
256
    if (ec) {
484
10
        return;
485
10
    }
486
487
    /// Debug info and symbol table sections may be split to separate binary.
488
246
    std::filesystem::path local_debug_info_path =
489
246
            canonical_path.parent_path() / canonical_path.stem();
490
246
    local_debug_info_path += ".debug";
491
246
    std::filesystem::path debug_info_path =
492
246
            std::filesystem::path("/usr/lib/debug") / canonical_path.relative_path();
493
246
    debug_info_path += ".debug";
494
495
    /// NOTE: This is a workaround for current package system.
496
    ///
497
    /// Since nfpm cannot copy file only if it exists,
498
    /// and so in cmake empty .debug file is created instead,
499
    /// but if we will try to load empty Elf file, then the CANNOT_PARSE_ELF
500
    /// exception will be thrown from the Elf::Elf.
501
723
    auto exists_not_empty = [](const std::filesystem::path& path) {
502
723
        return std::filesystem::exists(path) && !std::filesystem::is_empty(path);
503
723
    };
504
505
246
    if (exists_not_empty(local_debug_info_path)) {
506
0
        object_name = local_debug_info_path;
507
246
    } else if (exists_not_empty(debug_info_path)) {
508
0
        object_name = debug_info_path;
509
246
    } else if (object_build_id.size() >= 2) {
510
        // Check if there is a .debug file in .build-id folder. For example:
511
        // /usr/lib/debug/.build-id/e4/0526a12e9a8f3819a18694f6b798f10c624d5c.debug
512
231
        std::string build_id_hex;
513
231
        build_id_hex.resize(object_build_id.size() * 2);
514
515
231
        char* pos = build_id_hex.data();
516
4.62k
        for (auto c : object_build_id) {
517
4.62k
            write_hex_byte_lowercase(c, pos);
518
4.62k
            pos += 2;
519
4.62k
        }
520
521
231
        std::filesystem::path build_id_debug_info_path(
522
231
                fmt::format("/usr/lib/debug/.build-id/{}/{}.debug", build_id_hex.substr(0, 2),
523
231
                            build_id_hex.substr(2)));
524
231
        if (exists_not_empty(build_id_debug_info_path)) {
525
80
            object_name = build_id_debug_info_path;
526
151
        } else {
527
151
            object_name = canonical_path;
528
151
        }
529
231
    } else {
530
15
        object_name = canonical_path;
531
15
    }
532
    /// But we have to compare Build ID to check that debug info corresponds to the same executable.
533
534
246
    SymbolIndex::Object object;
535
246
    object.elf = std::make_unique<Elf>(object_name);
536
537
246
    std::string file_build_id = object.elf->getBuildID();
538
539
246
    if (!object_build_id.empty() && object_build_id != file_build_id) {
540
        /// If debug info doesn't correspond to our binary, fallback to the info in our binary.
541
2
        if (object_name != canonical_path) {
542
0
            object_name = canonical_path;
543
0
            object.elf = std::make_unique<Elf>(object_name);
544
545
            /// But it can still be outdated, for example, if executable file was deleted from filesystem and replaced by another file.
546
0
            file_build_id = object.elf->getBuildID();
547
0
            if (object_build_id != file_build_id) {
548
0
                return;
549
0
            }
550
2
        } else {
551
2
            return;
552
2
        }
553
2
    }
554
555
244
    object.address_begin = reinterpret_cast<const void*>(loaded_object.base_address);
556
244
    object.address_end =
557
244
            reinterpret_cast<const void*>(loaded_object.base_address + object.elf->size());
558
244
    object.name = object_name;
559
244
    objects.push_back(std::move(object));
560
244
    const auto& indexed_object = objects.back();
561
562
244
    searchAndCollectSymbolsFromELFSymbolTable(loaded_object.base_address, indexed_object.name,
563
244
                                              *indexed_object.elf, SHT_SYMTAB, ".strtab", symbols,
564
244
                                              resources);
565
244
    searchAndCollectSymbolsFromELFSymbolTable(loaded_object.base_address, indexed_object.name,
566
244
                                              *indexed_object.elf, SHT_DYNSYM, ".dynstr", symbols,
567
244
                                              resources);
568
244
}
569
570
/* Callback for dl_iterate_phdr.
571
 * Is called by dl_iterate_phdr for every loaded shared lib until something
572
 * else than 0 is returned by one call of this function.
573
 */
574
256
int collectLoadedObject(dl_phdr_info* info, size_t, void* data_ptr) {
575
256
    __msan_unpoison(info, sizeof(*info));
576
256
    __msan_unpoison_string(info->dlpi_name);
577
256
    auto& snapshot = *reinterpret_cast<LoadedObjectsSnapshot*>(data_ptr);
578
256
    if (snapshot.objects.size() == snapshot.objects.capacity()) {
579
0
        snapshot.overflow = true;
580
0
        return 0;
581
0
    }
582
583
256
    LoadedObject object;
584
256
    object.base_address = info->dlpi_addr;
585
256
    object.name_truncated = copyCString(info->dlpi_name, object.name, object.name_size);
586
256
    copyBuildIDFromProgramHeaders(info, object);
587
256
    snapshot.name_truncated |= object.name_truncated;
588
256
    snapshot.build_id_truncated |= object.build_id_truncated;
589
256
    snapshot.objects.push_back(object);
590
256
    return 0;
591
256
}
592
593
template <typename T>
594
9.97M
const T* find(const void* address, const std::vector<T>& vec) {
595
    /// First range that has left boundary greater than address.
596
597
9.97M
    auto it = std::lower_bound(
598
9.97M
            vec.begin(), vec.end(), address,
599
129M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
symbol_index.cpp:_ZZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6SymbolEEEPKT_PKvRKSt6vectorIS4_SaIS4_EEENKUlRKS3_S8_E_clESF_S8_
Line
Count
Source
599
104M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
symbol_index.cpp:_ZZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6ObjectEEEPKT_PKvRKSt6vectorIS4_SaIS4_EEENKUlRKS3_S8_E_clESF_S8_
Line
Count
Source
599
24.9M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
600
601
9.97M
    if (it == vec.begin()) {
602
0
        return nullptr;
603
9.97M
    } else {
604
9.97M
        --it; /// Last range that has left boundary less or equals than address.
605
9.97M
    }
606
607
9.97M
    if (address >= it->address_begin && address < it->address_end) {
608
9.97M
        return &*it;
609
9.97M
    } else {
610
2.75k
        return nullptr;
611
2.75k
    }
612
9.97M
}
symbol_index.cpp:_ZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6SymbolEEEPKT_PKvRKSt6vectorIS4_SaIS4_EE
Line
Count
Source
594
4.98M
const T* find(const void* address, const std::vector<T>& vec) {
595
    /// First range that has left boundary greater than address.
596
597
4.98M
    auto it = std::lower_bound(
598
4.98M
            vec.begin(), vec.end(), address,
599
4.98M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
600
601
4.98M
    if (it == vec.begin()) {
602
0
        return nullptr;
603
4.98M
    } else {
604
4.98M
        --it; /// Last range that has left boundary less or equals than address.
605
4.98M
    }
606
607
4.98M
    if (address >= it->address_begin && address < it->address_end) {
608
4.98M
        return &*it;
609
4.98M
    } else {
610
2.75k
        return nullptr;
611
2.75k
    }
612
4.98M
}
symbol_index.cpp:_ZN5doris12_GLOBAL__N_14findINS_11SymbolIndex6ObjectEEEPKT_PKvRKSt6vectorIS4_SaIS4_EE
Line
Count
Source
594
4.98M
const T* find(const void* address, const std::vector<T>& vec) {
595
    /// First range that has left boundary greater than address.
596
597
4.98M
    auto it = std::lower_bound(
598
4.98M
            vec.begin(), vec.end(), address,
599
4.98M
            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
600
601
4.98M
    if (it == vec.begin()) {
602
0
        return nullptr;
603
4.98M
    } else {
604
4.98M
        --it; /// Last range that has left boundary less or equals than address.
605
4.98M
    }
606
607
4.98M
    if (address >= it->address_begin && address < it->address_end) {
608
4.98M
        return &*it;
609
4.98M
    } else {
610
0
        return nullptr;
611
0
    }
612
4.98M
}
613
614
} // namespace
615
616
12
void SymbolIndex::update() {
617
12
    LoadedObjectsSnapshot snapshot;
618
12
    snapshot.objects.reserve(MAX_SYMBOL_INDEX_LOADED_OBJECTS);
619
620
    // glibc holds the loader lock while running dl_iterate_phdr callbacks. The callback only copies
621
    // fixed-size metadata into pre-reserved storage; ELF parsing and symbol vector growth happen
622
    // after the loader lock is released so jemalloc profiling cannot re-enter libunwind from here.
623
12
    dl_iterate_phdr(collectLoadedObject, &snapshot);
624
625
256
    for (const auto& object : snapshot.objects) {
626
256
        collectSymbolsFromELF(object, data.symbols, data.objects, data.resources, data.build_id);
627
256
    }
628
629
12
    if (snapshot.overflow) {
630
0
        LOG(WARNING) << "SymbolIndex skipped loaded objects after "
631
0
                     << MAX_SYMBOL_INDEX_LOADED_OBJECTS
632
0
                     << " entries; stack symbolization may be incomplete";
633
0
    }
634
12
    if (snapshot.name_truncated) {
635
0
        LOG(WARNING) << "SymbolIndex skipped at least one loaded object name longer than "
636
0
                     << MAX_SYMBOL_INDEX_OBJECT_NAME - 1
637
0
                     << " bytes; stack symbolization may be incomplete";
638
0
    }
639
12
    if (snapshot.build_id_truncated) {
640
0
        LOG(WARNING) << "SymbolIndex truncated a loaded object build id longer than "
641
0
                     << MAX_SYMBOL_INDEX_BUILD_ID
642
0
                     << " bytes; stack symbolization may be incomplete";
643
0
    }
644
645
12
    ::pdqsort(data.objects.begin(), data.objects.end(),
646
2.31k
              [](const Object& a, const Object& b) { return a.address_begin < b.address_begin; });
647
12
    ::pdqsort(data.symbols.begin(), data.symbols.end(),
648
849M
              [](const Symbol& a, const Symbol& b) { return a.address_begin < b.address_begin; });
649
    /// We found symbols both from loaded program headers and from ELF symbol tables.
650
12
    data.symbols.erase(std::unique(data.symbols.begin(), data.symbols.end(),
651
34.8M
                                   [](const Symbol& a, const Symbol& b) {
652
34.8M
                                       return a.address_begin == b.address_begin &&
653
34.8M
                                              a.address_end == b.address_end;
654
34.8M
                                   }),
655
12
                       data.symbols.end());
656
12
}
657
658
4.98M
const SymbolIndex::Symbol* SymbolIndex::findSymbol(const void* address) const {
659
4.98M
    return find(address, data.symbols);
660
4.98M
}
661
662
4.98M
const SymbolIndex::Object* SymbolIndex::findObject(const void* address) const {
663
4.98M
    return find(address, data.objects);
664
4.98M
}
665
666
0
std::string SymbolIndex::getBuildIDHex() const {
667
0
    std::string build_id_binary = getBuildID();
668
0
    std::string build_id_hex;
669
0
    build_id_hex.resize(build_id_binary.size() * 2);
670
671
0
    char* pos = build_id_hex.data();
672
0
    for (auto c : build_id_binary) {
673
0
        write_hex_byte_uppercase(c, pos);
674
0
        pos += 2;
675
0
    }
676
677
0
    return build_id_hex;
678
0
}
679
680
197k
MultiVersion<SymbolIndex>& SymbolIndex::instanceImpl() {
681
197k
    static MultiVersion<SymbolIndex> instance(std::unique_ptr<SymbolIndex>(new SymbolIndex));
682
197k
    return instance;
683
197k
}
684
685
197k
MultiVersion<SymbolIndex>::Version SymbolIndex::instance() {
686
197k
    return instanceImpl().get();
687
197k
}
688
689
6
void SymbolIndex::reload() {
690
6
    std::lock_guard<std::mutex> lock(symbolIndexReloadMutex());
691
6
    instanceImpl().set(std::unique_ptr<SymbolIndex>(new SymbolIndex));
692
    /// Also drop stacktrace cache.
693
6
    StackTrace::dropCache();
694
6
}
695
696
} // namespace doris
697
698
#endif