Skip to content
New issue

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

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

Already on GitHub? # 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 <sarvi@cisco.com>"]

[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?

# for free to join this conversation on GitHub. Already have an account? # 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