Including third party #[no_mangle] functions in a cdylib


#1

I made a crate that exposes some c-compatible #[no_mangle] functions in its public API. Is there a way to also include those symbols in another crate cdylib?

Here is a project to demonstrate the issue:

Crate a contains two no_mangle functions (make_world and print_world).

Crate b depends on a and re-exports those functions, and adds an additional function (goodbye_world). However, in the generated shared library, the functions from a don’t show up.

$ objdump -T a/target/debug/liba.so  | grep world
00000000000030c0 g    DF .text  0000000000000303  Base        print_world
0000000000002f20 g    DF .text  000000000000019a  Base        make_world
$ objdump -T b/target/debug/libb.so  | grep world
00000000000034a0 g    DF .text  0000000000000086  Base        goodbye_world

So I tried to create wrapper functions around every function from a (see b2 crate). They work like this:

extern crate a;

pub use a::{world_pointer_t};

#[no_mangle]
pub extern "C" fn make_world() -> *mut world_pointer_t {
    a::make_world()
}

However, with this approach the C code actually segfaults (see b2/test.c):

$ cd b2
$ cc test.c -L target/debug -l b2 -o test
$ LD_LIBRARY_PATH=target/debug ./test
Test start

From b2 crate:
Output: Goodbye, world!

From a crate:
Segmentation fault (core dumped)

Regarding the general problem of exposing 3rd party functions, is there a better way? This issue looks related: https://github.com/rust-lang/rust/issues/36342

And regarding the segfault, am I doing something wrong?


#2

@dbrgn

And regarding the segfault, am I doing something wrong?

You can turn on core dumping (ulimit -c unlimited), open core dump in gdb and ask gdb to show backtrace - bt, I suppose you see something like:

b2::make_world
b2::make_world
b2::make_world
b2::make_world

because of you call no_mangle function, so I suppose linker can not distinguish b2::f and a::f.


#3

@Dushistov ah, of course, you’re probably right :slight_smile:

Shouldn’t this trigger a compile warning/error, if two #[no_mangle] functions have the same name? I guess it would be worth to open an issue in the rust repository…


#4

Sometimes it is the right thing. For example if you want “hook” libc functions like malloc you can create shared library with function malloc and with LD_PRELOAD= insert it into executable address space.

For example in your case, in bX library you can write:

#[no_mangle]
pub extern "C" fn make_world() -> *mut world_pointer_t {
    type MakeWorldFunction = extern "C" fn() -> *mut world_pointer_t;
    let p = unsafe { dlsym(RTLD_NEXT, swig_c_str!("make_world")) };
    assert!(!p.is_null());
    let p: MakeWorldFunction = unsafe { mem::transmute(p) };
    p()
}

and then run your test code with:

LD_PRELOAD=full/path/to/liba.so ./test

and at least call of make_world it should pass without crash.


#5

I see.

Anyways, this problem would not even occur if I would find a way to expose 3rd party crate no_mangle functions into my .so file… @Dushistov do you know of a way to get that working?


#6

In current approach, without fixing issue in rustc that you’ve mentioned in top post I don’t see solution.
But may be you should change the way you are looking at problem.

For example, why you need reexport symbols, if you build liba.so,
you can just link it to test executable?


#7

Or for example, why you need make_world to be extern "C" + no_mangle in crate a?

If you make make_world normal function in crate a,
and in crate b you create extern "C" + no_mangle make_world that call a::make_world, then optimizaer should take care about this.
And your code should be as effective as you reexport a::make_world without calling it.


#8

The problem is that if I build both liba and libb in parallel, both bring along the rust stdlib leading to name conflicts during linking, right?

That’s actually a good point, thanks! :slight_smile:


#9

I never try, but I think this should not be the problem.
All symbols that you not define as pub explicity should be hidden
and not visible outside of cdylib.