blob: afc2cfb5ab7468ea7d80c04b1bd8baa3b0089354 [file]
// 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
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);
}
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