0

I have this x86 assembly code for a "hello world" program.

global    _start

section   .text

_start: 
  
mov       eax, 1                  ; system call for write       
mov       ebx, 1                  ; file handle 1 is stdout        
mov       ecx, message            ; address of string to output

mov       edx, message_len        ; length of the string      
syscall                           ; invoke operating system to do the write       
mov       eax, 60                 ; system call for exit          
mov       ebx, 0                  ; exit code 0         
syscall                           ; invoke operating system to ex
        
section   .data

message:  db "Hello, World!!!!", 10      ; newline at the end
message_len equ $-message                ; length of the string

This doesn't compile with nasm -felf64 hello.asm && ld hello.o && ./a.out on a 64-bit Linux machine.

But if I change the third line mov ecx, message to mov rsi, message it works!

My question is why is 64-bit NASM insisting on the RSI register?
Because I have seen people compiling with ECX on 32-bit Arch Linux.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
vinod-vms
  • 25
  • 6
  • **on 32-bit Arch Linux** I think you answered your own question. `ecx` is a 32-bit register. On a 32-bit system an address is 32 bits and fits in a 32-bit register. On a 64-bit system, an address is 64 bits so it doesn't. (And as mentioned below, the ABI expects it in a different register anyway.) – Nate Eldredge Jan 14 '22 at 05:26
  • (Well, okay, since you are building a non-PIE executable and using a static object, the address will fit in 32 bits, in this case. Still not a sensible practice in general.) – Nate Eldredge Jan 14 '22 at 05:29
  • @NateEldredge Well, you _can_ use 32‑bit addresses _in_ 64‑bits mode with the [x32‑ABI](https://en.wikipedia.org/wiki/X32_ABI), [NASM parameter `‑f elfx32`](https://www.nasm.us/xdoc/2.15.05/html/nasmdoc8.html#section-8.9). @user9160459 This ABI is rather suitable for niche applications, not your general desktop program. I mentioned it, because in assembly lots of things are pure _convention_. Nobody is forcing you to do anything in particular. – Kai Burghardt Jan 14 '22 at 13:06
  • `mov edi, message` is the optimal way to write this for x86-64, since you're linking into a static executable. You need to set RDI to the correct 64-bit address, and `mov r32, imm32` is the most efficient way when the address is known to be in the low 2GiB of virtual address space. `mov rdi, message` is a waste of code-size, even more bloated than the standard `lea rdi, [rel message]`. [How to load address of function or label into register](https://stackoverflow.com/q/57212012) – Peter Cordes Aug 13 '22 at 12:40

1 Answers1

3

x86 does not use the same calling convention as x64.

In x86, the first argument is EBX which contains the descriptor, ECX contains the buffer, EDX contains the length and EAX contains the system call ordinal.

In x64, the first argument is contained in RDI, second in RSI, third in RDX and fourth in RCX while RAX contains the ordinal for the system call.

That's why your call is working on x86 but needs to be adjusted to work on x64 as well.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Irelia
  • 3,407
  • 2
  • 10
  • 31
  • 1
    The code in the question won't work in 32-bit mode, except maybe on an AMD CPU where the `syscall` instruction is supported in 32-bit user-space. The critical difference isn't strictly the bitness of the program, it's which ABI you invoke. `int 0x80` always looks at ECX (yes, 32-bit ECX), even if invoked from 64-bit user-space. [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/a/46087731). The 64-bit `syscall` ABI is only available from 64-bit mode, and that's what uses different registers. – Peter Cordes Jul 30 '22 at 17:15
  • The other fun coincidence here is that EBX=0 on entry to `_start` in a static executable, and `write` to stdin happens to work on a normal terminal, where file descriptors 0, 1, and 2 are all duplicates of the same read/write file description. But to redirect the output, you'd need to use `./foo 0>&1 | less`. – Peter Cordes Jul 30 '22 at 17:17
  • In the 64-bit `syscall` ABI, the 4th arg goes in R10; that's the one difference from the function-calling convention. `syscall` itself overwrites RCX (with RIP so the kernel can know where to return). – Peter Cordes Jul 30 '22 at 17:18
  • @PeterCordes I actually wasn't aware of that. Is that for backwards compatibility on x64 systems which allow x86 emulation? – Irelia Jul 31 '22 at 04:12
  • 2
    The fact that `int 0x80` is usable in 64-bit processes? Yeah, that interrupt handler has to exist for 32-bit processes to work, even under a 64-bit kernel. (You can configure the kernel without CONFIG_IA32_EMULATION, in which case it will just segfault like `int 0x7f` or any other random interrupt number.) But yeah, with CONFIG_IA32_EMULATION, it would be extra work for the kernel to make `int 0x80` *not* Just Work to transfer control to the kernel's handler for that entry point, regardless of the current mode user-space is in (compat mode or full 64-bit long mode). – Peter Cordes Jul 31 '22 at 04:18