Here's the same code, using libc, for 3 modes of operation: Real, i386 and x86-64. Just a simple hello.
; hello16.asm
;
; Using TurboC 2.01 or Borland C++ 3.1:
;
; nasm -fobj hello16.asm -o hello16.obj
; tlink -L\tc2\lib \tc2\lib\c0s.obj+hello16.obj,hello16.exe,nul,cs.lib
;
; Change the absolute path if using Borland C++. Or, if using Microsoft C 6,
; the command line is entirely different (and, yet, you must link c0.obj and the
; small model libc library.
;
bits 16
; There is no '.text', '.data' or '.rodata' in MSDOS.
; and the CLASS is necessary for some linkers (tlink, for example).
section _DATA class=DATA
msg: db `Hello\n`,0
section _TEXT class=CODE
extern _printf
global _main
_main:
push msg
call _printf
add sp,2 ; cleanup (2 bytes)
xor ax,ax
ret
Notice, after the call we have to cleanup 2 bytes from the stack. Now, i386:
bits 32
section .rodata
msg: db `Hello\n',0
section .text
; As in 16 bits mode libc must be preceeded by _.
extern _printf
global _main
_main:
push msg
call _printf
add esp,4 ; Clean up, 4 bytes!
xor eax,eax
ret
Not only we have to clean 4 bytes, but the segment names changed. The `.` prefix can be used in NASM as a shortcut, but 16 bits code excpects to be named as `_TEXT`, `_DATA` or `_BSS` (there is no `.rodata` equivalent). The use of E?? registers here is of no consequence, since we could use them in 16 bits code as well.
Now... x86-64:
bits 64
default rel
section .rodata
msg: db `Hello\n`,0
section .text
; NO _ prefix for identifiers.
extern printf
global main
main:
%ifdef SYSVABI
sub rsp,8
xor eax,eax ; To inform # of fp args is mandatory in SysV ABI.
lea rdi,[msg] ; Needed to use RIP-relative addressing.
; mov rdi,msg works, but requires a relocation.
call printf wrt ..plt ; If in SysV ABI and linking dynamically.
%else
sub rsp,40
lea rcx,[msg] ; Needed to use RIP-relative addressing.
; mov rdi,msg works, but requires a relocation.
call printf
%endif
; NO stack cleanup...
xor eax,eax
ret
Suffice to say there is no RCX or RDI register in i386 or real modes, and if you are using SysV ABI (Linux, FreeBSD, MacOS), you need to zero AL before calling printf. SysV ABI use RDI as the first argument, MS ABI uses RCX... and we need to accomodate the shadow space in MS ABI (and align for DQWORD in both ABIs)...
The 3 sources are totally different at source level AND at binary level, hence they are NOT portable.