Is there a reason I am unable to specify the start offset of a Rust program in a linker script?

Viewed 301

I am trying to compile a Rust program for a Raspberry Pi. I am under the impression that the start address needs to be 0x8000, so I am using a custom linker script to lay out the program to follow this requirement:

SECTIONS
{
  .text 0x8000 : {
    *(.text)
  }

  .data : {
    *(.data)
  }
}

I specify this in the architecture file aarch64-unknown-none.json:

{
  "arch": "aarch64",
  "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
  "disable-redzone": true,
  "executables": true,
  "features": "+strict-align,+neon,+fp-armv8",
  "linker": "rust-lld",
  "linker-flavor": "ld.lld",
  "pre-link-args": {
    "ld.lld": [
      "-Taarch64-raspi3.ld"
    ]
  },
  "llvm-target": "aarch64-unknown-none",
  "max-atomic-width": 128,
  "panic-strategy": "abort",
  "relocation-model": "static",
  "target-pointer-width": "64",
  "unsupported-abis": [
    "stdcall",
    "stdcall-unwind",
    "fastcall",
    "vectorcall",
    "thiscall",
    "thiscall-unwind",
    "win64",
    "sysv64"
  ]
}

I build using the cargo build -Zbuild-std --features=raspi3 --target=aarch64-unknown-none.json --release command.

Here is my main.rs:

#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(test), no_main)]
#![feature(global_asm)]
#![feature(asm)]
#![feature(naked_functions)]

#[cfg(not(test))]
global_asm!(include_str!("platform/raspi3/start.s"));

mod aarch64;
mod panic;
mod platform;

Here is start.s:

.section .init
.global _start

.equ BASE,  0x3f200000 //Base address
.equ GPFSEL2, 0x08          //FSEL2 register offset 
.equ GPSET0,  0x1c          //GPSET0 register offset
.equ GPCLR0,0x28            //GPCLR0 register offset
.equ SET_BIT3,   0x08       //sets bit three b1000      
.equ SET_BIT21,  0x200000   //sets bit 21
.equ COUNTER, 0xf0000

_start:
    ldr x0, =BASE
    ldr x1, =SET_BIT3
    str x1, [x0, #GPFSEL2]
    ldr x1, =SET_BIT21
    str x1, [x0, #GPSET0]
    b _start

When I compile it, it places the start block at 0x0 as follows:

0000000000000000 <_start>:
   0:   d2a7e400        mov     x0, #0x3f200000                 // #1059061760
   4:   d2800101        mov     x1, #0x8                        // #8
   8:   f9000401        str     x1, [x0, #8]
   c:   d2a00401        mov     x1, #0x200000                   // #2097152
  10:   f801c001        stur    x1, [x0, #28]
  14:   17fffffb        b       0 <_start>

Is there a reason that this is happening?

1 Answers

When you write:

.text 0x8000 : {
    *(.text)
  }

you are telling the linker to set up the excutable .text section at address 0x8000, by concatenating the .text sections from every compilation module (*).

But then, in your assembly, the _start function is defined in the .section .init, that is not mentioned anywhere in the linker script. I'm not sure of what happens exactly with sections that are not mentioned in the linker script, but I'm pretty sure it is a bad idea to rely on that.

You probably want your .init section(s) to be at the start of the executable .text section:

  .text 0x8000 : {
    *(.init) /* _start */
    *(.text)
  }

NOTE: AFAIK, Rust does not emit all the code into a big .text section, but it creates a lot smaller .text.name_of_the_thing sections. You'll probably want to link them all of them together so:

  .text 0x8000 : {
    *(.init) /* _start */
    *(.text)
    *(.text.*)
  }
Related