在前面的S3C2440逻辑的LED实验中,我们用C语言控制了LED,在C语言前面我们还编写了一段汇编代码用来设置栈,下面我们以这个实验为例子看一下C语言的内部机制。
1.汇编代码
.text
.global _start
_start:
/* 设置内存: sp 栈 */
ldr sp, =4096 /* nand启动 */
// ldr sp, =0x40000000+4096 /* nor启动 */
/* 调用main */
bl main
halt:
b halt
2.C语言代码
int main()
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
/* 配置GPF4为输出引脚 */
*pGPFCON = 0x100;
/* 设置GPF4输出0 */
*pGPFDAT = 0;
return 0;
}
3.反汇编代码
led.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000
4: eb000000 bl c <main>
00000008 <halt>:
8: eafffffe b 8 <halt>
0000000c <main>:
c: e1a0c00d mov ip, sp
10: e92dd800 stmdb sp!, {fp, ip, lr, pc}
14: e24cb004 sub fp, ip, #4 ; 0x4
18: e24dd008 sub sp, sp, #8 ; 0x8
1c: e3a03456 mov r3, #1442840576 ; 0x56000000
20: e2833050 add r3, r3, #80 ; 0x50
24: e50b3010 str r3, [fp, #-16]
28: e3a03456 mov r3, #1442840576 ; 0x56000000
2c: e2833054 add r3, r3, #84 ; 0x54
30: e50b3014 str r3, [fp, #-20]
34: e51b2010 ldr r2, [fp, #-16]
38: e3a03c01 mov r3, #256 ; 0x100
3c: e5823000 str r3, [r2]
40: e51b2014 ldr r2, [fp, #-20]
44: e3a03000 mov r3, #0 ; 0x0
48: e5823000 str r3, [r2]
4c: e3a03000 mov r3, #0 ; 0x0
50: e1a00003 mov r0, r3
54: e24bd00c sub sp, fp, #12 ; 0xc
58: e89da800 ldmia sp, {fp, sp, pc}
Disassembly of section .comment:
00000000 <.comment>:
0: 43434700 cmpmi r3, #0 ; 0x0
4: 4728203a undefined
8: 2029554e eorcs r5, r9, lr, asr #10
c: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1}
10: Address 0x10 is out of bounds.
4.反汇编代码解析
在前面的汇编代码里面主要做了如下工作:
- 设置栈
- 调用main函数并把返回地址保存到lr寄存器中。
然后在C语言的main函数里面:
- 定义两个局部变量,
- 设置变量,
- return 0
我们需要回答下面几个问题:
- 为什么要用start.s设置了栈才能调用C函数:因为C函数要用到栈。
- 怎么使用栈: a.栈可以用来保存局部变量。b.保存lr等寄存器。
- 调用者如何传参数给被调用者,
- 被调用者如何传返回值给调用者。
- 怎么从栈中恢复寄存器。
下面我们开始分析反汇编代码,假设我们的程序是从nandflash启动,那么nandflash前4K的内容就会被拷贝到片内4K内存。那么我们的机器码就是保存在4K内存里面。
4.1第一条指令
上电后从0地址开始执行,
0: e3a0da01 mov sp, #4096 ; 0x1000
4.2第二条指令
eb000000 bl c <main>
然后是bl c 也就是跳转到main函数开始执行,同时让lr等于他的返回地址也就是下一条指令的地址,那么lr=8.
4.4第四条指令
c: e1a0c00d mov ip, sp
这个就是把sp赋值给IP,然后ip等于4096.
4.5第五条指令
10: e92dd800 stmdb sp!, {fp, ip, lr, pc}
这一条就是后面的fp, ip, lr, pc这四个寄存器的值保存在sp对应的内存地址中,其中stmdb中的db表示decrement before.然后搞编号的寄存器保存到高地址中,
fp的编号是r11,ip的编号是r12,lr寄存器的编号是r14,pc寄存器的是r15.
所以我们先存pc,pc等于当前指令地址+8, 也就是0x18.
接下来存放lr寄存器,lr=8.
ip=sp=4096,
fp未知值。
4.6第六条指令
14: e24cb004 sub fp, ip, #4 ; 0x4
fp=ip-4=4096-4=4092.
4.7第七条指令
18: e24dd008 sub sp, sp, #8 ; 0x8
sp = sp - 8 = 4080 - 8 = 4072.
4.8第八条指令
1c: e3a03456 mov r3, #1442840576 ; 0x56000000
r3 = 0x56000000
4.9第九条指令
20: e2833050 add r3, r3, #80 ; 0x50
r3=0x56000050
4.10第十条指令
24: e50b3010 str r3, [fp, #-16]
然后r3保存到内存为fp-16的地方,fp-16=4092-16=4076 .也就是内存地址为4076的地方存的值是0x56000050。
这里就相当于是把局部变量 unsigned int *pGPFCON = (unsigned int *)0x56000050; 保存到了栈中。
4.11-4.13第11-13条指令
28: e3a03456 mov r3, #1442840576 ; 0x56000000
2c: e2833054 add r3, r3, #84 ; 0x54
30: e50b3014 str r3, [fp, #-20]
r3=0x56000000
然后r3=0x56000054.
然后把rc保存到fp-20的地方, fp-20=4072的地方,
这里就相当于把局部变量 unsigned int *pGPFDAT = (unsigned int *)0x56000054; 保存到了栈中。
4.14-4.16 第14-16条指令
34: e51b2010 ldr r2, [fp, #-16]
38: e3a03c01 mov r3, #256 ; 0x100
3c: e5823000 str r3, [r2]
r2=fp-16,也就是把内存4076的地方取值保存到r2中,也就是r2=0x56000050.也就是pGPFCON。
然后r3=0x100,
然后把r3这个值存入r2所指的地方,
也就对应于C语言的 *pGPFCON = 0x100;。
4.17-4.19 第17-19条指令
40: e51b2014 ldr r2, [fp, #-20]
44: e3a03000 mov r3, #0 ; 0x0
48: e5823000 str r3, [r2]
把4072内存中对应的0x56000054,然后把r3=0存入0x56000054中,这三条对应于C语言的 *pGPFDAT = 0;。
4.20-4.21 第20-21条指令
4c: e3a03000 mov r3, #0 ; 0x0
50: e1a00003 mov r0, r3
r3=0,
然后把r3赋值给r0,那么r0=0,
这两句对应的是把C语言的return 0的返回值保存到r0中。
4.22第22条指令
54: e24bd00c sub sp, fp, #12 ; 0xc
sp = fp -12=4092-12=4080.
4.23第23条指令
58: e89da800 ldmia sp, {fp, sp, pc}
从栈中恢复寄存器,
fp=[4080]=原来保存的fp,
sp=[4084]=4096
pc=[4088]=8=调回0x8的地址。 #之前我们4088内存里面存的是lr寄存器的值,这样正好就把返回地址赋值给了PC,
栈:所谓栈就是sp寄存器所指向的内存。栈可以用来保存寄存器,保存局部变量。