Assembly examples

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

Compiling these examples

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

Running the examples

./prg

You can see the result of the program by issuing echo $? on the prompt after the program.

1. A very basic UNIX 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

2. A very basic Linux program

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)

Compile

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

3. If-statement

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

4. Loop

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

5. Loop 2

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

6. Compute factorials, stack

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

7. Memory references, data segment

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

8. Memory references, more

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

9. Calling libc functions

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.

10. Printing out with printf

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.

11. Arithmethic

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

12. Arithmethic, more

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.

13. Arithmethic, mul

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.

14. Arithmethic, div

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.

15. Conditional move (advanced)

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

Feedback?

Direct feedback to ska[at]bth[dot]se (change the obvious!)


$Id: ia32_examples.templ.html 4904 2005-10-04 12:10:44Z ska $