While doing some experiments inspired by many interesting articles on tiny ELF executables, I've noticed GNU's ld
generates a different executable when fed with a nasm
-generated .o
object file or with a (GNU)as
-generated one, both using (what I presume to be¹) an equivalent assembly source.
¹: Their object files differ only on non-code sections, and are bitwise identical if I strip --strip-section-headers *.o
, so I believe that's a fair assumption.
NASM (v2.16.1):
; tiny-nasm.asm
SECTION .text
GLOBAL _start
_start:
mov eax, 60 ; Select the _exit syscall (60 in Linux ABI)
mov edi, 42 ; Set the exit code argument for _exit
syscall ; Perform the selected syscall
GAS (v2.42):
# tiny-gas.S
.SECTION .text
.GLOBL _start
_start:
mov $60, %eax # Select the _exit syscall (60 in Linux ABI)
mov $42, %edi # Set the exit code argument for _exit
syscall # Perform the selected syscall
nasm -f elf64 tiny-nasm.asm && ld -no-pie -z noseparate-code tiny-nasm.o -o tiny-nasm.bin
as tiny-gas.S -o tiny-gas.o && ld -no-pie -z noseparate-code tiny-gas.o -o tiny-gas.bin
strip --strip-section-headers *.bin
wc -c *.bin
diff -u <(readelf -Wa tiny-nasm.bin) <(readelf -Wa tiny-gas.bin)
132 tiny-gas.bin
140 tiny-nasm.bin
272 total
--- /dev/fd/63 2024-11-26 02:56:40.248293325 -0300
+++ /dev/fd/62 2024-11-26 02:56:40.248293325 -0300
@@ -8,7 +8,7 @@
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
- Entry point address: 0x400080
+ Entry point address: 0x400078
Start of program headers: 64 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
@@ -25,7 +25,7 @@
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
- LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x00008c 0x00008c R E 0x1000
+ LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x000084 0x000084 R E 0x1000
There is no dynamic section in this file.
Both binaries work identically and as expected, and their only difference is the entry point address offset chosen by ld
, which accounts for the file size, 8 bytes smaller for as
.
ld
?As the arguments were identical, I assume the explanation is in the (stripped) sections, which I've omitted here for brevity but can include if needed. But what in those sections could trigger the different entry point address chosen by ld
?
If relevant: Ubuntu 24.04, Linux desktop 6.8.0-48 x86_64, binutils 2.42, AMD Ryzen 5 5700G
As documented in the NASM manual the default attributes for the ELF section .text
are section .text progbits alloc exec nowrite align=16
.
Apparently gas has a lower alignment, below-or-equal 8, so that's how you observed a difference. The solution is to specify an align=1
attribute in the section
directive, like so:
section .text align=1