The Brown Notebook

Colorless green ideas, sleeping furiously.

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

with 13 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

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:

  • _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.

About these ads

Written by mdevanr

27-Oct-2009 at 12:24 pm

Posted in Uncategorized

Tagged with ,

13 Responses

Subscribe to comments with RSS.

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

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

  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.

    leotherussian

    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)

    YASM:

    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

    NASM:

    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

    fabiok

    12-Jul-2010 at 12:11 pm

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

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

    segfault

    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:

    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

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

    Bert

    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

    https://github.com/EhevuTov/asm-fbsd64-hello-world

    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

    message:
    db ‘hello, world!’, 10, 0

    section .text

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

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

    `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


Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: