#![expect(dead_code)]
use std::{
error::Error,
ffi::{CStr, CString},
fmt,
ptr::NonNull,
};
use libc::{c_int, c_void, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NOLOAD, RTLD_NOW};
use syd::path::XPath;
pub fn check_vdso_has_getrandom() -> bool {
has_vdso_symbol(c"__vdso_getrandom")
}
pub fn vdso_list_calls() -> Result<Vec<&'static CStr>, DlError> {
const KERN_LEN: usize = 9; const VDSO_LEN: usize = 7; const VDSO_IDX: usize = 10; const VDSO_CALL_NAMES: &[&CStr] = &[
c"__kernel_clock_getres",
c"__kernel_clock_getres_time64",
c"__kernel_clock_gettime",
c"__kernel_clock_gettime64",
c"__kernel_getcpu",
c"__kernel_getrandom",
c"__kernel_gettimeofday",
c"__kernel_get_tbfreq", c"__kernel_riscv_hwprobe", c"__kernel_time",
c"__vdso_clock_getres",
c"__vdso_clock_getres_time64",
c"__vdso_clock_gettime",
c"__vdso_clock_gettime64",
c"__vdso_getcpu",
c"__vdso_getrandom",
c"__vdso_gettimeofday",
c"__vdso_get_tbfreq", c"__vdso_riscv_hwprobe", c"__vdso_time",
];
let vdso = vdso_open()?;
let mut out = Vec::with_capacity(VDSO_CALL_NAMES.len());
for (idx, &name) in VDSO_CALL_NAMES.iter().enumerate() {
if vdso.has_symbol(name) {
let plen = if idx < VDSO_IDX { KERN_LEN } else { VDSO_LEN };
let bytes = name.to_bytes_with_nul();
out.push(unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[plen..]) });
}
}
Ok(out)
}
pub fn has_vdso_symbol(sym: &CStr) -> bool {
vdso_open()
.map(|vdso| vdso.has_symbol(sym))
.unwrap_or(false)
}
pub fn vdso_open() -> Result<Library, DlError> {
vdso_open_by_name(c"linux-vdso.so.1").or_else(|_| vdso_open_by_name(c"linux-vdso64.so.1"))
}
pub fn vdso_open_by_name(name: &CStr) -> Result<Library, DlError> {
Library::open(Some(name), DlFlags::NOLOAD | DlFlags::LOCAL | DlFlags::NOW)
}
pub fn has_symbol(sym: &CStr) -> bool {
Library::this(DlFlags::NOW)
.map(|lib| lib.has_symbol(sym))
.unwrap_or(false)
}
pub struct Library {
handle: NonNull<c_void>,
}
impl Library {
pub fn open(name: Option<&CStr>, flags: DlFlags) -> Result<Library, DlError> {
clear_error();
let name = name.map_or(std::ptr::null(), CStr::as_ptr);
let handle = unsafe { libc::dlopen(name, flags.bits()) };
NonNull::new(handle)
.map(|handle| Library { handle })
.ok_or_else(DlError::last)
}
pub fn this(flags: DlFlags) -> Result<Library, DlError> {
Library::open(None, flags)
}
pub fn symbol(&self, name: &CStr) -> Result<NonNull<c_void>, DlError> {
clear_error();
let addr = unsafe { libc::dlsym(self.handle.as_ptr(), name.as_ptr()) };
if let Some(addr) = NonNull::new(addr) {
return Ok(addr);
}
if unsafe { libc::dlerror() }.is_null() {
Err(DlError::msg(c"symbol resolved to a NULL address"))
} else {
Err(DlError::last())
}
}
pub fn has_symbol(&self, name: &CStr) -> bool {
self.symbol(name).is_ok()
}
}
impl Drop for Library {
fn drop(&mut self) {
let _ = unsafe { libc::dlclose(self.handle.as_ptr()) };
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct DlFlags: c_int {
const LAZY = RTLD_LAZY;
const NOW = RTLD_NOW;
const GLOBAL = RTLD_GLOBAL;
const LOCAL = RTLD_LOCAL;
const NOLOAD = RTLD_NOLOAD;
}
}
fn clear_error() {
let _ = unsafe { libc::dlerror() };
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DlError {
message: CString,
}
impl DlError {
fn msg(message: &CStr) -> DlError {
let bytes = message.to_bytes_with_nul();
let mut buf = Vec::new();
if buf.try_reserve_exact(bytes.len()).is_ok() {
buf.extend_from_slice(bytes);
}
let message = CString::from_vec_with_nul(buf).unwrap_or_default();
DlError { message }
}
fn last() -> DlError {
let ptr = unsafe { libc::dlerror() };
if ptr.is_null() {
return DlError::msg(c"unknown dynamic linker error");
}
let message = unsafe { CStr::from_ptr(ptr) };
DlError::msg(message)
}
pub fn message(&self) -> &CStr {
&self.message
}
pub fn as_xpath(&self) -> &XPath {
XPath::from_bytes(self.message.to_bytes())
}
}
impl fmt::Display for DlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_xpath(), f)
}
}
impl Error for DlError {}
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
#[test]
fn test_dlerror_1() {
let e = DlError::msg(c"boom");
assert_eq!(e.message().to_bytes(), b"boom");
assert_eq!(e.as_xpath().as_bytes(), b"boom");
assert_eq!(format!("{e}"), "boom");
}
#[test]
fn test_dlerror_2() {
clear_error();
let e = DlError::last();
assert_eq!(e.message().to_bytes(), b"unknown dynamic linker error");
}
#[test]
fn test_dlerror_3() {
let e = DlError::msg(c"x");
assert_eq!(e, DlError::msg(c"x"));
assert_ne!(e, DlError::msg(c"y"));
assert!(format!("{e:?}").contains("DlError"));
}
#[cfg(not(target_env = "musl"))]
#[test]
fn test_library_1() {
let lib = Library::open(Some(c"libc.so.6"), DlFlags::NOW | DlFlags::LOCAL).unwrap();
assert!(lib.symbol(c"malloc").is_ok());
assert!(lib.has_symbol(c"malloc"));
}
#[test]
fn test_library_2() {
let e = Library::open(Some(c"syd-no-such-lib-xyz.so"), DlFlags::NOW)
.err()
.unwrap();
assert!(!e.message().to_bytes().is_empty());
}
#[cfg(not(target_env = "musl"))]
#[test]
fn test_library_3() {
let lib = Library::this(DlFlags::NOW).unwrap();
if Library::open(
Some(c"libc.so.6"),
DlFlags::NOLOAD | DlFlags::NOW | DlFlags::LOCAL,
)
.is_ok()
{
assert!(lib.has_symbol(c"malloc"));
}
assert!(!lib.has_symbol(c"syd_no_such_symbol_xyz"));
assert!(lib.symbol(c"syd_no_such_symbol_xyz").is_err());
}
#[cfg(not(target_env = "musl"))]
#[test]
fn test_vdso_open_1() {
assert!(vdso_open().is_ok());
}
#[cfg(not(target_env = "musl"))]
#[test]
fn test_vdso_open_by_name_1() {
assert!(vdso_open_by_name(c"linux-vdso.so.1").is_ok());
assert!(vdso_open_by_name(c"syd-no-such-vdso.so").is_err());
}
#[cfg(not(target_env = "musl"))]
#[test]
fn test_vdso_list_calls_1() {
assert!(!vdso_list_calls().unwrap().is_empty());
}
#[cfg(not(target_env = "musl"))]
#[test]
fn test_has_vdso_symbol_1() {
assert!(has_vdso_symbol(c"__vdso_gettimeofday"));
assert!(!has_vdso_symbol(c"__vdso_syd_no_such"));
}
#[test]
fn test_check_vdso_has_getrandom_1() {
assert_eq!(
check_vdso_has_getrandom(),
has_vdso_symbol(c"__vdso_getrandom")
);
}
#[cfg(not(target_env = "musl"))]
#[test]
fn test_has_symbol_1() {
if Library::open(
Some(c"libc.so.6"),
DlFlags::NOLOAD | DlFlags::NOW | DlFlags::LOCAL,
)
.is_ok()
{
assert!(has_symbol(c"malloc"));
}
assert!(!has_symbol(c"syd_no_such_symbol_xyz"));
}
}