microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
dl_elf_enumerator.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
8#pragma once
9
10#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && \
11 !defined(__DragonFly__)
12#error "microfmt/inspector/dl_elf_enumerator.hpp only supports Linux and BSD systems"
13#endif
14
15#include <cstdint>
16#include <cstring>
17#include <dlfcn.h>
18#include <link.h>
19
20#include "../microfmt.hpp"
21#include "elf_enumerator.hpp"
22
23namespace microfmt::detail {
24
33inline bool dl_phdr_image_bounds(const dl_phdr_info &info, uintptr_t &out_base, uintptr_t &out_size) noexcept {
34 uintptr_t min_vaddr = 0;
35 uintptr_t max_vaddr = 0;
36 bool have_load = false;
37
38 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
39
40 for (size_t i = 0; i < static_cast<size_t>(info.dlpi_phnum); ++i) {
41 const auto &phdr = info.dlpi_phdr[i];
42 if (phdr.p_type != PT_LOAD)
43 continue;
44 const auto start = static_cast<uintptr_t>(phdr.p_vaddr);
45 const auto end = start + static_cast<uintptr_t>(phdr.p_memsz);
46 if (!have_load || start < min_vaddr)
47 min_vaddr = start;
48 if (!have_load || end > max_vaddr)
49 max_vaddr = end;
50 have_load = true;
51 }
52
53 RELOCO_END_UNSAFE_BUFFER_USAGE;
54
55 if (!have_load)
56 return false;
57 out_base = static_cast<uintptr_t>(info.dlpi_addr) + min_vaddr;
58 out_size = max_vaddr - min_vaddr;
59 return true;
60}
61
62#if defined(PT_ARM_EXIDX)
71inline bool dl_phdr_exidx_bounds(const dl_phdr_info &info, uintptr_t &out_start, uintptr_t &out_end) noexcept {
72 uintptr_t start = 0;
73 uintptr_t end = 0;
74 bool have_exidx = false;
75
76 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
77
78 for (size_t i = 0; i < static_cast<size_t>(info.dlpi_phnum); ++i) {
79 const auto &phdr = info.dlpi_phdr[i];
80 if (phdr.p_type != PT_ARM_EXIDX)
81 continue;
82 start = static_cast<uintptr_t>(info.dlpi_addr) + static_cast<uintptr_t>(phdr.p_vaddr);
83 end = start + static_cast<uintptr_t>(phdr.p_memsz);
84 have_exidx = true;
85 break;
86 }
87
88 RELOCO_END_UNSAFE_BUFFER_USAGE;
89
90 if (!have_exidx || end <= start)
91 return false;
92 out_start = start;
93 out_end = end;
94 return true;
95}
96#endif // defined(PT_ARM_EXIDX)
97
98#if defined(PT_GNU_EH_FRAME)
99
100// Subset of the DW_EH_PE_* pointer-encoding constants (from the LSB/libgcc
101// `.eh_frame_hdr` ABI) needed to decode the `eh_frame_ptr` field. Only the
102// application/format combinations GCC and Clang actually emit are handled.
103inline constexpr unsigned char k_dw_eh_pe_omit = 0xff;
104inline constexpr unsigned char k_dw_eh_pe_format_mask = 0x0f;
105inline constexpr unsigned char k_dw_eh_pe_application_mask = 0x70;
106inline constexpr unsigned char k_dw_eh_pe_udata4 = 0x03;
107inline constexpr unsigned char k_dw_eh_pe_sdata4 = 0x0b;
108inline constexpr unsigned char k_dw_eh_pe_pcrel = 0x10;
109inline constexpr unsigned char k_dw_eh_pe_absptr_application = 0x00;
110
122// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
123// here.
124RELOCO_BEGIN_UNSAFE_CODE
125inline bool decode_eh_frame_hdr(uintptr_t hdr_start, uintptr_t hdr_end, uintptr_t &out_eh_frame_start) noexcept {
126 // 4-byte fixed header + at least a 4-byte encoded eh_frame_ptr field.
127 if (hdr_end < hdr_start || hdr_end - hdr_start < 8)
128 return false;
129
130 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
131 // NOLINTNEXTLINE(performance-no-int-to-ptr)
132 const auto *bytes = reinterpret_cast<const unsigned char *>(hdr_start);
133 const unsigned char version = bytes[0];
134 const unsigned char eh_frame_ptr_enc = bytes[1];
135 RELOCO_END_UNSAFE_BUFFER_USAGE;
136
137 if (version != 1 || eh_frame_ptr_enc == k_dw_eh_pe_omit)
138 return false;
139
140 const unsigned char format = static_cast<unsigned char>(eh_frame_ptr_enc & k_dw_eh_pe_format_mask);
141 const unsigned char application = static_cast<unsigned char>(eh_frame_ptr_enc & k_dw_eh_pe_application_mask);
142 if (application != k_dw_eh_pe_pcrel && application != k_dw_eh_pe_absptr_application)
143 return false; // Uncommon application; not worth mis-decoding.
144 if (format != k_dw_eh_pe_udata4 && format != k_dw_eh_pe_sdata4)
145 return false; // Uncommon format; not worth mis-decoding.
146
147 const uintptr_t field_addr = hdr_start + 4;
148 int32_t raw_value = 0;
149 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
150 // NOLINTNEXTLINE(performance-no-int-to-ptr)
151 std::memcpy(&raw_value, reinterpret_cast<const void *>(field_addr), sizeof(raw_value));
152 RELOCO_END_UNSAFE_BUFFER_USAGE;
153
154 const uintptr_t value = (format == k_dw_eh_pe_udata4) ? static_cast<uintptr_t>(static_cast<uint32_t>(raw_value))
155 : static_cast<uintptr_t>(raw_value);
156
157 out_eh_frame_start = (application == k_dw_eh_pe_pcrel) ? field_addr + value : value;
158 return true;
159}
161
182inline bool dl_phdr_eh_frame_bounds(const dl_phdr_info &info, uintptr_t &out_start, uintptr_t &out_end) noexcept {
183 uintptr_t hdr_start = 0;
184 uintptr_t hdr_end = 0;
185 bool have_eh_frame_hdr = false;
186
187 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
188
189 for (size_t i = 0; i < static_cast<size_t>(info.dlpi_phnum); ++i) {
190 const auto &phdr = info.dlpi_phdr[i];
191 if (phdr.p_type != PT_GNU_EH_FRAME)
192 continue;
193 hdr_start = static_cast<uintptr_t>(info.dlpi_addr) + static_cast<uintptr_t>(phdr.p_vaddr);
194 hdr_end = hdr_start + static_cast<uintptr_t>(phdr.p_memsz);
195 have_eh_frame_hdr = true;
196 break;
197 }
198
199 RELOCO_END_UNSAFE_BUFFER_USAGE;
200
201 if (!have_eh_frame_hdr)
202 return false;
203
204 uintptr_t eh_frame_start = 0;
205 if (!decode_eh_frame_hdr(hdr_start, hdr_end, eh_frame_start))
206 return false;
207
208 out_start = eh_frame_start;
209 out_end = UINTPTR_MAX;
210 return true;
211}
212#endif // defined(PT_GNU_EH_FRAME)
213
218 span<elf_image_info> buffer;
219 size_t count{0};
220};
221
226// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
227// here.
228RELOCO_BEGIN_UNSAFE_CODE
229inline int dl_enumerate_callback(dl_phdr_info *info, size_t, void *data) noexcept {
230 auto &state = *static_cast<dl_enumerate_state *>(data);
231 if (state.count >= state.buffer.size())
232 return 1; // Buffer already full; stop iterating.
233
234 uintptr_t base = 0;
235 uintptr_t size = 0;
236 if (!dl_phdr_image_bounds(*info, base, size))
237 return 0; // No PT_LOAD segments (e.g. the Linux VDSO on some ABIs); skip.
238
239 elf_image_info entry{};
240 entry.image_name =
241 (info->dlpi_name != nullptr && info->dlpi_name[0] != '\0') ? string_view(info->dlpi_name) : string_view("");
242 entry.load_base = base;
243 entry.image_size = size;
244#if defined(PT_ARM_EXIDX)
245 dl_phdr_exidx_bounds(*info, entry.exidx_start, entry.exidx_end);
246#endif
247#if defined(PT_GNU_EH_FRAME)
248 dl_phdr_eh_frame_bounds(*info, entry.debug_frame_start, entry.debug_frame_end);
249#endif
250 state.buffer[state.count++] = entry;
251 return 0;
252}
254
262inline bool dl_enumerate_images(span<elf_image_info> out_buffer, size_t &out_count) noexcept {
263 dl_enumerate_state state{out_buffer, 0};
264 ::dl_iterate_phdr(&dl_enumerate_callback, &state);
265 out_count = state.count;
266 return true;
267}
268
275 uintptr_t target_base{};
276 uintptr_t size{0};
277 uintptr_t exidx_start{0};
278 uintptr_t exidx_end{0};
279 uintptr_t debug_frame_start{0};
280 uintptr_t debug_frame_end{0};
281 bool found{false};
282};
283
289// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
290// here.
291RELOCO_BEGIN_UNSAFE_CODE
292inline int dl_find_size_callback(dl_phdr_info *info, size_t, void *data) noexcept {
293 auto &state = *static_cast<dl_find_size_state *>(data);
294 uintptr_t base = 0;
295 uintptr_t size = 0;
296 if (!dl_phdr_image_bounds(*info, base, size))
297 return 0;
298 if (base == state.target_base) {
299 state.size = size;
300#if defined(PT_ARM_EXIDX)
301 dl_phdr_exidx_bounds(*info, state.exidx_start, state.exidx_end);
302#endif
303#if defined(PT_GNU_EH_FRAME)
304 dl_phdr_eh_frame_bounds(*info, state.debug_frame_start, state.debug_frame_end);
305#endif
306 state.found = true;
307 return 1; // Match found; stop iterating.
308 }
309 return 0;
310}
312
321// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
322// here.
323RELOCO_BEGIN_UNSAFE_CODE
324inline bool dl_find_by_pc(uintptr_t pc, elf_image_info &out_info) noexcept {
325 Dl_info info{};
326 // NOLINTNEXTLINE(performance-no-int-to-ptr)
327 if (::dladdr(reinterpret_cast<const void *>(pc), &info) == 0 || info.dli_fbase == nullptr)
328 return false;
329
330 elf_image_info result{};
331 result.image_name =
332 (info.dli_fname != nullptr && info.dli_fname[0] != '\0') ? string_view(info.dli_fname) : string_view("");
333 result.load_base = reinterpret_cast<uintptr_t>(info.dli_fbase);
334
335 dl_find_size_state size_state{result.load_base};
336 ::dl_iterate_phdr(&dl_find_size_callback, &size_state);
337 if (size_state.found) {
338 result.image_size = size_state.size;
339 result.exidx_start = size_state.exidx_start;
340 result.exidx_end = size_state.exidx_end;
341 result.debug_frame_start = size_state.debug_frame_start;
342 result.debug_frame_end = size_state.debug_frame_end;
343 }
344
345 out_info = result;
346 return true;
347}
349
350} // namespace microfmt::detail
351
352namespace microfmt {
353
359
366
393
394 static bool enumerate(value_ref<const context_type>, span<elf_image_info> buffer, size_t &count) noexcept {
395 return detail::dl_enumerate_images(buffer, count);
396 }
397
398 static bool find_by_pc(value_ref<const context_type>, uintptr_t pc, elf_image_info &out_info) noexcept {
399 return detail::dl_find_by_pc(pc, out_info);
400 }
401};
402
403} // namespace microfmt
#define MICROFMT_API_CLASS
Definition compat.hpp:181
ELF image introspection, type-erased image enumeration, and fixed-capacity registries.
Definition bintime.hpp:22
RELOCO_BEGIN_UNSAFE_CODE int dl_find_size_callback(dl_phdr_info *info, size_t, void *data) noexcept
dl_iterate_phdr callback that records the mapped size (and, where available, the ....
Definition dl_elf_enumerator.hpp:292
RELOCO_BEGIN_UNSAFE_CODE int dl_enumerate_callback(dl_phdr_info *info, size_t, void *data) noexcept
dl_iterate_phdr callback that fills dl_enumerate_state::buffer with one elf_image_info per loaded ima...
Definition dl_elf_enumerator.hpp:229
bool dl_phdr_image_bounds(const dl_phdr_info &info, uintptr_t &out_base, uintptr_t &out_size) noexcept
Computes the runtime [base, base + size) range covered by an image's PT_LOAD segments.
Definition dl_elf_enumerator.hpp:33
RELOCO_END_UNSAFE_CODE RELOCO_BEGIN_UNSAFE_CODE bool dl_find_by_pc(uintptr_t pc, elf_image_info &out_info) noexcept
Resolves the image owning pc via dladdr, then cross-references dl_iterate_phdr to fill in the image's...
Definition dl_elf_enumerator.hpp:324
RELOCO_END_UNSAFE_CODE bool dl_enumerate_images(span< elf_image_info > out_buffer, size_t &out_count) noexcept
Fills out_buffer with every image currently reported by dl_iterate_phdr, up to its capacity.
Definition dl_elf_enumerator.hpp:262
void info(microfmt::string_view fmt, const Args &...args) noexcept
Definition logger.hpp:347
Definition assert.hpp:19
constexpr semver version(uint16_t major, uint16_t minor, uint16_t patch, microfmt::string_view prerelease="", microfmt::string_view build="", bool show_v=false) noexcept
Definition semver.hpp:34
RELOCO_CONSTEXPR20 buffer_sink< N > format(microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1786
constexpr bytes_view bytes(uint64_t count) noexcept
Definition format_helpers.hpp:82
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Mutable state threaded through dl_enumerate_callback.
Definition dl_elf_enumerator.hpp:217
span< elf_image_info > buffer
Definition dl_elf_enumerator.hpp:218
State used by dl_find_size_callback to locate the PT_LOAD (and, where available, PT_ARM_EXIDX/PT_GNU_...
Definition dl_elf_enumerator.hpp:274
uintptr_t size
Definition dl_elf_enumerator.hpp:276
Empty context for dl_elf_enumerator_tag: dladdr and dl_iterate_phdr query the dynamic linker directly...
Definition dl_elf_enumerator.hpp:365
Tag selecting the dlfcn.h/dl_iterate_phdr-backed elf_image_enumerator_traits specialization.
Definition dl_elf_enumerator.hpp:358
static bool enumerate(value_ref< const context_type >, span< elf_image_info > buffer, size_t &count) noexcept
Definition dl_elf_enumerator.hpp:394
static bool find_by_pc(value_ref< const context_type >, uintptr_t pc, elf_image_info &out_info) noexcept
Definition dl_elf_enumerator.hpp:398
Definition elf_enumerator.hpp:19
Describes a loaded ELF image and the locations of its unwind tables.
Definition elf_enumerator.hpp:30
microfmt::string_view image_name
Human-readable image/module name.
Definition elf_enumerator.hpp:34