Skip to main content

c/
dl_extra.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// dladdr / dl_iterate_phdr / dlinfo.
5
6#[repr(C)]
7pub struct DlInfo {
8    pub dli_fname: *const c_char,
9    pub dli_fbase: *mut c_void,
10    pub dli_sname: *const c_char,
11    pub dli_saddr: *mut c_void,
12}
13
14#[repr(C)]
15pub struct DlPhdrInfo {
16    pub dlpi_addr: u64,
17    pub dlpi_name: *const c_char,
18    pub dlpi_phdr: *const u8,
19    pub dlpi_phnum: u16,
20    __pad0: u16,
21    __pad1: u32,
22    pub dlpi_adds: u64,
23    pub dlpi_subs: u64,
24    pub dlpi_tls_modid: usize,
25    pub dlpi_tls_data: *mut c_void,
26}
27
28type DlIterateCb = unsafe extern "C" fn(*mut DlPhdrInfo, usize, *mut c_void) -> c_int;
29type LdsoIterateFn = unsafe extern "C" fn(Option<DlIterateCb>, *mut c_void) -> c_int;
30type LdsoDlinfoFn = unsafe extern "C" fn(*mut c_void, c_int, *mut c_void) -> c_int;
31
32static mut LDSO_ITERATE: Option<LdsoIterateFn> = None;
33static mut LDSO_DLINFO: Option<LdsoDlinfoFn> = None;
34
35#[no_mangle]
36pub unsafe extern "C" fn __ldso_register_dl_iterate(f: LdsoIterateFn) {
37    LDSO_ITERATE = Some(f);
38}
39
40#[no_mangle]
41pub unsafe extern "C" fn __ldso_register_dlinfo(f: LdsoDlinfoFn) {
42    LDSO_DLINFO = Some(f);
43}
44
45// ------------------------------------------------------------------
46// /proc/self/maps
47// ------------------------------------------------------------------
48
49static mut MAPS_PATH: [c_char; PATH_MAX_V] = [0; PATH_MAX_V];
50static mut MAPS_LINE: *mut c_char = core::ptr::null_mut();
51static mut MAPS_LINE_CAP: usize = 0;
52
53unsafe fn maps_parse_hex(p: *const u8) -> (u64, *const u8) {
54    let mut v: u64 = 0;
55    let mut q = p;
56    loop {
57        let c = *q;
58        let d = if c >= b'0' && c <= b'9' {
59            (c - b'0') as u64
60        } else if c >= b'a' && c <= b'f' {
61            (c - b'a' + 10) as u64
62        } else if c >= b'A' && c <= b'F' {
63            (c - b'A' + 10) as u64
64        } else {
65            break;
66        };
67        v = (v << 4) | d;
68        q = q.add(1);
69    }
70    (v, q)
71}
72
73/// Find the mapping containing `addr`: (base, path). The path is copied into
74/// MAPS_PATH and must be consumed before the next call.
75unsafe fn maps_lookup(addr: usize) -> (u64, u64, *const c_char, bool) {
76    MAPS_PATH[0] = 0;
77    let f = crate::fopen(b"/proc/self/maps\0".as_ptr() as *const c_char, b"r\0".as_ptr() as *const c_char);
78    if f.is_null() { return (0, 0, MAPS_PATH.as_ptr(), false); }
79    let mut start_out = 0u64;
80    let mut off_out = 0u64;
81    let mut found = false;
82    while crate::getline(&raw mut MAPS_LINE, &raw mut MAPS_LINE_CAP, f) >= 0 {
83        let p = MAPS_LINE as *const u8;
84        let (start, after) = maps_parse_hex(p);
85        if *after != b'-' { continue; }
86        let (end, after2) = maps_parse_hex(after.add(1));
87        if (addr as u64) < start || (addr as u64) >= end { continue; }
88        // Field order after the range: perms offset dev inode path.
89        let mut q = after2;
90        while *q == b' ' { q = q.add(1); }
91        while *q != 0 && *q != b' ' { q = q.add(1); }
92        while *q == b' ' { q = q.add(1); }
93        let (off, after_off) = maps_parse_hex(q);
94        start_out = start;
95        off_out = off;
96        let mut q2 = after_off;
97        for _ in 0..2 {
98            while *q2 == b' ' { q2 = q2.add(1); }
99            while *q2 != 0 && *q2 != b' ' { q2 = q2.add(1); }
100        }
101        while *q2 == b' ' { q2 = q2.add(1); }
102        if *q2 != 0 {
103            let mut i = 0usize;
104            while *q2 != 0 && *q2 != b'\n' && i + 1 < MAPS_PATH.len() {
105                MAPS_PATH[i] = *q2 as c_char;
106                i += 1;
107                q2 = q2.add(1);
108            }
109            MAPS_PATH[i] = 0;
110        }
111        found = true;
112        break;
113    }
114    crate::fclose(f);
115    (start_out, off_out, MAPS_PATH.as_ptr(), found)
116}
117
118/// Compute the load bias for a mapping given the mapping start and file offset.
119/// For a PT_LOAD segment, start = bias + p_vaddr - adj and off = p_offset - adj,
120/// so bias = (start - off) - (p_vaddr - p_offset).
121unsafe fn elf_bias_for(path: *const c_char, start: u64, off: u64) -> u64 {
122    let fd = crate::open(path, 0, 0);
123    if fd < 0 { return start.wrapping_sub(off); }
124    let mut eh = [0u8; 64];
125    if crate::pread(fd, eh.as_mut_ptr() as *mut c_void, 64, 0) != 64 || eh[0] != 0x7f {
126        crate::close(fd);
127        return start.wrapping_sub(off);
128    }
129    let e_phoff = rd64(eh.as_ptr().add(0x20));
130    let e_phentsize = rd16(eh.as_ptr().add(0x36)) as usize;
131    let e_phnum = rd16(eh.as_ptr().add(0x38)) as usize;
132    if e_phentsize != 56 || e_phnum == 0 || e_phnum > 512 {
133        crate::close(fd);
134        return start.wrapping_sub(off);
135    }
136    let total = e_phnum * e_phentsize;
137    let ph = crate::malloc(total) as *mut u8;
138    if ph.is_null() {
139        crate::close(fd);
140        return start.wrapping_sub(off);
141    }
142    let got = crate::pread(fd, ph as *mut c_void, total, e_phoff as i64);
143    crate::close(fd);
144    if got != total as isize {
145        crate::free(ph as *mut c_void);
146        return start.wrapping_sub(off);
147    }
148    let mut bias = start.wrapping_sub(off);
149    for i in 0..e_phnum {
150        let p = ph.add(i * e_phentsize);
151        let p_type = rd32(p);
152        if p_type != 1 { continue; }
153        let p_offset = rd64(p.add(8));
154        let p_vaddr = rd64(p.add(0x10));
155        // The loader maps each segment at file offset p_offset - (p_vaddr&0xfff).
156        let map_off = p_offset.wrapping_sub(p_vaddr & 0xfff);
157        if off == map_off {
158            bias = start.wrapping_sub(off).wrapping_sub(p_vaddr.wrapping_sub(p_offset));
159            break;
160        }
161    }
162    crate::free(ph as *mut c_void);
163    bias
164}
165
166// ------------------------------------------------------------------
167// ELF symbol lookup (for dladdr)
168// ------------------------------------------------------------------
169
170static mut DLADDR_NAME: [c_char; PATH_MAX_V] = [0; PATH_MAX_V];
171
172unsafe fn rd16(p: *const u8) -> u16 { core::ptr::read_unaligned(p as *const u16) }
173unsafe fn rd32(p: *const u8) -> u32 { core::ptr::read_unaligned(p as *const u32) }
174unsafe fn rd64(p: *const u8) -> u64 { core::ptr::read_unaligned(p as *const u64) }
175
176unsafe fn elf_symbol_lookup(path: *const c_char, addr: u64) -> (bool, u64) {
177    let fd = crate::open(path, 0, 0);
178    if fd < 0 { return (false, 0); }
179    let mut eh = [0u8; 64];
180    if crate::pread(fd, eh.as_mut_ptr() as *mut c_void, 64, 0) != 64 {
181        crate::close(fd);
182        return (false, 0);
183    }
184    if eh[0] != 0x7f || eh[1] != b'E' || eh[2] != b'L' || eh[3] != b'F' {
185        crate::close(fd);
186        return (false, 0);
187    }
188    let e_shoff = rd64(eh.as_ptr().add(0x28));
189    let e_shentsize = rd16(eh.as_ptr().add(0x3a)) as usize;
190    let e_shnum = rd16(eh.as_ptr().add(0x3c)) as usize;
191    if e_shoff == 0 || e_shentsize < 64 || e_shnum == 0 || e_shnum > 4096 {
192        crate::close(fd);
193        return (false, 0);
194    }
195    let shtotal = e_shnum * e_shentsize;
196    let sh = crate::malloc(shtotal) as *mut u8;
197    if sh.is_null() { crate::close(fd); return (false, 0); }
198    if crate::pread(fd, sh as *mut c_void, shtotal, e_shoff as i64) != shtotal as isize {
199        crate::free(sh as *mut c_void);
200        crate::close(fd);
201        return (false, 0);
202    }
203    let mut best: u64 = 0;
204    let mut have = false;
205    for i in 0..e_shnum {
206        let sec = sh.add(i * e_shentsize);
207        let sh_type = rd32(sec.add(4));
208        if sh_type != 2 && sh_type != 11 { continue; }
209        let sh_offset = rd64(sec.add(0x18));
210        let sh_size = rd64(sec.add(0x20)) as usize;
211        let sh_link = rd32(sec.add(0x28)) as usize;
212        let sh_entsize = rd64(sec.add(0x38)) as usize;
213        if sh_size == 0 || sh_entsize < 24 || sh_link >= e_shnum { continue; }
214        let syms = crate::malloc(sh_size) as *mut u8;
215        if syms.is_null() { continue; }
216        let got = crate::pread(fd, syms as *mut c_void, sh_size, sh_offset as i64);
217        if got != sh_size as isize {
218            crate::free(syms as *mut c_void);
219            continue;
220        }
221        let strsec = sh.add(sh_link * e_shentsize);
222        let str_off = rd64(strsec.add(0x18));
223        let str_size = rd64(strsec.add(0x20)) as usize;
224        if str_size == 0 || str_size > (1 << 24) {
225            crate::free(syms as *mut c_void);
226            continue;
227        }
228        let strs = crate::malloc(str_size) as *mut u8;
229        if strs.is_null() {
230            crate::free(syms as *mut c_void);
231            continue;
232        }
233        if crate::pread(fd, strs as *mut c_void, str_size, str_off as i64) != str_size as isize {
234            crate::free(strs as *mut c_void);
235            crate::free(syms as *mut c_void);
236            continue;
237        }
238        let count = sh_size / sh_entsize;
239        for j in 0..count {
240            let sym = syms.add(j * sh_entsize);
241            let st_name = rd32(sym) as usize;
242            let st_value = rd64(sym.add(8));
243            let st_size = rd64(sym.add(16));
244            let st_shndx = rd16(sym.add(6));
245            if st_name == 0 || st_name >= str_size || st_shndx == 0 { continue; }
246            if st_value > addr { continue; }
247            if st_size != 0 && addr >= st_value + st_size { continue; }
248            if !have || st_value > best {
249                best = st_value;
250                have = true;
251                let s = strs.add(st_name);
252                let mut k = 0usize;
253                while *s.add(k) != 0 && k + 1 < DLADDR_NAME.len() {
254                    DLADDR_NAME[k] = *s.add(k) as c_char;
255                    k += 1;
256                }
257                DLADDR_NAME[k] = 0;
258            }
259        }
260        crate::free(strs as *mut c_void);
261        crate::free(syms as *mut c_void);
262    }
263    crate::free(sh as *mut c_void);
264    crate::close(fd);
265    (have, best)
266}
267
268#[no_mangle]
269pub unsafe extern "C" fn dladdr(addr: *const c_void, info: *mut DlInfo) -> c_int {
270    if info.is_null() { ERRNO!() = EINVAL; return 0; }
271    (*info).dli_fname = core::ptr::null();
272    (*info).dli_fbase = core::ptr::null_mut();
273    (*info).dli_sname = core::ptr::null();
274    (*info).dli_saddr = core::ptr::null_mut();
275    if addr.is_null() { return 0; }
276    let (start, off, path, found) = maps_lookup(addr as usize);
277    if !found { return 0; }
278    let base = if MAPS_PATH[0] != 0 && MAPS_PATH[0] != b'[' as c_char {
279        elf_bias_for(path, start, off)
280    } else {
281        start.wrapping_sub(off)
282    };
283    (*info).dli_fname = path;
284    (*info).dli_fbase = base as *mut c_void;
285    if MAPS_PATH[0] == 0 || MAPS_PATH[0] == b'[' as c_char { return 1; }
286    let off = addr as u64 - base;
287    let (have, value) = elf_symbol_lookup(path, off);
288    if have {
289        (*info).dli_sname = DLADDR_NAME.as_ptr();
290        (*info).dli_saddr = (base + value) as *mut c_void;
291    }
292    1
293}
294
295// ------------------------------------------------------------------
296// dl_iterate_phdr
297// ------------------------------------------------------------------
298
299static mut EXE_PATH: [c_char; PATH_MAX_V] = [0; PATH_MAX_V];
300
301unsafe fn read_exe_path() -> *const c_char {
302    let n = crate::readlink(
303        b"/proc/self/exe\0".as_ptr() as *const c_char,
304        EXE_PATH.as_mut_ptr(),
305        EXE_PATH.len() - 1,
306    );
307    if n < 0 {
308        EXE_PATH[0] = 0;
309    } else {
310        EXE_PATH[n as usize] = 0;
311    }
312    EXE_PATH.as_ptr()
313}
314
315/// Returns (load bias, phdr pointer, phnum) for the main executable.
316unsafe fn main_program_info() -> (u64, *const u8, u16) {
317    let exe = read_exe_path();
318    if *exe != 0 {
319        let f = crate::fopen(b"/proc/self/maps\0".as_ptr() as *const c_char, b"r\0".as_ptr() as *const c_char);
320        if !f.is_null() {
321            while crate::getline(&raw mut MAPS_LINE, &raw mut MAPS_LINE_CAP, f) >= 0 {
322                let p = MAPS_LINE as *const u8;
323                let (start, after) = maps_parse_hex(p);
324                if *after != b'-' { continue; }
325                let (_end, after2) = maps_parse_hex(after.add(1));
326                let mut q = after2;
327                while *q == b' ' { q = q.add(1); }
328                while *q != 0 && *q != b' ' { q = q.add(1); }
329                while *q == b' ' { q = q.add(1); }
330                let (off, after_off) = maps_parse_hex(q);
331                if off != 0 { continue; }
332                let mut q2 = after_off;
333                for _ in 0..2 {
334                    while *q2 == b' ' { q2 = q2.add(1); }
335                    while *q2 != 0 && *q2 != b' ' { q2 = q2.add(1); }
336                }
337                while *q2 == b' ' { q2 = q2.add(1); }
338                if *q2 == 0 { continue; }
339                let mut path = [0 as c_char; PATH_MAX_V];
340                let mut i = 0usize;
341                while *q2 != 0 && *q2 != b'\n' && i + 1 < path.len() {
342                    path[i] = *q2 as c_char;
343                    i += 1;
344                    q2 = q2.add(1);
345                }
346                path[i] = 0;
347                if crate::strcmp(path.as_ptr() as *const u8, exe as *const u8) != 0 { continue; }
348                let base = start;
349                let eh = base as *const u8;
350                if *eh == 0x7f && *eh.add(1) == b'E' && *eh.add(2) == b'L' && *eh.add(3) == b'F' {
351                    let e_phoff = rd64(eh.add(32));
352                    let e_phentsize = rd16(eh.add(54));
353                    let e_phnum = rd16(eh.add(56));
354                    if e_phentsize as usize == 56 {
355                        crate::fclose(f);
356                        return (base, (base + e_phoff) as *const u8, e_phnum);
357                    }
358                }
359                crate::fclose(f);
360                return (base, core::ptr::null(), 0);
361            }
362            crate::fclose(f);
363        }
364    }
365    let phdr = crate::getauxval(3);
366    let phnum = crate::getauxval(5) as u16;
367    if phdr != 0 && phnum != 0 {
368        let (start, off, path, _f) = maps_lookup(phdr as usize);
369        let base = elf_bias_for(path, start, off);
370        return (base, phdr as *const u8, phnum);
371    }
372    (0, core::ptr::null(), 0)
373}
374
375#[no_mangle]
376pub unsafe extern "C" fn dl_iterate_phdr(
377    callback: Option<unsafe extern "C" fn(*mut DlPhdrInfo, usize, *mut c_void) -> c_int>,
378    data: *mut c_void,
379) -> c_int {
380    let cb = match callback { Some(c) => c, None => return 0 };
381    let (base, phdr, phnum) = main_program_info();
382    if !phdr.is_null() && phnum != 0 {
383        let info = DlPhdrInfo {
384            dlpi_addr: base,
385            dlpi_name: b"\0".as_ptr() as *const c_char,
386            dlpi_phdr: phdr,
387            dlpi_phnum: phnum,
388            __pad0: 0,
389            __pad1: 0,
390            dlpi_adds: 0,
391            dlpi_subs: 0,
392            dlpi_tls_modid: 0,
393            dlpi_tls_data: core::ptr::null_mut(),
394        };
395        let r = cb(&info as *const DlPhdrInfo as *mut DlPhdrInfo, core::mem::size_of::<DlPhdrInfo>(), data);
396        if r != 0 { return r; }
397    }
398    if let Some(f) = LDSO_ITERATE {
399        return f(Some(cb), data);
400    }
401    0
402}
403
404#[no_mangle]
405pub unsafe extern "C" fn dlinfo(handle: *mut c_void, request: c_int, info: *mut c_void) -> c_int {
406    if let Some(f) = LDSO_DLINFO {
407        return f(handle, request, info);
408    }
409    ERRNO!() = 95; // ENOTSUP
410    -1
411}