| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| // This file is copied from |
| // https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/SymbolIndex.cpp |
| // and modified by Doris |
| |
| #if defined(__ELF__) && !defined(__FreeBSD__) |
| |
| #include <common/symbol_index.h> |
| #include <link.h> |
| #include <pdqsort.h> |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <filesystem> |
| #include <optional> |
| |
| #include "common/stack_trace.h" |
| #include "vec/common/hex.h" |
| |
| /** |
| |
| ELF object can contain three different places with symbol names and addresses: |
| |
| 1. Symbol table in section headers. It is used for static linking and usually left in executable. |
| It is not loaded in memory and they are not necessary for program to run. |
| It does not relate to debug info and present regardless to -g flag. |
| You can use strip to get rid of this symbol table. |
| If you have this symbol table in your binary, you can manually read it and get symbol names, even for symbols from anonymous namespaces. |
| |
| 2. Hashes in program headers such as DT_HASH and DT_GNU_HASH. |
| It is necessary for dynamic object (.so libraries and any dynamically linked executable that depend on .so libraries) |
| because it is used for dynamic linking that happens in runtime and performed by dynamic loader. |
| Only exported symbols will be presented in that hash tables. Symbols from anonymous namespaces are not. |
| This part of executable binary is loaded in memory and accessible via 'dl_iterate_phdr', 'dladdr' and 'backtrace_symbols' functions from libc. |
| ClickHouse versions prior to 19.13 has used just these symbol names to symbolize stack traces |
| and stack traces may be incomplete due to lack of symbols with internal linkage. |
| But because ClickHouse is linked with most of the symbols exported (-rdynamic flag) it can still provide good enough stack traces. |
| |
| 3. DWARF debug info. It contains the most detailed information about symbols and everything else. |
| It allows to get source file names and line numbers from addresses. Only available if you use -g option for compiler. |
| It is also used by default for ClickHouse builds, but because of its weight (about two gigabytes) |
| it is split to separate binary and provided in clickhouse-common-static-dbg package. |
| This separate binary is placed in /usr/lib/debug/usr/bin/clickhouse.debug and is loaded automatically by tools like gdb, addr2line. |
| When you build ClickHouse by yourself, debug info is not split and present in a single huge binary. |
| |
| What ClickHouse is using to provide good stack traces? |
| |
| In versions prior to 19.13, only "program headers" (2) was used. |
| |
| In version 19.13, ClickHouse will read program headers (2) and cache them, |
| also it will read itself as ELF binary and extract symbol tables from section headers (1) |
| to also symbolize functions that are not exported for dynamic linking. |
| And finally, it will read DWARF info (3) if available to display file names and line numbers. |
| |
| What detail can you obtain depending on your binary? |
| |
| 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. |
| 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. |
| Otherwise you will get only exported symbols from program headers. |
| |
| */ |
| |
| #if defined(__clang__) |
| #pragma clang diagnostic ignored "-Wreserved-id-macro" |
| #pragma clang diagnostic ignored "-Wunused-macros" |
| #endif |
| |
| #define __msan_unpoison_string(X) // NOLINT |
| #if defined(__clang__) && defined(__has_feature) |
| #if __has_feature(memory_sanitizer) |
| #undef __msan_unpoison_string |
| #include <sanitizer/msan_interface.h> |
| #endif |
| #endif |
| |
| namespace doris { |
| |
| namespace { |
| |
| /// Notes: "PHDR" is "Program Headers". |
| /// To look at program headers, run: |
| /// readelf -l ./clickhouse-server |
| /// To look at section headers, run: |
| /// readelf -S ./clickhouse-server |
| /// Also look at: https://wiki.osdev.org/ELF |
| /// Also look at: man elf |
| /// http://www.linker-aliens.org/blogs/ali/entry/inside_elf_symbol_tables/ |
| /// https://stackoverflow.com/questions/32088140/multiple-string-tables-in-elf-object |
| |
| void updateResources(ElfW(Addr) base_address, std::string_view object_name, std::string_view name, |
| const void* address, SymbolIndex::Resources& resources) { |
| const char* char_address = static_cast<const char*>(address); |
| |
| if (name.starts_with("_binary_") || name.starts_with("binary_")) { |
| if (name.ends_with("_start")) { |
| name = name.substr((name[0] == '_') + strlen("binary_")); |
| name = name.substr(0, name.size() - strlen("_start")); |
| |
| auto& resource = resources[name]; |
| if (!resource.base_address || resource.base_address == base_address) { |
| resource.base_address = base_address; |
| resource.start = |
| std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor) |
| resource.object_name = object_name; |
| } |
| } |
| if (name.ends_with("_end")) { |
| name = name.substr((name[0] == '_') + strlen("binary_")); |
| name = name.substr(0, name.size() - strlen("_end")); |
| |
| auto& resource = resources[name]; |
| if (!resource.base_address || resource.base_address == base_address) { |
| resource.base_address = base_address; |
| resource.end = |
| std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor) |
| resource.object_name = object_name; |
| } |
| } |
| } |
| } |
| |
| /// Based on the code of musl-libc and the answer of Kanalpiroge on |
| /// https://stackoverflow.com/questions/15779185/list-all-the-functions-symbols-on-the-fly-in-c-code-on-a-linux-architecture |
| /// It does not extract all the symbols (but only public - exported and used for dynamic linking), |
| /// but will work if we cannot find or parse ELF files. |
| void collectSymbolsFromProgramHeaders(dl_phdr_info* info, std::vector<SymbolIndex::Symbol>& symbols, |
| SymbolIndex::Resources& resources) { |
| /* Iterate over all headers of the current shared lib |
| * (first call is for the executable itself) |
| */ |
| for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) { |
| /* Further processing is only needed if the dynamic section is reached |
| */ |
| if (info->dlpi_phdr[header_index].p_type != PT_DYNAMIC) { |
| continue; |
| } |
| |
| /* Get a pointer to the first entry of the dynamic section. |
| * It's address is the shared lib's address + the virtual address |
| */ |
| const ElfW(Dyn)* dyn_begin = reinterpret_cast<const ElfW(Dyn)*>( |
| info->dlpi_addr + info->dlpi_phdr[header_index].p_vaddr); |
| |
| /// For unknown reason, addresses are sometimes relative sometimes absolute. |
| auto correct_address = [](ElfW(Addr) base, ElfW(Addr) ptr) { |
| return ptr > base ? ptr : base + ptr; |
| }; |
| |
| /* Iterate over all entries of the dynamic section until the |
| * end of the symbol table is reached. This is indicated by |
| * an entry with d_tag == DT_NULL. |
| */ |
| |
| size_t sym_cnt = 0; |
| for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) { |
| ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr); |
| |
| // TODO: this branch leads to invalid address of the hash table. Need further investigation. |
| // if (it->d_tag == DT_HASH) |
| // { |
| // const ElfW(Word) * hash = reinterpret_cast<const ElfW(Word) *>(base_address); |
| // sym_cnt = hash[1]; |
| // break; |
| // } |
| if (it->d_tag == DT_GNU_HASH) { |
| /// This code based on Musl-libc. |
| |
| const uint32_t* buckets = nullptr; |
| const uint32_t* hashval = nullptr; |
| |
| const ElfW(Word)* hash = reinterpret_cast<const ElfW(Word)*>(base_address); |
| |
| buckets = hash + 4 + (hash[2] * sizeof(size_t) / 4); |
| |
| for (ElfW(Word) i = 0; i < hash[0]; ++i) { |
| if (buckets[i] > sym_cnt) { |
| sym_cnt = buckets[i]; |
| } |
| } |
| |
| if (sym_cnt) { |
| sym_cnt -= hash[1]; |
| hashval = buckets + hash[0] + sym_cnt; |
| do { |
| ++sym_cnt; |
| } while (!(*hashval++ & 1)); |
| } |
| |
| break; |
| } |
| } |
| |
| if (!sym_cnt) { |
| continue; |
| } |
| |
| const char* strtab = nullptr; |
| for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) { |
| ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr); |
| |
| if (it->d_tag == DT_STRTAB) { |
| strtab = reinterpret_cast<const char*>(base_address); |
| break; |
| } |
| } |
| |
| if (!strtab) { |
| continue; |
| } |
| |
| for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) { |
| ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr); |
| |
| if (it->d_tag == DT_SYMTAB) { |
| /* Get the pointer to the first entry of the symbol table */ |
| const ElfW(Sym)* elf_sym = reinterpret_cast<const ElfW(Sym)*>(base_address); |
| |
| /* Iterate over the symbol table */ |
| for (ElfW(Word) sym_index = 0; sym_index < ElfW(Word)(sym_cnt); ++sym_index) { |
| /* Get the name of the sym_index-th symbol. |
| * This is located at the address of st_name relative to the beginning of the string table. |
| */ |
| const char* sym_name = &strtab[elf_sym[sym_index].st_name]; |
| |
| if (!sym_name) { |
| continue; |
| } |
| |
| SymbolIndex::Symbol symbol; |
| symbol.address_begin = reinterpret_cast<const void*>( |
| info->dlpi_addr + elf_sym[sym_index].st_value); |
| symbol.address_end = reinterpret_cast<const void*>(info->dlpi_addr + |
| elf_sym[sym_index].st_value + |
| elf_sym[sym_index].st_size); |
| symbol.name = sym_name; |
| |
| /// We are not interested in empty symbols. |
| if (elf_sym[sym_index].st_size) { |
| symbols.push_back(symbol); |
| } |
| |
| /// But resources can be represented by a pair of empty symbols (indicating their boundaries). |
| updateResources(base_address, info->dlpi_name, symbol.name, |
| symbol.address_begin, resources); |
| } |
| |
| break; |
| } |
| } |
| } |
| } |
| |
| #if !defined USE_MUSL |
| std::string getBuildIDFromProgramHeaders(dl_phdr_info* info) { |
| for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) { |
| const ElfPhdr& phdr = info->dlpi_phdr[header_index]; |
| if (phdr.p_type != PT_NOTE) { |
| continue; |
| } |
| |
| return Elf::getBuildID(reinterpret_cast<const char*>(info->dlpi_addr + phdr.p_vaddr), |
| phdr.p_memsz); |
| } |
| return {}; |
| } |
| #endif |
| |
| void collectSymbolsFromELFSymbolTable(dl_phdr_info* info, const Elf& elf, |
| const Elf::Section& symbol_table, |
| const Elf::Section& string_table, |
| std::vector<SymbolIndex::Symbol>& symbols, |
| SymbolIndex::Resources& resources) { |
| /// Iterate symbol table. |
| const ElfSym* symbol_table_entry = reinterpret_cast<const ElfSym*>(symbol_table.begin()); |
| const ElfSym* symbol_table_end = reinterpret_cast<const ElfSym*>(symbol_table.end()); |
| |
| const char* strings = string_table.begin(); |
| |
| for (; symbol_table_entry < symbol_table_end; ++symbol_table_entry) { |
| if (!symbol_table_entry->st_name || !symbol_table_entry->st_value || |
| strings + symbol_table_entry->st_name >= elf.end()) { |
| continue; |
| } |
| |
| /// Find the name in strings table. |
| const char* symbol_name = strings + symbol_table_entry->st_name; |
| |
| if (!symbol_name) { |
| continue; |
| } |
| |
| SymbolIndex::Symbol symbol; |
| symbol.address_begin = |
| reinterpret_cast<const void*>(info->dlpi_addr + symbol_table_entry->st_value); |
| symbol.address_end = reinterpret_cast<const void*>( |
| info->dlpi_addr + symbol_table_entry->st_value + symbol_table_entry->st_size); |
| symbol.name = symbol_name; |
| |
| if (symbol_table_entry->st_size) { |
| symbols.push_back(symbol); |
| } |
| |
| updateResources(info->dlpi_addr, info->dlpi_name, symbol.name, symbol.address_begin, |
| resources); |
| } |
| } |
| |
| bool searchAndCollectSymbolsFromELFSymbolTable(dl_phdr_info* info, const Elf& elf, |
| unsigned section_header_type, |
| const char* string_table_name, |
| std::vector<SymbolIndex::Symbol>& symbols, |
| SymbolIndex::Resources& resources) { |
| std::optional<Elf::Section> symbol_table; |
| std::optional<Elf::Section> string_table; |
| |
| if (!elf.iterateSections([&](const Elf::Section& section, size_t) { |
| if (section.header.sh_type == section_header_type) { |
| symbol_table.emplace(section); |
| } else if (section.header.sh_type == SHT_STRTAB && |
| 0 == strcmp(section.name(), string_table_name)) { |
| string_table.emplace(section); |
| } |
| |
| return (symbol_table && string_table); |
| })) { |
| return false; |
| } |
| |
| collectSymbolsFromELFSymbolTable(info, elf, *symbol_table, *string_table, symbols, resources); |
| return true; |
| } |
| |
| void collectSymbolsFromELF(dl_phdr_info* info, std::vector<SymbolIndex::Symbol>& symbols, |
| std::vector<SymbolIndex::Object>& objects, |
| SymbolIndex::Resources& resources, std::string& build_id) { |
| std::string object_name; |
| std::string our_build_id; |
| #if defined(USE_MUSL) |
| object_name = "/proc/self/exe"; |
| our_build_id = Elf(object_name).getBuildID(); |
| build_id = our_build_id; |
| #else |
| /// MSan does not know that the program segments in memory are initialized. |
| __msan_unpoison_string(info->dlpi_name); |
| |
| object_name = info->dlpi_name; |
| our_build_id = getBuildIDFromProgramHeaders(info); |
| |
| /// If the name is empty and there is a non-empty build-id - it's main executable. |
| /// Find a elf file for the main executable and set the build-id. |
| if (object_name.empty()) { |
| object_name = "/proc/self/exe"; |
| |
| if (our_build_id.empty()) { |
| our_build_id = Elf(object_name).getBuildID(); |
| } |
| |
| if (build_id.empty()) { |
| build_id = our_build_id; |
| } |
| } |
| #endif |
| |
| std::error_code ec; |
| std::filesystem::path canonical_path = std::filesystem::canonical(object_name, ec); |
| if (ec) { |
| return; |
| } |
| |
| /// Debug info and symbol table sections may be split to separate binary. |
| std::filesystem::path local_debug_info_path = |
| canonical_path.parent_path() / canonical_path.stem(); |
| local_debug_info_path += ".debug"; |
| std::filesystem::path debug_info_path = |
| std::filesystem::path("/usr/lib/debug") / canonical_path.relative_path(); |
| debug_info_path += ".debug"; |
| |
| /// NOTE: This is a workaround for current package system. |
| /// |
| /// Since nfpm cannot copy file only if it exists, |
| /// and so in cmake empty .debug file is created instead, |
| /// but if we will try to load empty Elf file, then the CANNOT_PARSE_ELF |
| /// exception will be thrown from the Elf::Elf. |
| auto exists_not_empty = [](const std::filesystem::path& path) { |
| return std::filesystem::exists(path) && !std::filesystem::is_empty(path); |
| }; |
| |
| if (exists_not_empty(local_debug_info_path)) { |
| object_name = local_debug_info_path; |
| } else if (exists_not_empty(debug_info_path)) { |
| object_name = debug_info_path; |
| } else if (build_id.size() >= 2) { |
| // Check if there is a .debug file in .build-id folder. For example: |
| // /usr/lib/debug/.build-id/e4/0526a12e9a8f3819a18694f6b798f10c624d5c.debug |
| std::string build_id_hex; |
| build_id_hex.resize(build_id.size() * 2); |
| |
| char* pos = build_id_hex.data(); |
| for (auto c : build_id) { |
| vectorized::write_hex_byte_lowercase(c, pos); |
| pos += 2; |
| } |
| |
| std::filesystem::path build_id_debug_info_path( |
| fmt::format("/usr/lib/debug/.build-id/{}/{}.debug", build_id_hex.substr(0, 2), |
| build_id_hex.substr(2))); |
| if (exists_not_empty(build_id_debug_info_path)) { |
| object_name = build_id_debug_info_path; |
| } else { |
| object_name = canonical_path; |
| } |
| } else { |
| object_name = canonical_path; |
| } |
| /// But we have to compare Build ID to check that debug info corresponds to the same executable. |
| |
| SymbolIndex::Object object; |
| object.elf = std::make_unique<Elf>(object_name); |
| |
| std::string file_build_id = object.elf->getBuildID(); |
| |
| if (our_build_id != file_build_id) { |
| /// If debug info doesn't correspond to our binary, fallback to the info in our binary. |
| if (object_name != canonical_path) { |
| object_name = canonical_path; |
| object.elf = std::make_unique<Elf>(object_name); |
| |
| /// But it can still be outdated, for example, if executable file was deleted from filesystem and replaced by another file. |
| file_build_id = object.elf->getBuildID(); |
| if (our_build_id != file_build_id) { |
| return; |
| } |
| } else { |
| return; |
| } |
| } |
| |
| object.address_begin = reinterpret_cast<const void*>(info->dlpi_addr); |
| object.address_end = reinterpret_cast<const void*>(info->dlpi_addr + object.elf->size()); |
| object.name = object_name; |
| objects.push_back(std::move(object)); |
| |
| searchAndCollectSymbolsFromELFSymbolTable(info, *objects.back().elf, SHT_SYMTAB, ".strtab", |
| symbols, resources); |
| |
| /// Unneeded if they were parsed from "program headers" of loaded objects. |
| #if defined USE_MUSL |
| searchAndCollectSymbolsFromELFSymbolTable(info, *objects.back().elf, SHT_DYNSYM, ".dynstr", |
| symbols, resources); |
| #endif |
| } |
| |
| /* Callback for dl_iterate_phdr. |
| * Is called by dl_iterate_phdr for every loaded shared lib until something |
| * else than 0 is returned by one call of this function. |
| */ |
| int collectSymbols(dl_phdr_info* info, size_t, void* data_ptr) { |
| SymbolIndex::Data& data = *reinterpret_cast<SymbolIndex::Data*>(data_ptr); |
| |
| collectSymbolsFromProgramHeaders(info, data.symbols, data.resources); |
| collectSymbolsFromELF(info, data.symbols, data.objects, data.resources, data.build_id); |
| |
| /* Continue iterations */ |
| return 0; |
| } |
| |
| template <typename T> |
| const T* find(const void* address, const std::vector<T>& vec) { |
| /// First range that has left boundary greater than address. |
| |
| auto it = std::lower_bound( |
| vec.begin(), vec.end(), address, |
| [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; }); |
| |
| if (it == vec.begin()) { |
| return nullptr; |
| } else { |
| --it; /// Last range that has left boundary less or equals than address. |
| } |
| |
| if (address >= it->address_begin && address < it->address_end) { |
| return &*it; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| } // namespace |
| |
| void SymbolIndex::update() { |
| dl_iterate_phdr(collectSymbols, &data); |
| ::pdqsort(data.objects.begin(), data.objects.end(), |
| [](const Object& a, const Object& b) { return a.address_begin < b.address_begin; }); |
| ::pdqsort(data.symbols.begin(), data.symbols.end(), |
| [](const Symbol& a, const Symbol& b) { return a.address_begin < b.address_begin; }); |
| /// We found symbols both from loaded program headers and from ELF symbol tables. |
| data.symbols.erase(std::unique(data.symbols.begin(), data.symbols.end(), |
| [](const Symbol& a, const Symbol& b) { |
| return a.address_begin == b.address_begin && |
| a.address_end == b.address_end; |
| }), |
| data.symbols.end()); |
| } |
| |
| const SymbolIndex::Symbol* SymbolIndex::findSymbol(const void* address) const { |
| return find(address, data.symbols); |
| } |
| |
| const SymbolIndex::Object* SymbolIndex::findObject(const void* address) const { |
| return find(address, data.objects); |
| } |
| |
| std::string SymbolIndex::getBuildIDHex() const { |
| std::string build_id_binary = getBuildID(); |
| std::string build_id_hex; |
| build_id_hex.resize(build_id_binary.size() * 2); |
| |
| char* pos = build_id_hex.data(); |
| for (auto c : build_id_binary) { |
| vectorized::write_hex_byte_uppercase(c, pos); |
| pos += 2; |
| } |
| |
| return build_id_hex; |
| } |
| |
| MultiVersion<SymbolIndex>& SymbolIndex::instanceImpl() { |
| static MultiVersion<SymbolIndex> instance(std::unique_ptr<SymbolIndex>(new SymbolIndex)); |
| return instance; |
| } |
| |
| MultiVersion<SymbolIndex>::Version SymbolIndex::instance() { |
| return instanceImpl().get(); |
| } |
| |
| void SymbolIndex::reload() { |
| instanceImpl().set(std::unique_ptr<SymbolIndex>(new SymbolIndex)); |
| /// Also drop stacktrace cache. |
| StackTrace::dropCache(); |
| } |
| |
| } // namespace doris |
| |
| #endif |