1

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.

  • Why this discrepancy in 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

2
  • 1
    Have you tried section .text align=1 in the NASM source? Does that make any difference? Commented Nov 26, 2024 at 12:02
  • @ecm: Wow, that did it!!! NICE! Add as an answer so you have the full credit! Commented Nov 26, 2024 at 12:35

1 Answer 1

2

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.