I'm trying to convert this recursive Fibonacci code to arm assembly language. I'm new to this and not really sure how to do it. I have some code snippets of things that I've played with below.
Fib (n) {
if (n == 0 || n == 1) return 1;
else return Fib(n-2) + Fib(n-1);
}
Here is my attempt so far:
RO = 1
CMP RO #1
BGT P2
MOV R7 #1
B END
P2:
END LDR LR [SO,#0]
ADD SP SP, #8
MOV PC, LR
Help would be much appreciated
For the sake of avoiding spoon-feeding, I wrote a LEGv8 program that finds Fibonacci sequence using recursion. LEGv8 is slightly different than ARMv8, however the algorithm remains.
Please review the code, and change the commands / registers to their corresponding values in ARMv8.
I assumed that n (the range of the Fibonacci sequence) is stored in register X19.
I also assumed that we ought to store the Fibonacci sequence in an array, which has its base address stored in X20.
MOV X17, XZR // keep (previous) 0 in X17 for further use
ADDI X18, XZR, #1 // keep (Current) 1 in X18 for further use
ADDI X9, XZR, #0 // Assuming i = 0 is in register X9
fibo:
SUBI SP, SP, #24 // Adjust stack pointer for 3 items
STUR LR, [SP, #16] // save the return address
STUR X17, [SP, #8] //save content of X17 on the stack
STUR X18, [SP, #0] //save content of X18 on the stack
SUBS X10, X9, X19 // test for i==n
CBNZ X10, L1 // If i not equal to n, go to L1
MOV X6, XZR // keep 0 on X6
ADDI X5, XZR, #1 // keep 1 on X5
ADDI X2, X9, #1 //X9 increased by 1 for further use
STUR X6, [X20,#0] //store 0 in the array
STUR X5, [X20, #8] //store 1 in the array
ADDI SP, SP, #24 // pop 3 items from the stack
BR LR // return to the caller
L1:
ADD X16, X17, X18 // Next_Num = previous + Current
MOV X17, X18 // Previous = Current
MOV X18, X16 // Current= Next_Num
ADDI X9, X9, #1 // i++
BL fibo // call fibo
LDUR X18, [SP, #0] // return from BL; restore previous
LDUR X17, [SP, #8] // restore current
LDUR LR, [SP, #16] // restore the return address
ADDI SP, SP, #24 // adjust stack pointer to pop 3 items
ADD X7, X18, X17 // keep (previous + current) value on register X7
LSL X2, X2, #3 // Multiplying by 8 for offset
ADD X12, X20, X2 // address of the array increase by 8
STUR X7, [X12, #0] // store (previous + current) value on the array
SUBI X2, X2, #1 // X9 decreased by 1
BR LR // return
Related
I really have absolutely no idea how to do this, seems like the only solution online is behind a paywall. This is all I have:
.data
array: .word 1 2 3 4 5 6 7
.text
main:
la x12, array
addi x13, x0, 7
addi x13, x13, -1
slli x13, x13, 2
add x13, x13, x12
jal x1, reverse
beq x0, x0, END
##### the function would start here #####
reverse: jalr x0, 0(x1)
##### and end here #####
END: add x1, x0, x10
The procedure receives two memory locations, the start position and the end position of the array.
My guess was something like using srli, but I'm 99.9% sure it's wrong.
(this is not for a college assignment btw, just an unnecessarily long revision exercise that's been haunting me for a week and now my brain freezes every time I look at a single line of assembly code)
I'm trying to learn RISC-V under the Jupiter environment (risc32) and I came across a problem asking me to write a recursive program with RISC-V. I can't seem to get the sw instruction to work, as it always gives an error: invalid address
I've tried different offsets, different registers etc. nothing seems to work
.globl __start
.rodata
msg_input: .string "Enter a number: "
msg_result: .string "The result is: "
newline: .string "\n"
.text
__start:
# prints msg_input
li a0, 4
la a1, msg_input
ecall
#read from standard input
li a0, 5
ecall
#initialize stack
addi x31, x0, 2
addi sp, x0, 800
mv x5, a0
jal x1, recfunc
mv t0, x5
recfunc:
addi sp, sp, -8
sw x1, 0(sp)
bge x5, x31, true
lw x1, 0(sp)
addi x10, x0, 1
addi sp,sp, 8
jalr x0, 0(x1)
true:
div x5, x5, x31
jal x1, recfunc
lw x1, 0(sp)
addi sp,sp,8
mul x10, x10, x31
addi x10, x10, 1
jalr x0, 0(x1)
result:
#prints msg_result
li a0, 4
la a1 msg_result
ecall
#prints the result in t0
li a0, 1
mv a1, t0
ecall
#ends the program with status code 0
li x5, 10
ecall
Error occurs at:
sw x1, 0 (x2)
(error) attempting to write to an invalid memory address 0x00000318
I am trying to create a recursive program in RISC-V but I can't get it to get me the right result. It looks like it is calling itself only two times max, but I tried running it on paper and everything seems correct:
addi x31, x0, 4
addi x30, x0, 2
addi x2, x0, 1600 //initialize the stack to 1600, x2= stackpointer
ecall x5, x0, 5 //read the input to x5
jal x1, rec_func
ecall x0, x10, 2 //print the result
beq x0, x0, end
rec_func:
addi x2, x2, -16 //make room in stack
sd x1, 0(x2) //store pointer and result in stack
sd x10, 8(x2)
bge x5, x31, true // if i > 3, then go to true branch
addi x10, x0, 1 // if i <= 3, then return 1
addi x2, x2, 16 // reset stack point
jalr x0, 0(x1)
true:
addi x5, x5, -2 // compute i-2
jal x1, rec_func // call recursive func for i-2
ld x1, 0(x2) // load the return address
ld x10, 8(x2) // load result from last function call
addi x2, x2, 16 // reset stack point
mul x10, x10, x30 // multiply by 2
addi x10, x10, 1 // add 1
jalr x0, 0(x1) // return
end:
This is the original program logic:
if i<= 3 return 1
else return 2 * rec_func(i-2) +1
I don't have enough reputation to add a comment but have you tried running this with a debugger (GDB ?) instead of on paper? That should show what's actually in the registers and why it's not branching as you might expect. I'm not familiar enough with these instructions (learning x86 assembly) to figure the source out at the moment.
I got it working now. The changes that I made were following:
In the part where I was returning 1, I wasn't loading the stack pointer of the return call
I removed the storing of x10 into the memory as #Michael pointed it out that I was returning it and I didn't need it.
The final code looks like this:
addi x31, x0, 4
addi x30, x0, 2
addi x2, x0, 1600 // initialize the stack to 1600, x2= stackpointer
ecall x5, x0, 5 // read the input to x5
jal x1, rec_func
ecall x0, x10, 2 // print the result
beq x0, x0, end
rec_func:
addi x2, x2, -8 // make room in stack
sd x1, 0(x2) // store pointer and result in stack
bge x5, x31, true // if i > 3, then go to true branch
ld x1, 0(x2)
addi x10, x0, 1 // if i <= 3, then return 1
addi x2, x2, 8 // reset stack point
jalr x0, 0(x1)
true:
addi x5, x5, -2 // compute i-2
jal x1, rec_func // call recursive func for i-2
ld x1, 0(x2) // load the return address
addi x2, x2, 8 // reset stack point
mul x10, x10, x30 // multiply by 2
addi x10, x10, 1 // add 1
jalr x0, 0(x1) // return
end:
I can't seem to wrap my head around recursion in Assembly Language. I understand how it works in higher level languages, but I don't understand how it is possible in assembly when the return value cannot be passed directly to the function.
I'm trying to make a recursive factorial function in AVR, but I don't understand how the stack passes the value when factorial requires n * (n-1), requiring both n and n-1 simultaneously
I just helped another person with the small code below to calculate factorial in AVR AtMega assembly.
It produces a factorial from 1~10, resulting in decimal 3628800 (hex 0x375F00).
It uses exactly what the OP wanted, if selected 8! as number! in R2, it will move 8 to the resulting bytes, then multiply by number!-1 and so on, until it reaches 1, then it ends. The multiplication 24x8 is the trickiest I could write, saving registers and clock cycles. It doesn't use stack nor RAM, straight use of AVR registers.
; Input at R2, value 1~10, from 1! to 10!
; Result 1~3628800 (0x375F00) at: R20:R21:R22 (LSB)
; Temporary Multiplication Middle Byte: R17
ldi r16, low(RAMEND)
out SPL, r16
ldi r16, high(RAMEND)
out SPH, r16
Mov R16, R2 ; Get Value to factor
Rcall A0 ; Call Factorial
...
A0: Clr R20 ; Results = Number!
Clr R21 ;
Ldi R22, R16 ;
A1: Dec R16 ; Number! - 1
Cpi R16,1 ; If 1 then ended
Brne A2 ;
Ret
; This multiplication 24x8 is tricky, fast and save bytes
A2: Mul R22, R16 ; Mul Result LSB x Number!-1
Mov R22, R0 ; LSB Mul to Result LSB Byte
Mov R17, R1 ; MSB Mul to Temporary Middle Byte
Mul R20, R16 ; Mul Result MSB x Number!-1
Mov R20, R0 ; LSB Mul to MSB Result Byte, ignore MSB Mul, will be zero
Mul R21, R16 ; Mul Result Middle x Number!-1
Mov R21, R0 ; LSB Mul to Result Middle Byte
Add R21, R17 ; Add Temporary Middle to Result Middle Byte
Adc R20, R1 ; Add MSB Mul with Carry to Result MSB Byte
Rjmp A1
Using addition instead of multiplication
unsigned int accumulate(unsigned int n)
{
if(n) return(n+accumulate(n-1));
return(1);
}
and a different instruction set, perhaps easier to follow
00000000 <accumulate>:
0: e3500000 cmp r0, #0
4: 0a000005 beq 20 <accumulate+0x20>
8: e3a03000 mov r3, #0
c: e0833000 add r3, r3, r0
10: e2500001 subs r0, r0, #1
14: 1afffffc bne c <accumulate+0xc>
18: e2830001 add r0, r3, #1
1c: e12fff1e bx lr
20: e3a00001 mov r0, #1
24: e12fff1e bx lr
In this case the compiler didnt actually call the function, it detected what was going on and just made a loop.
Since there is nothing magic about recursion there is no difference in whether you call the same function or some other function.
unsigned int otherfun ( unsigned int );
unsigned int accumulate(unsigned int n)
{
if(n) return(n+otherfun(n-1));
return(1);
}
00000000 <accumulate>:
0: e92d4010 push {r4, lr}
4: e2504000 subs r4, r0, #0
8: 03a00001 moveq r0, #1
c: 0a000002 beq 1c <accumulate+0x1c>
10: e2440001 sub r0, r4, #1
14: ebfffffe bl 0 <otherfun>
18: e0800004 add r0, r0, r4
1c: e8bd4010 pop {r4, lr}
20: e12fff1e bx lr
so this shows how it works. Instead of using the stack to store the sum, the cheaper solution if you have the registers is to use a non-volatile register save that register to the stack then use that register during the funciton, depends on how many registers you have and how many local intermediate values you need to track. So r4 gets a copy of n coming in, then that is added (for factorial it is a multiply which depending on the instruction set and code can produce a lot more code that can confuse the understanding so I used addition instead) to the return value from the call to the next function (with recursion where the compiler didnt figure out what we were doing this would have been a call to ourselves, and we can write this asm and make it a call to ourselves to see how it works)
Then the function returns the sum.
If we assume that otherfun is really accumulate we enter this function with a 4 lets say
00000000 <accumulate>:
0: e92d4010 push {r4, lr}
4: e2504000 subs r4, r0, #0
8: 03a00001 moveq r0, #1
c: 0a000002 beq 1c <accumulate+0x1c>
10: e2440001 sub r0, r4, #1
14: ebxxxxxx bl accumulate
18: e0800004 add r0, r0, r4
1c: e8bd4010 pop {r4, lr}
20: e12fff1e bx lr
r4 and lr are saved on the stack (call this r4-4 and lr-4)
r4 = n (4)
r0 = n-1 (3)
call accumulate with n-1 (3)
r4 (4) and lr are saved on the stack (r4-3, lr-3) lr now points back into
r4 = n (3)
r0 = n-1 (2)
call accumulate with n-1 (2)
r4 (3) and lr are saved on the stack (r4-2, lr-2)
r4 = n (2)
r0 = n-1 (1)
call accumulate with n-1 (1)
r4 (2) and lr are saved on the stack (r4-1, lr-1)
r0 = n-1 (0)
call accumulate with n-1 (0)
now things change...
r0 = 1
return to lr-1 which is into accumulate after the call to accumulate
r4 gets 2 from the stack
r0 (1) = r0 (1) + r4 (2) = 3
return to lr-2 which is into accumulate r4 gets 3 from the stack
r0 (3) = r0 (3) + r4 (3) = 6
return to lr-3 which is into accumulate r4 gets 4 from the stack
r0 (6) = r0 (6) + r4 (4) = 10
return to lr-4 which is the function that called accumulate r4 is restored
to what it was before accumulate was first called, r4 is non-volatile you have to for this instruction set return r4 the way you found it (as well
as others, but we didnt modify those)
so the addition in this case multiplication in your desired case is
result = 1 + 2 + 3 + 4
How that happened is we basically pushed n on the stack then called the function with n-1. In this case we push 4, 3, 2, 1 then we start to unwind that and each return processes 1 then 2 then 3 then 4 as it returns
taking those from the stack essentially.
the bottom line is you dont have to care about recursion to support recursion simply use an abi that supports recursion, which is not hard to
do, then hand code the instructions in assembly as if you were the compiler
Maybe this makes it easier to see. n coming in is both a parameter coming in but also for the duration of the function it is a local variable, local
variables go on the stack.
unsigned int accumulate(unsigned int n)
{
unsigned int m;
m = n;
if(n) return(m+accumulate(n-1));
return(1);
}
back to this
unsigned int accumulate(unsigned int n)
{
if(n) return(n+accumulate(n-1));
return(1);
}
so independent of the instruction set
accumulate:
if(n!=0) jump over
return_reg = 1
return
over:
push n on the stack
first parameter (stack or register) = n - 1
call accumulate
pop or load n from the stack
return_reg = return_reg + n
clean stack
return
And also deal with return addresses for the instruction set if required.
The ABI may use the stack to pass parameters or registers.
If I didnt follow the arm abi I could implement
accumulate:
cmp r0,#0
bne over
mov r0,#1
bx lr
over:
push {lr}
push {r0}
sub r0,#1
bl accumulate
pop {r1}
add r0,r0,r1
pop {lr}
bx lr
for grins an instruction set that uses the stack for most things not
registers
00000000 <_accumulate>:
0: 1166 mov r5, -(sp)
2: 1185 mov sp, r5
4: 10a6 mov r2, -(sp)
6: 1d42 0004 mov 4(r5), r2
a: 0206 bne 18 <_accumulate+0x18>
c: 15c0 0001 mov $1, r0
10: 1d42 fffc mov -4(r5), r2
14: 1585 mov (sp)+, r5
16: 0087 rts pc
18: 1080 mov r2, r0
1a: 0ac0 dec r0
1c: 1026 mov r0, -(sp)
1e: 09f7 ffde jsr pc, 0 <_accumulate>
22: 6080 add r2, r0
24: 65c6 0002 add $2, sp
28: 1d42 fffc mov -4(r5), r2
2c: 1585 mov (sp)+, r5
2e: 0087 rts pc
it does a stack frame thing
gets the n parameter from the stack
saves that n parameter to the stack
compares and branches if not zero
in the if zero case we set the return value to 1
clean up the stack and return
now in the if not zero case
make the first parameter n-1
call a function (ourself)
do the addition and return
Can someone give me an example of how recursion would be done in ARM Assembly with only the instructions listed here (for visUAL)?
I am trying to do a recursive fibonacci and factorial function for class. I know recursion is a function that calls a function, but I have no idea how to simulate that in ARM.
https://salmanarif.bitbucket.io/visual/supported_instructions.html
In case the link doesn't work, I am using visUAL and these are the only instructions I can use:
MOV
MVN
ADR
LDR
ADD
ADC
SUB
SBC
RSB
RSC
AND
EOR
BIC
ORR
LSL
LSR
ASR
ROR
RRX
CMP
CMN
TST
TEQ
LDR
LDM
STM
B
BL
FILL
END
This doesn't load an older value for R4, so R4 just doubles every time the function calls itself.
;VisUAL initializess all registers to 0 except for R13/SP, which is -16777216
MOV R4, #0
MOV R5, #1
MOV r0, #4
MOV LR, #16 ;tells program to move to 4th instruction
FIB
STMDB SP!, {R4-R6, LR} ;Stores necessary values on stack (PUSH command)
LDR R4, [SP] ;Loads older value for R4 from memory
ADD R4, R4, R5 ;Adds R5 to R4
STR R4, [SP], #8 ;stores current value for R4 to memory
MOV R5, R4 ;Makes R5 = R4
CMP R4, #144 ;If R4 >= 144:
BGE POP ;Branch to POP
MOV PC, LR ;Moves to STMDB(PUSH) statement
POP
LDMIA SP!, {R4-R6, LR} ;Pops registers off stack
END ;ends program
You need to use the stack, STMDB and LDMIA instructions. On real ARM tools with "unified" notation, they also have mnemonics PUSH and POP.
Fibonnaci and factorial are not great examples as they don't "need" recursion. But let's pretend they do. I'll pick Fibonacci as you don't have a MUL instruction!? You want to do something like this:
START
MOV R0, #6
BL FIB
END ; pseudo-instruction to make your simulator terminate
FIB ; int fib(int i) {
STMDB SP!, {R4,R5,R6,LR} ; int n, tmp;
MOV R4, R0 ; n = i;
CMP R0, #2 ; if (i <= 2) {
MOV R0, #1 ; return 1;
BLE FIB_END ; }
SUB R0, R4, #2 ; i = n-2;
BL FIB ; i = fib(i);
MOV R5, R0 ; tmp = i;
SUB R0, R4, #1 ; i = n-1;
BL FIB ; i = fib(i);
ADD R0, R0, R5 ; i = i + tmp;
FIB_END ; return i;
LDMIA SP!, {R4,R5,R6,PC} ; }
It should terminate with R0 containing fib(6) == 8. Of course this code is very inefficient as it repeatedly calls FIB for the same values.
The STM is needed so you can use registers r4,r5 because another function call can change r0-r3 and LR. Pushing LR and popping PC is like B LR. If you were calling C code you should push an even number of registers to keep SP 64-bit aligned (we don't really need to do that here; ignore R6).
some other recursive function:
unsigned int so ( unsigned int x )
{
static unsigned int z=0;
z+=x;
if(x==0) return(z);
so(x-1);
return(z);
}
build/disassemble
arm-none-eabi-gcc -O2 -c Desktop/so.c -o so.o
arm-none-eabi-objdump -D so.o
00000000 <so>:
0: e92d4010 push {r4, lr}
4: e59f4034 ldr r4, [pc, #52] ; 40 <so+0x40>
8: e5943000 ldr r3, [r4]
c: e3500000 cmp r0, #0
10: e0803003 add r3, r0, r3
14: e5843000 str r3, [r4]
18: 1a000002 bne 28 <so+0x28>
1c: e1a00003 mov r0, r3
20: e8bd4010 pop {r4, lr}
24: e12fff1e bx lr
28: e2400001 sub r0, r0, #1
2c: ebfffffe bl 0 <so>
30: e5943000 ldr r3, [r4]
34: e8bd4010 pop {r4, lr}
38: e1a00003 mov r0, r3
3c: e12fff1e bx lr
40: 00000000
If you dont understand it then is it worth it. Is it cheating to let a tool do it for you?
push is a pseudo instruction for stm, pop a pseudo instruction for ldm, so you can use those.
I used a static local which I call a local global, it lands in .data not on the stack (well .bss in this case as I made it zero)
Disassembly of section .bss:
00000000 <z.4099>:
0: 00000000
the first to loads are loading this value into r3.
the calling convention says that r0 will contain the first parameter on entry into the function (there are exceptions, but it is true in this case).
so we go and get z from memory, r0 already has the parameter x so we add x to z and save it to memory
the compiler did the compare out of order for who knows performance reasons, the add and str as written dont modify flags so that is okay,
if x is not equal to zero it branches to 28 which does the so(x-1) call
reads r3 back from memory (the calling convention says that r0-r3 are volatile a function you can can modify them at will and doesnt have to preserve them so our version of z in r3 might have been destroyed but r4 is preserved by any callee, so we read z back into r3. we pop r4 and the return address off the stack, we prepare the return register r0 with z and do the return.
if x was equal to zero (bne on 18 failed we run 1c, then 20, then 24) then we copy z (r3 version) into r0 which is the register used for returning from this function per the calling convention used by this compiler (arms recommendation). and returns.
the linker is going to fill in the address of z to the offset 0x40, this is an object not a final binary...
arm-none-eabi-ld -Ttext=0x1000 -Tbss=0x2000 so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000
arm-none-eabi-objdump -D so.elf
so.elf: file format elf32-littlearm
Disassembly of section .text:
00001000 <so>:
1000: e92d4010 push {r4, lr}
1004: e59f4034 ldr r4, [pc, #52] ; 1040 <so+0x40>
1008: e5943000 ldr r3, [r4]
100c: e3500000 cmp r0, #0
1010: e0803003 add r3, r0, r3
1014: e5843000 str r3, [r4]
1018: 1a000002 bne 1028 <so+0x28>
101c: e1a00003 mov r0, r3
1020: e8bd4010 pop {r4, lr}
1024: e12fff1e bx lr
1028: e2400001 sub r0, r0, #1
102c: ebfffff3 bl 1000 <so>
1030: e5943000 ldr r3, [r4]
1034: e8bd4010 pop {r4, lr}
1038: e1a00003 mov r0, r3
103c: e12fff1e bx lr
1040: 00002000
Disassembly of section .bss:
00002000 <z.4099>:
2000: 00000000
the point here is not to cheat and use a compiler, the point here is there is nothing magical about a recursive function, certainly not if you follow a calling convention or whatever your favorite term is.
for example
if you have parameters r0 is first, r1 second, up to r3 (if they fit, make your code such that it does and you have four or less parameters)
the return value is in r0 if it fits
you need to push lr on the stack as you will be calling another function
r4 on up preserve if you need to modify them, if you want some local storage either use the stack by modifying the stack pointer accordingly (or doing pushes/stms). you can see that gcc instead saves what was in the register to the stack and then uses the register during the function, at least up to a few local variables worth, beyond that it would need to bang on the stack a lot, sp relative.
when you do the recursive call you do so as you would any other normal function according to the calling convention, if you need to save r0-r3 before calling then do so either in a register r4 or above or on the stack, restore after the function returns. you can see it is easier just to put the values you want to keep before and after a function call in a register r4 or above.
the compiler could have done the compare of r0 just before the branch, reads easier that way. Likewise could have done the mov to r0 of the return value before the pop
I didnt put parameters, so my build of gcc here appears to be armv4t, if I ask for something a little newer
arm-none-eabi-gcc -O2 -c -mcpu=mpcore Desktop/so.c -o so.o
arm-none-eabi-objdump -D so.o
so.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <so>:
0: e92d4010 push {r4, lr}
4: e59f402c ldr r4, [pc, #44] ; 38 <so+0x38>
8: e3500000 cmp r0, #0
c: e5943000 ldr r3, [r4]
10: e0803003 add r3, r0, r3
14: e5843000 str r3, [r4]
18: 1a000001 bne 24 <so+0x24>
1c: e1a00003 mov r0, r3
20: e8bd8010 pop {r4, pc}
24: e2400001 sub r0, r0, #1
28: ebfffffe bl 0 <so>
2c: e5943000 ldr r3, [r4]
30: e1a00003 mov r0, r3
34: e8bd8010 pop {r4, pc}
38: 00000000
You can see the returns read a little easier
although an optimization was missed it could have done an ldr r0,[r4] and saved an instruction. or leave that tail end as is and the bne could have been a beq to 30 (mov r0,r3; pop{r4,pc} and shared an exit.
a little more readable
so:
push {r4, lr}
# z += x
ldr r4, zptr
ldr r3, [r4]
add r3, r0, r3
str r3, [r4]
# if x==0 return z
cmp r0, #0
beq l30
# so(x - 1)
sub r0, r0, #1
bl so
ldr r3, [r4]
l30:
# return z
mov r0, r3
pop {r4, pc}
zptr: .word z
.section .bss
z: .word 0
arm-none-eabi-as so.s -o so.o
arm-none-eabi-objdump -D so.o
so.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <so>:
0: e92d4010 push {r4, lr} (stmdb)
4: e59f4024 ldr r4, [pc, #36] ; 30 <zptr>
8: e5943000 ldr r3, [r4]
c: e0803003 add r3, r0, r3
10: e5843000 str r3, [r4]
14: e3500000 cmp r0, #0
18: 0a000002 beq 28 <l30>
1c: e2400001 sub r0, r0, #1
20: ebfffff6 bl 0 <so>
24: e5943000 ldr r3, [r4]
00000028 <l30>:
28: e1a00003 mov r0, r3
2c: e8bd8010 pop {r4, pc} (ldmia)
00000030 <zptr>:
30: 00000000
Disassembly of section .bss:
00000000 <z>:
0: 00000000
EDIT
So lets walk through this last one.
push {r4,lr} which is a pseudo instruction for stmdb sp!,{r4,lr}
Lr is the r14 which is the return address look at the bl instruction
branch and link, so we branch to some address but lr (link register) is
set to the return address, the instruction after the bl. So when main or some other function calls so(4); lets assume so is at address 0x1000 so the program counter, r15, pc gets 0x1000, lr will get the value of the instruction after the caller so lets say that is 0x708. Lets also assume the stack pointer during this first call to so() from main is at 0x8000, and lets say that .bss is at 0x2000 so z lives at address 0x2000 (which also means the value at 0x1030, zptr is 0x2000.
We enter the function for the first time with r0 (x) = 4.
When you read the arm docs for stmdb sp!,{r4,lr} it decrements before (db) so sp on entry this time is 0x8000 so it decrements for the two items to 0x7FF8, the first item in the list is written there so
0x7FF8 = r4 from main
0x7FFC = 9x 0x708 return address to main
the ! means sp stays modified so sp-0x7ff8
then ldr r4,zptr r4 = 0x2000
ldr r3,[r4] this is an indirect load so what is at address r4 is read to
put in r3 so r3 = [0x2000] = 0x0000 at this point the z variable.
z+=x; add r3,r0,r3 r3 = r0 + r3 = 4 + 0 = 4
str r3,[r4] [r4] = r3, [0x2000] = r3 write 4 to 0x2000
cmp r0,#0 4 != 0
beq to 28 nope, not equal so no branch
sub r0,r0,#1 r0 = 4 - 1 = 3
bl so so this is so(3); pc = 0x1000 lr = 0x1024
so now we enter so for the second time with r0 = 3
stmdb sp!,{r4,lr}
0x7FF0 = r4 (saving from so(4) call but we dont care its value even though we know it)
0x7FF4 = lr from so(4) = 0x1024
sp=0x7FF0
ldr r4,zptr r4 = 0x2000
ldr r3,[r4] r3 = [0x2000] = 4
add r3,r0,r3 r3 = 3 + 4 = 7
str r3,[r4] write 7 to 0x2000
cmp r0,#0 3 != 0
beq 0x1028 not equal so dont branch
sub r0,r0,#1 r0 = 3-1 = 2
bl so pc=0x1000 lr=0x1024
so(2)
stmdb sp!,{r4,lr}
0x7FE8 = r4 from caller, just save it
0x7FEC = lr from caller, 0x1024
sp=0x7FE8
ldr r4,zprt r4=0x2000
ldr r3,[r4] r3 = read 7 from 0x2000
add r3,r0,r3 r3 = 2 + 7 = 9
str r3,[r4] write 9 to 0x2000
cmp r0,#0 2 != 0
beq 0x1028 not equal so dont branch
sub r0,r0,#1 r0 = 2 - 1 = 1
bl 0x1000 pc=0x1000 lr=0x1024
so(1)
stmdb sp!,{r4,lr}
0x7FE0 = save r4
0x7FE4 = lr = 0x1024
sp=0x7FE0
ldr r4,zptr r4=0x2000
ldr r3,[r4] r3 = read 9 from 0x2000
add r3,r0,r3 r3 = 1 + 9 = 10
str r3,[r4] write 10 to 0x2000
cmp r0,#0 1 != 0
beq 0x1028 not equal so dont branch
sub r0,r0,#1 r0 = 1 - 1 = 0
bl 0x1000 pc=0x1000 lr=0x1024
so(0)
stmdb sp!,{r4,lr}
0x7FD8 = r4
0x7FDC = lr = 0x1024
sp = 0x7FD8
ldr r4,zptr r4 = 0x2000
ldr r3,[r4] r3 = read 10 from 0x2000
add r3,r0,r3 r3 = 0 + 10 = 10
str r0,[r4] write 10 to 0x2000
cmp r0,#0 0 = 0 so it matches
beq 0x1028 it is equal so we finally take this branch
mov r0,r3 r0 = 10
ldmia sp!,{r4,pc}
increment after
r4 = [sp+0] = [0x7FD8] restore r4 from caller
pc = [sp+4] = [0x7FDC] = 0x1024
sp += 8 = 0x7FE0
(branch to 0x1024)(return from so(0) to so(1))
ldr r3,[r4] read 10 from 0x2000
mov r0,r3 r0 = 10
ldmia sp!,{r4,pc}
r4 = [sp+0] = [0x7FE0] restore r4 from caller
pc = [sp+4] = [0x7FE4] = 0x1024
sp += 8 = 0x7FE8
(branch to 0x1024)(return from so(1) to so(2))
ldr r3,[r4] read 10 from 0x2000
mov r0,r3 r0 = 10
ldmia sp!,{r4,pc}
r4 = [sp+0] = [0x7FE8] restore r4 from caller
pc = [sp+4] = [0x7FEC] = 0x1024
sp += 8 = 0x7FF0
(branch to 0x1024)(return from so(2) to so(3))
ldr r3,[r4] read 10 from 0x2000
mov r0,r3 r0 = 10
ldmia sp!,{r4,pc}
r4 = [sp+0] = [0x7FF0] restore r4 from caller
pc = [sp+4] = [0x7FF4] = 0x1024
sp += 8 = 0x7FF8
(branch to 0x1024)(return from so(3) to so(4))
ldr r3,[r4] read 10 from 0x2000
mov r0,r3 r0 = 10
ldmia sp!,{r4,pc}
r4 = [sp+0] = [0x7FF8] restore r4 from caller (main()'s r4)
pc = [sp+4] = [0x7FFC] = 0x708
sp += 8 = 0x8000
(branch to 0x708)(return from so(4) to main())
and we are done.
A stack is like a dixie cup holder which might be before your time. A cup holder where you pull a cup down and the next and rest of the cups stay in the holder, well you can shove one back up in there.
So a stack is temporary storage for the function, write one data item on the cup, then shove it up into the holder (save r4 from caller) write another item and shove it up into the holder (lr, return address from caller). we only used two items per function here, so each function I can push two cups up into the holder, each call of the function I get two NEW AND UNIQUE storage locations to store this local information. As I exit the function I pull the two cups down out of the holder and use their values (and discard them). This is to some extent the key to recursion, the stack gives you new local storage for each call, separate from prior calls to the same function, if nothing else you need a return address (although did make some even simpler recursion example that didnt when optimized was smart enough to make a loop out of it basically).
ldr rd,[rn] think of he brakets as saying the item at that address so read memory at the address in rn and save that value in rd.
str rd,[rn] the one messed up arm instruction as the rest the first parameter is the left side of the equals (add r1,r2,r3 r1 = r2 + r3, ldr r1,[r4] r1 = [r4]) this one is backward [rn] = rd store the value in rd to the memory location described by the address r4, one level of indirection.
stmdb sp!, means decrement the stack pointer before doing anything 4 bytes times the number of registers in the list, then write the first, lowest numbered register to [sp+0], then next to [sp+4] and so on the last one will be four less than the starting value of sp. The ! means the function finishes with sp being that decremented value. You can use ldm/stm for things other than stack pushes and pops. Like memcpy,but that is another story...
All of this is in the arm documentation from infocenter.arm.com which you should already have (arm architectural reference manual, armv5 is the preferred first one if you have not read one).