Skip to main content

elf_search/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use elf_parse::{Elf64FileHeader, Elf64ProgramHeader};
6use std::borrow::Cow;
7use std::ffi::{c_char, c_void};
8
9mod sys;
10use sys::{Elf64_Ehdr, Elf64_Phdr, elf_search_wrapper};
11
12#[derive(Debug)]
13pub struct ModuleInfo<'a> {
14    pub name: Cow<'a, str>,
15    pub vaddr: usize,
16    pub build_id: &'a [u8],
17    pub ehdr: &'a Elf64FileHeader,
18    pub phdrs: &'a [Elf64ProgramHeader],
19}
20
21pub fn for_each_module<F: FnMut(&ModuleInfo<'_>)>(
22    process: &zx::Process,
23    mut func: F,
24) -> Result<(), zx::Status> {
25    // SAFETY: the callback function will interpret the func pointer as F.
26    let status = unsafe {
27        elf_search_wrapper(
28            process.raw_handle(),
29            Some(callback::<F>),
30            &mut func as *mut F as *mut c_void,
31        )
32    };
33    zx::Status::ok(status)
34}
35
36unsafe extern "C" fn callback<F: FnMut(&ModuleInfo<'_>)>(
37    name: *const c_char,
38    name_len: usize,
39    vaddr: u64,
40    build_id: *const u8,
41    build_id_len: usize,
42    ehdr: *const Elf64_Ehdr,
43    phdrs: *const Elf64_Phdr,
44    phdrs_len: usize,
45    arg: *mut c_void,
46) {
47    // SAFETY: all the following pointers are guaranteed to be valid by the caller.
48    let name = unsafe { std::slice::from_raw_parts(name as *const u8, name_len) };
49    let build_id = unsafe { std::slice::from_raw_parts(build_id, build_id_len) };
50    let ehdr = unsafe { &*ehdr };
51    let phdrs = unsafe { std::slice::from_raw_parts(phdrs, phdrs_len) };
52
53    // Convert ehdr and phdrs into the corresponding Rust types from the elf_parse library.
54    let ehdr: &Elf64FileHeader = zerocopy::transmute_ref!(ehdr);
55    let phdrs: &[Elf64ProgramHeader] = zerocopy::transmute_ref!(phdrs);
56
57    let info = ModuleInfo {
58        name: String::from_utf8_lossy(name),
59        vaddr: vaddr as usize,
60        build_id,
61        ehdr,
62        phdrs,
63    };
64
65    // SAFETY: for_each_module guarantees that arg is a valid pointer to F.
66    let func = unsafe { &mut *(arg as *mut F) };
67    func(&info);
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use elf_parse::{SegmentFlags, SegmentType};
74
75    #[test]
76    fn test_find_executable_regions() {
77        // Enumerate all executable regions in the current process.
78        let mut regions = Vec::new();
79        for_each_module(&fuchsia_runtime::process_self(), |info| {
80            for phdr in info.phdrs {
81                if phdr.segment_type == SegmentType::Load as u32
82                    && (phdr.flags & SegmentFlags::EXECUTE.bits()) != 0
83                {
84                    let start = info.vaddr + phdr.vaddr as usize;
85                    let size = phdr.memsz as usize;
86                    regions.push((start, size));
87                }
88            }
89        })
90        .unwrap();
91
92        // Locate the address of this test function within the regions.
93        let test_address = test_find_executable_regions as *const () as usize;
94        let count = regions
95            .iter()
96            .filter(|(start, size)| (*start..*start + *size).contains(&test_address))
97            .count();
98
99        assert_eq!(
100            count, 1,
101            "test address {:x} not covered exactly once by {:?}",
102            test_address, regions
103        );
104    }
105}