Debugging a rust bindgen issue

Chris Evans
4 min readJan 14, 2018

--

I ran into a bindgen issue when trying to create bindings for a simple C++ library that I thought I would share.

Right after running into the issue (which we’ll see below — I’ll save you from my actual code), I created a minimal example. This means making the code as small and simple as possible while still retaining the bug/issue. It’s just one function declared in an .hpp file and defined in a .cpp file. The function declaration is wrapped in extern “C" because maybe we also want to link this code into a C project in the future.

I then made my build.rs buildscript, created a simple lib.rs to wrap the unsafe functions that bindgen creates in a safe abstraction, and I made a simple main.rs to test it all out. I started following the bindgen user’s guide, but it showed an example of binding to a precompiled library, bzip2, which already exists in one of the “usual places” for system libraries and includes. I think bindgen is probably using its clang-sys dependency, among other things, for its default include search path and default library search path.

I could have written a Makefile to compile my library into a .so or .a and used that in a very similar way to the bindgen user’s guide. In this case, I’m fine with just including the two files inside my rust project instead of building the .a, or .so separately. I remembered seeing other examples of bindgen that used the cc crate to compile C or C++ directly in the cargo build step. So here’s my three additional files:

But attempting to build the project fails with:

error: linking with `cc` failed: exit code: 1

[HUGE “cc” command snipped out here]

= note: /tmp/bindigstest/libfoo-sys/target/debug/deps/liblibfoo_sys-7b4c3d78e3ff18de.rlib(libfoo_sys-7b4c3d78e3ff18de.libfoo_sys0.rcgu.o): In function `libfoo_sys::rust_foo’:
/tmp/bindigstest/libfoo-sys/src/lib.rs:9: undefined reference to `foo’
collect2: error: ld returned 1 exit status

Yikes! Ok we know this is a linking error because rustc told us so. The last few lines show the output of ld, which is the linker. It looks like the .rlib file, which according to the docs, is rust’s intermediate object file, contains a reference to a symbol foo but the linker can’t find it in any of the .o (or .rcgu.o files which appear to have something to do with incremental compilation? I don’t know why the error output lists a .rlib file and then .rcgu.o file in parentheses). Let’s take a look at the .rlib file ourselves to see if the symbol foo is there or not:

Here we are using objdump to inspect the symbol table of an object file. It looks like this .rlib file contains an undefined reference to _Z3foo, which is a mangled name. This is interesting, since I thought we had used extern "C" in our .hpp file to specify we wanted C linkage. If I compile directly with g++ I get the following:

Which is a non-mangled foo. Hm. Well, at this point let’s take a look at the libfoo.a file which our build.rs made using the cc crate.

Interesting… so libfoo.a appears to contain the non mangled foo symbol, and the .rlib expects the mangled name. So I guess we either need to make cc::Build give us a mangled name (possibly by removing extern "C" from the .hpp file), or we need to tell bindgen not to expect mangled names somehow.

At this point I decided to take a look at bindngen's source to see if there was a way to do this. After grep-ing for the term mangle in the project’s lib.rs and mod.rs, I came across some code that looked fairly related. The linked issue indicates some people had problems with clang emitting mangled names when it shouldn’t have, and that updating to clang 3.9 helped (I am on clang 3.8). The documentation says that this feature is true by default, so let’s try turning it off:

That seems to work!

cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/libfoo-sys`
x = 3

I went and verified that the .rlib no longer contains a reference to _Z3foo, and now references foo. One other trick that I didn’t think of at the time was to look at the bindings themselves! You can find the file that bindgen wrote to by running find . -name bindings.rs. The following is the before and after of that file:

I’m not very familiar with rust’s attributes, so I’m not sure what’s going on there with u{1}. Maybe it’s some sort of substitution? But it looks like we are no longer overriding rustc's mechanism for coming up with object names.

The full working code can be found here, which I hope can also serve as a minimal example of how to use bindgenand cc together to create bindings to C or C++ code embedded in your rust project.

--

--