Reputation: 5022
I'm writing a sdram test program on a bare-metal ARM. I wrote it in C, but now I want to modify the generated assembly to prevent the program from using sdram, which implies, among other things, no stack.
I started to learn ARM assembly recently, and I don't understand how the compiler generated assembly is using the stack in the following code (and I don't find the answer by reading the ARM ARM :/). The 32 bits variable value is placed on the stack, but why does push reserves 3 times 32 bits at the beginning of the function? Can someone please explain the stack manipulation here?
The C code:
/* ugly to have it as global but it reduces stack usage*/
unsigned int const led_port[]= {0,0,1,1,2,2,3,3,4,4};
unsigned int const led_value_on[]={0x90,0x9,0x90,0x9,0x90,0x9,0x90,0x9,0x90,0x9};
unsigned int const masks[] = {0xf0,0xf,0xf0,0xf,0xf0,0xf,0xf0,0xf,0xf0,0xf};
unsigned int const led_value_off[]={0x80,0x8,0x80,0x8,0x80,0x8,0x80,0x8,0x80,0x8};
void gbe_led_on(int i)
{
unsigned int value = 0;
phy_read(led_port[i], 0x10, &value);
value &= ~masks[i];
value |= led_value_on[i];
phy_write(led_port[i], 0x10, value);
}
The generated assembly (from gcc-arm-elf):
<gbe_led_off>:
push {r4, r5, r6, lr} /* ;reserve space on the stack for 3 32 bits variables + return address */
ldr r5, [pc, #84] ; ffff1578 <gbe_led_off+0x60> /*r5=led_port (array base address) */
sub sp, sp, #8 /* sp = sp-8 (decimal 8) what does it point to??*/
ldr r4, [r5, r0, lsl #2] /* r4 = *(led_port+i)&0x00ff, (shift from 16 bits) */
add r2, sp, #8 /* r2 = sp+8 (decimal 8) why???*/
mov r6, r0 /* r6 = i */
mov r3, #0 /* r3 = 0 */
mov r0, r4 /* r0 = led_port[i]*/
str r3, [r2, #-4]! /* r3 = *(sp+8-4); update r2, to which value???*/
add r5, r5, r6, lsl #2 /* r5 = led_port[i] & 0x00ff */
mov r1, #16 /* r1 = 16 (decimal) */
bl ffff13f8 <phy_read> /* call phy_read with arguments on r0, r1, r2*/
ldr r1, [r5, #40] ; 0x28 /* r1 = masks[i] */
ldr r3, [sp, #4] /* r3 = *(sp+4) ????*/
ldr r2, [r5, #120] ; 0x78 /* r2 = led_value_on[i] */
bic r3, r3, r1 /* value &= masks[i] */
orr r3, r3, r2 /* value |= led_value_on[i] */
mov r0, r4 /* r0 = led_port[i] */
mov r2, r3 /* r2 = value */
mov r1, #16 /* r1 = 16 */
str r3, [sp, #4] /* *(sp+4) = value; why do we do that???*/
bl ffff13cc <phy_write> /* branch to phy_write with arguments on r0,r1,r2*/
add sp, sp, #8 /* sp = sp+8 restore stack pointer before pop? */
pop {r4, r5, r6, pc} /* remove 4 bytes from the stack and branch to return address */
.word 0xffff1a30
Upvotes: 2
Views: 3321
Reputation: 12515
The push is to save the registers r4
, r5
, and r6
all of which must be preserved according to the ARM programming model. The push of lr
is to preserve the return address as you are calling other functions that will modify it. The sub 8
from the stack reserves another 8 bytes of memory for other variable use (value
variable) - used later on the str r3, [2, #-4]
line. In addition, the Branch Link bl
to phy_read
and phy_write
could also be modifying stack space so your problem with stack memory might be bigger than you think. Also, your last pop comment about 4 bytes is incorrect - it is 16-bytes of space being freed.
Now, what kind of resources will you have available RAM-wise to use? You need something or else your unsigned int value
won't have any place to work from not to mention your calls. You have to have something available. If you do, you can tell your C program about it through linker scripts and section
directives, saving you the trouble of assembler.
Upvotes: 3