Solved x86_64 assembly hello world (again)

I can't get my simple “Hello World” program (see below) to run in x86_64 assembly!

I read the section in the developers handbook, browsed the web and used information in this thread: assembly-simple-hello-world

… and came up with the following:

Code:
        .section .rodata

msg:
        .ascii "Hello World!\n"

        .section .text
        .global _start

_start:
        movq    $1, %rax        # SYS_write
        movq    $1, %rdi        # STDOUT
        movabsq $msg, %rsi      # Output buffer
        movq    $13, %rdx       # length   
        syscall

        movq    $60, %rax       # SYS_exit
        movq    $0, %rdi        # EXIT_SUCCESS
        syscall

I assembled it with the following command, which executes without error:
Code:
cc -g -x assembler -static -nostartfiles -nostdlib hello_world.s -o hello_world

When executing hello_world nothing appears on the console and the exit code is 1

The output of objdump -d ./hello_world looks ok to me:
Code:
hello_world:    file format elf64-x86-64

Disassembly of section .text:

0000000000201130 <_start>:
  201130: 48 c7 c0 01 00 00 00          movq    $0x1, %rax
  201137: 48 c7 c7 01 00 00 00          movq    $0x1, %rdi
  20113e: 48 be 20 01 20 00 00 00 00 00 movabsq $0x200120, %rsi         # imm = 0x200120
  201148: 48 c7 c2 0d 00 00 00          movq    $0xd, %rdx
  20114f: 0f 05                         syscall
  201151: 48 c7 c0 3c 00 00 00          movq    $0x3c, %rax
  201158: 48 c7 c7 00 00 00 00          movq    $0x0, %rdi
  20115f: 0f 05                         syscall

Using lldb to find out what's going on I discovered that the first syscall exits the process.

What did I do wrong?
 
Arrggh … those darn Linuxisms! I messed up the syscall IDs!

Here is a program that works:
Code:
        .section .rodata

msg:
        .ascii "Hello World!\n"

        .section .text
        .global _start

_start:
        movq    $4, %rax        # SYS_write
        movq    $1, %rdi        # STDOUT
        movabsq $msg, %rsi      # Output buffer
        movq    $13, %rdx       # length
        syscall

        movq    $1, %rax        # SYS_exit
        movq    $0, %rdi        # EXIT_SUCCESS
        syscall
 
FWIW, I know this was mostly a cut-n-paste experiment, but you should ALWAYS define assembly macros for the constants in your code...like for the stdout file descriptor and for the syscall opcode: much more readable then.
 
To later improve your test code here's my 2c:
- you can specify 32b registers, upper half will be zero extended: movl $4, %eax will set upper half of %rax to 0 : produces smaller code
- while it works in this example movabsq is really not what you want to use. have a look at lea instruction
- your msg is not a truly \0 ended string. In most cases you do want to have \0 terminated string, you can use .asciz directive in as
- with . (current position) you can calculate the len of string (current - label) is its size
- I'm printing hello with absolute adresing (loading effective address to a register), bye with relative addressing (relative to rip). With this (relative addressing) you can later create PIC (position independent code).

Example of such code:
Code:
.section .data
        msg:    .asciz  "hello world\n"
        len1 = . - msg
        bye:    .asciz  "bye!\n"
        len2 = . - bye

.section .text
        .globl _start

_start:
        /* write(1, msg, len) */
        movl $1, %edi
        leaq msg, %rsi
        movl $len1, %edx
        movl $4, %eax
        syscall

        /* write(1, msg2, len2) */
        movl $1, %edi
        leaq bye(%rip), %rsi
        movl $len2, %edx
        movl $4, %eax
        syscall

        /* exit(42) */
        movl $42, %edi
        movl $1, %eax
        syscall

Even FreeBSD handbook mentions GNU/as from binutils as an option. I use them daily, I use them on FreeBSD too.
I compiled above code with:
Code:
/usr/local/bin/as -o test.o test.S
/usr/local/bin/ld -o test test.o

You can debug with lldb, I personally prefer gdb. truss(1) can help you quickly see if parameters to syscall make sense.
Code:
$ truss ./test
hello world
write(1,"hello world\n\0",13)             = 13 (0xd)
bye!
write(1,"bye!\n\0",6)                 = 6 (0x6)
exit(0x2a)
process exit, rval = 42

Hope this helps.
 
Back
Top