microcoded

Mar 14

ARM assembly on Android part 3

Before I dug into anything deeper I wanted to code a slightly larger sample and get to know some of the common instructions. I expanded the Hello World code and added a naive length function that iterates over the string and computes the number of characters between the start address and the null terminator.

Here’s the code:

.data
string:
	.asciz	"Hello, world!\n"
string2:
	.asciz	"String is %d characters\n"

.text
.global main
main:
	push		{r4, lr}
	ldr     	r4, =string
	mov     	r0, r4
	bl      	printf
	mov     	r0, r4
	bl      	length
	mov     	r0, #0
	pop		{r4, pc}
	
.global length
length:
	push		{r4, lr}
	mov 		r1, #0
	mov 		r4, #0
length_loop:
	ldr     	r3, [r0, r4]
	and		2, r3, #0x000000FF
	cmp     	r2, #0
	beq     	length_outro
	add     	r1, r1, #1
	and     	r2, r3, #0x0000FF00
	cmp     	r2, #0
	beq     	length_outro
	add     	r1, r1, #1
	and     	r2, r3, #0x00FF0000
	cmp     	r2, #0
	beq     	length_outro
	add     	r1, r1, #1
	and     	r2, r3, #0xFF000000
	cmp     	r2, #0
	beq     	length_outro
	add     	r1, r1, #1
	add     	r4, r4, #4 
	b       	length_loop
length_outro:
	ldr     	r0, =string2
	bl      	printf
	pop     	{r4, pc}

The main function is very similar to the previous example with mainly two differences: we load the address of string in r4 before moving it over to r0 as an argument to the function calls and we also have a second function call.

Between function calls only registers r4-r11 are guaranteed to be preserved. r0-r3 might not have the same values as when the function was called which is why we need to move the address of string into r0 before both function calls. The initial ldr instruction is a pseudo instruction and does more work under the hood. Because of that I chose to save the result in r4 and simply move it to r0 before every function call to potentially save a cycle or two.

The first call is to printf which, like the previous example, prints our string. The second call is to our new label length. Note that I use label and function interchangeably. Here we pass in as an argument the address of the string, just like the printf call.

First thing we do in length is to push r4 and lr to the stack. In order to follow the procedure calling standard for ARM we need to make sure r4 will have the same value when our function returns as when it was called. Since we’ll be using the register we need to save the contents.

Next we move 0 into r1 which will be used to keep track of the length of the string. r4 is also loaded with 0 and this register will be used as an offset to read from the memory where we have our string. We want to start reading from the start so we begin with offset 0.

Next we have the length_loop label which denotes the start of the loop. Every iteration we load the next 4 bytes of the string and test each byte individually by isolating the byte and checking if it’s 0. For every byte that isn’t equal to 0 we increment the value in r1. When a zero byte is found, we jump to the label length_outro and out of the loop.

Now we simply load the second string into r0 to pass in as an argument to printf with the second argument (the length of the string) already present in r1. Finally we pop the saved registers from the stack which returns from our function.