Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

no_mangle/used static is only present in output when in reachable module #47384

Closed
phil-opp opened this issue Jan 12, 2018 · 61 comments
Closed
Labels
A-incr-comp Area: Incremental compilation A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@phil-opp
Copy link
Contributor

phil-opp commented Jan 12, 2018

Statics that are marked no_mangle and/or used only get to the linker if they are in a reachable module of a reachable crate. The static itself does not need to be used in code, only some function in the same module. This previously worked in our project, so this seems like a regression.

Example

The issue is best explained with a small example crate named bug_test, which can be found here:

src/lib.rs:

pub mod foo {
    #[no_mangle]
    #[used]
    pub static STATIC: [u32; 10] = [1; 10];

    pub fn hello() {}
}

pub fn bar() {
    foo::hello(); // STATIC not present if commented out
}

src/main.rs:

extern crate bug_test;

fn main() {
    bug_test::bar(); // STATIC not present if commented out 
}

Linker script linker.ld:

SECTIONS
{
    .static : ALIGN(4)
    {
        KEEP(*(.rodata.STATIC));
    }
}

Build using:

RUSTFLAGS='-Z pre-link-args=-Tlinker.ld' cargo build

Show contents of .static section:

> objdump -s -j".static" target/debug/bug_test

target/debug/bug_test:     file format elf64-x86-64

Contents of section .static:
 3354c 01000000 01000000 01000000 01000000  ................
 3355c 01000000 01000000 01000000 01000000  ................
 3356c 01000000 01000000                    ........  

Comment out one of the STATIC not present if commented out lines and recompile. Then STATIC no longer exist in the output and the .static section is empty:

> objdump -s -j".static" target/debug/bug_test

target/debug/bug_test:     file format elf64-x86-64

objdump: section '.static' mentioned in a -j option, but not found in any input file

Versions

> rustc --version
rustc 1.27.0-nightly (ac3c2288f 2018-04-18)
> cargo --version
cargo 1.26.0-nightly (008c36908 2018-04-13)
> ld --version
GNU ld (GNU Binutils for Ubuntu) 2.26.1

Edit: Added the used attribute.

@phil-opp
Copy link
Contributor Author

cc @oli-obk

@nagisa
Copy link
Member

nagisa commented Jan 12, 2018

Doesn’t seem like a bug to me.

The library bug_test is a rlib, so it has no guarantees as to what appears in the binary or what doesn’t. Eventually rlib probably is expected to not contain any executable code whatsoever and not use a linker, which would break your linker script approach either way.

In your scenario it is entirely up to Rust whether:

  1. it uses a linker when generating code for the intermediate crates;
  2. it emits any binary or linkable code into the rlib or just aggregates intermediate representations and generates all the code in one go during the final binary compilation;
  3. it optimises out unused globals for the binary (in absence of preventative attributes such as #[used]) before they reach the linker.

@petrochenkov
Copy link
Contributor

IIRC, @joshtriplett reported that lang team discussed this a few months ago and decided to make link-time visibility of no_mangle items based purely on their pub annotation and not reachability.
I can't find that comment though and supposedly there was no progress since then.

@joshtriplett
Copy link
Member

@petrochenkov I recall that as well, and that sounds accurate.

@oli-obk
Copy link
Contributor

oli-obk commented Jan 13, 2018

IIRC we tried both the used attribute and making it public and it gave us the same behaviour.

@phil-opp
Copy link
Contributor Author

phil-opp commented Jan 13, 2018

IIRC we tried both the used attribute and making it public and it gave us the same behaviour.

Yes, used does not change anything. This behaves exactly the same:

// src/lib.rs

#![feature(used)]

pub mod foo {
    #[no_mangle]
    #[used]
    pub static STATIC: [u32; 10] = [1; 10];

    pub fn hello() {}
}

pub fn bar() {
    foo::hello(); // STATIC not present if commented out
}

Edit: I updated the title and the issue description to reflect this.

@pietroalbini pietroalbini added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 6, 2018
@phil-opp phil-opp changed the title no_mangle static is only present in output when in reachable module no_mangle/used static is only present in output when in reachable module Apr 20, 2018
@phil-opp
Copy link
Contributor Author

phil-opp commented May 4, 2018

So @japaric explained that this is a problem with our linker script and has nothing to do with the no_mangle and used attributes: #40289 (comment). The fix is to add an EXTERN(STATIC); declaration to the linker script, which tells the linker to keep looking for a STATIC symbol, even if all other symbols are already resolved.

@phil-opp phil-opp closed this as completed May 4, 2018
@dtolnay dtolnay added C-bug Category: This is a bug. and removed C-enhancement Category: An issue proposing an enhancement or a PR with one. labels May 18, 2019
@dtolnay dtolnay reopened this May 18, 2019
@dtolnay
Copy link
Member

dtolnay commented May 18, 2019

Reopening because people continue to have problems with #[used] statics failing to appear, even in use cases without a custom linker script.

@dtolnay dtolnay added the A-linkage Area: linking into static, shared libraries and binaries label May 18, 2019
@CJKay
Copy link

CJKay commented May 18, 2019

FYI I managed to work around this in my case by doing the following:

  • In the linker script, specify EXTERN(xyz) where xyz is a symbol you are trying to export (you actually only need to do this for one symbol if all your symbols are in one module, presumably because it stops the linker from removing anything else in that library (object file?))
  • Mark the symbol as #[no_mangle] and pub
  • pub use the symbol from the binary

Realistically, this is not a long-term solution - the first step is reasonable, but steps 2 and 3 are just hacks. There's no reason the symbol should have to be given an unmangled name, and definitely no reason the binary should have to pollute its own code to work around unusual linker behaviour.

Additionally, some of the time rustc warns that the #[no_mangle] is not used, but I can't reproduce that reliably:

 --> src/lib.rs:3:1
  |
3 | #[no_mangle]
  | ^^^^^^^^^^^^
  |
  = note: #[warn(unused_attributes)] on by default```

@oli-obk
Copy link
Contributor

oli-obk commented May 26, 2019

Additionally, some of the time rustc warns that the #[no_mangle] is not used, but I can't reproduce that reliably:

That should be fixed on master, can you confirm that it is fixed for you now?

@zachreizner
Copy link
Contributor

I ran into this issue today, and strangely only in debug builds. With --release, the #[used] static items from an otherwise unreachable module appeared in the resulting binary. My theory is that the divergent behavior is due the default incremental builds in debug mode. Adding this to my Cargo.toml seems to fix this issue, but it feels way to brittle to rely on:

[profile.dev]
incremental = false

@Shnatsel
Copy link
Member

Shnatsel commented Jul 31, 2020

This issue is still present for me on rustc 1.45.1. It occurs in release mode both with and without LTO. Using #[link_section = "foo"] doesn't help. Actually using the static in some other code does preserve it.

Comment out the println! with a function call to reproduce: https://github.com/Shnatsel/rust-audit/blob/master/hello-auditable/src/main.rs

Declaration of the static: https://github.com/Shnatsel/rust-audit/blob/master/auditable/src/lib.rs

@carbotaniuman
Copy link
Contributor

carbotaniuman commented Mar 27, 2022

If anyone read my last message, I think that's just my computer getting hit by cosmic rays/some hardware bug, because trying to reproduce the fix led to cascading failures across my Linux install, sigh, so much time wasted. Sorry for any noise.

Running everything again on my laptop now and the case where you're including a static into the final main.rs appears to work with #[used(linker)]. Everything else, such as linkme, inventory won't get solved with #[used(linker)] due to the linker behavior mentioned above, and it looks like it'll require proper compiler support. It should be possible if #93791 doesn't get merged, to force all extern objects into their own CGU.

Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Apr 10, 2022
Generate synthetic object file to ensure all exported and used symbols participate in the linking

Fix rust-lang#50007 and rust-lang#47384

This is the synthetic object file approach that I described in rust-lang#95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed.

Related rust-lang#93791, rust-lang#95363

r? `@petrochenkov`
cc `@carbotaniuman`
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Apr 11, 2022
Generate synthetic object file to ensure all exported and used symbols participate in the linking

Fix rust-lang#50007 and rust-lang#47384

This is the synthetic object file approach that I described in rust-lang#95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed.

Related rust-lang#93791, rust-lang#95363

r? ``@petrochenkov``
cc ``@carbotaniuman``
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Apr 11, 2022
Generate synthetic object file to ensure all exported and used symbols participate in the linking

Fix rust-lang#50007 and rust-lang#47384

This is the synthetic object file approach that I described in rust-lang#95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed.

Related rust-lang#93791, rust-lang#95363

r? ```@petrochenkov```
cc ```@carbotaniuman```
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Apr 17, 2022
Generate synthetic object file to ensure all exported and used symbols participate in the linking

Fix rust-lang#50007 and rust-lang#47384

This is the synthetic object file approach that I described in rust-lang#95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed.

Related rust-lang#93791, rust-lang#95363

r? `@petrochenkov`
cc `@carbotaniuman`
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Apr 18, 2022
Generate synthetic object file to ensure all exported and used symbols participate in the linking

Fix rust-lang#50007 and rust-lang#47384

This is the synthetic object file approach that I described in rust-lang#95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed.

Related rust-lang#93791, rust-lang#95363

r? `@petrochenkov`
cc `@carbotaniuman`
bors added a commit to rust-lang-ci/rust that referenced this issue Apr 25, 2022
Generate synthetic object file to ensure all exported and used symbols participate in the linking

Fix rust-lang#50007 and rust-lang#47384

This is the synthetic object file approach that I described in rust-lang#95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed.

Related rust-lang#93791, rust-lang#95363

r? `@petrochenkov`
cc `@carbotaniuman`
@bjorn3
Copy link
Member

bjorn3 commented May 16, 2022

Has this been fixed by #95604?

Edit: according to the description for #95604, yes. It just didn't get picked up by github.

@bjorn3 bjorn3 closed this as completed May 16, 2022
yvt added a commit to r3-os/r3 that referenced this issue May 23, 2022
Fixes linker errors in an application that is linked to `r3_port_riscv`
and does not use the `riscv-rt`-compatible startup routine provided by
`r3_port_riscv::use_rt!`.

Rust implements `rlib`s as static libraries (archives). Linkers treat
archives differently from object files: all object files participate in
linking, while archives will only participate in linking if they can
satisfy at least one undefined reference (version scripts don't count).
This means that in an application that does not use `use_rt!`,
`libriscv_rt*.rlib` does not participate in linking at all. This
behavior also causes `#[no_mangle]` and `#[used]` items to be ignored by
the linker (`KEEP` in linker scripts can't keep them either), leading to
a long-standing bug in Rust ([rust-lang/rust#47384][2]).

The situation changed with the merge of [rust-lang/rust#95604][1]. To
fix [rust-lang/rust#47384][2], this PR introduced a synthetic object
file containing references to all symbols pertaining to `#[no_mangle]`
and `#[used]` items. `libriscv_rt*.rlib` now participates in linking,
but the expectation is that `--gc-sections` will remove unused items in
the end, unless they are explicitly retained by `KEEP` in linker scripts
or other means.

This change unexpectedly caused breakage in the tests for `qemu_sifive_u
_s_rv(64,32)` targets, which use a custom startup routine instead of
`use_rt!`. For some reason, the linker didn't respect `--gc-sections`
for some items from `libriscv_rt*.rlib` and decided to include them in
the output binary. These items reference symbols that are defined by
the `riscv-rt` linker script, which isn't used by `qemu_sifive_u_s_
rv(64,32)` targets, hence the linker error.

The thing is, `riscv-rt` outputs these items in the `.init` section[3],
which is one of the section names hard-coded in LLD[4] to be excluded
from `--gc-sections`. As a result, these items were considered for
inclusion despite being referenced from nowhere.

This commit works around this problem by making `r3_port_riscv`'s
`riscv-rt` dependency optional, ensuring that `riscv-rt` participates
in linking only if needed by the application.

[1]: rust-lang/rust#95604
[2]: rust-lang/rust#47384
[3]: https://github.com/rust-embedded/riscv-rt/blob/7de3d2744a465ad723519c04f13c56664e138cb9/asm.S#L20
[4]: https://github.com/llvm/llvm-project/blob/b86440ecde5c1dec5b898a3f1bc08ab9853d5ed9/lld/ELF/MarkLive.cpp#L183
yvt added a commit to r3-os/r3 that referenced this issue May 24, 2022
Fixes linker errors in an application that is linked to `r3_port_riscv`
and does not use the `riscv-rt`-compatible startup routine provided by
`r3_port_riscv::use_rt!`.

Rust implements `rlib`s as static libraries (archives). Linkers treat
archives differently from object files: all object files participate in
linking, while archives will only participate in linking if they can
satisfy at least one undefined reference (version scripts don't count).
This means that in an application that does not use `use_rt!`,
`libriscv_rt*.rlib` does not participate in linking at all. This
behavior also causes `#[no_mangle]` and `#[used]` items to be ignored by
the linker (`KEEP` in linker scripts can't keep them either), leading to
a long-standing bug in Rust ([rust-lang/rust#47384][2]).

The situation changed with the merge of [rust-lang/rust#95604][1]. To
fix [rust-lang/rust#47384][2], this PR introduced a synthetic object
file containing references to all symbols pertaining to `#[no_mangle]`
and `#[used]` items. `libriscv_rt*.rlib` now participates in linking,
but the expectation is that `--gc-sections` will remove unused items in
the end, unless they are explicitly retained by `KEEP` in linker scripts
or other means.

This change unexpectedly caused breakage in the tests for `qemu_sifive_u
_s_rv(64,32)` targets, which use a custom startup routine instead of
`use_rt!`. For some reason, the linker didn't respect `--gc-sections`
for some items from `libriscv_rt*.rlib` and decided to include them in
the output binary. These items reference symbols that are defined by
the `riscv-rt` linker script, which isn't used by `qemu_sifive_u_s_
rv(64,32)` targets, hence the linker error.

The thing is, `riscv-rt` outputs these items in the `.init` section[3],
which is one of the section names hard-coded in LLD[4] to be excluded
from `--gc-sections`. As a result, these items were considered for
inclusion despite being referenced from nowhere.

This commit works around this problem by making `r3_port_riscv`'s
`riscv-rt` dependency optional, ensuring that `riscv-rt` participates
in linking only if needed by the application.

P.S. An exclamation mark (`!`) in the commit headline will be used to
indicate breaking changes from now on, as per [Conventional Commits
1.0.0][5].

[1]: rust-lang/rust#95604
[2]: rust-lang/rust#47384
[3]: https://github.com/rust-embedded/riscv-rt/blob/7de3d2744a465ad723519c04f13c56664e138cb9/asm.S#L20
[4]: https://github.com/llvm/llvm-project/blob/b86440ecde5c1dec5b898a3f1bc08ab9853d5ed9/lld/ELF/MarkLive.cpp#L183
[5]: https://www.conventionalcommits.org/en/v1.0.0/
yvt added a commit to r3-os/r3 that referenced this issue May 24, 2022
Fixes linker errors in an application that is linked to `r3_port_riscv`
and does not use the `riscv-rt`-compatible startup routine provided by
`r3_port_riscv::use_rt!`.

Rust implements `rlib`s as static libraries (archives). Linkers treat
archives differently from object files: all object files participate in
linking, while archives will only participate in linking if they can
satisfy at least one undefined reference (version scripts don't count).
This means that in an application that does not use `use_rt!`,
`libriscv_rt*.rlib` does not participate in linking at all. This
behavior also causes `#[no_mangle]` and `#[used]` items to be ignored by
the linker (`KEEP` in linker scripts can't keep them either), leading to
a long-standing bug in Rust ([rust-lang/rust#47384][2]).

The situation changed with the merge of [rust-lang/rust#95604][1]. To
fix [rust-lang/rust#47384][2], this PR introduced a synthetic object
file containing references to all symbols pertaining to `#[no_mangle]`
and `#[used]` items. `libriscv_rt*.rlib` now participates in linking,
but the expectation is that `--gc-sections` will remove unused items in
the end, unless they are explicitly retained by `KEEP` in linker scripts
or other means.

This change unexpectedly caused breakage in the tests for `qemu_sifive_u
_s_rv(64,32)` targets, which use a custom startup routine instead of
`use_rt!`. For some reason, the linker didn't respect `--gc-sections`
for some items from `libriscv_rt*.rlib` and decided to include them in
the output binary. These items reference symbols that are defined by
the `riscv-rt` linker script, which isn't used by `qemu_sifive_u_s_
rv(64,32)` targets, hence the linker error.

The thing is, `riscv-rt` outputs these items in the `.init` section[3],
which is one of the section names hard-coded in LLD[4] to be excluded
from `--gc-sections`. As a result, these items were considered for
inclusion despite being referenced from nowhere.

This commit works around this problem by making `r3_port_riscv`'s
`riscv-rt` dependency optional, ensuring that `riscv-rt` participates
in linking only if needed by the application.

P.S. I'll try to use an exclamation mark (`!`) in the commit headline
to indicate breaking changes from now on, as per [Conventional Commits
1.0.0][5].

[1]: rust-lang/rust#95604
[2]: rust-lang/rust#47384
[3]: https://github.com/rust-embedded/riscv-rt/blob/7de3d2744a465ad723519c04f13c56664e138cb9/asm.S#L20
[4]: https://github.com/llvm/llvm-project/blob/b86440ecde5c1dec5b898a3f1bc08ab9853d5ed9/lld/ELF/MarkLive.cpp#L183
[5]: https://www.conventionalcommits.org/en/v1.0.0/
Raekye added a commit to Raekye/export-symbols-test that referenced this issue Oct 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-incr-comp Area: Incremental compilation A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests