Link_section not appearing in release

I am trying to use the #[link_sections] attribute to inject data into a program after it has been compiled.
This works nicely with programs compiled in debug mode but the section disappears from the executable when it is compiled with the cargo --release flag.
Sample:

use std::ffi::CStr;

#[link_section = ".test_section"]
static TEST_VAR: [u8; 1024] = [0; 1024];

fn main() {
    let mut len = 0;
    for (idx, value) in TEST_VAR.iter().enumerate() {
        if *value == 0 {
            len = idx;
            break;
        }
    }
    if len > 0 {
        match CStr::from_bytes_with_nul(&TEST_VAR[0..=len]) {
            Ok(cstr) => {
                println!("Injected string: '{}'", &*cstr.to_string_lossy());
            }
            Err(why) => {
                eprintln!("Error: Invalid string found, error: {}", why);
            }
        }
    } else {
        eprintln!("No String found");
    }
}

Will work as designed in release or debug mode.
When compiled in debug mode I can find the segment with

readelf -S target/debug/test_link_section | grep test_section
  [18] .test_section     PROGBITS         00000000000353d7  000353d7

But when compiled with --release the section does not exist.

Any ideas on how to fix this problem or other options on injecting data into a compiled program, so that it can be accessed at runtime are welcome..

Does adding #[used] to the static declaration work?

Alternatively, you could use include_bytes! to inject the data at compile time instead of later in the build process.

@2e71828, thanks for your remarks, I have worked with include_bytes but in this case I want to inject stuff after compiling.

I have tried to add #[no_mangle] and #[used] but it makes no difference. I have also played with using #[export_name] with the same effect. The symbols appear only in the debug version.

At least for the #[export_name] variant it looks like the symbols are being stripped together with debug symbols.

It’s been a long time since I did this sort of thing, but you should be able to specify an extern global and use binutils’ objcopy to transform a raw binary file into an ELF object file with one symbol; that can be linked with the Rust application as late as you’d like.

Unfortunately, I don’t remember the objcopy incantation that does this.

It's because the compiler optimized out every access to the static variable since it's initialized with all zero and can nobody change its value at runtime. To prevent such optimization you can replace every access to it with std::ptr::read_volatile.

Isn’t that the exact thing that #[used] is supposed to prevent?

No, it affects nothing on optimization.

https://doc.rust-lang.org/reference/abi.html#the-used-attribute

From that documentation:

The used attribute can only be applied to static items. This attribute forces the compiler to keep the variable in the output object file (.o, .rlib, etc. excluding final binaries) even if the variable is not used, or referenced, by any other item in the crate.

This makes it sound like the symbol should survive optimization, but it then goes on to say:

However, the linker is still free to remove such an item.

So, it sounds like #[used] is supposed to keep the symbol alive through optimization until the final link stage, but you might need to pass linker options through rustc to ensure it is included in the final executable.

@Hyeonu , looks like you are right. Playing around with this I have noticed the same thing. If I initialize the variables with actual data like so :

#[link_section = ".test_section"]
static TEST_VAR1: [u8;16] = [ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

the sections as well as the symbol make it to the executable even in a release mode.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.