Previously I showed how I set everything up in order to run asm code on android. It was far from exciting but I would have loved to have that post when I started. In this part I show my simple Hello World code. First, have a look at the following links:
I based my code on the Hello World example and use calls to printf rather than system calls. Nevertheless, the primer posts are a very good introduction to the architecture.
Let’s get on with it, the contents of the hello.s file:
.data
string:
.asciz "Hello, world!\n"
.text
.global main
main:
push {fp, lr}
ldr r0, =string
bl printf
mov r0, #0
pop {fp, pc}
First line we have the assembler directive .data which specifies the beginning of the data section. Here we put all our initialized data. The second line we have a label string which is needed to grab the data from underneath. Without the label the assembler wouldn’t know where the string would be located in memory. The third line contains the .asciz directive together with our string. This directive tells the assembler the type of the data (ascii in this case) and makes sure to add a null terminator at the end, just a 0 really.
The .text directive specifies the beginning of a new section that holds all the code. The following .global directive makes the symbol main visible externally so other code (like the OS) can call it.
When some code calls our now externally visible label main it will put the program counter at the line following the label and the execution will start from there. This is where the meat of the code is.
push {fp, lr}
{fp, lr} is a list of registers containing fp and lr. The fp register is the frame pointer and lr is the link register. The link register holds the next instruction in the code that called our main label and this is were we need to return when we’re done. If we happen to call a function from our code, the call instruction will make sure to set the link register so the new function knows where to return. Because of this we don’t want the contents of this register to be lost so we push it onto the stack. In order to keep to the standard and to get optimal performance we need to keep the stack aligned on an 8 byte boundary. Pushing only the link register would cause it to advance 4 bytes so we need to push 4 more. This is the reason the frame pointer is pushed in this case, but any other register would have worked. You can read more about why this is done here.
ldr r0, =string
The ldr instruction is actually a pseudo instruction which resolves to a load relative to the program counter. The address of the string is placed (together with others if present) in a literal pool. The offset from the program counter is calculated and this is used to load that address into r0.
bl printf
When calling a function the arguments are passed in the registers r0 to r3. If more than 4 arguments are passed the rest will have to be placed on the stack. The previous line in the code loaded the address of our string into r0, which will serve as the only argument passed to printf. The bl instruction is a branch with link, meaning that we jump to the first line of the code for the printf function and at the same time we store the address of the line _after_ our bl printf in the link register. This is the reason why we had to push lr on the stack at the beginning of our code, otherwise its value would have been lost now.
mov r0, #0
When printf returns it will jump to this line. This is fairly simple, we move the value 0 into the register r0. When a function returns its return value needs to be placed in r0. We have prepared our register and the next line handles the actual jump.
pop {fp, pc}
At the beginning of our function we pushed two registers onto the stack: the frame pointer that served as a dummy register to align the stack and the link register. Since we want to return from our function we want the program counter to be set to the value of the link register when our function was called. The pop handles this by specifying the pc register as the second destination in the register list causing the saved value from the link register to be loaded directly into the register that’s used as the program counter.
Next up, some more code.