`__attribute__((weak))` declarations are emitted as ordinary externs, producing strong undefined references on ELF

Open
#3,402 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
52/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
c, cpp, rust
Domain
devtools

Research direction

Start with the weak_api.h reproducer and run.sh, then inspect the generated bindings.rs and the ELF symbol results described in the issue. Trace how bindgen handles attribute((weak)) declarations and compare the Rust output with the C caller. Done means weak imports remain optional, or bindgen reports that the selected output mode cannot preserve them.

Written by the indexing model from the issue text.

Description

Input C/C++ Header
int api_version(void);
int optional_api(int value) __attribute__((weak));

The first declaration makes sure the test still depends on the shared library
when optional_api is missing. The weak function is used in the usual way:

return optional_api ? optional_api(value) : value + 100;
Bindgen Invocation
$ bindgen weak_api.h \
    --rust-target 1.75 \
    --no-layout-tests \
    --no-rustfmt-bindings \
    --output bindings.rs \
    -- -I.
Actual Results

The relevant output, reformatted for readability, is:

extern "C" {
    pub fn api_version() -> ::std::os::raw::c_int;
}
extern "C" {
    pub fn optional_api(
        value: ::std::os::raw::c_int,
    ) -> ::std::os::raw::c_int;
}

optional_api is represented like an ordinary required function. With bindgen
0.72.1 and rustc 1.75, the C and Rust callers lower to:

; C
declare extern_weak i32 @optional_api(i32 noundef)

; Rust, using bindings.rs unchanged
declare noundef i32 @optional_api(i32 noundef) unnamed_addr

A source build from current main at
9d26c6eddeff9192ddedb563192abe3128fc5aae produced the same ordinary Rust
declaration. In that run, the ELF symbol tables contain:

C caller:    WEAK   UND optional_api
Rust caller: GLOBAL UND optional_api

The full reproducer is
weak-function-linkage-repro.zip.
It builds an old library that exports only api_version, and a new library
that also exports optional_api:

$ unzip weak-function-linkage-repro.zip
$ cd repro
$ BINDGEN=/path/to/bindgen ./run.sh

Both the 0.72.1 and current-main runs gave these results:

C linked to old:                 version=1 fallback=105
C linked to new:                 version=2 optional=205
Rust linked to new:              version=2 optional=205
Rust linked directly to old:     link error, undefined reference to optional_api
C linked to new, run with old:   version=1 fallback=105
Rust linked to new, run with old: exit 127, undefined symbol: optional_api

The C caller can run with the old library and take its fallback path. The Rust
caller using the unchanged generated declaration either fails to link or fails
during loader symbol resolution.

As a header-level example, JACK marks APIs such as
jack_set_latency_callback with JACK_WEAK_EXPORT so a caller can check
whether the function exists when using an older JACK library. Bindgen emits
that declaration as an ordinary extern function too.

--dynamic-loading WeakApi is a separate opt-in libloading workaround.
Without --dynamic-link-require-all, both api_version and optional_api
become fallible lookups. It does not preserve the default linker behavior or
infer optionality from the weak attribute.

$ bindgen --version
bindgen 0.72.1
$ clang --version
Ubuntu clang version 15.0.7
$ rustc --version
rustc 1.75.0

Release target: x86_64-unknown-linux-gnu.

The current-main run used rustc 1.82.0. Its bindgen CLI reported 0.72.0, so the
commit above identifies that build.

Expected Results

A binding that keeps optional_api optional, or an explicit diagnostic if the
selected output mode cannot preserve weak imports.

I am not prescribing a particular Rust representation. Silently emitting an
ordinary extern function changes the weak declaration into a required symbol
on ELF.

Dominant language
Rust
Stars
5.3k
Forks
846
Avg merge
2d 6h
Merged PRs (30d)
11

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from rust-lang/rust-bindgen

All issues in rust-lang/rust-bindgen

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.