Skip to main content

plat/
linux_glibc.rs

1// linux_glibc -- a program linked against glibc (--cfg jfgui_glibc; build_rs.py section "glibc",
2// docs/GPU.md "Linux"): every Linux GPU driver is a glibc library
3//
4//   _start, glibc_main          the process start through __libc_start_main (ld.so has set up the
5//                               main thread's TLS and run libc's initialisers by then), then what
6//                               linux_entry does in a static program
7//   std_start()                 (feature std: every program with std on Linux is this build) the
8//                               start from the program's fn main, std's arguments and environment
9//                               as plat reads them from the initial stack, the end through std
10//   exit(code)                  plat::exit: glibc's exit (atexit handlers, the destructors of the
11//                               loaded libraries, stdio); so does returning from the program's main.
12//                               Crash reports end in rt::os::exit as everywhere: exit_group, no
13//                               destructor runs inside a signal handler
14//   at_exit(f)                  f at that normal end, before the handlers registered earlier
15//   dl_open / dl_sym / dl_close / dl_error    the system's dynamic linker (lib/gpu: libvulkan.so.1);
16//                               plugins keep their own loader (linux_elfload.rs: they import nothing)
17//   glibc_version()             the running libc ("2.43")
18//   glibc_thread_ids()          the calling thread's FS base, pthread_self, &errno (gputest --test glibc)
19//
20// Threads: linux_thread.rs, its last part: pthread_create on the same mapping as in a static
21// program.  Everything else of plat stays raw system calls next to glibc, which is fine: they do
22// not touch errno or glibc's state.  The fault handlers (linux_crash.rs, linux_guard.rs) use
23// rt_sigaction directly and leave glibc's two internal signals alone.  A driver's own threads have
24// no alternate signal stack, the report runs on their stack; Mesa's block every signal but SIGSEGV
25// and SIGSYS, so another fault there (SIGILL in llvmpipe's JIT code) ends the process at once, by the
26// kernel, without a report.  The allocator (rt::mem) has its own mappings and takes no TLS, so a
27// driver's thread may call back into it (debug messages); a child after a raw fork only makes system
28// calls until its exec.  plat::setenv changes JFLib's list, which plat::env and child processes see;
29// a driver's getenv reads the start environment.
30
31use alloc::string::String;
32use core::ffi::c_void;
33
34unsafe extern "C" {
35    fn __libc_start_main(main: extern "C" fn(i32, *const usize, *const usize) -> i32, argc: i32, argv: *const usize,
36                         init: usize, fini: usize, rtld_fini: usize, stack_end: *const u8) -> i32;
37    fn __cxa_atexit(f: extern "C" fn(*mut c_void), arg: *mut c_void, dso: *mut c_void) -> i32;
38    fn __errno_location() -> *mut i32;
39    #[cfg(not(feature = "std"))] // with std: std::process::exit (hosted.rs)
40    #[link_name = "exit"]
41    fn c_exit(code: i32) -> !;
42    fn gnu_get_libc_version() -> *const u8;
43    fn pthread_self() -> usize;
44    fn dlopen(name: *const u8, flags: i32) -> *mut c_void;
45    fn dlsym(handle: *mut c_void, name: *const u8) -> *mut c_void;
46    fn dlclose(handle: *mut c_void) -> i32;
47    fn dlerror() -> *const u8;
48    pub(super) fn pthread_attr_init(attr: *mut PthreadAttr) -> i32;
49    pub(super) fn pthread_attr_destroy(attr: *mut PthreadAttr) -> i32;
50    pub(super) fn pthread_attr_setstack(attr: *mut PthreadAttr, stack: *mut u8, size: usize) -> i32;
51    pub(super) fn pthread_create(thread: *mut usize, attr: *const PthreadAttr, start: extern "C" fn(*mut u8) -> *mut u8,
52                                 arg: *mut u8) -> i32;
53    pub(super) fn pthread_join(thread: usize, ret: *mut *mut u8) -> i32;
54}
55
56/// pthread_attr_t of x86-64 (56 bytes)
57#[repr(C, align(8))]
58pub(super) struct PthreadAttr(pub(super) [u8; 56]);
59
60const ARCH_GET_FS: usize = 0x1003;
61const RTLD_NOW: i32 = 2;
62
63// the ABI's entry: rsp -> argc, argv[], 0, envp[], 0; rdx = ld.so's finaliser (registered by
64// __libc_start_main).  init and fini 0: this program has neither (glibc takes both as optional).
65// A program with std has the C runtime's _start and std_start.
66#[cfg(not(feature = "std"))]
67core::arch::global_asm!(
68    ".globl _start",
69    "_start:",
70    "    xor ebp, ebp",
71    "    mov r9, rdx",
72    "    pop rsi",
73    "    mov rdx, rsp",
74    "    and rsp, -16",
75    "    push rax",
76    "    push rsp",
77    "    xor r8d, r8d",
78    "    xor ecx, ecx",
79    "    lea rdi, [rip + {main}]",
80    "    call {start}",
81    "    ud2",
82    main = sym glibc_main,
83    start = sym __libc_start_main,
84);
85
86#[cfg(not(feature = "std"))]
87extern "C" fn glibc_main(_argc: i32, argv: *const usize, _envp: *const usize) -> i32 {
88    // SAFETY: argv lies on the initial stack right after argc, as linux.rs reads it; written once
89    // before anything reads it, on the only thread of this program.
90    unsafe { super::START_SP = argv.sub(1) };
91    super::crash::crash_init();
92    super::thread::wake_init();
93    crate::cpu::simd_override();
94    rt::boot::set_handler(crate::boot::start); // rt::main!: name, app://, manifest
95    // SAFETY: the program's entry symbol (ui::main!, rt::main!); the return value goes to exit
96    unsafe { super::jfgui_main() }
97}
98
99/// (feature std) the start of a program with std, from its `fn main` (rt::main!, ui::main! or the
100/// program's own): std's arguments and environment as a block like the initial stack, then what
101/// glibc_main does, with rt's panic hook first; the end through std::process::exit
102#[cfg(feature = "std")]
103pub fn std_start() -> ! {
104    // SAFETY: written once, first thing in the program's main, before anything reads it.
105    unsafe { super::START_SP = crate::hosted::initial_block() };
106    rt::panic_hook();
107    super::crash::crash_init();
108    super::thread::wake_init();
109    crate::cpu::simd_override();
110    rt::boot::set_handler(crate::boot::start); // rt::main!: name, app://, manifest
111    // SAFETY: the program's entry symbol (ui::main!, rt::main!).
112    let code = unsafe { super::jfgui_main() };
113    crate::exit(code) // hosted.rs: std::process::exit
114}
115
116/// end the process: glibc's exit (at_exit handlers, the destructors of the loaded libraries);
117/// from any thread, not from a signal handler
118#[cfg(not(feature = "std"))]
119pub fn exit(code: i32) -> ! {
120    // SAFETY: ends the process.
121    unsafe { c_exit(code) }
122}
123
124extern "C" fn run_at_exit(f: *mut c_void) {
125    // SAFETY: at_exit stored a fn() here.
126    let f = unsafe { core::mem::transmute::<*mut c_void, fn()>(f) };
127    f();
128}
129
130/// run f at the normal end of the process (plat::exit, or the program's main returns), not after
131/// a crash; before the handlers registered earlier
132pub fn at_exit(f: fn()) {
133    // SAFETY: a fn pointer fits the argument; 0: not tied to a library that could be unloaded.
134    unsafe { __cxa_atexit(run_at_exit, f as *mut c_void, core::ptr::null_mut()) };
135}
136
137/// open a library through the system's dynamic linker (a name, searched as ld.so does, or a path)
138/// and run its initialisers -> its handle
139pub fn dl_open(name: &str) -> Option<usize> {
140    if name.contains('\0') {
141        return None;
142    }
143    let mut z: alloc::vec::Vec<u8> = name.as_bytes().to_vec();
144    z.push(0);
145    // SAFETY: z is NUL-terminated.
146    let h = unsafe { dlopen(z.as_ptr(), RTLD_NOW) };
147    if h.is_null() { None } else { Some(h as usize) }
148}
149
150/// the address of a symbol of a library from dl_open (0: none); name ends in NUL
151pub fn dl_sym(handle: usize, name: &[u8]) -> usize {
152    if name.last() != Some(&0) {
153        return 0;
154    }
155    // SAFETY: a handle of dl_open, a NUL-terminated name.
156    unsafe { dlsym(handle as *mut c_void, name.as_ptr()) as usize }
157}
158
159/// close a library from dl_open
160pub fn dl_close(handle: usize) {
161    // SAFETY: a handle of dl_open; the caller no longer uses what it got from it.
162    unsafe { dlclose(handle as *mut c_void) };
163}
164
165unsafe fn c_text(p: *const u8) -> String {
166    if p.is_null() {
167        return String::new();
168    }
169    // SAFETY: a NUL-terminated string (the caller's source says so).
170    unsafe { super::cstr(p) }
171}
172
173/// why the calling thread's last dl_open / dl_sym failed ("": it did not, or it was already asked)
174pub fn dl_error() -> String {
175    // SAFETY: dlerror returns null or a string that stays valid until this thread's next dl call.
176    unsafe { c_text(dlerror()) }
177}
178
179/// the version of the running glibc ("2.43")
180pub fn glibc_version() -> String {
181    // SAFETY: a static NUL-terminated string.
182    unsafe { c_text(gnu_get_libc_version()) }
183}
184
185/// the calling thread's FS base, pthread_self() and the address of errno: its thread control block
186/// and thread-local storage (a raw clone would have 0 and the creator's)
187pub fn glibc_thread_ids() -> (usize, usize, usize) {
188    let mut fs = 0usize;
189    // SAFETY: arch_prctl writes the FS base to fs; the two libc calls have no preconditions on a
190    // thread started by glibc.
191    unsafe {
192        rt::os::syscall3(rt::os::SYS_ARCH_PRCTL, ARCH_GET_FS, &mut fs as *mut usize as usize, 0);
193        (fs, pthread_self(), __errno_location() as usize)
194    }
195}