From 330be8b889adf248bfb18ae7312002a75f9e5e6f Mon Sep 17 00:00:00 2001 From: Brad Campbell Date: Mon, 24 Jul 2023 23:12:54 -0400 Subject: [PATCH 1/2] modify segments that start before flash The linker appears to be able to place segements before the start of flash. We detect this case and edit the segment to remove the portion that exists before the specified start of flash. --- src/convert.rs | 167 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 114 insertions(+), 53 deletions(-) diff --git a/src/convert.rs b/src/convert.rs index f5b2f37..44e18d8 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -172,7 +172,7 @@ pub fn elf_to_tbf( }) .collect(); - let elf_phdrs: Vec = elf_file + let mut elf_phdrs: Vec = elf_file .segments() .expect("Failed to locate ELF program headers") .iter() @@ -262,25 +262,42 @@ pub fn elf_to_tbf( // Check for fixed addresses. // - // We do this with different mechanisms for flash and ram. For flash, we - // look at loadable segments in the ELF, and find the segment with the - // lowest address that is both executable and non-zero in file size. Since - // this is a segment that must be loaded to execute the application, we know - // that this must be a flash address. + // For the most reliable results, we expect that the linker script for the + // app created two symbols which we can use to determine where the app + // expects to be placed in flash and ram. // - // For RAM, we can't quite use the ELF file in the same way as with flash. - // Since nothing _actually_ has to be loaded into RAM, the ELF file does not - // have to keep track of the first address in RAM. Further, Tock apps - // typically put a .stack section at the beginning of RAM, and the stack is - // just a memory holder and doesn't contain any actual data. The start of - // RAM address will almost certainly exist somewhere in the ELF, but - // reliably extracting it from different ELFs linked with different - // toolchains from different linker scripts would I think require resorting - // to a bit of heuristics and guessing. To avoid the potential issues there, - // we instead require that a `_sram_origin` symbol be present to explicitly - // mark the start of RAM. + // - `_flash_origin`: The address in flash the app was compiled for. + // - `_sram_origin`: The address in ram the app was compiled for. // - // In both cases we check to see if the address matches our expected PIC + // For RAM, we use this method because there does not seem to be a reliable + // method to extract the RAM start address from the elf. Since nothing + // _actually_ has to be loaded into RAM, the ELF file does not have to keep + // track of the first address in RAM. Further, Tock apps typically put a + // .stack section at the beginning of RAM, and the stack is just a memory + // holder and doesn't contain any actual data. The start of RAM address will + // almost certainly exist somewhere in the ELF, but reliably extracting it + // from different ELFs linked with different toolchains from different + // linker scripts would I think require resorting to a bit of heuristics and + // guessing. To avoid the potential issues there, we instead require that a + // `_sram_origin` symbol be present to explicitly mark the start of RAM. + // + // For flash, we use the `_flash_origin` symbol if the linker script + // includes it. In theory, we can deduce the start address of flash based on + // the physaddr of segments. However, in some cases the linker appears to + // place segments _before_ the start of flash. Note, it always seems to + // place _sections_ within the specified flash region, but, at this point, + // it is increasing untenable to reliably detect the start address of flash + // based on segments and sections alone. To essentially sidestep this issue, + // we default to passing the flash start address via the `_flash_origin` + // symbol. + // + // If the elf doesn't include the `_flash_origin` symbol, we look at + // loadable segments in the ELF, and find the segment with the lowest + // address that is both executable and non-zero in file size. Since this is + // a segment that must be loaded to execute the application, we assume this + // is the start address of flash. + // + // In both case we check to see if the address matches our expected PIC // addresses: // - RAM: 0x00000000 // - flash: 0x80000000 @@ -294,44 +311,71 @@ pub fn elf_to_tbf( let mut fixed_address_flash_pic: bool = false; // Do flash address. - for segment in &elf_phdrs { - // Only consider nonzero segments which are set to be loaded. - if segment.p_type != elf::abi::PT_LOAD || segment.p_filesz == 0 { - continue; + + // Try to get the flash address via the `_flash_origin` symbol. + let flash_origin_address = if let Ok(Some((symtab, sym_strtab))) = elf_file.symbol_table() { + // We are looking for the `_flash_origin` symbol and its value. If it + // exists, this tells us the first address of flash when the app was + // compiled. + if let Some(flash_origin) = symtab.iter().find(|sym| { + let name = sym_strtab + .get(sym.st_name as usize) + .expect("Failed to parse symbol name"); + name == "_flash_origin" + }) { + Some(flash_origin.st_value as u32) + } else { + None } + } else { + None + }; - // Flash segments have to be marked executable, and we only care about - // segments that actually contain data to be loaded into flash. - if (segment.p_flags & elf::abi::PF_X) > 0 - && section_exists_in_segment(&elf_sections, segment) - { - // If this is standard Tock PIC, then this virtual address will be - // at 0x80000000. Otherwise, we interpret this to mean that the - // binary was compiled for a fixed address in flash. Once we confirm - // this we do not need to keep checking. - if segment.p_vaddr == 0x80000000 || fixed_address_flash_pic { - fixed_address_flash_pic = true; - } else { - // We need to see if this segment represents the lowest address - // in flash that we are going to specify this app needs to be - // loaded at. To do this we compare this segment to any previous - // and keep track of the lowest address. - let segment_start = segment.p_paddr as u32; - - fixed_address_flash = match fixed_address_flash { - Some(prev_addr) => { - if segment_start < prev_addr { + // Figure out if this is a PIC app or not, and if we couldn't find the + // symbol then we estimate the address from segments. + if let Some(flash_origin) = flash_origin_address { + if flash_origin == 0x80000000 { + // Matches the PIC address. + fixed_address_flash_pic = true; + } else { + // We are a fixed address app, so we just use the given address. + fixed_address_flash = Some(flash_origin) + } + } else { + // We didn't find the symbol, so estimate from the segments. + for segment in &elf_phdrs { + // Only consider nonzero segments which are set to be loaded. + if segment.p_type != elf::abi::PT_LOAD || segment.p_filesz == 0 { + continue; + } + + // Flash segments have to be marked executable, and we only care about + // segments that actually contain data to be loaded into flash. + if (segment.p_flags & elf::abi::PF_X) > 0 + && section_exists_in_segment(&elf_sections, segment) + { + // If this is standard Tock PIC, then this virtual address will be + // at 0x80000000. Otherwise, we interpret this to mean that the + // binary was compiled for a fixed address in flash. Once we confirm + // this we do not need to keep checking. + if segment.p_vaddr == 0x80000000 || fixed_address_flash_pic { + fixed_address_flash_pic = true; + } else { + // We need to see if this segment represents the lowest + // address in flash that we are going to specify this app + // needs to be loaded at. To do this we compare this segment + // to any previous and keep track of the lowest address. + let segment_start = segment.p_paddr as u32; + + fixed_address_flash = match fixed_address_flash { + Some(prev_addr) => Some(cmp::min(segment_start, prev_addr)), + None => { + // We found our first valid segment and haven't set + // our lowest address yet, so we do that now. Some(segment_start) - } else { - Some(prev_addr) } - } - None => { - // We found our first valid segment and haven't set our - // lowest address yet, so we do that now. - Some(segment_start) - } - }; + }; + } } } } @@ -577,7 +621,7 @@ pub fn elf_to_tbf( // Iterate over ELF's Program Headers to assemble the binary image as a // contiguous memory block. Only take into consideration segments where // filesz is greater than 0. - for segment in &elf_phdrs { + for segment in &mut elf_phdrs { // Only consider segments which are set to be loaded. if segment.p_type != elf::abi::PT_LOAD { continue; @@ -589,6 +633,23 @@ pub fn elf_to_tbf( continue; } + // It's possible the linker started this segment _before_ the start of + // the flash region. We edit the segment to remove the portion that + // starts before the start of flash. + if let Some(flash_address) = fixed_address_flash { + let flash_address: u64 = flash_address as u64; + if segment.p_paddr < flash_address { + // We need to truncate the start of the segment. + let truncate_length = flash_address - segment.p_paddr; + + segment.p_offset += truncate_length; + segment.p_paddr += truncate_length; + segment.p_vaddr += truncate_length; + segment.p_filesz -= truncate_length; + segment.p_memsz -= truncate_length; + } + } + // Insert padding between segments if needed. if let Some(last_segment_address_end) = last_segment_address_end { // We have a previous segment. Now, check if there is any padding From 8ab90b7ea5bb3e9f0176f8fbf79b9fb35802819b Mon Sep 17 00:00:00 2001 From: Brad Campbell Date: Mon, 31 Jul 2023 22:29:57 -0400 Subject: [PATCH 2/2] ignore segments that start entirely before flash --- src/convert.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/convert.rs b/src/convert.rs index 44e18d8..f69936e 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -633,6 +633,15 @@ pub fn elf_to_tbf( continue; } + // Check if the segment starts entirely before the start of flash. If + // so, skip this segment. + if let Some(flash_address) = fixed_address_flash { + let flash_address: u64 = flash_address as u64; + if segment.p_paddr + segment.p_filesz < flash_address { + continue; + } + } + // It's possible the linker started this segment _before_ the start of // the flash region. We edit the segment to remove the portion that // starts before the start of flash.