The Brown Notebook

Colorless green ideas, sleeping furiously.

Native 64-bit “Hello World” With NASM on FreeBSD

with 16 comments

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

    db      'hello, world!', 0

section .text

global _start
    mov     rax, 4
    mov     rdi, 1
    mov     rsi, message
    mov     rdx, 13

    mov     rax, 1
    xor     rdi, rdi


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 $?


Yeah, that looks good!

Some asides:

  • _start is 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.


Written by mdevanr

27-Oct-2009 at 12:24 pm

Posted in Uncategorized

Tagged with ,

16 Responses

Subscribe to comments with RSS.

  1. Message should be

    db 'hello, world!', 10, 0

    so you get a newline.


    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.


      29-Oct-2009 at 9:29 am

  2. Does it work on amd? I mean amd uses sysenter…


    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).


      10-Nov-2009 at 10:12 am

  3. 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.


    2-Mar-2010 at 11:50 am

  4. 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)


    00000000004000b0 :
    4000b0: 48 c7 c0 04 00 00 00 mov $0x4,%rax
    4000b7: 48 c7 c7 01 00 00 00 mov $0x1,%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 $0x1,%rax
    4000d5: 48 31 ff xor %rdi,%rdi
    4000d8: 0f 05 syscall


    00000000004000b0 :
    4000b0: 48 b8 04 00 00 00 00 mov $0x4,%rax
    4000b7: 00 00 00
    4000ba: 48 bf 01 00 00 00 00 mov $0x1,%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 $0x1,%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


    12-Jul-2010 at 12:11 pm

    • NOOPS or NOPS are used for padding purposes (obviously) generally performing alignment; this could result from using GNU compilers and linkers even in FreeBSD (also occurs with GNU tools in Windows).

      Little bit more difficult to determine the reasoning behind NAsm using NOP padding; aside, it may be carry over from GCC exclusivity days when NOPs were a certainty if not mandatory. NAsm is, once again, under development and possibly subject to refinements addressing padding with NOPs implicitly -here’s to hoping.

      YASM seems to natively address the issue where alignment has to be explicitly set rather than implied (assumed); that is definitely good from a file size position. NOPs don’t completely solve performance related issues using memory boundaries and at the assembler level can actually waste more than offer.

      Nice code snippet comparison (YAsm & NAsm) too!
      …and to the author of the OP -good stuff. I use FAsm mostly, but have started eyeballing NAsm again (reactivated by new devs) and YAsm out of general curiosity. Great article and still very relevant -Thnx!


      12-Nov-2017 at 11:51 pm

  5. This compiles on LInux 64 and prints the message, but exits with a segfault. Did I do it wrong?


    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

  6. Compiles and runs nicely on my AMD64 machine running FreeBSD 8.2. Used Nasm and Yasm.


    5-Jul-2011 at 8:09 am

  7. 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:

    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!


    Frank Kotler

    19-Jan-2012 at 12:56 am

    • Linux and FreeBSD syscalls are internal to the kernels of each and mutually exclusive; the whole syscalls will work identically with each is somewhat invalid. Standard syscalls directly managed by each kernel are done so within their own libraries and are not to be confused with CPU based Syscall instructions. Linux also requires registers be used for parameter passing in a specific fashion (much like an extended C style DOS) where FreeBSD uses the stack for params (more like C++).

      At any rate -issue surrounds the “exit” process also requiring review of the registers and their value assignments when the application returns as FBSD and Linux expect an exit opcode and return value, but in different manners -this will cause the seg fault you and others have encountered using the FreeBSD NAsm example code in this article without editing it for Linux…


      13-Nov-2017 at 12:05 am

  8. Works on my Freebsd 9 amd64 box, but adds a white % on black background.


    16-May-2012 at 8:13 am

  9. Thanks for this post. I played around with my current 32bit code and got it working in fBSD 9.1rc3

    James Gosnell

    11-Nov-2012 at 4:50 pm

  10. @Frank: you’re correct. In Linux, 1 instead of 4 for sys_write and 3C for sys_exit. The final code goes like:

    section .data

    db ‘hello, world!’, 10, 0

    section .text

    global _start
    mov rax, 1 ; syscall write() opcode
    mov rdi, 1 ; fd 1 (stdout)
    mov rsi, message
    mov rdx, 14 ; message size

    mov rax, 3Ch ; syscall exit() opcode
    xor rdi, rdi ; exit status 0

    `ld` alone produces a 954 bytes executable, and `strip -s` reduces it to 536 bytes. Curiously `gcc -s -nostdlib` produces a 712 bytes executable that can’t be further reduced with strip.

    Rodrigo Silva

    23-Aug-2013 at 3:20 pm

    • You my friend are a genius.
      After hitting the great wall of push and call:70 from other examples it all came to this epiphany: si , source index, the string (of course) and di, destination index, stdout (of course) and dx, data , the length.
      So simple when you’re in good company.
      Thanks for the help.


      16-Aug-2016 at 1:18 am

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: