2.解析C语言的内部运行机制

目录

1.解析C语言的内部机制

2.了解ARM-THUMB 子程序调用规则 ATPCS

3.分析C语言的反汇编代码


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] 地址的内容。

再返回去看看,我们在程序开始运行之前这些地址保存的东西是什么?


猜你喜欢

转载自blog.csdn.net/qq_36243942/article/details/84674067