zig icon indicating copy to clipboard operation
zig copied to clipboard

zig objcopy -O binary creates binary file with some zero padding at the start

Open apettel opened this issue 2 months ago • 4 comments

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

apettel avatar Oct 21 '25 20:10 apettel

Related / duplicate https://github.com/ziglang/zig/issues/24522

MatthiasPortzel avatar Oct 22 '25 15:10 MatthiasPortzel

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

ghostiam avatar Oct 25 '25 23:10 ghostiam

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;

Spooky309 avatar Nov 13 '25 21:11 Spooky309

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?

taylorh140 avatar Nov 20 '25 18:11 taylorh140