We will compile -and link- a few lines of assembly, using GAS (GNU Assembler) in Linux.

[ Check out all posts in “low-level” series here. ]

I was reading Programming from the Ground Up by Jonathan Bartlett some time ago. ( It is good. )

It is based on x86, Linux, with GNU toolchain.

The first example is a program that just exits with a specific status code. The file prog_g32.s looks like this:

    .section .data

    .section .text
    .global _start
_start:
    movl $1, %eax
    movl $0, %ebx
    int $0x80

Here is an overview of the code: 1

  • We defined the label _start under the .text section.
  • We made it visible to the linker by marking it .global.
  • We authored the actual CPU instructions.

The label _start is the default executable entry point name ld will look for during link. 2

“Exiting” involves making a system call. So the instructions (following _start) are about setting up the inputs to the call, and executing it.

Let’s look at the instructions line-by-line:

%eax : Holds the system call number for x86.

movl $1, %eax  # System call number for sys_exit: 1

%ebx : Holds the return status ( first syscall param for x86 calling conventions ).

movl $0, %ebx  # Exit status number: 0

Interrupt to notify kernel to make the system call.

int $0x80      # Deprecated ( and probably linux specific ).

Now, this seems to still work when we build an x86-64 executable.

However, there is a better way in x86-64 world for making system calls: syscall instruction.

(Other fast entry instructions exist for x86 and x86-64, but I will omit them.)

The version I modified to use syscall (named prog_g64.s):

    .section .data

    .section .text
    .global _start
_start:
    mov $60, %rax
    mov $0, %rdi
    syscall

As you can see, every line is different. Here is the explanation:

%rax : Holds the system call number for syscall in x86-64.

mov $60, %rax # System call number for sys_exit in x86-64 is different: 60

%rdi : Holds the return status ( first syscall param for x86-64 calling conventions ).

mov $0, %rdi  # Exit status number: 0

Portable x86-64 insn for fast system call:

syscall

(And nowadays there is an even more optimized way to make system calls. I might talk about it in a later post.)

Here is the makefile that I use to build both versions:

# Shell replacement to print the rule being run.
OLD_SHELL := $(SHELL)
SHELL := $(warning Building $@$(if $<, (from $<))$(if $?, ($? newer)))$(OLD_SHELL)

DEPS   =
OBJ    = prog
LD     = ld

.PRECIOUS:  %.s _%.o
_%_g32.o    : %_g32.s $(DEPS)               ; @echo "RULE> $@ : $^" ; as -o $@ $< --32
_%_g64.o    : %_g64.s $(DEPS)               ; @echo "RULE> $@ : $^" ; as -o $@ $< --64
prog_g32    : $(patsubst %,_%,$(OBJ)_g32.o) ; @echo "RULE> $@ : $^" ; $(LD) $^ -o $@ -m elf_i386
prog_g64    : $(patsubst %,_%,$(OBJ)_g64.o) ; @echo "RULE> $@ : $^" ; $(LD) $^ -o $@ -m elf_x86_64 -pie -static -no-dynamic-linker

Below will make the x86 (i386) binary:

make prog_g32

And to make the x86-64 binary:

make prog_g64

After running the program, you can echo $? to print the status number returned from the previous command. Both should return “0”.

  1. I am not very experienced in assembly. If you find errors, please report in the blog’s Issues page

  2. The name of the entry point ld looks for can be modified.