From c485a9090a3b623a5de0c2e6da6c857770bf079a Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Fri, 29 Sep 2023 14:48:23 -0400 Subject: Use an absolute address for __dcd LLVM's lld and GNU's ld have different ways of handling assignments in output sections. Unless we specify ABSOLUTE, ld treats the number '0' as a relative address from the section start, 0x6000_0000. On the other hand, lld treats '0' as if it were written with ABSOLUTE, and it ignores the ABSOLUTE function. So depending on your linker, __dcd would change values. This commit forces an absolute number for __dcd, ensuring a consistent value no matter the linker. --- src/host/imxrt-boot-header.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/imxrt-boot-header.x b/src/host/imxrt-boot-header.x index ed9d789..615fc65 100644 --- a/src/host/imxrt-boot-header.x +++ b/src/host/imxrt-boot-header.x @@ -71,7 +71,7 @@ SECTIONS __dcd_start = .; KEEP(*(.dcd)); /* Device Configuration Data */ __dcd_end = .; - __dcd = ((__dcd_end - __dcd_start) > 0) ? __dcd_start : 0; + __dcd = ((__dcd_end - __dcd_start) > 0) ? __dcd_start : ABSOLUTE(0); *(.Reset); /* Jam the imxrt-rt reset handler into flash. */ *(.__pre_init); /* Also jam the pre-init function, since we need it to run before instructions are placed. */ . = ORIGIN(FLASH) + 0x2000; /* Reserve the remaining 8K as a convenience for a non-XIP boot. */ -- cgit v1.2.3 From cfaac90ddfbab00aa5709c1f6dc2b796e63c6d77 Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Fri, 29 Sep 2023 15:11:55 -0400 Subject: Refactor LMA computation in inspect_elf Depending on the ordering and contents of program headers, the previous predicate for "is this the program header for this section?" could select the wrong header. GNU's ld and LLVM's lld produce that different header ordering and contents, causing select asserts to fail when using GNU's linker. This commit changes how we select the program header, approximating the way GNU objdump figures the value. This new approach needs more information from the section header, so I'm changing the API to make it easier to call the section_header method. The previous approach was influenced by LLVM objdump. Turns out that LLVM objdump will also compute the wrong LMA for these binaries when they're linked with GNU ld. GNU objdump always produces the correct section LMA, no matter the LLVM or GNU linker. --- tests/inspect_elf.rs | 79 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/tests/inspect_elf.rs b/tests/inspect_elf.rs index fa2c11d..5638801 100644 --- a/tests/inspect_elf.rs +++ b/tests/inspect_elf.rs @@ -134,16 +134,27 @@ impl<'a> ImxrtBinary<'a> { .ok_or_else(|| format!("Could not find {section_name} in program").into()) } - fn section_lma(&self, section: &Section) -> u64 { + fn section_lma(&self, section_name: &str) -> u64 { + let sec = self + .section_header(section_name) + .unwrap_or_else(|| panic!("Section {section_name} not found")); + + let contains_section = |phdr: &&goblin::elf::ProgramHeader| { + // The section resides in this part of the program. + sec.sh_offset >= phdr.p_offset + && (sec.sh_offset - phdr.p_offset) + sec.sh_size <= phdr.p_filesz + // The section's address fits in the program's memory. + && sec.sh_addr >= phdr.p_vaddr + && (sec.sh_addr - phdr.p_vaddr) + sec.sh_size <= phdr.p_memsz + }; + self.elf .program_headers .iter() .filter(|phdr| goblin::elf::program_header::PT_LOAD == phdr.p_type) - .find(|phdr| { - phdr.p_vaddr <= section.address && (phdr.p_vaddr + phdr.p_memsz) > section.address - }) - .map(|phdr| section.address - phdr.p_vaddr + phdr.p_paddr) - .unwrap_or(section.address) // VMA == LMA + .find(contains_section) + .map(|phdr| sec.sh_addr + phdr.p_paddr - phdr.p_vaddr) + .unwrap_or(sec.sh_addr) // VMA == LMA } } @@ -205,7 +216,7 @@ fn imxrt1010evk() { stack, "stack not at ORIGIN(DTCM), or not 8 KiB large" ); - assert_eq!(binary.section_lma(&stack), stack.address); + assert_eq!(binary.section_lma(".stack"), stack.address); let vector_table = binary.section(".vector_table").unwrap(); assert_eq!( @@ -220,12 +231,12 @@ fn imxrt1010evk() { vector_table.address % 1024 == 0, "vector table is not 1024-byte aligned" ); - assert_eq!(binary.section_lma(&vector_table), 0x6000_2000); + assert_eq!(binary.section_lma(".vector_table"), 0x6000_2000); let text = binary.section(".text").unwrap(); assert_eq!(text.address, ITCM, "text"); assert_eq!( - binary.section_lma(&text), + binary.section_lma(".text"), 0x6000_2000 + vector_table.size, "text VMA expected behind vector table" ); @@ -236,7 +247,7 @@ fn imxrt1010evk() { 0x6000_2000 + vector_table.size + aligned(text.size, 16), "rodata LMA & VMA expected behind text" ); - assert_eq!(rodata.address, binary.section_lma(&rodata)); + assert_eq!(rodata.address, binary.section_lma(".rodata")); let data = binary.section(".data").unwrap(); assert_eq!(data.address, 0x2020_0000, "data VMA in OCRAM"); @@ -245,7 +256,7 @@ fn imxrt1010evk() { "blink-rtic expected to have a single static mut u32" ); assert_eq!( - binary.section_lma(&data), + binary.section_lma(".data"), rodata.address + aligned(rodata.size, 4), "data LMA starts behind rodata" ); @@ -256,7 +267,7 @@ fn imxrt1010evk() { data.address + aligned(data.size, 4), "bss in OCRAM behind data" ); - assert_eq!(binary.section_lma(&bss), bss.address, "bss is NOLOAD"); + assert_eq!(binary.section_lma(".bss"), bss.address, "bss is NOLOAD"); let uninit = binary.section(".uninit").unwrap(); assert_eq!( @@ -265,7 +276,7 @@ fn imxrt1010evk() { "uninit in OCRAM behind bss" ); assert_eq!( - binary.section_lma(&uninit), + binary.section_lma(".uninit"), uninit.address, "uninit is NOLOAD" ); @@ -280,7 +291,7 @@ fn imxrt1010evk() { "1 KiB heap in DTCM behind vector table" ); assert_eq!(heap.size, 1024); - assert_eq!(binary.section_lma(&heap), heap.address, "Heap is NOLOAD"); + assert_eq!(binary.section_lma(".heap"), heap.address, "Heap is NOLOAD"); } fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { @@ -314,7 +325,7 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { stack, "stack not at ORIGIN(DTCM), or not 8 KiB large" ); - assert_eq!(binary.section_lma(&stack), stack.address); + assert_eq!(binary.section_lma(".stack"), stack.address); let vector_table = binary.section(".vector_table").unwrap(); assert_eq!( @@ -329,16 +340,16 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { vector_table.address % 1024 == 0, "vector table is not 1024-byte aligned" ); - assert_eq!(binary.section_lma(&vector_table), 0x6000_2000); + assert_eq!(binary.section_lma(".vector_table"), 0x6000_2000); let text = binary.section(".text").unwrap(); assert_eq!( text.address, - binary.section_lma(&vector_table) + vector_table.size, + binary.section_lma(".vector_table") + vector_table.size, "text" ); assert_eq!( - binary.section_lma(&text), + binary.section_lma(".text"), 0x6000_2000 + vector_table.size, "text VMA expected behind vector table" ); @@ -350,8 +361,8 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { "rodata LMA & VMA expected behind text" ); assert_eq!( - binary.section_lma(&rodata), - binary.section_lma(&text) + aligned(text.size, 16) + binary.section_lma(".rodata"), + binary.section_lma(".text") + aligned(text.size, 16) ); let data = binary.section(".data").unwrap(); @@ -365,8 +376,8 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { "blink-rtic expected to have a single static mut u32" ); assert_eq!( - binary.section_lma(&data), - binary.section_lma(&rodata) + aligned(rodata.size, 4), + binary.section_lma(".data"), + binary.section_lma(".rodata") + aligned(rodata.size, 4), "data LMA starts behind rodata" ); @@ -376,7 +387,7 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { data.address + aligned(data.size, 4), "bss in DTCM behind data" ); - assert_eq!(binary.section_lma(&bss), bss.address, "bss is NOLOAD"); + assert_eq!(binary.section_lma(".bss"), bss.address, "bss is NOLOAD"); let uninit = binary.section(".uninit").unwrap(); assert_eq!( @@ -385,7 +396,7 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { "uninit in DTCM behind bss" ); assert_eq!( - binary.section_lma(&uninit), + binary.section_lma(".uninit"), uninit.address, "uninit is NOLOAD" ); @@ -399,7 +410,7 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { heap, "1 KiB heap in DTCM behind uninit" ); - assert_eq!(binary.section_lma(&heap), heap.address, "Heap is NOLOAD"); + assert_eq!(binary.section_lma(".heap"), heap.address, "Heap is NOLOAD"); } #[test] @@ -492,7 +503,7 @@ fn imxrt1170evk_cm7() { stack, "stack not at ORIGIN(DTCM), or not 8 KiB large" ); - assert_eq!(binary.section_lma(&stack), stack.address); + assert_eq!(binary.section_lma(".stack"), stack.address); let vector_table = binary.section(".vector_table").unwrap(); assert_eq!( @@ -507,12 +518,12 @@ fn imxrt1170evk_cm7() { vector_table.address % 1024 == 0, "vector table is not 1024-byte aligned" ); - assert_eq!(binary.section_lma(&vector_table), 0x3000_2000); + assert_eq!(binary.section_lma(".vector_table"), 0x3000_2000); let text = binary.section(".text").unwrap(); assert_eq!(text.address, ITCM, "text"); assert_eq!( - binary.section_lma(&text), + binary.section_lma(".text"), 0x3000_2000 + vector_table.size, "text VMA expected behind vector table" ); @@ -524,7 +535,7 @@ fn imxrt1170evk_cm7() { "rodata moved to DTCM behind vector table" ); assert_eq!( - binary.section_lma(&rodata), + binary.section_lma(".rodata"), 0x3000_2000 + vector_table.size + aligned(text.size, 16), ); @@ -535,8 +546,8 @@ fn imxrt1170evk_cm7() { "blink-rtic expected to have a single static mut u32" ); assert_eq!( - binary.section_lma(&data), - binary.section_lma(&rodata) + aligned(rodata.size, 4), + binary.section_lma(".data"), + binary.section_lma(".rodata") + aligned(rodata.size, 4), "data LMA starts behind rodata" ); @@ -546,7 +557,7 @@ fn imxrt1170evk_cm7() { data.address + aligned(data.size, 4), "bss in OCRAM behind data" ); - assert_eq!(binary.section_lma(&bss), bss.address, "bss is NOLOAD"); + assert_eq!(binary.section_lma(".bss"), bss.address, "bss is NOLOAD"); let uninit = binary.section(".uninit").unwrap(); assert_eq!( @@ -555,7 +566,7 @@ fn imxrt1170evk_cm7() { "uninit in OCRAM behind bss" ); assert_eq!( - binary.section_lma(&uninit), + binary.section_lma(".uninit"), uninit.address, "uninit is NOLOAD" ); @@ -569,5 +580,5 @@ fn imxrt1170evk_cm7() { heap, "0 byte heap in DTCM behind rodata table" ); - assert_eq!(binary.section_lma(&heap), heap.address, "Heap is NOLOAD"); + assert_eq!(binary.section_lma(".heap"), heap.address, "Heap is NOLOAD"); } -- cgit v1.2.3 From af9f52fb4ac327240d949df220a5a51d8e73fb81 Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Fri, 29 Sep 2023 16:15:09 -0400 Subject: Relax asserts for .rodata and .text offsets Haven't fully dug into this one, but GNU ld and LLVM lld have slightly different section positions when we cross from text to data. We can relax the asserts to show ordering without strictly requiring an offset. --- tests/inspect_elf.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/inspect_elf.rs b/tests/inspect_elf.rs index 5638801..34da382 100644 --- a/tests/inspect_elf.rs +++ b/tests/inspect_elf.rs @@ -360,10 +360,7 @@ fn baseline_teensy4(binary: &ImxrtBinary, dcd_at_runtime: u32) { vector_table.address + vector_table.size, "rodata LMA & VMA expected behind text" ); - assert_eq!( - binary.section_lma(".rodata"), - binary.section_lma(".text") + aligned(text.size, 16) - ); + assert!(binary.section_lma(".rodata") >= binary.section_lma(".text") + aligned(text.size, 4)); let data = binary.section(".data").unwrap(); assert_eq!( @@ -534,9 +531,8 @@ fn imxrt1170evk_cm7() { vector_table.address + vector_table.size, "rodata moved to DTCM behind vector table" ); - assert_eq!( - binary.section_lma(".rodata"), - 0x3000_2000 + vector_table.size + aligned(text.size, 16), + assert!( + binary.section_lma(".rodata") >= 0x3000_2000 + vector_table.size + aligned(text.size, 4), ); let data = binary.section(".data").unwrap(); -- cgit v1.2.3 From 96cea217ae8bb0464d7e5774d806435abd6ebb7f Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Fri, 29 Sep 2023 16:16:43 -0400 Subject: Ensure __pre_init remains in binary GNU's LTO has a tendency to remove __pre_init, which is written in inline assembly. It doesn't realize that the reset handler references this symbol, because the reset handler is also written in inline assembly. Not sure why LLVM's linker doesn't also optimize it away, but this commit ensures that __pre_init remains in the output file. --- src/host/imxrt-link.x | 1 + 1 file changed, 1 insertion(+) diff --git a/src/host/imxrt-link.x b/src/host/imxrt-link.x index 71b697e..6c28f99 100644 --- a/src/host/imxrt-link.x +++ b/src/host/imxrt-link.x @@ -15,6 +15,7 @@ ENTRY(Reset); EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ EXTERN(DefaultHandler); +EXTERN(__pre_init); PROVIDE(NonMaskableInt = DefaultHandler); EXTERN(HardFaultTrampoline); -- cgit v1.2.3 From 567d3c5853b47ef4e7ca3938fb4976db7dbc9d79 Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Fri, 29 Sep 2023 16:21:44 -0400 Subject: Test with GNU ld in CI Ensure that we can use GCC's linker to generate our Rust firmware. This is useful for exploring mixed-language firmware builds. --- .github/workflows/rust.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml index 0f3616c..6b58a5e 100644 --- a/.github/workflows/rust.yaml +++ b/.github/workflows/rust.yaml @@ -53,6 +53,15 @@ jobs: with: command: test args: --tests -- --include-ignored + - name: Install ARM GCC + run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi + - name: Check binaries that are linked with GNU's ld + env: + CARGO_TARGET_THUMBV7EM_NONE_EABIHF_RUSTFLAGS: "-C linker=arm-none-eabi-gcc -C link-arg=-mcpu=cortex-m7 -C link-arg=-mfloat-abi=hard -C link-arg=-mfpu=fpv5-d16 -C link-arg=-nostartfiles" + uses: actions-rs/cargo@v1 + with: + command: test + args: --tests -- --ignored boards: runs-on: ubuntu-latest -- cgit v1.2.3