tock / libtock-rs

Rust userland library for Tock
Apache License 2.0
163 stars 109 forks source link

runtime/libtock_layout.ld: ASSERT well-aligned RAM address #477

Closed lschuermann closed 1 year ago

lschuermann commented 1 year ago

Rust's default linker (llvm-ld) as used with the Rust toolchain versions of at least 2022-06-10, 2023-01-26, and 2023-06-27 can produce broken ELF binaries when the RAM region's start address is not well-aligned to a 4kB boundary. Unfortunately, this behavior is rather tricky to debug: instead of refusing to produce a binary or producing a corrupt output, it generates an ELF file which includes a segment that points to the ELF file's header itself. elf2tab will include this segment in the final binary (as it is set to be loaded), producing broken TBFs. This (overrideable) check is designed to warn users that the linker may be misbehaved under these conditions.

To reproduce this on this current git revision, with a Rust toolchain version 2023-06-27, change the opentitan.ld layout file to add an offset of 0x800 bytes to the RAM segment's start address:

@@ -5,7 +5,7 @@ MEMORY {
    * the kernel binary, check for the actual address of APP_MEMORY!
    */
   FLASH (X) : ORIGIN = 0x20030000, LENGTH = 32M
-  RAM   (W) : ORIGIN = 0x10004000, LENGTH = 512K
+  RAM   (W) : ORIGIN = 0x10004000 + 0x800, LENGTH = 512K - 0x800
 }

 TBF_HEADER_SIZE = 0x60;

Further, remove the .tbf_header reservation:

@@ -67,7 +67,7 @@ SECTIONS {
     /* Add a section where elf2tab will place the TBF headers, so that the rest
      * of the FLASH sections are in the right locations. */ .tbf_header (NOLOAD) : {
-        . = . + TBF_HEADER_SIZE;
+        /* . = . + TBF_HEADER_SIZE; */
     } > FLASH

     /* Runtime header. Contains values the linker knows that the runtime needs

Now, compile the console example for the opentitan target. When looking at the generated ELF file, it contains a segment at (an ELF-file) offset 0x000000, set to be LOADed, with a non-zero size. This segment thus points to the ELF file header itself.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x10004034 0x10004034 0x000c0 0x000c0 R   0x4
  LOAD           0x000000 0x10004000 0x10004000 0x000f4 0x000f4 R   0x1000
  LOAD           0x001000 0x20030000 0x20030000 0x00d64 0x00d64 R E 0x1000
  LOAD           0x001d64 0x20030d64 0x20030d64 0x001ec 0x001ec R   0x1000
  LOAD           0x002800 0x10004800 0x20030f50 0x00100 0x00100 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0

 Section to Segment mapping:
  Segment Sections...
   00
   01
   02     .start .text
   03     .rodata
   04     .stack
   05

When switching to the GNU LD instead, we get a correct ELF file. For that, make the following change to .cargo/config:

@@ -9,6 +9,8 @@ rtv7em = "rthumbv7em"
 # Common settings for all embedded targets
 [target.'cfg(any(target_arch = "arm", target_arch = "riscv32"))']
 rustflags = [
+    "-C", "linker=riscv64-unknown-elf-ld",
+    "-C", "link-arg=-melf32lriscv",
     "-C", "relocation-model=static",
     "-C", "link-arg=-Tlayout.ld",
 ]

Further, change the .start section's alignment, as GNU LD does not support specifying a section alignment constraint without a preceding section:

@@ -73,7 +73,8 @@ SECTIONS {
     /* Runtime header. Contains values the linker knows that the runtime needs
      * to look up. */
-    .start ALIGN(4) : {
+    . = ALIGN(4);
+    .start : {
         /* We combine rt_header and _start into a single section. If we don't,
          * elf2tab does not parse the ELF file correctly for unknown reasons.
          */

This is the segment layout of the resulting binary, which is correct and no longer references the ELF header itself:

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x10004000 0x10004000 0x00094 0x00900 RW  0x1000
  LOAD           0x001000 0x20030000 0x20030000 0x00f50 0x00f50 R E 0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

 Section to Segment mapping:
  Segment Sections...
   00     .stack
   01     .start .text .rodata
   02

(In this case, the .stack section is included in the final binary in a segment of size 0x900, to account for the segment alignment constraints. This also points to the ELF-internal offset of 0x000000, so referencing the ELF header. Yet this is fine in this case, as the .stack symbol within that region is beyond the allocated MemSiz offset (at an offset of 0x800), so zero-initialized as intended. The .stack should not at all end in the final binary, and never at a PhysAddr / load address within the RAM section; however this is an orthogonal issue.)

jrvanwhy commented 1 year ago

bors r+

IDK if Bors still works atm, we'll see.

bors[bot] commented 1 year ago

Build succeeded!

The publicly hosted instance of bors-ng is deprecated and will go away soon.

If you want to self-host your own instance, instructions are here. For more help, visit the forum.

If you want to switch to GitHub's built-in merge queue, visit their help page.

bradjc commented 1 year ago
make raspberry_pi_pico EXAMPLE=console
LIBTOCK_PLATFORM=raspberry_pi_pico cargo run --example console  \
        --target=thumbv6m-none-eabi --release
   Compiling libtock v0.1.0 (/Users/bradjc/git/libtock-rs)
error: linking with `rust-lld` failed: exit status: 1
  |
  = note: "rust-lld" "-flavor" "gnu" "/var/folders/_4/n3tmwxks3318tsb36d913f600000gn/T/rustcCgQTDB/symbols.o" "/Users/bradjc/git/libtock-rs/target/thumbv6m-none-eabi/release/examples/console-b8d0ccdcc2019f4a.console.958763e2-cgu.0.rcgu.o" "--as-needed" "-L" "/Users/bradjc/git/libtock-rs/target/thumbv6m-none-eabi/release/deps" "-L" "/Users/bradjc/git/libtock-rs/target/release/deps" "-L" "/Users/bradjc/git/libtock-rs/target/thumbv6m-none-eabi/release/build/libtock_runtime-99ba9d61b296ff5e/out" "-L" "/Users/bradjc/.rustup/toolchains/nightly-2022-06-10-x86_64-apple-darwin/lib/rustlib/thumbv6m-none-eabi/lib" "--start-group" "--end-group" "-Bstatic" "/Users/bradjc/.rustup/toolchains/nightly-2022-06-10-x86_64-apple-darwin/lib/rustlib/thumbv6m-none-eabi/lib/libcompiler_builtins-be4ad9a9d51b230c.rlib" "-Bdynamic" "--eh-frame-hdr" "-znoexecstack" "-L" "/Users/bradjc/.rustup/toolchains/nightly-2022-06-10-x86_64-apple-darwin/lib/rustlib/thumbv6m-none-eabi/lib" "-o" "/Users/bradjc/git/libtock-rs/target/thumbv6m-none-eabi/release/examples/console-b8d0ccdcc2019f4a" "--gc-sections" "-O1" "-Tlayout.ld"
  = note: rust-lld: error:
          Start of RAM region must be well-aligned to a 4kB boundary for LLVM's lld to
          work. Refer to https://github.com/tock/libtock-rs/pull/477 for more
          information. Set LIBTOCKRS_OVERRIDE_RAM_ORIGIN_CHECK = 1 to override this check
          (e.g., when using a different linker).

          rust-lld: error:
          Start of RAM region must be well-aligned to a 4kB boundary for LLVM's lld to
          work. Refer to https://github.com/tock/libtock-rs/pull/477 for more
          information. Set LIBTOCKRS_OVERRIDE_RAM_ORIGIN_CHECK = 1 to override this check
          (e.g., when using a different linker).

error: could not compile `libtock` due to previous error
make: *** [raspberry_pi_pico] Error 101

What do I do?

lschuermann commented 1 year ago

@bradjc Merge #476. That fixes this issue.