Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

associated functions don't seem to be working as intended for cdylib, but works for dylib #76211

Open
sarvi opened this issue Sep 1, 2020 · 1 comment
Labels
A-FFI Area: Foreign function interface (FFI) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@sarvi
Copy link

sarvi commented Sep 1, 2020

The suspicion on the RUST forum is that assocaited functions are not working correctly for cdylib
Associated RUST forum thread
https://users.rust-lang.org/t/ld-preload-works-as-dylib-and-doesnt-as-cdylib-which-is-the-right-choice/48174

I am building an LD_PRELOAD library.
It was recommended in the rust forum that I use cdylib as crate type to build the Rust LD_PRELOAD shared library I am working with. I was using dylib before that.
My reading of other threads i this group also backsup his recommendation
https://users.rust-lang.org/t/what-is-the-difference-between-dylib-and-cdylib/28847

Interestingly enough dylib was working for me. I then switched cdylib and the LD_PRELOAD intercept portion stopped working. Though the .so still gets exectued as shown below.

Here is an example rust shared library code I used for testing. It intercepts readlink to print some debug print messages and continues to the original readlink.

extern crate core;
extern crate libc;
#[macro_use]
extern crate ctor; 


use libc::{c_void,c_char,c_int,size_t,ssize_t};

use std::sync::atomic;

#[cfg(any(target_os = "macos", target_os = "ios"))]
pub mod dyld_insert_libraries;

/* Some Rust library functionality (e.g., jemalloc) initializes
 * lazily, after the hooking library has inserted itself into the call
 * path. If the initialization uses any hooked functions, this will lead
 * to an infinite loop. Work around this by running some initialization
 * code in a static constructor, and bypassing all hooks until it has
 * completed. */

static INIT_STATE: atomic::AtomicBool = atomic::AtomicBool::new(false);

pub fn initialized() -> bool {
    INIT_STATE.load(atomic::Ordering::SeqCst)
}

#[ctor]
fn initialize() {
    Box::new(0u8);
    INIT_STATE.store(true, atomic::Ordering::SeqCst);
    println!("Constructor");
}


#[link(name = "dl")]
extern "C" {
    fn dlsym(handle: *const c_void, symbol: *const c_char) -> *const c_void;
}

const RTLD_NEXT: *const c_void = -1isize as *const c_void;

pub unsafe fn dlsym_next(symbol: &'static str) -> *const u8 {
    let ptr = dlsym(RTLD_NEXT, symbol.as_ptr() as *const c_char);
    if ptr.is_null() {
        panic!("redhook: Unable to find underlying function for {}", symbol);
    }
    ptr as *const u8
}


#[allow(non_camel_case_types)]
pub struct readlink {__private_field: ()}
#[allow(non_upper_case_globals)]
static readlink: readlink = readlink {__private_field: ()};

impl readlink {
    fn get(&self) -> unsafe extern fn (path: *const c_char, buf: *mut c_char, bufsiz: size_t) -> ssize_t  {
        use ::std::sync::Once;

        static mut REAL: *const u8 = 0 as *const u8;
        static mut ONCE: Once = Once::new();

        unsafe {
            ONCE.call_once(|| {
                REAL = dlsym_next(concat!("readlink", "\0"));
            });
            ::std::mem::transmute(REAL)
        }
    }

    #[no_mangle]
    pub unsafe extern "C" fn readlink(path: *const c_char, buf: *mut c_char, bufsiz: size_t) -> ssize_t {
        println!("readlink");
        if initialized() {
            println!("initialized");
            ::std::panic::catch_unwind(|| my_readlink ( path, buf, bufsiz )).ok()
        } else {
            println!("not initialized");
            None
        }.unwrap_or_else(|| readlink.get() ( path, buf, bufsiz ))
    }
}

pub unsafe fn my_readlink(path: *const c_char, buf: *mut c_char, bufsiz: size_t) -> ssize_t {
    println!("my_readlink");
    readlink.get()(path, buf, bufsiz)
}

My Cargo.toml looks like this

[package]
name = "readlink"
version = "0.1.0"
authors = ["Saravanan Shanmugham <[email protected]>"]

[lib]
name = "readlink"
crate_type = ["dylib"]

[dependencies]
libc = "0.2"
ctor = "0.1.15"

And this works. I see the constructor executing, and my_readlink which is my itnercept function and ls -al /tmp/link works and shows the symlink as expected, so the original readlink was executed as well. So all is well here

bash-4.4$ LD_PRELOAD=target/debug/libreadlink.so ls -al /tmp/link 
Constructor
readlink
initialized
my_readlink
lrwxrwxrwx 1 sarvi eng 9 Aug 31 11:11 /tmp/link -> /tmp/file
bash-4.4$ 

I then changed
crate_type = ["dylib"]
to
crate_type = ["cdylib"]
And I see this. Only the constructor of libreadlink.so gets executed. But none of the interception happens.

bash-4.4$ LD_PRELOAD=target/debug/libreadlink.so ls -al /tmp/link 
Constructor
lrwxrwxrwx 1 sarvi eng 9 Aug 31 11:11 /tmp/link -> /tmp/file
bash-4.4$ 

Is cdylib the right choice? and why doesnt my interception not work as the recommended cdylib but works as a dylib ?
Considering dylib is supposed to be smaller and is working. Can I continue as dylib? Or should I be worried about any other pitfalls for dylibs exposing C externs for calls from other C programs?

<code>

I expected to see this happen: explanation

Instead, this happened: explanation

Meta

rustc --version --verbose:

bash-4.4$ rustc -vV
rustc 1.47.0-nightly (7e6d6e5f5 2020-08-16)
binary: rustc
commit-hash: 7e6d6e5f535321c2223f044caba16f97b825009c
commit-date: 2020-08-16
host: x86_64-unknown-linux-gnu
release: 1.47.0-nightly
LLVM versio
Backtrace

<backtrace>

@sarvi sarvi added the C-bug Category: This is a bug. label Sep 1, 2020
@jonas-schievink jonas-schievink added the A-FFI Area: Foreign function interface (FFI) label Sep 1, 2020
bors added a commit to rust-lang-ci/rust that referenced this issue Aug 13, 2021
Associated functions that contain extern indicator or have `#[rustc_std_internal_symbol]` are reachable

Previously these fails to link with ``undefined reference to `foo'``:

<details>
<summary>Example 1</summary>

```rs
struct AssocFn;

impl AssocFn {
    #[no_mangle]
    fn foo() {}
}

fn main() {
    extern "Rust" {
        fn foo();
    }
    unsafe { foo() }
}
```
([Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=f1244afcdd26e2a28445f6e82ca46b50))
</details>

<details>
<summary>Example 2</summary>

```rs
#![crate_name = "lib"]
#![crate_type = "lib"]

struct AssocFn;

impl AssocFn {
    #[no_mangle]
    fn foo() {}
}
```
```rs
extern crate lib;

fn main() {
    extern "Rust" {
        fn foo();
    }
    unsafe { foo() }
}
```
</details>

But I believe they should link successfully, because this works:
<details>

```rs
#[no_mangle]
fn foo() {}

fn main() {
    extern "Rust" {
        fn foo();
    }
    unsafe { foo() }
}
```
([Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=789b3f283ee6126f53939429103ed98d))
</details>

This PR fixes the problem, by adding associated functions that have "custom linkage" to `reachable_set`, just like normal functions.

I haven't tested whether rust-lang#76211 and [Miri](rust-lang/miri#1837) are fixed by this PR yet, but I'm submitting this anyway since this fixes the examples above.

I added a `run-pass` test that combines my two examples above, but I'm not sure if that's the right way to test this. Maybe I should add / modify an existing codegen test (`src/test/codegen/export-no-mangle.rs`?) instead?
@Enselic Enselic added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Dec 17, 2023
@Enselic
Copy link
Member

Enselic commented Dec 17, 2023

Triage: Looks like this was fixed by #86492. Can you confirm please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-FFI Area: Foreign function interface (FFI) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

3 participants