Skip to content

Commit ce01628

Browse files
authored
[elf2bin] Exclude zero-length segments from --bincombined. (#481)
(And also from --vhxcombined, which uses the same underlying code, but that didn't fit in a sensible-length subject line.) Last week I found Arm Toolchain had generated an ELF image in which three segments had p_paddr in the bottom few Kb of the address space, but there was also a segment with zero p_filesz (but nonzero p_memsz), with p_paddr > 0x20000000. Running that through `elf2bin --physical --bincombined`, it didn't notice that the segment at a high address had length 0, so it padded the output file to half a Gb in size to reach the start address, and then didn't actually write anything there. I worked around it at the time by selecting a subset of the segments to put in the --bincombined output file. But I think a better answer is to discard the zero-length segment completely when writing --bincombined output, because the padding in the output file is only there to get the file position to the place where some actually important data needs to be written. If there _is_ no important data to put at that position, there's no need to go there at all. (Incidentally, I think I know why my image came out like that. The target memory map had Flash at 0, and RAM at 0x20000000. The code segments were in Flash, and the initialized data segment had its p_paddr in Flash but its p_vaddr at 0x20000000, representing respectively where the startup code would memcpy it from and to. The segment with p_filesz==0 represented the program's zero-initialized data, so its p_vaddr was in RAM, representing where the data would be at run time – but since it didn't need to be memcpy()ed from anywhere, the linker had left its p_paddr the same as its p_vaddr, and not bothered to allocate it a different one. In other words, the linker felt that if the segment was 0 bytes long then it didn't need a meaningful p_paddr at all.) The new behavior adapts appropriately if you use the --zi option: in that situation, a segment with zero p_filesz but nonzero p_memsz doesn't count as empty, because the user _does_ want zero bytes in the --bincombined output file at its address.
1 parent 47f0e08 commit ce01628

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

arm-software/shared/elf2bin/bin.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,19 @@ static std::unique_ptr<BinaryDataStream>
210210
combined_prepare(const InputObject &inobj,
211211
const std::vector<Segment> &segments_orig, bool include_zi,
212212
std::optional<uint64_t> baseaddr) {
213+
// Discard segments that contribute no actual bytes to the file, in case one
214+
// of those appears at the highest address in the list and causes trailing
215+
// padding of the binary output file.
216+
std::vector<Segment> filtered;
217+
std::copy_if(segments_orig.begin(), segments_orig.end(),
218+
std::back_inserter(filtered),
219+
[include_zi](const Segment &seg) {
220+
uint64_t size = include_zi ? seg.memsize : seg.filesize;
221+
return size != 0;
222+
});
223+
213224
// Sort the segments by base address, in case they weren't already.
214-
std::vector<Segment> sorted = segments_orig;
225+
std::vector<Segment> sorted = std::move(filtered);
215226
std::stable_sort(sorted.begin(), sorted.end(),
216227
[](const Segment &a, const Segment &b) {
217228
return a.baseaddr < b.baseaddr;

arm-software/shared/elf2bin/test/test.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,64 @@ def testBaseAddr(self):
556556
"0x1235",
557557
)
558558

559+
def testZeroLength(self):
560+
"Test that a trailing length-0 segment doesn't cause trailing padding."
561+
562+
segment1_contents = bytes(range(0x10))
563+
564+
for bigend in False, True:
565+
for sixtyfour in False, True:
566+
with self.tempsubdir():
567+
makeelf(
568+
"input.elf",
569+
bigend,
570+
sixtyfour,
571+
[
572+
# A segment that is definitely not empty
573+
SegmentDesc(1, 0x1000, segment1_contents),
574+
575+
# A segment that has no initialized contents but
576+
# does have ZI contents, so it's normally empty,
577+
# but stops counting as empty if you say
578+
# --zi
579+
SegmentDesc(1, 0x1100, b'', pad=0x80),
580+
581+
# A completely empty segment
582+
SegmentDesc(1, 0x1200, b''),
583+
],
584+
)
585+
586+
# Without --zi: the first segment is the only non-empty
587+
# one, so we expect the output file to contain nothing but
588+
# the bytes in segment1_contents.
589+
self.elf2bin(
590+
"--bincombined", "-o", "output.bin", "input.elf"
591+
)
592+
with open("output.bin", "rb") as fh:
593+
bindata = fh.read()
594+
self.assertEqual(
595+
bindata,
596+
segment1_contents,
597+
)
598+
599+
# With --zi: the second segment also counts as non-empty,
600+
# containing 0x80 zero bytes. So the output file contains
601+
# the 0x10 bytes of segment1_contents, the 0xF0 bytes of
602+
# padding to get from there to the second segment's
603+
# address, and the 0x80 ZI bytes from that segment. But we
604+
# stop there, and don't go on to the third segment, which
605+
# is still completely empty.
606+
self.elf2bin(
607+
"--bincombined", "-o", "output.bin", "input.elf",
608+
"--zi"
609+
)
610+
with open("output.bin", "rb") as fh:
611+
bindata = fh.read()
612+
self.assertEqual(
613+
bindata,
614+
segment1_contents + b'\0' * (0xF0 + 0x80),
615+
)
616+
559617

560618
class vhx(Base):
561619
def testOneSegment(self):

0 commit comments

Comments
 (0)