Fns disappearing during link

I'm trying to compile the following code in a binary crate using the riscv32-imc toolchain and the default linker script (that is, I haven't added anything to link-arg yet):

#![feature(core_intrinsics)]
#![feature(start)]
#![no_main]
#![no_std]

use core::panic::PanicInfo;
use core::intrinsics::volatile_store;

#[repr(C)]
pub struct UART {
    rdata: u32,
    wdata: u32,
}

#[start]
#[no_mangle]
pub fn start() -> ! {
    let uart0 = 0x8051_0000 as *mut UART;
    loop {
        unsafe {
            volatile_store(&mut (*uart0).wdata, 'g' as u32);
        }
    }
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

But when I do the resulting elf file doesn't contain any .text section, it's just got some debugging bits in it. Looking at the intermediate .o file it's correctly generated the code, it just seems like the final link step is optimizing it out or otherwise dropping it.

Am I doing something obviously wrong? Subtly wrong? :upside_down_face:

What does your Cargo.toml look like?

What default linker script are you using? Can you give us a link to it?

What you're describing will happen if the linker script doesn't use the name "start" for the entry point, or doesn't contain an appropriate KEEP directive for it. If the linker script was not designed for embedded use (e.g. it targets RISC-V Linux), for example, it will be expecting _start and likely putting your entry point at the wrong address.

1 Like

Try the #[used] attribute. I think it is Link Time Optimzation (LTO) that is cleaning out what it thinks are unused functions.

My Cargo.toml is as simple as it gets:

[package]                                                                                                                                                                                                                                                      
name = "foo"                                                                                                                                                                                                                                                   
version = "0.1.0"                                                                                                                                                                                                                                              
authors = ["Garret Kelly <garret.kelly@gmail.com>"]                                                                                                                                                                                                            
edition = "2018"                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                               
[dependencies]

I'm building with cargo 1.35.0-nightly (0e35bd8af 2019-03-13).

I'm using the default linker script from riscv32imc-unknown-none-elf, or at least believe I am because I'm not specifying any kind of override for it. But, you were right. The default linker script evidently specifies ENTRY(_start), and having:

#[no_mangle]
fn _start() -> ! {

does end up with the symbol in the final ELF. And now using a custom linker script with a custom ENTRY (and #[no_mangle]) on that symbol does work.

The part that confused to me is #![feature(start)] and #[start], which I thought was aliasing the fn I used it on to something that was declared ENTRY in the linker script but that appears to have been a wrong assumption. Thanks for pointing me the right direction!

Glad that helped! Happy hacking!