Native 64-bit “Hello World” With NASM on FreeBSD
In yet another effort to fend off boredom, I tried to write a “hello world” in assembly, on FreeBSD 7.2/amd64. I wanted it to be a native 64-bit (more fun that way), so the developer’s handbook was not too helpful. And since by the logic of the Internet, there’s someone somewhere right now is looking exactly for this, I thought I’d share it here.
Aside: The Assembler used here is NASM. I grew up on 8085 and Z80, and find myself mentally exchanging the operands while reading gas-syntax assembly listings.
First off, the good old int 80h is not the way to make syscalls on the amd64. The SYSCALL instruction (see the Intel docs) is used instead. From the Intel docs, SYSCALL looks to be lighter and faster than an interrupt (should time it and compare sometime!). The SYSCALL clobbers RCX though, so libc saves RCX in R10 before the syscall. That implies that R10 is preserved by the SYSCALL. All this info was gathered from /usr/src/lib/libc/amd64/SYS.h. In case you haven’t installed /usr/src, you really should be doing it now.
The arguments passed seems to be in accordance with the (except-for-Windows-)standard 64-bit ABI (specs here), although I couldn’t find any documentation about this (!). The register RAX holds the syscall number. The full list of syscalls can be found at /usr/src/sys/kern/syscalls.master. Let’s see the ones we need to use:
1 AUE_EXIT STD { void sys_exit(int rval); } exit \
sys_exit_args void
4 AUE_NULL STD { ssize_t write(int fd, const void *buf, \
size_t nbyte); }
Armed with this info, we can now write our assembler source:
section .data
message:
db 'hello, world!', 0
section .text
global _start
_start:
mov rax, 4
mov rdi, 1
mov rsi, message
mov rdx, 13
syscall
mov rax, 1
xor rdi, rdi
syscall
The first call sets RAX=4 to invoke write, RDI=1 being the file descriptor of standard output, RSI to the address of the message to write, and RDX=13 bytes to write. The syscall sets the carry flag on failure, so do a jb to catch errors.
The second call simply exits the process, RAX=1 corresponds to exit and RDI=0 is the exit code.
Assemble with:
~$ nasm -f elf64 -o p.o p.nasm
and then link with:
~$ ld -o p p.o
and have fun with:
~$ ./p hello, world!~$ echo $? 0
Yeah, that looks good!
Some asides:
_startis understood by ELF.- The executable seems to have acquired a 2-byte .bss (?) and a 31-byte .comment section along the way. The comment contains
"\x00The Netwide Assembler 2.05.01\x00". Couldn’t find any NASM command-line options or directives to turn this off. Not nice. - Strip off the unwanted sections with
"strip -R .comment -R .bss ./p"to reduce the executable size. It is now 536 bytes long. (If you liked that, you might find this interesting.) - Is it possible to avoid ‘ld’ and get NASM to directly generate an executable ELF?
Heh. That was fun. Now let me quickly sneak out, grab lunch, watch a movie and get back to boredom.
Message should be
message:
db 'hello, world!', 10, 0
so you get a newline.
Tordek
29-Oct-2009 at 1:49 am
@Tordek: Sure. Also do “mov rdx, 14″ to ensure it goes out. Actually the 0 is also not required.
mdevanr
29-Oct-2009 at 9:29 am
Does it work on amd? I mean amd uses sysenter…
Ernesto
9-Nov-2009 at 10:40 pm
AMD has SYSCALL too, though I’m not sure if SYSENTER works in 64-bit mode. The FreeBSD libc does not distinguish between Intel/AMD processors. So in all, yes, it should work on AMD too (don’t have a machine to try out right now).
mdevanr
10-Nov-2009 at 10:12 am
Cool, I was looking for something like this to get me started with nasm on my OpenBSD/amd64 box. The example works great. Thanks for posting this.
leotherussian
2-Mar-2010 at 11:50 am
im using yasm too just to compare.. and using objdump –disassemble in both codes:
(i create a section exit: int the source for syscall 1)
YASM:
00000000004000b0 :
4000b0: 48 c7 c0 04 00 00 00 mov $0×4,%rax
4000b7: 48 c7 c7 01 00 00 00 mov $0×1,%rdi
4000be: 48 c7 c6 e0 00 50 00 mov $0x5000e0,%rsi
4000c5: 48 c7 c2 0b 00 00 00 mov $0xb,%rdx
4000cc: 0f 05 syscall
4000ce: 48 c7 c0 01 00 00 00 mov $0×1,%rax
4000d5: 48 31 ff xor %rdi,%rdi
4000d8: 0f 05 syscall
NASM:
00000000004000b0 :
4000b0: 48 b8 04 00 00 00 00 mov $0×4,%rax
4000b7: 00 00 00
4000ba: 48 bf 01 00 00 00 00 mov $0×1,%rdi
4000c1: 00 00 00
4000c4: 48 be f0 00 50 00 00 mov $0x5000f0,%rsi
4000cb: 00 00 00
4000ce: 48 ba 0b 00 00 00 00 mov $0xb,%rdx
4000d5: 00 00 00
4000d8: 0f 05 syscall
00000000004000da :
4000da: 48 b8 01 00 00 00 00 mov $0×1,%rax
4000e1: 00 00 00
4000e4: 48 31 ff xor %rdi,%rdi
4000e7: 0f 05 syscall
Nasm version have this nops “00 00 00″ between every instruction.. wonder why..
and yasm cut the exit branch and glue the code.. look smart
how about a bigger code.. look this nops .. would waste some cpu cicles .. :s
fabiok
12-Jul-2010 at 12:11 pm
This compiles on LInux 64 and prints the message, but exits with a segfault. Did I do it wrong?
lulzfish
8-Sep-2010 at 6:07 am
Yep, me too, displays string the seg fault.
James E Davies
1-Jul-2011 at 11:32 pm
Compiles and runs nicely on my AMD64 machine running FreeBSD 8.2. Used Nasm and Yasm.
segfault
5-Jul-2011 at 8:09 am
Re: that “not nice” .comment section… More recent versions of Nasm do not add this section (I guess they heard ya). I don’t recall offhand just which version eliminated it… Get the most recent versions from:
http://www.nasm.us
Re: the segfault on Linux64… I think Linux changed the syscall numbers (odd that BSD didn’t), so the intended exit may not be working… Try 1 for sys_write and 3Ch for sys_exit (?).
No idea why the “padding” in Nasm vs Yasm… bears looking into!
Thanks for the example!
Best,
Frank
Frank Kotler
19-Jan-2012 at 12:56 am
Works on my Freebsd 9 amd64 box, but adds a white % on black background.
Bert
16-May-2012 at 8:13 am
Thanks for this post. I played around with my current 32bit code and got it working in fBSD 9.1rc3
https://github.com/EhevuTov/asm-fbsd64-hello-world
James Gosnell
11-Nov-2012 at 4:50 pm