Coverage Report

Created: 2026-07-07 23:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/common/phdr_cache.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/base/phdr_cache.cpp
19
// and modified by Doris
20
21
#include "common/phdr_cache.h"
22
23
#if defined(__clang__)
24
#pragma clang diagnostic ignored "-Wreserved-identifier"
25
#endif
26
27
/// This code was based on the code by Fedor Korotkiy https://www.linkedin.com/in/fedor-korotkiy-659a1838/
28
29
#if defined(__linux__) && !defined(THREAD_SANITIZER) && !defined(USE_MUSL)
30
#define USE_PHDR_CACHE 1
31
#endif
32
33
/// Thread Sanitizer uses dl_iterate_phdr function on initialization and fails if we provide our own.
34
#ifdef USE_PHDR_CACHE
35
36
#if defined(__clang__)
37
#pragma clang diagnostic ignored "-Wreserved-id-macro"
38
#pragma clang diagnostic ignored "-Wunused-macros"
39
#endif
40
41
#define __msan_unpoison(X, Y) // NOLINT
42
#if defined(__clang__) && defined(__has_feature)
43
#if __has_feature(memory_sanitizer)
44
#undef __msan_unpoison
45
#include <sanitizer/msan_interface.h>
46
#endif
47
#endif
48
49
#include <dlfcn.h>
50
#include <link.h>
51
52
#include <algorithm>
53
#include <array>
54
#include <atomic>
55
#include <cstddef>
56
#include <cstring>
57
#include <limits>
58
#include <memory>
59
#include <mutex>
60
#include <stdexcept>
61
#include <string>
62
#include <utility>
63
#include <vector>
64
65
#if defined(USE_UNWIND) && USE_UNWIND
66
#ifndef UNW_LOCAL_ONLY
67
#define UNW_LOCAL_ONLY
68
#endif
69
#include <libunwind.h>
70
#endif
71
72
namespace {
73
74
// This is adapted from
75
// https://github.com/scylladb/seastar/blob/master/core/exception_hacks.hh
76
// https://github.com/scylladb/seastar/blob/master/core/exception_hacks.cc
77
78
constexpr size_t MAX_PHDR_CACHE_LOADED_OBJECTS = 4096;
79
constexpr size_t MAX_PHDR_CACHE_PROGRAM_HEADERS = 128;
80
constexpr size_t MAX_PHDR_CACHE_OBJECT_NAME = 4096;
81
82
using DLIterateFunction = int (*)(int (*callback)(dl_phdr_info* info, size_t size, void* data),
83
                                  void* data);
84
85
69.4k
DLIterateFunction getOriginalDLIteratePHDR() {
86
69.4k
    void* func = dlsym(RTLD_NEXT, "dl_iterate_phdr");
87
69.4k
    if (!func) {
88
0
        throw std::runtime_error("Cannot find dl_iterate_phdr function with dlsym");
89
0
    }
90
69.4k
    return reinterpret_cast<DLIterateFunction>(func);
91
69.4k
}
92
93
struct RawPHDRCacheEntry {
94
    dl_phdr_info info {};
95
    std::array<ElfW(Phdr), MAX_PHDR_CACHE_PROGRAM_HEADERS> phdrs {};
96
    size_t phdr_count = 0;
97
    std::array<char, MAX_PHDR_CACHE_OBJECT_NAME> name {};
98
    uintptr_t address_begin = std::numeric_limits<uintptr_t>::max();
99
    uintptr_t address_end = 0;
100
};
101
102
struct RawPHDRCacheSnapshot {
103
    std::array<RawPHDRCacheEntry, MAX_PHDR_CACHE_LOADED_OBJECTS> entries {};
104
    size_t size = 0;
105
    bool overflow = false;
106
    bool phdr_truncated = false;
107
    bool name_truncated = false;
108
};
109
110
struct PHDRCacheEntry {
111
    dl_phdr_info info {};
112
    std::vector<ElfW(Phdr)> phdrs;
113
    std::string name;
114
    uintptr_t address_begin = std::numeric_limits<uintptr_t>::max();
115
    uintptr_t address_end = 0;
116
117
1.19k
    bool contains(uintptr_t ip) const {
118
1.19k
        return ip == 0 || (address_begin <= ip && ip < address_end);
119
1.19k
    }
120
};
121
122
using PHDRCache = std::vector<PHDRCacheEntry>;
123
std::atomic<PHDRCache*> phdr_cache {};
124
// This flag is flipped inside the stack-trace signal handler. Force a static TLS access model so
125
// reading it from our dl_iterate_phdr interposer does not call into the dynamic loader's TLS path.
126
__thread bool use_phdr_cache __attribute__((tls_model("initial-exec"))) = false;
127
128
331
uintptr_t saturated_segment_end(uintptr_t begin, uintptr_t size) {
129
331
    const uintptr_t max_address = std::numeric_limits<uintptr_t>::max();
130
331
    return begin > max_address - size ? max_address : begin + size;
131
331
}
132
133
void copy_object_name(const char* source, RawPHDRCacheEntry* entry,
134
151
                      RawPHDRCacheSnapshot* snapshot) {
135
151
    if (source == nullptr) {
136
0
        entry->name[0] = '\0';
137
0
        return;
138
0
    }
139
140
151
    size_t length = 0;
141
3.34k
    while (length + 1 < entry->name.size() && source[length] != '\0') {
142
3.19k
        entry->name[length] = source[length];
143
3.19k
        ++length;
144
3.19k
    }
145
151
    entry->name[length] = '\0';
146
151
    if (source[length] != '\0') {
147
0
        snapshot->name_truncated = true;
148
0
    }
149
151
}
150
151
151
int collectPHDRCacheEntry(dl_phdr_info* info, size_t /*size*/, void* data) {
152
151
    auto* snapshot = reinterpret_cast<RawPHDRCacheSnapshot*>(data);
153
151
    if (snapshot->size >= snapshot->entries.size()) {
154
0
        snapshot->overflow = true;
155
0
        return 0;
156
0
    }
157
158
151
    auto& entry = snapshot->entries[snapshot->size++];
159
151
    entry.info = *info;
160
151
    copy_object_name(info->dlpi_name, &entry, snapshot);
161
162
151
    const size_t phdr_count = std::min<size_t>(info->dlpi_phnum, entry.phdrs.size());
163
151
    if (phdr_count < info->dlpi_phnum) {
164
0
        snapshot->phdr_truncated = true;
165
0
    }
166
151
    entry.phdr_count = phdr_count;
167
151
    entry.info.dlpi_phnum = static_cast<ElfW(Half)>(phdr_count);
168
151
    entry.info.dlpi_name = nullptr;
169
151
    entry.info.dlpi_phdr = nullptr;
170
171
151
    if (info->dlpi_phdr == nullptr) {
172
0
        return 0;
173
0
    }
174
151
    std::memcpy(entry.phdrs.data(), info->dlpi_phdr, phdr_count * sizeof(ElfW(Phdr)));
175
176
1.32k
    for (size_t i = 0; i < phdr_count; ++i) {
177
1.17k
        const auto& phdr = entry.phdrs[i];
178
1.17k
        if (phdr.p_type != PT_LOAD || phdr.p_memsz == 0) {
179
842
            continue;
180
842
        }
181
331
        const auto begin = static_cast<uintptr_t>(info->dlpi_addr + phdr.p_vaddr);
182
331
        const auto end = saturated_segment_end(begin, static_cast<uintptr_t>(phdr.p_memsz));
183
331
        entry.address_begin = std::min(entry.address_begin, begin);
184
331
        entry.address_end = std::max(entry.address_end, end);
185
331
    }
186
151
    return 0;
187
151
}
188
189
11
PHDRCache* buildPHDRCache(const RawPHDRCacheSnapshot& snapshot) {
190
11
    auto* cache = new PHDRCache;
191
11
    cache->reserve(snapshot.size);
192
162
    for (size_t i = 0; i < snapshot.size; ++i) {
193
151
        const auto& raw_entry = snapshot.entries[i];
194
151
        PHDRCacheEntry entry;
195
151
        entry.info = raw_entry.info;
196
151
        entry.phdrs.assign(raw_entry.phdrs.begin(), raw_entry.phdrs.begin() + raw_entry.phdr_count);
197
151
        entry.name = raw_entry.name.data();
198
151
        entry.address_begin = raw_entry.address_begin;
199
151
        entry.address_end = raw_entry.address_end;
200
151
        cache->emplace_back(std::move(entry));
201
151
    }
202
151
    for (auto& entry : *cache) {
203
151
        entry.info.dlpi_phdr = entry.phdrs.data();
204
151
        entry.info.dlpi_name = entry.name.c_str();
205
151
    }
206
11
    return cache;
207
11
}
208
209
int iteratePHDRCache(int (*callback)(dl_phdr_info* info, size_t size, void* data), void* data,
210
1.03k
                     uintptr_t ip) {
211
1.03k
    auto* current_phdr_cache = phdr_cache.load(std::memory_order_acquire);
212
1.03k
    if (current_phdr_cache == nullptr) {
213
0
        return 0;
214
0
    }
215
216
1.03k
    int result = 0;
217
1.19k
    for (auto& entry : *current_phdr_cache) {
218
1.19k
        if (!entry.contains(ip)) {
219
143
            continue;
220
143
        }
221
1.05k
        result = callback(&entry.info, sizeof(dl_phdr_info), data);
222
1.05k
        if (result != 0) {
223
1.02k
            break;
224
1.02k
        }
225
1.05k
    }
226
1.03k
    return result;
227
1.03k
}
228
229
} // namespace
230
231
extern "C"
232
#ifndef __clang__
233
        [[gnu::visibility("default")]] [[gnu::externally_visible]]
234
#endif
235
        int
236
69.4k
        dl_iterate_phdr(int (*callback)(dl_phdr_info* info, size_t size, void* data), void* data) {
237
69.4k
    if (!use_phdr_cache) {
238
69.4k
        return getOriginalDLIteratePHDR()(callback, data);
239
69.4k
    }
240
241
2
    return iteratePHDRCache(callback, data, 0);
242
69.4k
}
243
244
extern "C"
245
#ifndef __clang__
246
        [[gnu::visibility("default")]] [[gnu::externally_visible]]
247
#endif
248
        int
249
        doris_unwind_iterate_phdr(int (*callback)(dl_phdr_info* info, size_t size, void* data),
250
1.02k
                                  void* data, uintptr_t ip) {
251
1.02k
    return iteratePHDRCache(callback, data, ip);
252
1.02k
}
253
254
#include "util/debug/leak_annotations.h"
255
256
11
void updatePHDRCache() {
257
    // Fill out ELF header cache for access without locking.
258
    // Old snapshots are intentionally kept alive because another thread may already be unwinding
259
    // through the previous cache when a Doris-controlled dlopen/dlclose refreshes this one.
260
261
11
    auto raw_snapshot = std::make_unique<RawPHDRCacheSnapshot>();
262
11
    getOriginalDLIteratePHDR()(
263
151
            [](dl_phdr_info* info, size_t size, void* data) {
264
                // `info` is created by dl_iterate_phdr, which is a non-instrumented
265
                // libc function, so we have to unpoison it manually.
266
151
                __msan_unpoison(info, sizeof(*info));
267
268
151
                return collectPHDRCacheEntry(info, size, data);
269
151
            },
270
11
            raw_snapshot.get());
271
272
11
    PHDRCache* new_phdr_cache = buildPHDRCache(*raw_snapshot);
273
11
    phdr_cache.store(new_phdr_cache, std::memory_order_release);
274
275
    /// Memory is intentionally leaked.
276
11
    ANNOTATE_LEAKING_OBJECT_PTR(new_phdr_cache);
277
11
}
278
279
21
bool hasPHDRCache() {
280
21
    return phdr_cache.load(std::memory_order_acquire) != nullptr;
281
21
}
282
283
1
void configureLibunwindPHDRCache() {
284
1
#if defined(USE_UNWIND) && USE_UNWIND
285
1
    static std::once_flag once;
286
1
    std::call_once(once, [] {
287
1
        unw_context_t context;
288
1
        unw_cursor_t cursor;
289
1
        (void)unw_getcontext(&context);
290
1
        (void)unw_init_local(&cursor, &context);
291
        // Doris-patched libunwind gets FDEs from the PHDR snapshot. Disable the global DWARF
292
        // register-state cache so a signal handler cannot self-deadlock on libunwind's cache mutex
293
        // after interrupting a thread that was already unwinding.
294
1
        (void)unw_set_caching_policy(unw_local_addr_space, UNW_CACHE_NONE);
295
1
    });
296
1
#endif
297
1
}
298
299
7
ScopedPHDRCacheRead::ScopedPHDRCacheRead() : _previous(use_phdr_cache) {
300
7
    use_phdr_cache = true;
301
7
}
302
303
7
ScopedPHDRCacheRead::~ScopedPHDRCacheRead() {
304
7
    use_phdr_cache = _previous;
305
7
}
306
307
#else
308
309
void updatePHDRCache() {}
310
311
void configureLibunwindPHDRCache() {}
312
313
#if defined(USE_MUSL)
314
/// With statically linked with musl, dl_iterate_phdr is immutable.
315
bool hasPHDRCache() {
316
    return true;
317
}
318
#else
319
bool hasPHDRCache() {
320
    return false;
321
}
322
#endif
323
324
ScopedPHDRCacheRead::ScopedPHDRCacheRead() = default;
325
326
ScopedPHDRCacheRead::~ScopedPHDRCacheRead() = default;
327
328
#endif