A simple "Hello World" LD_PRELOAD library segfaults when used with another LD_PRELOAD library

I am bringing this question to internals group in the hope of getting a few more eyes on the problem for insights.

There is a existing LD_PRELOAD program that is used by our team libcpio_preload.so

I am writing attempting to write a new LD_PRELOAD program libwisktrack.so. The basic "Hello world" version, which just simply eprintln!("Hello World"); segfaults. But only if the libwisktrack.so comes after libcpio_preload.so

The following segfaults

LD_PRELOAD=“/nobackup/sarvi/iosxr/platforms/common/thinxr/build/obj-x86-linux/libcpio_preload.so:/ws/sarvi-sjc/wisktrack/\${LIB}/libwisktrack.so” <anyhello world program>

The following does not segfault. The difference is the order of the libwisktrack.so and libcpio_preload.so

LD_PRELOAD=“/ws/sarvi-sjc/wisktrack/\${LIB}/libwisktrack.so:/nobackup/sarvi/iosxr/platforms/common/thinxr/build/obj-x86-linux/libcpio_preload.so” <anyhello world program>

libwisktrack.so has been stripped down to the following code. There is nothing else in the code.

#[link_section = ".init_array"]
pub static INITIALIZE: extern "C" fn() = crate::rust_ctor;

#[no_mangle]
pub extern "C" fn rust_ctor() {
    eprintln!("Hello World");
}

If the eprintln!() was replaced by simple global static initialization, the segfault does not happen. Generates no useable backtrace, and doesnt print anything, even the "Hello World"

If I did the same basic version of libwisktrack.so in "C", no segfaults.

Question:

  1. any insights on how to debug this or what the cause might be
  2. I am suspecting something about what RUST standardly compiles into this library libwisktack.so, or something about RUST seems to be causing this problem as the C version doesnt crash. Any ideas on that path. How can I move forward.

PS: there is a thread in the RUST lang forum on this same subject

What is the backtrace?

There is no backtrace

(gdb) exec-file /ws/sarvi-sjc/wisktrack/tests/testprog64
(gdb) run
Starting program: /ws/sarvi-sjc/wisktrack/tests/testprog64 readlink
During startup program terminated with signal SIGSEGV, Segmentation fault.
(gdb) bt
No stack.
(gdb) show environment LD_PRELOAD
LD_PRELOAD = /nobackup/sarvi/iosxr/platforms/common/thinxr/build/obj-x86-linux/libcpio_preload.so:/ws/sarvi-sjc/wisktrack/${LIB}/libwisktrack.so
(gdb) show args
Argument list to give program being debugged when it is started is "readlink".
(gdb) 
(gdb) exec-file /ws/sarvi-sjc/wisktrack/tests/testprog64
(gdb) run
Starting program: /ws/sarvi-sjc/wisktrack/tests/testprog64 readlink
During startup program terminated with signal SIGSEGV, Segmentation fault.
(gdb) bt
No stack.
(gdb) show environment LD_PRELOAD
LD_PRELOAD = /nobackup/sarvi/iosxr/platforms/common/thinxr/build/obj-x86-linux/libcpio_preload.so:/ws/sarvi-sjc/wisktrack/${LIB}/libwisktrack.so
(gdb) show args
Argument list to give program being debugged when it is started is "readlink".
(gdb) 

The segfault seems to be happening in the library constructor of libwisktrack.so or earlier. If I dont do eprintln!() in the constructor, the crash doesnt happen, when I replace it with a simple assignment statement.

That said, gdb seems to crashig early and without backtrace.

It almost, looks to me like there is something about the RUST compiled code running in the library constructor context that is causing the crash. If I wrote a comparable "Hello World" LD_PRELOAD code in C, the crash doesnt happen.

A wild guess, but could it be possible that thread local storage is not yet initialized when all entries in .init_array are called? println!() uses thread local storage.

Here is an interesting data point. The following works. It uses direct syscalls to write to a fd

pub fn write(fd: usize, buf: &[u8]) -> Result<usize, &'static str> {
    let x = unsafe { libc::syscall(libc::SYS_write, fd, buf.as_ptr() as usize, buf.len()) };
    if x < 0 {
        return Err("Error Writing to FD");
    }
    Ok(x as usize)
}
#[ctor]
fn cfoo() {
    let x = format!("Hello World: {}\n", 1);
    write(0, x.as_bytes()).unwrap();
//    let mut f:fs::File = unsafe { FromRawFd::from_raw_fd(0) };
}

Just uncommenting that one line segfaults. All the line does is create a File object crashes/segfaults.

Basically just turning an File descriptor number to a file object in the constructor crashes, when the LD_PRELOAD happens along with the other libcpio_preload.so

mprotect(0x7feacd1bd000, 12288, PROT_READ) = 0
mprotect(0x601000, 4096, PROT_READ)     = 0
mprotect(0x7feacd3e2000, 4096, PROT_READ) = 0
munmap(0x7feacd39e000, 253881)          = 0
set_tid_address(0x7feacd399a90)         = 43339
set_robust_list(0x7feacd399aa0, 24)     = 0
rt_sigaction(SIGRTMIN, {0x7feacc698860, [], SA_RESTORER|SA_SIGINFO, 0x7feacc6a1630}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {0x7feacc6988f0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7feacc6a1630}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
brk(NULL)                               = 0x23b8000
brk(0x23d9000)                          = 0x23d9000
write(0, "Hello World: 1\n", 15)        = 15
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++