| // 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/base/phdr_cache.cpp | |
| // and modified by Doris | |
| #include "common/phdr_cache.h" | |
| #if defined(__clang__) | |
| #pragma clang diagnostic ignored "-Wreserved-identifier" | |
| #endif | |
| /// This code was based on the code by Fedor Korotkiy https://www.linkedin.com/in/fedor-korotkiy-659a1838/ | |
| #if defined(__linux__) && !defined(THREAD_SANITIZER) && !defined(USE_MUSL) | |
| #define USE_PHDR_CACHE 1 | |
| #endif | |
| /// Thread Sanitizer uses dl_iterate_phdr function on initialization and fails if we provide our own. | |
| #ifdef USE_PHDR_CACHE | |
| #if defined(__clang__) | |
| #pragma clang diagnostic ignored "-Wreserved-id-macro" | |
| #pragma clang diagnostic ignored "-Wunused-macros" | |
| #endif | |
| #define __msan_unpoison(X, Y) // NOLINT | |
| #if defined(__clang__) && defined(__has_feature) | |
| #if __has_feature(memory_sanitizer) | |
| #undef __msan_unpoison | |
| #include <sanitizer/msan_interface.h> | |
| #endif | |
| #endif | |
| #include <dlfcn.h> | |
| #include <link.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <atomic> | |
| #include <cstddef> | |
| #include <cstring> | |
| #include <limits> | |
| #include <memory> | |
| #include <mutex> | |
| #include <stdexcept> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #if defined(USE_UNWIND) && USE_UNWIND | |
| #ifndef UNW_LOCAL_ONLY | |
| #define UNW_LOCAL_ONLY | |
| #endif | |
| #include <libunwind.h> | |
| #endif | |
| namespace { | |
| // This is adapted from | |
| // https://github.com/scylladb/seastar/blob/master/core/exception_hacks.hh | |
| // https://github.com/scylladb/seastar/blob/master/core/exception_hacks.cc | |
| constexpr size_t MAX_PHDR_CACHE_LOADED_OBJECTS = 4096; | |
| constexpr size_t MAX_PHDR_CACHE_PROGRAM_HEADERS = 128; | |
| constexpr size_t MAX_PHDR_CACHE_OBJECT_NAME = 4096; | |
| using DLIterateFunction = int (*)(int (*callback)(dl_phdr_info* info, size_t size, void* data), | |
| void* data); | |
| DLIterateFunction getOriginalDLIteratePHDR() { | |
| void* func = dlsym(RTLD_NEXT, "dl_iterate_phdr"); | |
| if (!func) { | |
| throw std::runtime_error("Cannot find dl_iterate_phdr function with dlsym"); | |
| } | |
| return reinterpret_cast<DLIterateFunction>(func); | |
| } | |
| struct RawPHDRCacheEntry { | |
| dl_phdr_info info {}; | |
| std::array<ElfW(Phdr), MAX_PHDR_CACHE_PROGRAM_HEADERS> phdrs {}; | |
| size_t phdr_count = 0; | |
| std::array<char, MAX_PHDR_CACHE_OBJECT_NAME> name {}; | |
| uintptr_t address_begin = std::numeric_limits<uintptr_t>::max(); | |
| uintptr_t address_end = 0; | |
| }; | |
| struct RawPHDRCacheSnapshot { | |
| std::array<RawPHDRCacheEntry, MAX_PHDR_CACHE_LOADED_OBJECTS> entries {}; | |
| size_t size = 0; | |
| bool overflow = false; | |
| bool phdr_truncated = false; | |
| bool name_truncated = false; | |
| }; | |
| struct PHDRCacheEntry { | |
| dl_phdr_info info {}; | |
| std::vector<ElfW(Phdr)> phdrs; | |
| std::string name; | |
| uintptr_t address_begin = std::numeric_limits<uintptr_t>::max(); | |
| uintptr_t address_end = 0; | |
| bool contains(uintptr_t ip) const { | |
| return ip == 0 || (address_begin <= ip && ip < address_end); | |
| } | |
| }; | |
| using PHDRCache = std::vector<PHDRCacheEntry>; | |
| std::atomic<PHDRCache*> phdr_cache {}; | |
| // This flag is flipped inside the stack-trace signal handler. Force a static TLS access model so | |
| // reading it from our dl_iterate_phdr interposer does not call into the dynamic loader's TLS path. | |
| __thread bool use_phdr_cache __attribute__((tls_model("initial-exec"))) = false; | |
| uintptr_t saturated_segment_end(uintptr_t begin, uintptr_t size) { | |
| const uintptr_t max_address = std::numeric_limits<uintptr_t>::max(); | |
| return begin > max_address - size ? max_address : begin + size; | |
| } | |
| void copy_object_name(const char* source, RawPHDRCacheEntry* entry, | |
| RawPHDRCacheSnapshot* snapshot) { | |
| if (source == nullptr) { | |
| entry->name[0] = '\0'; | |
| return; | |
| } | |
| size_t length = 0; | |
| while (length + 1 < entry->name.size() && source[length] != '\0') { | |
| entry->name[length] = source[length]; | |
| ++length; | |
| } | |
| entry->name[length] = '\0'; | |
| if (source[length] != '\0') { | |
| snapshot->name_truncated = true; | |
| } | |
| } | |
| int collectPHDRCacheEntry(dl_phdr_info* info, size_t /*size*/, void* data) { | |
| auto* snapshot = reinterpret_cast<RawPHDRCacheSnapshot*>(data); | |
| if (snapshot->size >= snapshot->entries.size()) { | |
| snapshot->overflow = true; | |
| return 0; | |
| } | |
| auto& entry = snapshot->entries[snapshot->size++]; | |
| entry.info = *info; | |
| copy_object_name(info->dlpi_name, &entry, snapshot); | |
| const size_t phdr_count = std::min<size_t>(info->dlpi_phnum, entry.phdrs.size()); | |
| if (phdr_count < info->dlpi_phnum) { | |
| snapshot->phdr_truncated = true; | |
| } | |
| entry.phdr_count = phdr_count; | |
| entry.info.dlpi_phnum = static_cast<ElfW(Half)>(phdr_count); | |
| entry.info.dlpi_name = nullptr; | |
| entry.info.dlpi_phdr = nullptr; | |
| if (info->dlpi_phdr == nullptr) { | |
| return 0; | |
| } | |
| std::memcpy(entry.phdrs.data(), info->dlpi_phdr, phdr_count * sizeof(ElfW(Phdr))); | |
| for (size_t i = 0; i < phdr_count; ++i) { | |
| const auto& phdr = entry.phdrs[i]; | |
| if (phdr.p_type != PT_LOAD || phdr.p_memsz == 0) { | |
| continue; | |
| } | |
| const auto begin = static_cast<uintptr_t>(info->dlpi_addr + phdr.p_vaddr); | |
| const auto end = saturated_segment_end(begin, static_cast<uintptr_t>(phdr.p_memsz)); | |
| entry.address_begin = std::min(entry.address_begin, begin); | |
| entry.address_end = std::max(entry.address_end, end); | |
| } | |
| return 0; | |
| } | |
| PHDRCache* buildPHDRCache(const RawPHDRCacheSnapshot& snapshot) { | |
| auto* cache = new PHDRCache; | |
| cache->reserve(snapshot.size); | |
| for (size_t i = 0; i < snapshot.size; ++i) { | |
| const auto& raw_entry = snapshot.entries[i]; | |
| PHDRCacheEntry entry; | |
| entry.info = raw_entry.info; | |
| entry.phdrs.assign(raw_entry.phdrs.begin(), raw_entry.phdrs.begin() + raw_entry.phdr_count); | |
| entry.name = raw_entry.name.data(); | |
| entry.address_begin = raw_entry.address_begin; | |
| entry.address_end = raw_entry.address_end; | |
| cache->emplace_back(std::move(entry)); | |
| } | |
| for (auto& entry : *cache) { | |
| entry.info.dlpi_phdr = entry.phdrs.data(); | |
| entry.info.dlpi_name = entry.name.c_str(); | |
| } | |
| return cache; | |
| } | |
| int iteratePHDRCache(int (*callback)(dl_phdr_info* info, size_t size, void* data), void* data, | |
| uintptr_t ip) { | |
| auto* current_phdr_cache = phdr_cache.load(std::memory_order_acquire); | |
| if (current_phdr_cache == nullptr) { | |
| return 0; | |
| } | |
| int result = 0; | |
| for (auto& entry : *current_phdr_cache) { | |
| if (!entry.contains(ip)) { | |
| continue; | |
| } | |
| result = callback(&entry.info, sizeof(dl_phdr_info), data); | |
| if (result != 0) { | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| } // namespace | |
| // ASAN's slow unwinder can reach this interposer while dlsym is freeing its previous error | |
| // string. Resolving the original function with dlsym here would reenter that cleanup and | |
| // free the same string twice. Keep ASAN on the system implementation instead. | |
| #if !defined(ADDRESS_SANITIZER) | |
| extern "C" | |
| #ifndef __clang__ | |
| [[gnu::visibility("default")]] [[gnu::externally_visible]] | |
| #endif | |
| int | |
| dl_iterate_phdr(int (*callback)(dl_phdr_info* info, size_t size, void* data), void* data) { | |
| if (!use_phdr_cache) { | |
| return getOriginalDLIteratePHDR()(callback, data); | |
| } | |
| return iteratePHDRCache(callback, data, 0); | |
| } | |
| #endif | |
| extern "C" | |
| #ifndef __clang__ | |
| [[gnu::visibility("default")]] [[gnu::externally_visible]] | |
| #endif | |
| int | |
| doris_unwind_iterate_phdr(int (*callback)(dl_phdr_info* info, size_t size, void* data), | |
| void* data, uintptr_t ip) { | |
| return iteratePHDRCache(callback, data, ip); | |
| } | |
| #include "util/debug/leak_annotations.h" | |
| void updatePHDRCache() { | |
| // Fill out ELF header cache for access without locking. | |
| // Old snapshots are intentionally kept alive because another thread may already be unwinding | |
| // through the previous cache when a Doris-controlled dlopen/dlclose refreshes this one. | |
| auto raw_snapshot = std::make_unique<RawPHDRCacheSnapshot>(); | |
| getOriginalDLIteratePHDR()( | |
| [](dl_phdr_info* info, size_t size, void* data) { | |
| // `info` is created by dl_iterate_phdr, which is a non-instrumented | |
| // libc function, so we have to unpoison it manually. | |
| __msan_unpoison(info, sizeof(*info)); | |
| return collectPHDRCacheEntry(info, size, data); | |
| }, | |
| raw_snapshot.get()); | |
| PHDRCache* new_phdr_cache = buildPHDRCache(*raw_snapshot); | |
| phdr_cache.store(new_phdr_cache, std::memory_order_release); | |
| /// Memory is intentionally leaked. | |
| ANNOTATE_LEAKING_OBJECT_PTR(new_phdr_cache); | |
| } | |
| bool hasPHDRCache() { | |
| return phdr_cache.load(std::memory_order_acquire) != nullptr; | |
| } | |
| void configureLibunwindPHDRCache() { | |
| #if defined(USE_UNWIND) && USE_UNWIND | |
| static std::once_flag once; | |
| std::call_once(once, [] { | |
| unw_context_t context; | |
| unw_cursor_t cursor; | |
| (void)unw_getcontext(&context); | |
| (void)unw_init_local(&cursor, &context); | |
| // Doris-patched libunwind gets FDEs from the PHDR snapshot. Disable the global DWARF | |
| // register-state cache so a signal handler cannot self-deadlock on libunwind's cache mutex | |
| // after interrupting a thread that was already unwinding. | |
| (void)unw_set_caching_policy(unw_local_addr_space, UNW_CACHE_NONE); | |
| }); | |
| #endif | |
| } | |
| ScopedPHDRCacheRead::ScopedPHDRCacheRead() : _previous(use_phdr_cache) { | |
| use_phdr_cache = true; | |
| } | |
| ScopedPHDRCacheRead::~ScopedPHDRCacheRead() { | |
| use_phdr_cache = _previous; | |
| } | |
| #else | |
| void updatePHDRCache() {} | |
| void configureLibunwindPHDRCache() {} | |
| #if defined(USE_MUSL) | |
| /// With statically linked with musl, dl_iterate_phdr is immutable. | |
| bool hasPHDRCache() { | |
| return true; | |
| } | |
| #else | |
| bool hasPHDRCache() { | |
| return false; | |
| } | |
| #endif | |
| ScopedPHDRCacheRead::ScopedPHDRCacheRead() = default; | |
| ScopedPHDRCacheRead::~ScopedPHDRCacheRead() = default; | |
| #endif |