findshlibs/linux/
mod.rs

1//! Linux-specific implementation of the `SharedLibrary` trait.
2
3use libc;
4
5use crate::Segment as SegmentTrait;
6use crate::SharedLibrary as SharedLibraryTrait;
7use crate::{Bias, IterationControl, SharedLibraryId, Svma};
8
9use std::any::Any;
10use std::borrow::Cow;
11use std::env::current_exe;
12use std::ffi::{CStr, CString, OsStr};
13use std::fmt;
14use std::iter;
15use std::marker::PhantomData;
16use std::mem;
17use std::os::unix::ffi::OsStrExt;
18use std::os::unix::ffi::OsStringExt;
19use std::panic;
20use std::slice;
21use std::usize;
22
23#[cfg(target_pointer_width = "32")]
24type Phdr = libc::Elf32_Phdr;
25
26#[cfg(target_pointer_width = "64")]
27type Phdr = libc::Elf64_Phdr;
28
29const NT_GNU_BUILD_ID: u32 = 3;
30
31// Normally we would use `Elf32_Nhdr` on 32-bit platforms and `Elf64_Nhdr` on
32// 64-bit platforms. However, in practice it seems that only `Elf32_Nhdr` is
33// used, and reading through binutil's `readelf` source confirms this.
34#[repr(C)]
35#[derive(Debug, Clone, Copy)]
36struct Nhdr {
37    pub n_namesz: libc::Elf32_Word,
38    pub n_descsz: libc::Elf32_Word,
39    pub n_type: libc::Elf32_Word,
40}
41
42/// A mapped segment in an ELF file.
43#[derive(Debug)]
44pub struct Segment<'a> {
45    phdr: *const Phdr,
46    shlib: PhantomData<&'a SharedLibrary<'a>>,
47}
48
49impl<'a> Segment<'a> {
50    fn phdr(&self) -> &'a Phdr {
51        unsafe { self.phdr.as_ref().unwrap() }
52    }
53
54    /// You must pass this segment's `SharedLibrary` or else this is wild UB.
55    unsafe fn data(&self, shlib: &SharedLibrary<'a>) -> &'a [u8] {
56        let phdr = self.phdr();
57        let avma = (shlib.addr as usize).wrapping_add(phdr.p_vaddr as usize);
58        slice::from_raw_parts(avma as *const u8, phdr.p_memsz as usize)
59    }
60
61    fn is_note(&self) -> bool {
62        self.phdr().p_type == libc::PT_NOTE
63    }
64
65    /// Parse the contents of a `PT_NOTE` segment.
66    ///
67    /// Returns a triple of
68    ///
69    /// 1. The `NT_*` note type.
70    /// 2. The note name.
71    /// 3. The note descriptor payload.
72    ///
73    /// You must pass this segment's `SharedLibrary` or else this is wild UB.
74    unsafe fn notes(
75        &self,
76        shlib: &SharedLibrary<'a>,
77    ) -> impl Iterator<Item = (libc::Elf32_Word, &'a [u8], &'a [u8])> {
78        // `man 5 readelf` says that all of the `Nhdr`, name, and descriptor are
79        // always 4-byte aligned, but we copy this alignment behavior from
80        // `readelf` since that seems to match reality in practice.
81        let alignment = std::cmp::max(self.phdr().p_align as usize, 4);
82        let align_up = move |data: &'a [u8]| {
83            if alignment != 4 && alignment != 8 {
84                return None;
85            }
86
87            let ptr = data.as_ptr() as usize;
88            let alignment_minus_one = alignment - 1;
89            let aligned_ptr = ptr.checked_add(alignment_minus_one)? & !alignment_minus_one;
90            let diff = aligned_ptr - ptr;
91            if data.len() < diff {
92                None
93            } else {
94                Some(&data[diff..])
95            }
96        };
97
98        let mut data = self.data(shlib);
99
100        iter::from_fn(move || {
101            if (data.as_ptr() as usize % alignment) != 0 {
102                return None;
103            }
104
105            // Each entry in a `PT_NOTE` segment begins with a
106            // fixed-size header `Nhdr`.
107            let nhdr_size = mem::size_of::<Nhdr>();
108            let nhdr = try_split_at(&mut data, nhdr_size)?;
109            let nhdr = (nhdr.as_ptr() as *const Nhdr).as_ref().unwrap();
110
111            // No need to `align_up` after the `Nhdr`
112            // It is followed by a name of size `n_namesz`.
113            let name_size = nhdr.n_namesz as usize;
114            let name = try_split_at(&mut data, name_size)?;
115
116            // And after that is the note's (aligned) descriptor payload of size
117            // `n_descsz`.
118            data = align_up(data)?;
119            let desc_size = nhdr.n_descsz as usize;
120            let desc = try_split_at(&mut data, desc_size)?;
121
122            // Align the data for the next `Nhdr`.
123            data = align_up(data)?;
124
125            Some((nhdr.n_type, name, desc))
126        })
127        .fuse()
128    }
129}
130
131fn try_split_at<'a>(data: &mut &'a [u8], index: usize) -> Option<&'a [u8]> {
132    if data.len() < index {
133        None
134    } else {
135        let (head, tail) = data.split_at(index);
136        *data = tail;
137        Some(head)
138    }
139}
140
141impl<'a> SegmentTrait for Segment<'a> {
142    type SharedLibrary = SharedLibrary<'a>;
143
144    fn name(&self) -> &str {
145        unsafe {
146            match self.phdr.as_ref().unwrap().p_type {
147                libc::PT_NULL => "NULL",
148                libc::PT_LOAD => "LOAD",
149                libc::PT_DYNAMIC => "DYNAMIC",
150                libc::PT_INTERP => "INTERP",
151                libc::PT_NOTE => "NOTE",
152                libc::PT_SHLIB => "SHLIB",
153                libc::PT_PHDR => "PHDR",
154                libc::PT_TLS => "TLS",
155                libc::PT_GNU_EH_FRAME => "GNU_EH_FRAME",
156                libc::PT_GNU_STACK => "GNU_STACK",
157                libc::PT_GNU_RELRO => "GNU_RELRO",
158                _ => "(unknown segment type)",
159            }
160        }
161    }
162
163    #[inline]
164    fn is_code(&self) -> bool {
165        let hdr = self.phdr();
166        // 0x1 is PT_X for executable
167        hdr.p_type == libc::PT_LOAD && (hdr.p_flags & 0x1) != 0
168    }
169
170    #[inline]
171    fn is_load(&self) -> bool {
172        self.phdr().p_type == libc::PT_LOAD
173    }
174
175    #[inline]
176    fn stated_virtual_memory_address(&self) -> Svma {
177        Svma(self.phdr().p_vaddr as _)
178    }
179
180    #[inline]
181    fn len(&self) -> usize {
182        self.phdr().p_memsz as _
183    }
184}
185
186/// An iterator of mapped segments in a shared library.
187pub struct SegmentIter<'a> {
188    inner: std::slice::Iter<'a, Phdr>,
189}
190
191impl<'a> Iterator for SegmentIter<'a> {
192    type Item = Segment<'a>;
193
194    #[inline]
195    fn next(&mut self) -> Option<Self::Item> {
196        self.inner.next().map(|phdr| Segment {
197            phdr: phdr,
198            shlib: PhantomData,
199        })
200    }
201}
202
203impl<'a> fmt::Debug for SegmentIter<'a> {
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        let ref phdr = self.inner.as_slice()[0];
206
207        f.debug_struct("SegmentIter")
208            .field("phdr", &DebugPhdr(phdr))
209            .finish()
210    }
211}
212
213/// A shared library on Linux.
214pub struct SharedLibrary<'a> {
215    size: usize,
216    addr: *const u8,
217    name: Cow<'a, CStr>,
218    headers: &'a [Phdr],
219}
220
221struct IterState<F> {
222    f: F,
223    panic: Option<Box<dyn Any + Send>>,
224    idx: usize,
225}
226
227const CONTINUE: libc::c_int = 0;
228const BREAK: libc::c_int = 1;
229
230impl<'a> SharedLibrary<'a> {
231    unsafe fn new(info: &'a libc::dl_phdr_info, size: usize, is_first_lib: bool) -> Self {
232        // try to get the name from the dl_phdr_info.  If that fails there are two
233        // cases we can and need to deal with.  The first one is if we are the first
234        // loaded library in which case the name is the executable which we can
235        // discover via env::current_exe (reads the proc/self symlink).
236        //
237        // Otherwise if we have a no name we might be a dylib that was loaded with
238        // dlopen in which case we can use dladdr to recover the name.
239        let mut name = Cow::Borrowed(if info.dlpi_name.is_null() {
240            CStr::from_bytes_with_nul_unchecked(b"\0")
241        } else {
242            CStr::from_ptr(info.dlpi_name)
243        });
244        if name.to_bytes().is_empty() {
245            if is_first_lib {
246                if let Ok(exe) = current_exe() {
247                    name = Cow::Owned(CString::from_vec_unchecked(exe.into_os_string().into_vec()));
248                }
249            } else {
250                let mut dlinfo: libc::Dl_info = mem::zeroed();
251                if libc::dladdr(info.dlpi_addr as *const libc::c_void, &mut dlinfo) != 0 {
252                    name = Cow::Owned(CString::from(CStr::from_ptr(dlinfo.dli_fname)));
253                }
254            }
255        }
256
257        SharedLibrary {
258            size: size,
259            addr: info.dlpi_addr as usize as *const _,
260            name,
261            headers: slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize),
262        }
263    }
264
265    unsafe extern "C" fn callback<F, C>(
266        info: *mut libc::dl_phdr_info,
267        size: usize,
268        state: *mut libc::c_void,
269    ) -> libc::c_int
270    where
271        F: FnMut(&Self) -> C,
272        C: Into<IterationControl>,
273    {
274        if (*info).dlpi_phdr.is_null() {
275            return CONTINUE;
276        }
277
278        let state = &mut *(state as *mut IterState<F>);
279        state.idx += 1;
280
281        match panic::catch_unwind(panic::AssertUnwindSafe(|| {
282            let info = info.as_ref().unwrap();
283            let shlib = SharedLibrary::new(info, size, state.idx == 1);
284
285            (state.f)(&shlib).into()
286        })) {
287            Ok(IterationControl::Continue) => CONTINUE,
288            Ok(IterationControl::Break) => BREAK,
289            Err(panicked) => {
290                state.panic = Some(panicked);
291                BREAK
292            }
293        }
294    }
295
296    fn note_segments(&self) -> impl Iterator<Item = Segment<'a>> {
297        self.segments().filter(|s| s.is_note())
298    }
299}
300
301impl<'a> SharedLibraryTrait for SharedLibrary<'a> {
302    type Segment = Segment<'a>;
303    type SegmentIter = SegmentIter<'a>;
304
305    #[inline]
306    fn name(&self) -> &OsStr {
307        OsStr::from_bytes(self.name.to_bytes())
308    }
309
310    fn id(&self) -> Option<SharedLibraryId> {
311        // Search for `PT_NOTE` segments, containing auxilliary information.
312        // Such segments contain a series of "notes" and one kind of note is
313        // `NT_GNU_BUILD_ID`, whose payload contains a unique identifier
314        // generated by the linker. Return the first one we find, if any.
315        for segment in self.note_segments() {
316            for (note_type, note_name, note_descriptor) in unsafe { segment.notes(self) } {
317                if note_type == NT_GNU_BUILD_ID && note_name == b"GNU\0" {
318                    return Some(SharedLibraryId::GnuBuildId(note_descriptor.to_vec()));
319                }
320            }
321        }
322
323        None
324    }
325
326    #[inline]
327    fn segments(&self) -> Self::SegmentIter {
328        SegmentIter {
329            inner: self.headers.iter(),
330        }
331    }
332
333    #[inline]
334    fn virtual_memory_bias(&self) -> Bias {
335        Bias(self.addr as usize)
336    }
337
338    #[inline]
339    fn each<F, C>(f: F)
340    where
341        F: FnMut(&Self) -> C,
342        C: Into<IterationControl>,
343    {
344        let mut state = IterState {
345            f: f,
346            panic: None,
347            idx: 0,
348        };
349
350        unsafe {
351            libc::dl_iterate_phdr(Some(Self::callback::<F, C>), &mut state as *mut _ as *mut _);
352        }
353
354        if let Some(panic) = state.panic {
355            panic::resume_unwind(panic);
356        }
357    }
358}
359
360impl<'a> fmt::Debug for SharedLibrary<'a> {
361    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
362        write!(
363            f,
364            "SharedLibrary {{ size: {:?}, addr: {:?}, ",
365            self.size, self.addr
366        )?;
367        write!(f, "name: {:?}, headers: [", self.name)?;
368
369        // Debug does not usually have a trailing comma in the list,
370        // last element must be formatted separately.
371        let l = self.headers.len();
372        self.headers[..(l - 1)]
373            .into_iter()
374            .map(|phdr| write!(f, "{:?}, ", &DebugPhdr(phdr)))
375            .collect::<fmt::Result>()?;
376
377        write!(f, "{:?}", &DebugPhdr(&self.headers[l - 1]))?;
378
379        write!(f, "] }}")
380    }
381}
382
383struct DebugPhdr<'a>(&'a Phdr);
384
385impl<'a> fmt::Debug for DebugPhdr<'a> {
386    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
387        let phdr = self.0;
388
389        // The layout is different for 32-bit vs 64-bit,
390        // but since the fields are the same, it shouldn't matter much.
391        f.debug_struct("Phdr")
392            .field("p_type", &phdr.p_type)
393            .field("p_flags", &phdr.p_flags)
394            .field("p_offset", &phdr.p_offset)
395            .field("p_vaddr", &phdr.p_vaddr)
396            .field("p_paddr", &phdr.p_paddr)
397            .field("p_filesz", &phdr.p_filesz)
398            .field("p_memsz", &phdr.p_memsz)
399            .field("p_align", &phdr.p_align)
400            .finish()
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use crate::linux;
407    use crate::{IterationControl, Segment, SharedLibrary};
408
409    #[test]
410    fn have_libc() {
411        let mut found_libc = false;
412        linux::SharedLibrary::each(|info| {
413            found_libc |= info
414                .name
415                .to_bytes()
416                .split(|c| *c == b'.' || *c == b'/')
417                .find(|s| s == b"libc")
418                .is_some();
419        });
420        assert!(found_libc);
421    }
422
423    #[test]
424    fn can_break() {
425        let mut first_count = 0;
426        linux::SharedLibrary::each(|_| {
427            first_count += 1;
428        });
429        assert!(first_count > 2);
430
431        let mut second_count = 0;
432        linux::SharedLibrary::each(|_| {
433            second_count += 1;
434
435            if second_count == first_count - 1 {
436                IterationControl::Break
437            } else {
438                IterationControl::Continue
439            }
440        });
441        assert_eq!(second_count, first_count - 1);
442    }
443
444    #[test]
445    fn get_name() {
446        use std::ffi::OsStr;
447        let mut names = vec![];
448        linux::SharedLibrary::each(|shlib| {
449            println!("{:?}", shlib);
450            let name = shlib.name();
451            if name != OsStr::new("") {
452                names.push(name.to_str().unwrap().to_string());
453            }
454        });
455
456        assert!(names.iter().any(|x| x.contains("findshlibs")));
457        assert!(names.iter().any(|x| x.contains("libc.so")));
458    }
459
460    #[test]
461    #[cfg(target_os = "linux")]
462    fn get_id() {
463        use std::path::Path;
464        use std::process::Command;
465
466        linux::SharedLibrary::each(|shlib| {
467            let name = shlib.name();
468            let id = shlib.id();
469            if id.is_none() {
470                println!("no id found for {:?}", name);
471                return;
472            }
473            let path: &Path = name.as_ref();
474            if !path.is_absolute() {
475                return;
476            }
477            let gnu_build_id = id.unwrap().to_string();
478            let readelf = Command::new("readelf")
479                .arg("-n")
480                .arg(path)
481                .output()
482                .unwrap();
483            for line in String::from_utf8(readelf.stdout).unwrap().lines() {
484                if let Some(index) = line.find("Build ID: ") {
485                    let readelf_build_id = line[index + 9..].trim();
486                    assert_eq!(readelf_build_id, gnu_build_id);
487                }
488            }
489            println!("{}: {}", path.display(), gnu_build_id);
490        });
491    }
492
493    #[test]
494    fn have_load_segment() {
495        linux::SharedLibrary::each(|shlib| {
496            println!("shlib = {:?}", shlib.name());
497
498            let mut found_load = false;
499            for seg in shlib.segments() {
500                println!("    segment = {:?}", seg.name());
501
502                found_load |= seg.name() == "LOAD";
503            }
504            assert!(found_load);
505        });
506    }
507}