rust-embedded / cortex-m-rt

Minimal startup / runtime for Cortex-M microcontrollers
https://rust-embedded.github.io/cortex-m-rt/
Apache License 2.0
358 stars 85 forks source link

Error compiling with rwpi relocation model. #320

Closed Acciente717 closed 3 years ago

Acciente717 commented 3 years ago

For research purpose, I'm trying to build an image for my cortex-m4 microcontroller with rwpi relocation model. However, during linking stage, lld throws me the error:

rust-lld: error: SBREL relocation to __sbss without static base

Below is an easy way to reproduce this error. This follows the tutorial in The Embedded Rust Book.

  1. cargo install cargo-generate to install an handy tool.
  2. cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart to download the example code.
  3. cd into the cloned directory. Modify .cargo/config. Add "-C", "relocation-model=rwpi" to rustflags.
  4. Run cargo build.

The example code in main.rs uses the cortex-m-rt crate, which during compile time will generate a link script for the linker. The generated link script can be found at ./target/thumbv7m-none-eabi/debug/build/cortex-m-rt-xxxxxxxxxxxxxxxx/out/link.x. I suppose that some information is missing for the linker. What should I do to resolve the error?

The same question is asked on Stackoverflow here.

adamgreig commented 3 years ago

Unfortunately rwpi is not fully supported by Rust on ARM at the moment. It turns out to be quite complicated; ARM contributed support to LLVM a while back of which some was merged but I don't believe anything has made it into Rust yet. If I remember correctly, I think anything static or any vtables won't work because they need to be moved into sdata instead of sbss and relocated correctly, but are not. There was some earlier discussion of this on the rust-embedded Matrix chat here too.

It would be nice to have, but right now it's not something we can do anything about in this crate. If you find out anything more, please update this thread!

Acciente717 commented 3 years ago

I successfully built and ran an OS binary image with rwpi. The code is here: https://github.com/Acciente717/mini-cortex-m4-os

Most of the code in boot.rs is copied from cortex-m-rt. I made some hack to the Reset() function to get it compile. The key to resolve this problem is to find out a way to read the linker script defined symbols.

Although the correct way of using, e.g., the __sbss symbol is to first declare it in the Rust source file and then take the address of it, it doesn't compile in this way. Instead, I tried using inline assembly

unsafe extern "C" fn Reset() -> ! {
    extern "C" {
        // These symbols come from `link.x`
        static mut __sbss: u32;
        static mut __ebss: u32;
        static mut __sdata: u32;
        static mut __edata: u32;
        static __sidata: u32;
    }

    asm!(
        "ldr r9, ={sdata}",   // load static base
        "ldr r0, ={sbss}",
        "ldr r1, ={ebss}",
        "bl  {memzero}",      // zero out bss section
        "ldr r0, ={sdata}",
        "ldr r1, ={edata}",
        "ldr r2, ={sidata}",
        "bl  {memcopy}",      // initialize data section in RAM
        "bl  {main}",         // jump to main
        sbss = sym __sbss,
        ebss = sym __ebss,
        sdata = sym __sdata,
        edata = sym __edata,
        sidata = sym __sidata,
        memzero = sym memzero,
        memcopy = sym memcopy,
        main = sym main
    );

    loop {}
}

Magically, this compiles. From the disassembly, we can see that these symbols get allocated in ROM(flash).

08000400 <Reset>:
 8000400:       b580            push    {r7, lr}
 8000402:       466f            mov     r7, sp
 8000404:       f8df 9018       ldr.w   r9, [pc, #24]   ; 8000420 <_stext+0x20>
 8000408:       4806            ldr     r0, [pc, #24]   ; (8000424 <_stext+0x24>)
 800040a:       4907            ldr     r1, [pc, #28]   ; (8000428 <_stext+0x28>)
 800040c:       f000 f8a5       bl      800055a <_ZN17mini_cortex_m4_os4boot7memzero17h719c1bc8e3de9fd9E>
 8000410:       4806            ldr     r0, [pc, #24]   ; (800042c <_stext+0x2c>)
 8000412:       4907            ldr     r1, [pc, #28]   ; (8000430 <_stext+0x30>)
 8000414:       4a07            ldr     r2, [pc, #28]   ; (8000434 <_stext+0x34>)
 8000416:       f000 f8b5       bl      8000584 <_ZN17mini_cortex_m4_os4boot7memcopy17h28a08465ccbd97e2E>
 800041a:       f000 fa85       bl      8000928 <_ZN17mini_cortex_m4_os4main17ha05f5ee7bef82e53E>
 800041e:       e7fe            b.n     800041e <Reset+0x1e>
 8000420:       20000000        .word   0x20000000  ; this is __sdata
 8000424:       20000004        .word   0x20000004  ; this is __sbss
 8000428:       20000130        .word   0x20000130  ; this is __ebss
 800042c:       20000000        .word   0x20000000  ; this is __sdata again
 8000430:       20000004        .word   0x20000004  ; this is __edata
 8000434:       08004da4        .word   0x08004da4  ; this is __sidata

I don't know exactly why it works. It seems that we should always take the address of a compiler defined symbol, which doesn't compile, but should not read it as a variable, which does compile. Wierd. Any explanation on this is greatly appreciated.