Help: Section not linked into program

Hi!

I am following the Embedonomicon book to try to make a minimum bare metal program run on RP2040 Pico. RP2040 MCUs require a bootloader stage2 in size of 256 bytes at the beginning of the flash, just before the vector table. I have had build.rs to compile the bootloader stage2 assembly source file, and added the section of the bootloader to link.x. However it turns out that the section was not linked into the program as expected.

Please help. Thanks!

Output of building (cargo build --verbose):

  Compiling cc v1.0.79
     Running `rustc --crate-name cc --edition=2018 D:\devel\rust\.cargo\registry\src\github.com-1ecc6299db9ec823\cc-1.0.79\src\lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=179 --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 -C metadata=e6c181523489c735 -C extra-filename=-e6c181523489c735 --out-dir D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\deps -L dependency=D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\deps --cap-lints allow`
   Compiling pico_min v0.1.0 (D:\devel_projects\rust_playground\bare_metal\pico_min)
     Running `rustc --crate-name build_script_build --edition=2021 build.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=179 --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 -C metadata=10c9512b47d3e6da -C extra-filename=-10c9512b47d3e6da --out-dir D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\build\pico_min-10c9512b47d3e6da -C incremental=D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\incremental -L dependency=D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\deps --extern cc=D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\deps\libcc-e6c181523489c735.rlib`    
     Running `D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\build\pico_min-10c9512b47d3e6da\build-script-build`
     Running `rustc --crate-name pico_min --edition=2021 src\main.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=179 --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 -C metadata=0df10126ed1b3803 -C extra-filename=-0df10126ed1b3803 --out-dir D:\devel_projects\rust_playground\bare_metal\pico_min\target\thumbv6m-none-eabi\debug\deps --target thumbv6m-none-eabi -C incremental=D:\devel_projects\rust_playground\bare_metal\pico_min\target\thumbv6m-none-eabi\debug\incremental -L dependency=D:\devel_projects\rust_playground\bare_metal\pico_min\target\thumbv6m-none-eabi\debug\deps -L dependency=D:\devel_projects\rust_playground\bare_metal\pico_min\target\debug\deps -C link-arg=-Tlink.x -L D:\devel_projects\rust_playground\bare_metal\pico_min\target\thumbv6m-none-eabi\debug\build\pico_min-3cd12687556d8125\out -L native=D:\devel_projects\rust_playground\bare_metal\pico_min\target\thumbv6m-none-eabi\debug\build\pico_min-3cd12687556d8125\out -l static=bs2_default_padded_checksummed`
    Finished dev [unoptimized + debuginfo] target(s) in 7.98s

Examine the bootloader object file, and things seem OK. The section named .boot2 with the right size (256 bytes) is there:

> arm-none-eabi-objdump -x .\target\thumbv6m-none-eabi\debug\build\pico_min-3cd12687556d8125\out\bs2_default_padded_checksummed.o

.\target\thumbv6m-none-eabi\debug\build\pico_min-3cd12687556d8125\out\bs2_default_padded_checksummed.o:     file format elf32-littlearm
.\target\thumbv6m-none-eabi\debug\build\pico_min-3cd12687556d8125\out\bs2_default_padded_checksummed.o
architecture: armv6s-m, flags 0x00000010:
HAS_SYMS
start address 0x00000000
private flags = 0x5000000: [Version5 EABI]

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000000  00000000  00000000  00000034  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000034  2**0
                  ALLOC
  3 .boot2        00000100  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  4 .ARM.attributes 00000022  00000000  00000000  00000134  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .boot2 00000000 .boot2
00000000 l    d  .ARM.attributes        00000000 .ARM.attributes

But that section .boot2 does not end up in the final ELF program: (It was expected to be at address 0, before .vector_table)

> arm-none-eabi-objdump .\target\thumbv6m-none-eabi\debug\pico_min -x

.\target\thumbv6m-none-eabi\debug\pico_min:     file format elf32-littlearm
.\target\thumbv6m-none-eabi\debug\pico_min
architecture: armv6s-m, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000009

Program Header:
    LOAD off    0x00010000 vaddr 0x00000000 paddr 0x00000000 align 2**16
         filesz 0x00000008 memsz 0x00000008 flags r--
    LOAD off    0x00010008 vaddr 0x00000008 paddr 0x00000008 align 2**16
         filesz 0x00000004 memsz 0x00000004 flags r-x
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**0
         filesz 0x00000000 memsz 0x00000000 flags rw-
private flags = 0x5000200: [Version5 EABI] [soft-float ABI]

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .vector_table 00000008  00000000  00000000  00010000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .text         00000004  00000008  00000008  00010008  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .debug_abbrev 00000127  00000000  00000000  0001000c  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  3 .debug_info   000005e6  00000000  00000000  00010133  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  4 .debug_aranges 00000030  00000000  00000000  00010719  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  5 .debug_ranges 00000018  00000000  00000000  00010749  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  6 .debug_str    000004d6  00000000  00000000  00010761  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  7 .debug_pubnames 000000ce  00000000  00000000  00010c37  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  8 .debug_pubtypes 0000038a  00000000  00000000  00010d05  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  9 .ARM.attributes 00000032  00000000  00000000  0001108f  2**0
                  CONTENTS, READONLY
 10 .debug_frame  00000038  00000000  00000000  000110c4  2**2
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 11 .debug_line   00000051  00000000  00000000  000110fc  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 12 .comment      00000013  00000000  00000000  0001114d  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*  00000000 12t2aefmlsgsvu09
00000004 g     O .vector_table  00000004 RESET_VECTOR
00000008 g     F .text  00000004 Reset

All of the source files are as following.

Cargo.toml:

[package]
name = "pico_min"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[build-dependencies]
cc = "1.0.25"

.cargo/config.toml:

[target.thumbv6m-none-eabi]
rustflags = [
    # "-C", "linker=arm-none-eabi-gcc", 
    "-C", "link-arg=-Tlink.x",
]

[build]
target = "thumbv6m-none-eabi"        # Cortex-M0 and Cortex-M0+

link.x:

/* Memory layout of the LM3S6965 microcontroller */
/* 1K = 1 KiBi = 1024 bytes */
MEMORY
{
  FLASH : ORIGIN = 0, LENGTH = 2M
  RAM : ORIGIN = 0x20000000, LENGTH = 256K
}

/* The entry point is the reset handler */
ENTRY(Reset);

/* EXTERN(RESET_VECTOR); */

SECTIONS
{

  .boot2 ORIGIN(FLASH) :
  {
    KEEP(*(.boot2));
  } > FLASH

  .vector_table :
  {
    /* First entry: initial Stack Pointer value */
    LONG(ORIGIN(RAM) + LENGTH(RAM));

    /* Second entry: reset vector */
    KEEP(*(.vector_table.reset_vector));
  } > FLASH

  .text :
  {
    *(.text .text.*);
  } > FLASH

  /DISCARD/ :
  {
    *(.ARM.exidx .ARM.exidx.*);
  }
}

src/main.rs:

#![no_main]
#![no_std]

use core::panic::PanicInfo;

#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
    // let _x = 42;

    // can't return so we go into an infinite loop here
    loop {}
}

// The reset vector, a pointer into the reset handler
#[link_section = ".vector_table.reset_vector"]
#[no_mangle]
pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset;

#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
    loop {}
}

bs2_default_padded_checksummed.S, the bootloader:

// Bootloader stage2 for RP2040 Pico

.cpu cortex-m0plus
.thumb

.section .boot2, "ax"

.byte 0x00, 0xb5, 0x32, 0x4b, 0x21, 0x20, 0x58, 0x60, 0x98, 0x68, 0x02, 0x21, 0x88, 0x43, 0x98, 0x60
.byte 0xd8, 0x60, 0x18, 0x61, 0x58, 0x61, 0x2e, 0x4b, 0x00, 0x21, 0x99, 0x60, 0x02, 0x21, 0x59, 0x61
.byte 0x01, 0x21, 0xf0, 0x22, 0x99, 0x50, 0x2b, 0x49, 0x19, 0x60, 0x01, 0x21, 0x99, 0x60, 0x35, 0x20
.byte 0x00, 0xf0, 0x44, 0xf8, 0x02, 0x22, 0x90, 0x42, 0x14, 0xd0, 0x06, 0x21, 0x19, 0x66, 0x00, 0xf0
.byte 0x34, 0xf8, 0x19, 0x6e, 0x01, 0x21, 0x19, 0x66, 0x00, 0x20, 0x18, 0x66, 0x1a, 0x66, 0x00, 0xf0
.byte 0x2c, 0xf8, 0x19, 0x6e, 0x19, 0x6e, 0x19, 0x6e, 0x05, 0x20, 0x00, 0xf0, 0x2f, 0xf8, 0x01, 0x21
.byte 0x08, 0x42, 0xf9, 0xd1, 0x00, 0x21, 0x99, 0x60, 0x1b, 0x49, 0x19, 0x60, 0x00, 0x21, 0x59, 0x60
.byte 0x1a, 0x49, 0x1b, 0x48, 0x01, 0x60, 0x01, 0x21, 0x99, 0x60, 0xeb, 0x21, 0x19, 0x66, 0xa0, 0x21
.byte 0x19, 0x66, 0x00, 0xf0, 0x12, 0xf8, 0x00, 0x21, 0x99, 0x60, 0x16, 0x49, 0x14, 0x48, 0x01, 0x60
.byte 0x01, 0x21, 0x99, 0x60, 0x01, 0xbc, 0x00, 0x28, 0x00, 0xd0, 0x00, 0x47, 0x12, 0x48, 0x13, 0x49
.byte 0x08, 0x60, 0x03, 0xc8, 0x80, 0xf3, 0x08, 0x88, 0x08, 0x47, 0x03, 0xb5, 0x99, 0x6a, 0x04, 0x20
.byte 0x01, 0x42, 0xfb, 0xd0, 0x01, 0x20, 0x01, 0x42, 0xf8, 0xd1, 0x03, 0xbd, 0x02, 0xb5, 0x18, 0x66
.byte 0x18, 0x66, 0xff, 0xf7, 0xf2, 0xff, 0x18, 0x6e, 0x18, 0x6e, 0x02, 0xbd, 0x00, 0x00, 0x02, 0x40
.byte 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x5f, 0x00, 0x21, 0x22, 0x00, 0x00
.byte 0xf4, 0x00, 0x00, 0x18, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a

build.rs:

use std::{env, error::Error, fs::File, io::Write, path::PathBuf};

use cc::Build;

fn main() -> Result<(), Box<dyn Error>> {
    // build directory for this crate
    let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());

    // extend the library search path
    println!("cargo:rustc-link-search={}", out_dir.display());

    // put `link.x` in the build directory
    File::create(out_dir.join("link.x"))?.write_all(include_bytes!("link.x"))?;

    // assemble the `asm.s` file
    Build::new().file("bs2_default_padded_checksummed.S").compile("bs2_default_padded_checksummed"); // <- NEW!

    // rebuild if `asm.s` changed
    println!("cargo:rerun-if-changed=bs2_default_padded_checksummed.S"); // <- NEW!

    Ok(())
}

I'm not familiar with your platform, just some general suggestions:

first, check your linker script is actually picked up by the toolchain, i.e. put something like this in the code:

#[link_section = ".boot2"]
#[no_mangle]
pub static BOOTLOADER: [u8; 256] = [0; 256];

check whether the .boot2 section is placed in the right place

if the linker script does work, then you must forget to actually link the bootloader into the final image. check your build script.

Hi, nerditation! Thanks for your reply. Actually earlier I have reworked my code almost the same way as what you suggested (I also initialized the static array with actual bytes). And that did work. The MCU booted to the reset handler successfully. (BTW I made a mistake about the flash address in the linker script but that's another story). Would you kindly please have a look at my build.rs (the source code is in my first post) to see if there is something missing?

I believe cc would build a static library by default, and sections from libraries are only copied to the final binary if the linker decides it is being referenced. one possible solutions Is:

you export a symbol from the bootloader and reference it from your rust code:

// bootloader.S

// export label
.global BOOTLOADER

// actual code here
BOOTLOADER:
.byte 0x00, ...
// main.rs

// declare external symbol, the actual type is not important here, we need its address
extern "C" {
	static BOOTLOADER: u8;
}

// dummy reference, extern static need unsafe
static __: &u8 =  unsafe { &bootloader } ;

just a reminder, I see your linker script doesn't have a .rodata section, so the dummy reference might also be discarded, you either change your linker script, or you change the dummy reference into, e.g., the .text section

well, just check the documentation for cc and found this api: link_lib_modifier()

you could try adding the +whole-archive modifer and forget about what I just said in the previous comment.

// build.rs
Build::new().file("bs2_default_padded_checksummed.S").link_lib_modifier("+whole-archive").compile("bs2_default_padded_checksummed");
2 Likes

Problem resolved nicely! A lot of thanks to @nerditation !!

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.