目录
1.解析C语言的内部机制
1.把上一节编译第10节的C语言控制代码在Linux系统反汇编文件,led.dis文件传windows系统查看,然后分析这个程序时如何运行的,具体看上一节内容:点我查看
led.dis的文件内容如下:
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.
看起来,比直接使用汇编语言复杂很多,其实c语言最终也是要转换成汇编语言,最终转换成机器码(二进制),烧写到soc上运行的。
先捋一下思路:
1.在汇编文件中,设置栈内存的地址
2.使用bl命令调用main,且设置返回地址存入lr寄存器
3.在c程序中,有一个main函数,写寄存器的值
问题:
1.为何要设置栈?
答:因为c语言要设置局部变量,有变量就需要内存
2.如何使用栈?
答:局部变量是保存在栈中的,保存lr等寄存器
问题:
1.调用者如何向被调用者传递参数?
2.被调用者如何向传返回着给调用者?
3.如何从栈中恢复寄存器?
想要了解这些问题需要对ARM-THUMB 子程序调用规则有所了解。
2.了解ARM-THUMB 子程序调用规则 ATPCS
为了使 C 语言程序和汇编程序之间能够互相调用,必须为子程序间的调用制定规则,在ARM 处理器中,这个规则被称为 ATPCS:ARM 程序和 Thumb 程序中子程序调用的规则。基本的ATPCS 规则包括寄存器使用规则、数据栈使用规则、参数传递规则。
2.1. 寄存器使用规则
ARM 处理器中有 r0~r15 共 16 个寄存器,它们的用途有一些约定的习惯,并依具这些用途定义了别名,如下表所示:
- PCS 中各寄存器的使用规则及其名称
寄存器 别名 使用规则
r15 pc 程序计数器
r14 lr 连接寄存器
r13 sp 数据栈指针
r12 ip 子程序内部调用的 scratch 寄存器
r11 v8 ARM 状态局部变量寄存器 8
r10 v7、s1 ARM 状态局部变量寄存器 7、在支持数据栈检查的 ATPCS 中为数据栈限制指针
r9 v6、sb ARM 状态局部变量寄存器 6、在支持 RWPI 的 ATPCS 中为静态基址寄存器
r8 v5 ARM 状态局部变量寄存器 5
r7 v4、wr ARM 状态局部变量寄存器 4、Thumb 状态工作寄存器
r6 v3 ARM 状态局部变量寄存器 3
r5 v2 ARM 状态局部变量寄存器 2
r4 v1 ARM 状态局部变量寄存器 1
r3 a4 参数/结果/scratch 寄存器 4
r2 a3 参数/结果/scratch 寄存器 3
r1 a2 参数/结果/scratch 寄存器 2
r0 a1 参数/结果/scratch 寄存器 1
寄存器的使用规则总结如下:
- 子程序间通过寄存器 r0~r3 来传递参数,这时可以使用它们的别名 a0~a3。被调用的子程序返回前无需恢复 r0~r3 的内容。
- 在子程序中,使用 r4~r11 来保存局部变量,这时可以使用它们的别名 v1~v8。如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,在返回前恢复它们;对于子程序中没有使用到的寄存器则不必进行这些操作。在 Thumb 程序中,通常只能使用寄存器 r4~r7 来保存局部变量。
- 寄存器 r12 用作子程序间 scratch 寄存器,别名为 ip。
- 寄存器r13用作数据栈指针,别名为sp。在子程序中寄存器r13不能用作其他用途。它的值在进入、退出子程序时必须相等。
- 寄存器 r14 被称为连接寄存器,别名为 lr。它用于保存子程序的返回地址。如果在子程序中保存了返回地址(比如将 lr 值保存到数据栈中),r14 可以用作其他用途。
- 寄存器 r15 是程序计数器,别名为 pc。它不能用作其他用途。
2.2.数据栈使用规则
数据栈有两个增长方向:向内存地址减小的方向增长时,称为 DESCENDING 栈;向内地址增加的方向增长时,称为 ASCENDING 栈。
所谓数据栈的增长就是移动栈指针。当栈指针指向栈顶元素(最后一个入栈的数据)时,称为 FULL 栈;当栈指针指向栈顶元素(最后一个入栈的数据)相邻的一个空的数据单元时,称为 EMPTY 栈。
综合这两个特点,数据栈可以分为以下 4 种:
① FD Full Descending,满递减
② ED Empty Descending,空递减
③ FA Full Ascending,满递增
④ EA Empty Ascending,空递增
注意:ATPCS 规定数据栈为 FD 类型,并且对数据栈的操作是 8 字节对齐的。使用 stmdb/ldmia批量内存访问指令来操作 FD 数据栈。使用 stmdb 命令往数据栈中保存内容时,“先递减 sp 指针,再保存数据”,使用 ldmia命令从数据栈中恢复数据时, “先获得数据,再递增 sp 指针”──sp 指针总是指向栈顶元素,这刚好是 FD 栈的定义。
2.3. 参数传递规则
一般来说,当参数个数不超过 4 个时,使用 r0~r3 这 4 个寄存器来传递参数;如果参数个数超过 4 个,剩余的参数通过数据栈来传递。对于一般的返回结果,通常使用 a0~a3 来传递。
简图分析如下:
3.分析C语言的反汇编代码
3.1.知识基础
假设程序从Nand启动,对于Nand启动的程序,硬件上会把Nand Flash 前4K的内容完全复制到片内的4K内存上,那么上面的哪些机器码在程序运行就会保存在片内内存4k内存的前面。
机器码在片内RAM的存储结构简图如下所示:
3.2.从第一句开始分析代码:
开发版一上电,从0地址开始执行
接着开始执行main函数的内容了
再执行下面的语句:
得到的结果使用简图表示为:
接着分析下一条语句:
执行完如下图所示:
继续
因为r0-r3用来保存调用和被调用者的参数,因此,C语言中的ruturn 应该保存在这几个变量中的一个。
退出主函数时恢复栈
从栈中恢复寄存器采用的是 ldmia 指令
指令:ldm
含义:读内存读取数据,然后把读取的数据写入多个寄存器
命令解析:
例子:
ldmia sp, {fp, sp, pc}
假设:sp=4080
例子:ia的含义是过后增加(Increment After),就是先读取后增加,而且的顺序的依据是:高编号的寄存器存储在高地址
fp,sp, pc 这三个的寄存器编号分别如下所示(ARM编程手册查看)
pc->R15,sp->R13,fp->R11.所以存取的顺序是:fp-sp-pc(与指令顺序无关)
附录:
其他形式简单的描述指令的行为,意思分别是过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before)。
因此执行完这条指令之后:fp = [4080-4083] 地址的内容。sp =[4084-4087]地址的内容,pc=[4088-4092] 地址的内容。
再返回去看看,我们在程序开始运行之前这些地址保存的东西是什么?