#ld-preload #constructor #override

no-std ld_preload_helpers

Macros to run code at load time and override C functions

2 releases

0.1.1 May 28, 2023
0.1.0 May 27, 2023

#1765 in Rust patterns


54 lines


Rust macros to run code at load time and override C functions.

Mostly useful for LD_PRELOAD hooks, hence the name.


C function overrides

Here open is overridden to print something then forward to the real open. In the rare case that you should wish to recurse into the override, rather than call the real API, use crate::open. From anywhere else in the module, open will refer to the override. To refer to the real function from elsewhere in the program, use real_open (as named at the declaration).

Multiple overrides may be specified within a single macro invocation and multiple extern_c_overrides invocations are supported if desired. This is demonstrated below by an override of getuid which adds 2000 to the user's id.

In std mode (the default), panics in the override are caught and cause the catch block to execute. In no_std mode (not properly supported due to use of OnceLock; see below), there is no special panic handling and the catch block is ignored.

extern_c_overrides! {
  unsafe fn open/real_open(pathname: *const c_char, flags: c_int, mode: libc::mode_t) -> c_int {
    println!("RUST OPEN {:?} {:x} {:x}", unsafe { CStr::from_ptr(pathname) }, flags, mode);
    // panic!("oops");
    return open(pathname, flags, mode);
  } catch {
    return -1;

  unsafe fn getuid/real_getuid() -> libc::uid_t {
    getuid() + 2000
  } catch { u32::MAX }

Image load hook

Called whenever the program/library is loaded, prior to the main entry point being run. Note this demonstrates calling the overridden getuid and real getuid.

Currently there can be only one of these owing to a fixed name being used for the generated function. But this may be extended in future to allow the user to pass a name.

on_load! {{
  println!("INIT LIB {} {}", unsafe { getuid() }, unsafe { real_getuid() });

Example runs

$ LD_PRELOAD=target/debug/liboverride.so id
INIT LIB 2501 501
uid=2501 gid=501 euid=501
$ LD_PRELOAD=target/debug/liboverride.so wc Cargo.lock
INIT LIB 2501 501
RUST OPEN "Cargo.lock" 0 52c47940
  820  1588 21572 Cargo.lock

Notes on no_std usage

  • std::sync::OnceLock is the only dependency on std. Should consider conquer_once or similar crates to support no_std.
  • Could also disable the need for this in single thread code to allow no_std.
  • TODO: In no_std client code, the catch block is unused; consider not requiring/accepting it in that case.