rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.32k stars 683 forks source link

What causes __bindgen_padding and how to avoid / deal with it when cross-compiling? #1709

Closed brocaar closed 4 years ago

brocaar commented 4 years ago

First of all, thanks for the great tool! It really makes it easy to bind against C code :-)

With my project I'm running into an issue and I'm not sure what is causing it. I'm integrating a C library into my project, which will run on embedded / ARM hardware. It (cross)compiles fine when using the FROM rust:1.40-stretch Docker image. Unfortunately I run into issues when I upgraded to FROM rust:1.40-buster. In the latter environment it complains that I'm not setting the __bindgen_padding_ fields when initializing structs. What is different between these two environments is the clang and arm-linux-gnueabihf package versions. I have seen the same issue when using Yocto Zeus, meta-rust and meta-clang (which uses clang / llvm 9.0 I believe).

I'm looking for some advise on how to deal with these __bindgen_padding_ fields and how I can potentially avoid them being generated.

rust:1.40-stretch environment

Native

Compiles fine.

ARM cross-compiling

Compiles fine.

rust:1.40-buster environment

Native

Compiles fine.

ARM cross-compiling

I get the following errors:

   Compiling libloragw-sx1302 v1.0.0 (/chirpstack-concentratord/libloragw-sx1302)
error[E0063]: missing field `__bindgen_padding_0` in initializer of `wrapper::coord_s`
   --> libloragw-sx1302/src/gps/mod.rs:183:19
    |
183 |     let mut loc = wrapper::coord_s {
    |                   ^^^^^^^^^^^^^^^^ missing `__bindgen_padding_0`

error[E0063]: missing field `__bindgen_padding_0` in initializer of `wrapper::coord_s`
   --> libloragw-sx1302/src/gps/mod.rs:189:19
    |
189 |     let mut err = wrapper::coord_s {
    |                   ^^^^^^^^^^^^^^^^ missing `__bindgen_padding_0`

error[E0063]: missing field `__bindgen_padding_0` in initializer of `wrapper::coord_s`
   --> libloragw-sx1301/src/gps/mod.rs:183:19
    |
183 |     let mut loc = wrapper::coord_s {
    |                   ^^^^^^^^^^^^^^^^ missing `__bindgen_padding_0`

error[E0063]: missing field `__bindgen_padding_0` in initializer of `wrapper::coord_s`
   --> libloragw-sx1301/src/gps/mod.rs:189:19
    |
189 |     let mut err = wrapper::coord_s {
    |                   ^^^^^^^^^^^^^^^^ missing `__bindgen_padding_0`

error[E0063]: missing field `__bindgen_padding_0` in initializer of `wrapper::lgw_conf_rxif_s`
   --> libloragw-sx1301/src/hal/mod.rs:275:16
    |
275 |     let conf = wrapper::lgw_conf_rxif_s {
    |                ^^^^^^^^^^^^^^^^^^^^^^^^ missing `__bindgen_padding_0`

error[E0063]: missing fields `__bindgen_padding_0`, `__bindgen_padding_1` in initializer of `wrapper::lgw_conf_rxif_s`
   --> libloragw-sx1302/src/hal/mod.rs:353:20
    |
353 |     let mut conf = wrapper::lgw_conf_rxif_s {
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^ missing `__bindgen_padding_0`, `__bindgen_padding_1`

I do understand the error (I'm trying to initialize a struct, but I'm not initializing the __bindgen_padding_ field). What I do not understand what is causing the __bindgen_padding_ / why it is there. Especially because it seems to depend on the version of the clang / arm-linux-gnueabihf package versions, this can be problematic because the __bindgenpadding` might be there or it might not.

Input C headers

Example of one of the headers:

struct coord_s {
    double  lat;    /*!> latitude [-90,90] (North +, South -) */
    double  lon;    /*!> longitude [-180,180] (East +, West -)*/
    short   alt;    /*!> altitude in meters (WGS 84 geoid ref.) */
};

https://github.com/Lora-net/sx1302_hal/blob/master/libloragw/inc/loragw_gps.h#L51

Reproducing the issue

The issue can be reproduced using the Docker Compose development environment: https://github.com/brocaar/chirpstack-concentratord/tree/test

git clone https://github.com/brocaar/chirpstack-concentratord -b test

rust:1.40-stretch

docker-compose run --rm chirpstack-concentratord-stretch bash

# native (works)
make build-native-debug

# arm crosscompile (works)
make build-armv7-debug

rust:1.40-buster

docker-compose run --rm chirpstack-concentratord-buster bash

# native (works)
make build-native-debug

# arm crosscompile (fails)
make build-armv7-debug
brocaar commented 4 years ago

Would this be the suggested solution when the number of fields (with / without __bindgen_padding) might vary depending the target architecture and used toolchain versions?

impl Default for SRC_DATA {
    fn default() -> Self {
        unsafe { std::mem::zeroed() }
    }
}

https://users.rust-lang.org/t/dealing-with-bindgen-generated-padding/34408/2

If so, would it be an idea to document this somewhere (maybe add it to the FAQ: https://rust-lang.github.io/rust-bindgen/faq.html)? It seems more people have asked questions about this. See the above link but also https://github.com/rust-lang/rust-bindgen/issues/1379

emilio commented 4 years ago

So that does allow you to write code like:

coord_s {
    lat: 10.,
    lon: 12.
    alt: 5,
    .. Default::default(),
}

That is an ok way to deal with this, I suspect. Though I am confused about why padding is needed for such a simple struct...

Padding is to make the compiler aware of your alignment and size requirements. But in those cases double and such don't have weird alignment and size... Are you compiling with some exotic clang flags like packing fields or such? What architectures is this building for?

brocaar commented 4 years ago

@emilio thanks for the quite response! I'm quite new to Rust and was not aware that you could also initialize a struct with .. Default::default(), to cover the remaining fields. That is a really helpful suggestion :-)

I'm cross-compiling for ARMv7. As far as I'm aware, I'm not using any exotic clang flags. Note that the padding is also dependent on the version of the toolchain used.

/usr/local/cargo/config:

[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

build.rs:

extern crate bindgen;

use std::env;
use std::path::PathBuf;

fn main() {
    // Tell cargo to tell rustc to link the loragw
    // shared library.
    println!("cargo:rustc-link-lib=loragw-sx1301");

    // The bindgen::Builder is the main entry point
    // to bindgen, and lets you build up options for
    // the resulting bindings.
    let bindings = bindgen::Builder::default()
        // The input header we would like to generate
        // bindings for.
        .header("wrapper.h")
        // Finish the builder and generate the bindings.
        .generate()
        // Unwrap the Result and panic on failure.
        .expect("Unable to generate bindings");

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

Makefile:

build-armv7-debug:
    BINDGEN_EXTRA_CLANG_ARGS="--sysroot=/usr/arm-linux-gnueabihf" cargo build --target armv7-unknown-linux-gnueabihf

Using the impl Default .. or the derive_default does work for both native and cross-compilation, so that will at least solve the issue for me for now.

Having an item in the FAQ about these __bindgen_padding fields might be helpful to others (it would have been helpful to me, especially being new to Rust).


You mentioned that you didn't expect the padding fields for such a simple C struct. If you would like to dive into this, note that this issue can be easily reproduced within the Docker Compose environment (see the commands and link in my issue description). This also takes care of compiling and putting the C libraries in the right place and setting up the ARM toolchain for cross-compiling (within the Docker container). So other than Docker and Docker Compose, this does not require any additional software on your machine.

emilio commented 4 years ago

Ok, thanks!

So a reduced test-case could be:

struct coord_s {
    double  lat;
    double  lon;
    short   alt;
};

Running it like:

bindgen t.h -- -target armv7-unknown-linux-gnueabihf

I can indeed reproduce the weird padding on newer clang, which is a bit weird. Will take a look.

I agree having docs to deal with this better would be great... Maybe you can send a PR? :)

emilio commented 4 years ago

Ok, so I took a look and #1710 should address this.

brocaar commented 4 years ago

I agree having docs to deal with this better would be great... Maybe you can send a PR? :)

Yes, I'll create a PR for this.