Tutorial: Assembly Language with the Raspberry Pi

From dftwiki3
Revision as of 08:48, 4 July 2015 by Thiebaut (talk | contribs) (Recursive Towers of Hanoi)
Jump to: navigation, search

--D. Thiebaut (talk) 08:19, 3 July 2015 (EDT)


This is a very basic introduction to coding in assembly language on the ARM processor of the Raspberry Pi. It is intended to provide examples of code for typical operations one may want to do, not as an introduction to assembly language.
You can find more tutorials here.



References



Setup


  • Please refer to this page for information on how to set your system up. I'm using a Mac connected to the Pi via wifi, and ssh-ing to it in a Terminal window.
  • The controller used in this tutorial is a Raspberry Pi 2, running 2015-05-05-raspbian-wheezy. No special customization was performed, except setting up USB-based Wifi communication.


Program that Returns an Exit Code


  • This example is taken from thinkingeek.com's tutorial on assembly on the Pi. It is repeated here for completeness. Refer to thinkingeek.com's excellent tutorials for a deeper understanding of what is going on.


/* first.s
   from thinkingeek.com
   http://thinkingeek.com/2013/01/09/arm-assembler-raspberry-pi-chapter-1/
   Defines a main function that returns 2 as an exit code.
	
*/
	
.global main    /* 'main' must be visible by the C compiler. */
	
.func main      /* declare 'main' as a function              */
main: 
    mov r0, #2       /* load immediate value 2 into Register r0 */
    bx lr            /* return 2 to Operating Sytem             */


Create the program


Note that all the commands below must be run on the Raspberry Pi, either directly, if you have connected a keyboard and video monitor to it, or remotely, via ssh and an ethernet cable or a Wifi link.


  • ssh to the Pi using the Terminal Window. Refer to this page for how to do this.
  • Use your favorite editor on the Pi (mine is emacs), create a file called first.s and enter this code. If you are interested in bypassing the editor, simply type this:
    cat > first.s

and paste the code above. Then press ENTER, Control-D, ENTER, and this should create the file for you.
  • To verify that the file is created, type this:
   cat first.s

and you should see the contents of the file.


PiAssembly1.png


Assemble, Compile, and Run!


  • Assemble the code to create an object file:
   as -o first.o first.s

This will create the object file first.o
  • Compile it. The object file is not executable. A C compiler can read it, though, and transform it into an executable.
   gcc -o first first.o

This takes the object file first.o and generates the executable file first.
  • Run it!
   ./first

  • The program should run and not display anything. As long as you don't get an error, your have been successful. The reason there is no output is that the program does not explicitly print anything, and simply returns a number that the operating system gets. Whenever you run a program on the command line, it returns a number at the end, revealing some status information about whether the program was successful in doing whatever we asked it to do. To reveal the number, we can use the shell command "echo $?" which displays the number returned by the last program that was run from the command line.


    ./first | echo $?
    2 

The number 2 should be printed.


Hello World!


  • Here's the program:


@ hello.s
@   D. Thiebaut
@   Just your regular Hello World program!
@	


@ ---------------------------------------
@	Data Section
@ ---------------------------------------
	
	.data
string: .asciz "\nHello World!\n"
	
@ ---------------------------------------
@	Code Section
@ ---------------------------------------
	
	.text
	.global main
	.extern printf

main:
        push {ip, lr}

        ldr r0, =string
        bl printf

        pop {ip, pc}


Assemble, Link/Compile, and Run


PiAssembly2.png



Arithmetic With Integer Variables


@ sum1.s
@   D. Thiebaut
@   add 2 variables together
@	


@ ---------------------------------------
@	Data Section
@ ---------------------------------------
	
	.data
	.balign 4	
string: .asciz "\na + b = %d\n"
a:	.word	33
b:	.word	44
c:	.word 	0		@ will contain a+b 
	
@ ---------------------------------------
@	Code Section
@ ---------------------------------------
	
.text
.global main
.extern printf

main:	
        push 	{ip, lr}	@ push return address + dummy register
				@ for alignment

        ldr	r1, =a		@ get address of a into r1
	ldr	r1, [r1]	@ get a into r1
	ldr	r2, =b		@ get address of b into r2
	ldr	r2, [r2]	@ get b into r2
	add	r1, r1, r2	@ add r1 to r2 and store into r1
	ldr	r2, =c		@ get address of c into r2
	str	r1, [r2]	@ store r1 into c
	
	
        ldr 	r0, =string 	@ get address of string into r0
	ldr	r1, [r2]	@ pass c=a+b into r1
        bl 	printf		@ print string and r1 as param

        pop 	{ip, pc}	@ pop return address into pc


PiAssembly3.png


Arithmetic With Integer Variables (Version 2)


  • This version performs the same arithmetic, but generates a more sophisticated output.


@   sum2.s
@   D. Thiebaut
@   add 2 variables together and print the result.
@	


@ ---------------------------------------
@	Data Section
@ ---------------------------------------
	
	.data
	.balign 4	
string: .asciz "\n%d + %d = %d\n"
a:	.word	33
b:	.word	44
c:	.word 	0		@ will contain a+b 
	
@ ---------------------------------------
@	Code Section
@ ---------------------------------------
	
.text
.global main
.extern printf

main:
        push 	{ip, lr}	@ push return address + dummy register
				@ for alignment

	ldr	r1, =a		@ get address of a into r1
	ldr	r1, [r1]	@ get a into r1
	ldr	r2, =b		@ get address of b into r2
	ldr	r2, [r2]	@ get b into r2
	add	r1, r1, r2	@ add r1 to r2 and store into r1
	ldr	r2, =c		@ get address of c into r2
	str	r1, [r2]	@ store r1 into c
	
	
        ldr 	r0, =string 	@ get address of string into r0
	ldr	r1, =a		@ r1 <- a
	ldr	r1, [r1]
	ldr	r2, =b		@ r2 <- b
	ldr	r2, [r2]
	ldr	r3, =c		@ r3 <- c
	ldr	r3, [r3]	@ 
        bl 	printf		@ print string and pass params
				@ into r1, r2, and r3

        pop 	{ip, pc}	@ pop return address into pc


PiAssembly4.png


Passing Parameters by Value


  • In this example, two ints are passed by value to the function sumFunc. The function adds them up, and returns the result in r0.


@  sum3.s
@  D. Thiebaut
@  Illustrates how to pass 2 ints by value
@  to a function that adds them up and returns	
@  the sum in r0.

@ ---------------------------------------
@	Data Section
@ ---------------------------------------
	
	.data
	.balign 4	
string: .asciz "\n%d + %d = %d\n"
a:	.word	33
b:	.word	44
c:	.word 	0		@ will contain a+b 
	
@ ---------------------------------------
@	Code Section
@ ---------------------------------------
	
.text
.global main
.extern printf

	
@ ---------------------------------------
@ sumFunc: gets 2 ints in r1 and r2, adds
@ 	them up and saves the results in
@	r0.	
sumFunc:
	push	{ip, lr}
	add	r0, r1, r2
	pop	{ip, pc}

@ ---------------------------------------
@ main: passes 2 ints to sumFunc and prints
@ the resulting value using printf	
main:   push 	{ip, lr}	@ push return address + dummy register
				@ for alignment
	ldr	r1, =a		@ get address of a into r1
	ldr	r1, [r1]	@ get a into r1
	ldr	r2, =b		@ get address of b into r2
	ldr	r2, [r2]	@ get b into r2

	bl	sumFunc		@ pass (r1, r2) to sumFunc
				@ gets sum back in r0
	ldr	r2, =c		@ get address of c into r2
	str	r0, [r2]	@ store r0 into c
	

	@ printf( "%d + %d = %d\n", r1, r2, r3 )
	@ (format-string address passed in r0)
        ldr 	r0, =string 	@ get address of string into r0
	ldr	r1, =a		@ r1 <- a
	ldr	r1, [r1]
	ldr	r2, =b		@ r2 <- b
	ldr	r2, [r2]
	ldr	r3, =c		@ r3 <- c
	ldr	r3, [r3]	@ 
        bl 	printf		@ print string and pass params
				@ into r1, r2, and r3

@ return to OS	
        pop 	{ip, pc}	@ pop return address into pc


pi@raspberrypi ~/temp $ as -o sum3.o sum3.s
pi@raspberrypi ~/temp $ gcc -o sum3 sum3.o
pi@raspberrypi ~/temp $ ./sum3

33 + 44 = 77
pi@raspberrypi ~/temp $ 


Passing Parameters by Reference


@ sum4.s
@   D. Thiebaut
@   Illustrates how to pass 2 ints by reference
@   to a function that adds them up and returns	
@   the sum in r0.

@ ---------------------------------------
@	Data Section
@ ---------------------------------------
	
	.data
	.balign 4	
string: .asciz "\n%d + %d = %d\n"
a:	.word	33
b:	.word	44
c:	.word 	0		@ will contain a+b 
d:	.word	55
e:	.word	22
	
@ ---------------------------------------
@	Code Section
@ ---------------------------------------
	
.text
.global main
.extern printf

	
@ ---------------------------------------
@ sumFunc: gets 2 ints in r1 and r2, adds
@ 	them up and saves the results in
@	r0.	
sumFunc:
	push	{ip, lr}
	ldr	r1, [r1]
	ldr	r2, [r2]
	add	r0, r1, r2
	pop	{ip, pc}

@ ---------------------------------------
@ printFunc: prints [r1], [r2], r3 in this way.
@ printf( "%d + %d = %d\n", r1, r2, r3 )
@ (format-string address passed in r0)
printFunc:
	push	{ip, lr}
        ldr 	r0, =string 	@ get address of string into r0
	ldr	r1, [r1]
	ldr	r2, [r2]
	mov	r3, r3		@ not necessary...
        bl 	printf		@ print string and pass params
				@ into r1, r2, and r3

	pop	{ip, pc}
	
@ ---------------------------------------
@ main: passes 2 ints to sumFunc and prints
@ the resulting value using printf	
main:   push 	{ip, lr}	@ push return address + dummy register
				@ for alignment

@ c = a + b
	ldr	r1, =a		@ get address of a into r1
	ldr	r2, =b		@ get address of b into r2

	bl	sumFunc		@ pass (r1, r2) to sumFunc
				@ gets sum back in r0
	ldr	r3, =c		@ get address of c into r2
	str	r0, [r3]	@ store r0 into c

@ print a + b = c
	ldr	r1, =a
	ldr	r2, =b
	ldr	r3, =c
	ldr	r3, [r3]
	bl	printFunc

	
@ return to OS	
        pop 	{ip, pc}	@ pop return address into pc


pi@raspberrypi ~/temp $ as -o sum4.o sum4.s
pi@raspberrypi ~/temp $ gcc -o sum4 sum4.o
pi@raspberrypi ~/temp $ ./sum4

33 + 44 = 77
pi@raspberrypi ~/temp $ 


Using the C-function scanf() for User Input


@ scanfExample1.s
@ D. Thiebaut
@ A simple example illustrating how one
@ can call the C function scanf() from
@ assembly.
@ The C example for this code would be:
@
@	int num = 0;
@	printf( "> " );
@	scanf( "%d", &num );
@	print( "your input: %d\n", num ) ;
@

@ ---------------------------------------
@	Data Section
@ ---------------------------------------
	
	.data
	.balign 4	
prompt:	.asciz	"> "
format: .asciz 	"%d"
num:	.int	0
output: .asciz 	"your input: %d\n"
	
@ ---------------------------------------
@	Code Section
@ ---------------------------------------
	
	.text
	.global main
	.extern printf
	.extern scanf

main:   push 	{ip, lr}	@ push return address + dummy register
				@ for alignment

	ldr	r0, =prompt	@ print the prompt
	bl	printf

	ldr     r0, =format	@ call scanf, and pass address of format
	ldr	r1, =num	@ string and address of num in r0, and r1,
	bl	scanf		@ respectively.

	ldr	r1, =num	@ print num formatted by output string.
	ldr	r1, [r1]
	ldr	r0, =output
	bl	printf


        pop 	{ip, pc}	@ pop return address into pc


pi@raspberrypi ~/temp $ make
as   -o scanfExample1.o scanfExample1.s
gcc -o scanfExample1 scanfExample1.o
pi@raspberrypi ~/temp $ ./scanfExample1  
> 123456789
your input: 123456789
pi@raspberrypi ~/temp $ 



Recursive Towers of Hanoi


Tower of Hanoi 4.gif
  • This is the typical recursive Towers of Hanoi program. You can read more about this typical computer science problem in Wikipedia.
  • We are passing the pegs through r5, r6, and r7, and n through r4. We need swap the pegs when calling recursively, so r5, r6 and r7 get modified inside a call, and we cannot let the recursion modify them. Similarly, we decrement n (r4) by 1 inside the call, and we don't want the recursion to alter n. Because of this, we save r4, r5, r6 and r7 in the stack when calling moveDisks(). We add r8 because we are also saving pc, and we need an even number of registers for good alignment in the stack.


@   hanoi.s
@   D. Thiebaut
@   Recursive towers of Hanoi
@   See https://en.wikipedia.org/wiki/Tower_of_Hanoi Wikipedia for details.

@ ---------------------------------------
@	Data Section
@ ---------------------------------------
	
	.data
	.balign 4	
string: .asciz  "move disk from %c to %c\n\0"
peg1:	.int	'A'		@ name of Peg 1, must be a char
peg2:	.int	'B'
peg3:	.int	'C'
n:	.int	4		@ number of disks to move

	
@ ---------------------------------------
@	Code Section
@ ---------------------------------------
	
	.text
	.global main
	.extern printf

	
@ ---------------------------------------
@ moveDisks( n, Source, Dest, Extra )
@	    r4   r5     r6    r7
@    if n==1:
@	printf( "move disk from %c to %c\n", Source, Dest )
@    else:
@	moveDisks( n-1, Source, Extra, Desk )
@	printf( "move disk from %c to %c\n", Source, Dest )
@	moveDisks( n-1, Extra, Dest, Source )
@	
moveDisks:	
	push	{r4-r8, lr}

	@ if n==1:
	cmp	r4, #1
	bgt	moveN_1Disks

move1Disk:	
	@ print( "move disk from %c to %c\n", Source, Dest )
	ldr	r0, =string
	mov	r1, r5
	mov	r2, r6
	bl	printf
	b	endMoveDisk

moveN_1Disks:	
	@ moveDisks( n-1, Source, Extra, Dest )
	mov	r8, r7		@ swap Dest & Extra
	mov	r7, r6
	mov	r6, r8
	sub	r4, #1
	bl	moveDisks
	mov	r8, r7		@ unswap Dest & Extra
	mov	r7, r6
	mov	r6, r8

	@ print( "move disk from %c to %c\n", Source, Dest )
	ldr	r0, =string
	mov	r1, r5
	mov	r2, r6
	bl	printf

	@ moveDisks( n-1, Extra, Dest, Source )	
	mov	r8, r5		@ swap Source and Extra, i.e. r5 and r7.
	mov	r5, r7
	mov	r7, r8
	bl	moveDisks
	
endMoveDisk:	
	pop	{r4-r8, pc}

	
@ ---------------------------------------
@ main: call moveDisks( n, peg1, peg2, peg3 )

main:   push 	{ip, lr}	@ push return address + dummy register
				@ for alignment

	ldr	r4, =n		@ pass n
	ldr	r4, [r4]
	ldr	r5, =peg1	@ pass peg1
	ldr	r5, [r5]
	ldr	r6, =peg2	@ pass peg2
	ldr	r6, [r6]
	ldr	r7, =peg3	@ pass peg3
	ldr	r7, [r7]
	bl	moveDisks	@ call moveDisks( n, peg1, peg2, peg3 )

	
@ return to OS	
        pop 	{ip, pc}	@ pop return address into pc


pi@raspberrypi ~/temp $ make
as -o hanoi.o hanoi.s
gcc -o hanoi hanoi.o
pi@raspberrypi ~/temp $ ./hanoi 
move disk from A to C
move disk from A to B
move disk from C to B
move disk from A to C
move disk from B to A
move disk from B to C
move disk from A to C
move disk from A to B
move disk from C to B
move disk from C to A
move disk from B to A
move disk from C to B
move disk from A to C
move disk from A to B
move disk from C to B
pi@raspberrypi ~/temp $