1 程序处理的4个步骤
以led寄存器工程为例,其有两个文件:main.c
和start.s
,处理过程如下:
1.1 编译过程
- 预处理
使用预处理器把源文件main.c
经过预处理生成main.i
文件,宏定义展开、头文件展开(将所有的#include头文件以及宏定义替换成其真正的内容)、条件编译等,同时将代码中的注释删除,这里并不会检查语法。
- 编译
检查语法,使用编译器将预处理文件main.i
编译成汇编文件main.s
。
- 汇编
使用汇编器(keil中为armasm)将汇编文件main.s
转换成目标文件main.o
(二进制文件,即机器码)。
- 链接
链接过程使用链接器(keil中为armlink)将该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件led.elf
(keil中为led.axf
)。
使用Detect It Easy软件可以查看可执行文件的基本信息(内存地址、栈大小等) :
汇编与反汇编概念:
- 汇编:汇编文件转换为目标文件(里面是机器码#pic_center)
- 反汇编:可执行文件(目标文件,里面是机器码),转换为汇编文件
1.2 Keil工程的文件类型
2 生成反汇编文件
2.1 Keil环境
在Keil的User
选项中,添加以下命令可生成bin
和dis
反汇编文件:
fromelf --bin --output=led.bin Objects\led_c.axf # 生成bin文件
fromelf --text -a -c --output=led.dis Objects\led_c.axf # 生成dis反汇编文件
其中,Objects\led_c.axf
为编译生成的.axf
文件相对于工程文件.uvprojx
的位置。
2.2 GCC环境
使用GCC工具链编译程序时,在Makefile
中有:
$(OBJDUMP) -D -m arm led.elf > led.dis # OBJDUMP = arm-linux-gnueabihf-objdump
即可将可执行程序led.elf
反汇编得到led.dis
。
3 机器码与汇编
3.1 LDR伪指令解析
在工程启动文件start.s
中跳转到main
函数前加了一条伪指令LDR
:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT main
LDR SP, =(0x20000000+0x10000#pic_center)
BL main
通过反汇编来查看编译器最后转换成的实际汇编指令:
实际上将pc+4地址上的数值0x20010000
加载到当前地址0x08000088
中,此时PC值为:pc+4 = 0x8000090
,则pc = 0x800008c
,而当前指令地址0x08000088
,所以pc = 当前地址+4。
这是因为ARM指令采用流水线机制:当前指令的PC值为下两条指令的地址
- PC = A +4 (Thumb/Thumb2指令集#pic_center)
- PC = A + 8 (ARM指令集#pic_center)
CM3中采用Thumb/Thumb2指令集混合编程,即PC = A +4 。
3.2 机器码分析
详见ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition
中A8.8.64 LDR(literal)
3.2.1 Thumb/Thumb2指令集
3.2.2 ARM指令集
上面使用的是32位Thumb2 LDR指令,LDR sp,[pc,#4]
汇编指令对应的机器码为f8dfd004
,转换成二进制为11111 00011011111 1101 000000000100
- 高16位
11111 00011011111
与其对应,其中U=1
,后四位1111
即为PC(R15); - 低16位中
1101
即为Rt,在这里就是SP寄存器(R13),剩下的000000000100
为12位立即数,即为此处的#4
(0x0100)。
参考资料:
- 程序的组成、存储与运行
- 如何设置KEIL MDK生成反汇编和BIN文件
- ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition
- ARMv7-M Architecture Reference Manual
END