zig objcopy -O binary creates binary file with some zero padding at the start
Zig Version
0.15.1
Steps to Reproduce and Observed Behavior
I used the blinky.elf file from the project stm32-baremetal-zig, but other elf files should also show the same behavior.
I run the command
zig objcopy -O binary blinky.elf blinky.bin
with zig 0.14.1 I get the correct output:
1 │ 00000000 00 00 05 20 99 20 00 08 49 22 00 08 49 22 00 08 |... . ..I"..I"..|
2 │ 00000010 49 22 00 08 49 22 00 08 49 22 00 08 00 00 00 00 |I"..I"..I"......|
3 │ 00000020 00 00 00 00 00 00 00 00 00 00 00 00 49 22 00 08 |............I"..|
4 │ 00000030 49 22 00 08 00 00 00 00 49 22 00 08 51 22 00 08 |I"......I"..Q"..|
with zig 0.15.1 there is some zero padding at the start, which is wrong:
1 │ 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2 │ *
3 │ 000001c0 00 00 00 00 00 00 00 00 00 00 05 20 99 20 00 08 |........... . ..|
4 │ 000001d0 49 22 00 08 49 22 00 08 49 22 00 08 49 22 00 08 |I"..I"..I"..I"..|
5 │ 000001e0 49 22 00 08 00 00 00 00 00 00 00 00 00 00 00 00 |I"..............|
6 │ 000001f0 00 00 00 00 49 22 00 08 49 22 00 08 00 00 00 00 |....I"..I"......|
Expected Behavior
produce a correct bin file
Related / duplicate https://github.com/ziglang/zig/issues/24522
I ran into the same issue, and for now I’m calling an external objcopy as a temporary workaround. https://github.com/ghostiam/ch32_zig/blob/master/ObjCopyExternal.zig#L151
It seems to me that the issue is caused by an incorrect offset when writing to the bin file. When I commented out this line, it started working correctly for me. https://github.com/ziglang/zig/blob/master/lib/compiler/objcopy.zig#L531
As a resident non-expert, I believe this happens because when parsing the input ELF to find segments, it considers zero-size segments (like the STACK segment .bss uses). Then, later, when it outputs segments into the BIN file, it considers ALL segments (even those with zero size...) So, for example, if you're linking to run from 0x70000000, but .bss has a PADDR of 0x20002000 (i.e. equal to its VADDR), then it's going to seek to 0x4FFFE000 to begin writing.
There is no workaround - forcing a higher PADDR for .bss just makes it pad the end of the output file instead, and using any address used by any other section naturally causes the linker to (understandably) freak out. I "fixed" it by making it so objcopy only considers segments with a nonzero physical size when setting the base physical address, and before seeking to a segment's start address. No idea if that's actually the behaviour you want, but it seems intuitive to me (guy who hasn't read any of the documentation).
Worth mentioning GNU Objcopy treats these zero-size segments as you'd expect when copying an ELF to a BIN.
le patch:
diff --git a/objcopy_master.zig b/objcopy.zig
index ee7456a..b96d43d 100644
--- a/objcopy_master.zig
+++ b/objcopy.zig
@@ -338,8 +338,11 @@ fn emitElf(
switch (options.ofmt) {
.raw => {
for (binary_elf_output.sections.items) |section| {
- try out.seekTo(section.binaryOffset);
- try writeBinaryElfSection(in, out, section);
+ // No point in writing a section with zero file size (i.e. .bss)
+ if (section.fileSize != 0) {
+ try out.seekTo(section.binaryOffset);
+ try writeBinaryElfSection(in, out, section);
+ }
}
try padFile(out, options.pad_to);
},
@@ -477,6 +480,9 @@ const BinaryElfOutput = struct {
mem.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);
for (self.segments.items, 0..) |firstSegment, i| {
+ // Only consider segments that have a non-zero file size when computing binary offsets.
+ if (firstSegment.fileSize == 0) continue;
+
if (firstSegment.firstSection) |firstSection| {
const diff = firstSection.elfOffset - firstSegment.elfOffset;
Surprisingly this is my biggest issue with 0.15.2 ... so far. Luckly the elf file is fine, so an alternate objcopy just needs to be used. Just so I'm clear, it sounds like this was a regression caused by the move to/toward incremental builds?