vdso/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! This module provides functions for reading symbols from the Linux vDSO.
4
5#![deny(missing_docs)]
6#![deny(clippy::all)]
7
8use crt0stack::{Entry, Reader};
9use std::ffi::CStr;
10use std::os::raw::c_char;
11use std::slice::from_raw_parts;
12
13#[cfg(target_pointer_width = "64")]
14mod elf {
15    pub use goblin::elf64::dynamic::*;
16    pub use goblin::elf64::header::*;
17    pub use goblin::elf64::program_header::*;
18    pub use goblin::elf64::section_header::*;
19    pub use goblin::elf64::sym::Sym;
20
21    pub const CLASS: u8 = ELFCLASS64;
22    pub type Word = u64;
23}
24
25#[cfg(target_pointer_width = "32")]
26mod elf {
27    pub use goblin::elf32::dynamic::*;
28    pub use goblin::elf32::header::*;
29    pub use goblin::elf32::program_header::*;
30    pub use goblin::elf32::section_header::*;
31    pub use goblin::elf32::sym::Sym;
32
33    pub const CLASS: u8 = ELFCLASS32;
34    pub type Word = u32;
35}
36
37#[repr(transparent)]
38struct Header(elf::Header);
39
40impl Header {
41    #[allow(clippy::trivially_copy_pass_by_ref)]
42    pub unsafe fn from_ptr(ptr: &()) -> Option<&Self> {
43        let hdr = &*(ptr as *const _ as *const Self);
44
45        if hdr.0.e_ident[..elf::ELFMAG.len()] != elf::ELFMAG[..] {
46            return None;
47        }
48
49        if hdr.0.e_ident[elf::EI_CLASS] != elf::CLASS {
50            return None;
51        }
52
53        Some(hdr)
54    }
55
56    unsafe fn ptr<T>(&self, off: impl Into<elf::Word>) -> *const T {
57        let addr = self as *const _ as *const u8;
58        addr.add(off.into() as usize) as *const T
59    }
60
61    unsafe fn slice<T>(&self, off: impl Into<elf::Word>, len: impl Into<elf::Word>) -> &[T] {
62        from_raw_parts::<u8>(self.ptr(off), len.into() as usize)
63            .align_to()
64            .1
65    }
66
67    unsafe fn shtab(&self) -> &[elf::SectionHeader] {
68        self.slice(self.0.e_shoff, self.0.e_shentsize * self.0.e_shnum)
69    }
70
71    unsafe fn section<T>(&self, kind: u32) -> Option<&[T]> {
72        for sh in self.shtab() {
73            if sh.sh_type == kind {
74                return Some(self.slice(sh.sh_offset, sh.sh_size));
75            }
76        }
77
78        None
79    }
80
81    unsafe fn symbol(&self, name: &str) -> Option<&Symbol> {
82        let symstrtab: &[c_char] = self.section(elf::SHT_STRTAB)?;
83        let symtab: &[elf::Sym] = self.section(elf::SHT_DYNSYM)?;
84
85        // Yes, we could spead up the lookup by checking against the hash
86        // table. But the reality is that there is less than a dozen symbols
87        // in the vDSO, so the gains are trivial.
88
89        for sym in symtab {
90            let cstr = CStr::from_ptr(&symstrtab[sym.st_name as usize]);
91            if let Ok(s) = cstr.to_str() {
92                if s == name {
93                    let addr = self.ptr(sym.st_value) as *const Symbol;
94                    return Some(&*addr);
95                }
96            }
97        }
98
99        None
100    }
101}
102
103/// A resolved symbol
104///
105/// Since vDSO symbols have no type information, this type is opaque.
106/// Generally, you will cast a `&Symbol` to the appropriate reference type.
107pub enum Symbol {}
108
109/// This structure represents the Linux vDSO
110pub struct Vdso<'a>(&'a Header);
111
112impl Vdso<'static> {
113    /// Locates the vDSO by parsing the auxiliary vectors
114    pub fn locate() -> Option<Self> {
115        for aux in Reader::from_environ().done() {
116            if let Entry::SysInfoEHdr(addr) = aux {
117                let hdr = unsafe { Header::from_ptr(&*(addr as *const _))? };
118                return Some(Self(hdr));
119            }
120        }
121
122        None
123    }
124}
125
126impl<'a> Vdso<'a> {
127    /// Find a vDSO symbol by its name
128    ///
129    /// The return type is essentially a void pointer. You will need to cast
130    /// it for the type of the symbol you are looking up.
131    pub fn lookup(&self, name: &str) -> Option<&'a Symbol> {
132        unsafe { self.0.symbol(name) }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use libc::time_t;
140    use std::mem::transmute;
141    use std::ptr::null_mut;
142
143    #[test]
144    fn time() {
145        let vdso = Vdso::locate().unwrap();
146        let func = vdso.lookup("time").unwrap();
147        let func: extern "C" fn(*mut time_t) -> time_t = unsafe { transmute(func) };
148
149        let libc = unsafe { libc::time(null_mut()) };
150        let vdso = func(null_mut());
151        assert!(vdso - libc <= 1);
152    }
153}