You can find some simple x86 assembly examples below. All are in AT&T-style assembly and can be built using the GNU tools.
The examples can be downloaded here: examples.tar.gz
Place in a file named prg.S (or something) and issue:
as -gstabs -o prg.o prg.S gcc -nostdlib -lc -o prg prg.o
-gstabs specifies that we want debugging symbols in the object-file (to be able to debug it in GDB).
-nostdlib states that we should not link with the standard libraries (if we don't use this, _start will already be defined).
-lc specifies that we link with libc, i.e. to get the exit symbol defined.
Note: on BSD systems, you might need to define the __progname and environ symbols (thanks to Anders Olofsson and Ulrik Mikaelsson for pointing this out). In this case, create a file workaround.S which contains
.data .globl __progname __progname: .int 0 .globl environ environ: .int 0
And compile that as above (with as). Then link with:
gcc -nostdlib -lc -o prg prg.o workaround.o
./prg
You can see the result of the program by issuing echo $? on the prompt after the program.
This is a complete program that immediately exits.
.section .text
.globl _start # Make start symbol visible
_start:
pushl $2 # argument 2 to exit
call exit
The result should be 2
This program does the same thing as the last, but issues a Linux system call directly instead of using libc.
.section .text
.globl _start # Make start symbol visible
_start:
movl $1, %eax # Exit
movl $2, %ebx # argument 2 to exit
int $0x80 # Soft interrupt 0x80 (linux syscall)
This must be compiled slightly differently:
as -gstabs -o prg.o prg.S gcc -nostdlib -o prg prg.o
We don't need -lc here since we don't use any functionality from libc.
The result should be 2
This shows one way of implementing an if-statement in x86 assembly. It also shows a non-standard way of passing arguments to functions (i.e. not through the stack).
.section .text
if_stmt:
cmpl $1, %eax # if (%eax == 1)
je then # goto then
movl $2, %ebx # else %ebx = 2
jmp out # goto out
then:
movl $3, %ebx # %ebx = 3
out:
ret
.globl _start
_start:
movl $1, %eax # Argument to if_stmt
call if_stmt
## %ebx will be 3 here
pushl %ebx
call exit
The result should be 3
This shows one way of implementing a "while" loop in assembly. See the other examples for other ways of looping (sometimes more efficient than this one).
.section .text
while_stmt:
xorl %ebx, %ebx # %ebx = 0
l1: cmpl $0, %eax # if (%eax == 0)
je 1f # exit loop (note the "local" forward-label)
addl $2, %ebx # add 2 to %ebx, will be done %eax times
decl %eax # %eax--
jmp l1 # loop
1: # This is a local label
ret
.globl _start
_start:
movl $5, %eax
call while_stmt
## %ebx will be 10 here
pushl %ebx
call exit
The result should be 10
Loops can also be implemented with the "loop" instruction.
.section .text
while_stmt:
xorl %ebx, %ebx # zero %ebx
l1: addl $2, %ebx # add 2 to %ebx, will be done %ecx times
loop l1 # %ecx--; if (%ecx != 0) goto l1;
ret
.globl _start
_start:
movl $5, %ecx # Init %ecx to 5 (not the C calling convention!)
call while_stmt
## %ebx will be 10 here
pushl %ebx
call exit
The result should be 10
This program shows a function implementation, a loop and the calculation of the factorial of 5. It also shows usage of the stack as well as the performing multiplications.
.section .text
fac:
movl 4(%esp), %ecx # Get the argument
movl $1, %eax # Init the result
l1: mull %ecx # %eax = %eax * %ecx
loop l1 # %ecx--, jump to l1 if %ecx != 0
ret
.globl _start
_start:
pushl $5 # Push the argument to the faculty program
call fac # Pushes the return address on the stack, calls fac
addl $4, %esp # Restore the stack
pushl %eax # Push the result as exit code
call exit
The result should be 120, the factorial of 5
This program shows memory references and using the data segment. The program adds together the numbers in NBRS until a zero is found.
.section .data
NBRS: .long 1
.long 2
.long 3
.long 0
.section .text
adder:
xorl %eax, %eax # zero %eax
movl $NBRS, %ebx # The address of NBRS
l: movl (%ebx), %ecx # Get the number at (%ebx)
cmpl $0, %ecx # if %ecx == 0
je out # return
addl %ecx, %eax # (else) %eax = %eax + %ecx
addl $4, %ebx # Point %ebx to the next number
jmp l # Loop
out:
ret
.globl _start
_start:
call adder
pushl %eax # Push the result
call exit
The result should be 6
This program is a more advanced version of the last (not exactly the same, but similar), which adds together the three numbers in NBRS. It uses a more advanced memory referecning and the loop instruction instead of jumping. Note the local label as well.
Exercise for the reader: In what order are the numbers added together? How can the "extra" addl be removed?
.section .data
NBRS: .long 1
.long 2
.long 3
.section .text
adder:
xorl %eax, %eax # zero the result
movl $2, %ecx # init %ecx
movl $NBRS, %edi # The address of NBRS
1: addl (%edi, %ecx, 4), %eax # Get the number at (%edi+%ecx*4), add to %eax
loop 1b
addl (%edi, %ecx, 4), %eax # The last number
ret
.globl _start
_start:
call adder
pushl %eax # Push the result
call exit
The result should be 6
This calls the rand() function and returns the result (i.e. a not-so random number generator).
.section .text .globl _start _start: pushl $100 # Argument to srand call srand # Call srand addl $4, %esp # Restore the stack call rand # rand takes no arguments andl $31, %eax # Mask out the 5 least significant bits (a value between 0 and 31) pushl %eax # Pass the value to exit call exit
The result depends on your system, but is between 0 and 31.
This example shows how to call printf
.section .data fmt_str: .asciz "Hello, the numbers is %d, %d\n" .section .text .globl _start _start: movl $2, %eax # Init eax to some value pushl $5 # Push the last number pushl %eax # Push the second last number pushl $fmt_str # The adress of the format string call printf # Print it out! addl $12, %esp # Restore the stack pushl $0 # Argument to exit call exit
The result is 0.
This example shows simple arithmetic and memory references.
.data NBRS: .long 1 # NBRS .text .globl _start # Make start symbol visible _start: movl $5, %eax addl %eax, NBRS # (NBRS) + 5 pushl NBRS # push the first dword on the stack (arg to exit) call exit
The result should be 6
This example shows arithmetic with word-sized operands and some more memory references.
.data NBRS: .long 1 # NBRS+0 .long 2 # NBRS+4 .word 4 # NBRS+8, Word-size (2 bytes)! .byte 5 # NBRS+10, byte-size! .text .globl _start # Make start symbol visible _start: movl $NBRS, %edi # Address of NBRS in %edi movl 4(%edi), %eax # Second number in %eax subl (%edi), %eax # Subtract the first number from the second addw 8(%edi), %ax # Add the word to %ax (i.e. the low 16 bits) pushl %eax # Should be 5 call exit
The result should be 5.
This shows unsigned integer multiplication and memory references.
.data NBRS: .long 2 # NBRS+0 .long 15 # NBRS+4 .text .globl _start # Make start symbol visible _start: movl $NBRS, %edi # Address of NBRS in %edi movl 0(%edi), %eax # %eax = NBRS[0] mull 4(%edi) # %eax = NBRS[1]*%eax pushl %eax # Should be 30 call exit
The result should be 30.
This example shows unsigned integer division. Try pushing %edx as the argument to exit instead. Note the %edx:%eax notation, where the 64-bit value %edx << 32 | %eax is divided by %ebx
.text .globl _start # Make start symbol visible _start: xorl %edx, %edx # Zero %edx movl $33, %eax movl $15, %ebx ## %eax = %edx:%eax/%ebx, ## %edx = %edx:%eax MOD %ebx divl %ebx pushl %eax # Should be 2 call exit
The result should be 2.
This example shows conditional moves, a series of instructions found in Pentium Pro and above. The advantage with this is that there will be no missed branch predictions. The code below shows a if-else construction without branches.
.section .text
.globl _start
_start:
movl $10, %ebx # Init ebx to something
movl $15, %ecx # ecx = 15
movl $5, %eax # eax = 5 (assume else)
## The cmovzl will copy %ecx to %eax iff %ebx == 11
cmpl $11, %ebx # if ebx == 11, set flags
cmovzl %ecx,%eax # if (zero flag set) eax = ecx
pushl %eax
call exit
The result should be 5
Direct feedback to ska[at]bth[dot]se (change the obvious!)