buildid/
elf.rs

1use crate::align::align_up;
2use core::mem;
3use core::mem::MaybeUninit;
4use core::{convert::TryInto, fmt};
5use log::{debug, error, warn};
6
7// FIXME: dl_phdr_info references are actually unsafe here because of how glibc defines
8// dl_phdr_info (to have some fields present depending on the size provided (<glibc-2.4 omits
9// them).
10//
11// We'll probably get away with this because we're not accessing those fields, but this is
12// technically wrong because we can access those fields in safe code.
13//
14
15#[cfg(target_pointer_width = "64")]
16mod elf {
17    pub type ElfPhdr = libc::Elf64_Phdr;
18}
19#[cfg(target_pointer_width = "32")]
20mod elf {
21    pub type ElfPhdr = libc::Elf32_Phdr;
22}
23use elf::*;
24
25const NT_GNU_BUILD_ID: u32 = 3;
26
27// TODO: experiment with using more custom DST here to avoid manually extracting fields
28#[derive(Debug)]
29struct Note {
30    data: [u8],
31}
32
33const MIN_NOTE_SIZE: usize = mem::size_of::<usize>() * 3;
34
35#[derive(Debug)]
36enum NoteError {
37    MissingHeader { size: usize },
38    Truncated { have: usize, need: usize },
39}
40
41impl fmt::Display for NoteError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::MissingHeader { size } => write!(
45                f,
46                "have {} bytes, but need at least {}",
47                size, MIN_NOTE_SIZE
48            ),
49            Self::Truncated { have, need } => {
50                write!(f, "have {} bytes, but need at least {}", have, need)
51            }
52        }
53    }
54}
55
56impl Note {
57    // NOTE: the _standards_ say to use 8 byte alignment in 64-bit land. But llvm and others note
58    // that everyone actually uses 4 byte alignment. Perfect. Hopefully this always works.
59    const ALIGN: usize = 4;
60
61    // technically safe because we'll only panic if I screw up
62    fn from_bytes_raw(data: &[u8]) -> &Self {
63        unsafe { &*(data as *const [u8] as *const Note) }
64    }
65
66    fn from_bytes(data: &[u8]) -> Result<(&Self, &[u8]), NoteError> {
67        let u = core::mem::size_of::<u32>();
68        if data.len() < u * 3 {
69            return Err(NoteError::MissingHeader { size: data.len() });
70        }
71
72        Self::from_bytes_raw(data).split_trailing()
73    }
74
75    fn name_len(&self) -> usize {
76        u32::from_ne_bytes(self.data[..core::mem::size_of::<u32>()].try_into().unwrap()) as usize
77    }
78
79    fn desc_len(&self) -> usize {
80        let u = core::mem::size_of::<u32>();
81        u32::from_ne_bytes(self.data[u..(u + u)].try_into().unwrap()) as usize
82    }
83
84    fn type_(&self) -> u32 {
85        let u = core::mem::size_of::<u32>();
86        u32::from_ne_bytes(self.data[(u + u)..(u + u + u)].try_into().unwrap())
87    }
88
89    fn name(&self) -> &[u8] {
90        let u = core::mem::size_of::<u32>();
91        let b = u * 3;
92        &self.data[b..(b + self.name_len())]
93    }
94
95    fn desc(&self) -> &[u8] {
96        let u = core::mem::size_of::<u32>();
97        let b = u * 3 + align_up(self.name_len(), Self::ALIGN);
98        &self.data[b..(b + self.desc_len())]
99    }
100
101    fn split_trailing(&self) -> Result<(&Self, &[u8]), NoteError> {
102        let u = core::mem::size_of::<u32>();
103        let end =
104            u * 3 + align_up(self.name_len(), Self::ALIGN) + align_up(self.desc_len(), Self::ALIGN);
105        if end > self.data.len() {
106            Err(NoteError::Truncated {
107                need: end,
108                have: self.data.len(),
109            })
110        } else {
111            Ok((Self::from_bytes_raw(&self.data[0..end]), &self.data[end..]))
112        }
113    }
114}
115
116// Ideally, we'd use a trait alias instead of a type alias and construct the type out of the
117// trait. But that's not stable right now (see https://github.com/rust-lang/rust/issues/41517)
118unsafe extern "C" fn phdr_cb(
119    info: *mut libc::dl_phdr_info,
120    size: libc::size_t,
121    data: *mut libc::c_void,
122) -> libc::c_int {
123    let closure: &mut &mut dyn FnMut(&libc::dl_phdr_info, usize) -> libc::c_int = &mut *(data
124        as *mut &mut dyn for<'r> core::ops::FnMut(&'r libc::dl_phdr_info, usize) -> i32);
125    let info = &*info;
126
127    closure(info, size)
128}
129
130fn object_map<F: FnMut(&'static libc::dl_phdr_info, usize) -> libc::c_int>(
131    mut cb: F,
132) -> libc::c_int {
133    let mut cb: &mut dyn FnMut(&'static libc::dl_phdr_info, usize) -> libc::c_int = &mut cb;
134    let cb = &mut cb;
135    unsafe { libc::dl_iterate_phdr(Some(phdr_cb), cb as *mut _ as *mut _) }
136}
137
138/// Iterate over program headers described by a dl_phdr_info
139struct PhdrIter<'a> {
140    info: &'a libc::dl_phdr_info,
141    i: u16,
142}
143
144impl<'a> Iterator for PhdrIter<'a> {
145    type Item = &'a libc::Elf64_Phdr;
146
147    fn next(&mut self) -> Option<Self::Item> {
148        if self.i >= self.info.dlpi_phnum {
149            return None;
150        }
151
152        let phdr = unsafe { &*self.info.dlpi_phdr.add(self.i as usize) };
153        self.i += 1;
154        Some(phdr)
155    }
156}
157
158impl<'a> From<&'a libc::dl_phdr_info> for PhdrIter<'a> {
159    fn from(info: &'a libc::dl_phdr_info) -> Self {
160        PhdrIter { info, i: 0 }
161    }
162}
163
164/// Iterate over notes stored in a PT_NOTE program section
165#[derive(Debug)]
166struct NoteIter<'a> {
167    segment: &'a [u8],
168}
169
170impl<'a> NoteIter<'a> {
171    fn new(info: &'a libc::dl_phdr_info, phdr: &'a ElfPhdr) -> Option<Self> {
172        // NOTE: each dl_phdr_info describes multiple program segments. In this iterator, we're
173        // only examining one of them.
174        //
175        // We'll also need to iterate over program segments to pick out the PT_NOTE one we need.
176
177        if phdr.p_type != libc::PT_NOTE {
178            None
179        } else {
180            let segment_base = (info.dlpi_addr + phdr.p_vaddr) as *const u8;
181            let segment = unsafe {
182                // FIXME: consider p_memsz vs p_filesz question here.
183                // llvm appears to use filesz
184                core::slice::from_raw_parts(segment_base, phdr.p_filesz as usize)
185            };
186            Some(NoteIter { segment })
187        }
188    }
189}
190
191impl<'a> Iterator for NoteIter<'a> {
192    type Item = Result<&'a Note, NoteError>;
193    fn next(&mut self) -> Option<Self::Item> {
194        if self.segment.is_empty() {
195            return None;
196        }
197
198        let (n, r) = match Note::from_bytes(self.segment) {
199            Err(e) => return Some(Err(e)),
200            Ok(v) => v,
201        };
202
203        self.segment = r;
204
205        Some(Ok(n))
206    }
207}
208
209pub fn build_id() -> Option<&'static [u8]> {
210    // find the shared object that contains our own `build_id()` fn (this fn itself)
211    let data = {
212        let mut data = MaybeUninit::uninit();
213        let addr = build_id as *const libc::c_void;
214        if unsafe { libc::dladdr(addr, data.as_mut_ptr()) } == 0 {
215            // TODO: consider if we have fallback options here
216            error!("dladdr failed to find our own symbol");
217            return None;
218        }
219
220        unsafe { data.assume_init() }
221    };
222
223    let mut res = None;
224    // FIXME: we probably should avoid ignoring `size` here so we can bounds check our
225    // accesses. Basically need to treat this data as a big array we happen to have pointers
226    // into, and convert those pointers to offsets.
227    object_map(|info, _size| {
228        let mut map_start = None;
229
230        for phdr in PhdrIter::from(info) {
231            // FIXME: unclear why the first PT_LOAD is the right thing to use here
232            if phdr.p_type == libc::PT_LOAD {
233                map_start = Some(info.dlpi_addr + phdr.p_vaddr);
234                break;
235            }
236        }
237
238        let map_start = match map_start {
239            Some(v) => v,
240            None => {
241                debug!(
242                    "no PT_LOAD segment found in object {:?}, skipping",
243                    info.dlpi_name
244                );
245                return 0;
246            }
247        };
248
249        // check if this phdr (map_start) is the one that contains our fn (data.dli_fbase)
250        if map_start != data.dli_fbase as u64 {
251            debug!(
252                "map_start ({:?}) != data.dli_fbase ({:?}), skipping",
253                map_start, data.dli_fbase
254            );
255            return 0;
256        }
257
258        'phdr: for phdr in PhdrIter::from(info) {
259            let ni = match NoteIter::new(info, phdr) {
260                Some(v) => v,
261                None => continue,
262            };
263
264            // iterate over notes
265            for note in ni {
266                let note = match note {
267                    Err(e) => {
268                        warn!("note program segment had invalid note {}", e);
269                        continue 'phdr;
270                    }
271                    Ok(v) => v,
272                };
273                if note.type_() == NT_GNU_BUILD_ID
274                    && !note.desc().is_empty()
275                    && note.name() == b"GNU\0"
276                {
277                    res = Some(note.desc());
278                    break 'phdr;
279                }
280            }
281        }
282
283        0
284    });
285
286    res
287}