Cortex-M反编译入门

我们在写单片机裸机程序时,在主函数之前,会有一段启动代码,而启动代码是用汇编写的,有些朋友可能看到汇编头都大了,当时要想深入研究底层架构,这快硬骨头就必须去啃。
汇编 :汇编文件转换为目标文件(里面是机器码)。
反汇编 :可执行文件(目标文件,里面是机器码),转换为汇编文件。
关于汇编的基础知识,请看笔者以前的文章。
今天笔者以stm32f1的点灯程序为例,带领大家进行反汇编,并阅读反汇编后的代码。
1 新建led裸机程序关于stm32裸机程序的创建,请看笔者博文:
https://bruceou.blog.csdn.net/article/details/78244735
但是今天这个程序非常简单,不应那么复杂。
1.新建文件夹
新建文件夹“stm32f1”,当然名字也可以另取,在 stm32f1文件夹下,我们新建五个文件夹,分别为cmsis、listing、output、project、user。
其中cmsis文件夹放启动文件:
笔者的开发板芯片是stm32f103ze,这个文件是根据stm32的固件库startup_stm32f10x_md.s文件修改而来。
2.新建工程
打开keil,在工具栏 project->new μvision project…新建我们的工程文件。
输入工程名,保存即可。
窗口是让我们选择公司跟芯片的型号,我们用的芯片是 st 公司的stm32f103ze,有64k sram,512k flash,属于高集成度的芯片。按如下选择即可。
然后点击项目管理。
最后修改后的内容如下:
并添加相应的文件。
其中main.c的内容如下所示:
/** * @brief 延时函数 * @param d * @retval none */void delay(int d){ while(d--);}/** * @brief main * @param none * @retval int */int main(void){ unsigned int *preg; /* 使能gpiob */ preg = (unsigned int *)(0x40021000 + 0x18); *preg |= (1<<3); /* 设置gpiob0为输出引脚 */ preg = (unsigned int *)(0x40010c00 + 0x00); *preg |= (1<<0); preg = (unsigned int *)(0x40010c00 + 0x0c); while (1) { /* 设置gpiob0输出1 */ *preg |= (1<<0); delay(1000000); /* 设置gpiob0输出0 */ *preg &= ~(1<<0); delay(1000000); }}startup.s文件的内容如下:
;************************************ stm32f1 ************************************;* file name : startup.s;* author : bruceou;* version : v1.0;* date : 2021-06-27;* description : stm32f10x medium density devices vector table for mdk-arm ;* toolchain. ;* this module performs:;* - set the initial sp;* - set the initial pc == reset_handler;* - set the vector table entries with the exceptions isr address;* - configure the clock system;* - branches to __main in the c library (which eventually;* calls main()).;* after reset the cortexm3 processor is in thread mode,;* priority is privileged, and the stack is set to main.;******************************************************************************* preserve8 thumb; vector table mapped to address 0 at reset area reset, data, readonly export __vectors __vectors dcd 0 dcd reset_handler ; reset handler area |.text|, code, readonly; reset handlerreset_handler proc export reset_handler [weak] import main ldr sp, =(0x20000000+0x10000) bl main endp end接下来还有配置工程。
选择output文件夹。
选择listing文件夹。
基本配置就这些,接下来编译工程。
只要没有错误就可以了,最后就是下载程序,笔者使用的是j-link,最后的现象如下:
led会不停闪烁。
2 keil反汇编接下来才是今天正题,反汇编。
在keil的user选项中,如下图添加这两项:
fromelf --bin --output=stm32f1.bin ../output/stm32f1.axf
fromelf --text -a -c --output=stm32f1.dis ../output/stm32f1.axf
然后重新编译,即可得到二进制文件stm32f1.bin(以后会分析)、反汇编文件stm32f1.dis。
如下图所示:
正常编译过程是分为四个阶段进行的,即预处理(也称预编译,preprocessing)、编译(compilation)、汇编 (assembly)和链接(linking)。
但是反编译是讲为二进制文件反编译成汇编文件,因此反汇编的流程如下:
3 反汇编代码解析接下来就是查看反编译代码,打开反编译文件project/stm32f1.dis。这里只截取一段查看,因为格式都是一样的,知识每条内容不同罢了。
第一列是链接地址,第二列是机器码,第三列是汇编指令。
根本汇编指令,我们找到arm®v7-m architecture reference manual_ddi 0403e.d (id070218)中的ldr指令。
我们将f8dfd004变成二进制。
这个使用的32位的thumb2指令集。
其中b0~b11是立即数,这里是4,对应的汇编代码的也是4,这里要注意的是,arm指令采用流水线机制,当前执行地址a的指令,同时已经在对下一条指令进行译码同时已经在读取下下一条指令:pc = a +4 (thumb/thumb2指令集)。
b12~b15是寄存器,这段大小是0xc,对应的寄存器就是sp;
后面16bit除了23位意外,全是固定的,其中‘u’表示无条件执行,这里置为1。
其他的汇编指令对应的机器码也是类似的,值得注意的是,不同的架构对应的机器码也是不同的,这也就回答了为了不同的处理器架构会对应不同的指令集。
有兴趣的可以对比cortex-m系列和cortex-a系列的的指令集。请参考以下手册:
arm architecture reference manual armv7-a and armv7-r edition.pdf
arm®v7-m architecture reference manual.pdf
4 反汇编代码全解析进入debug模式,在view下选择disassembly window。
这样就可将机器码和对应的代码对应起来。当程序运行起来了,也就从异常向量表中跳转到reset_handler中,然后跳转到main函数中,而main函数是在栈中,因此需要设置占空间的起始位置。根据stm32的参考手册,sram的其起始地址和大小如下:
因此栈顶为起始位置加上栈的大小即可,只要不超过sram即可。
值得注意的是,栈是__向低地址扩展的数据结构__,是一块连续的内存区域,栈顶的地址和栈的最大容量是在通过ldr设置,因此需要根据应用需求合理分配栈空间。
接下来往下走,如果在汇编中不打断点,会默认进入main函数的一条指令,就从这里分析。为了分析方便,这里还有使用上一节方便出来的文件。
【c代码33行】
从内存地址0x0800 017c拷贝数据0x40021018到r3中,也就是
r3 = * 0x0800 017c
也就是将preg指针保存到r3中。
【c代码34行】
这里对应3条指令
首先将r3拷贝到r0中,然后将r0或上1左移3位,也就是
orr r0,r0,#8
最后将r0的值写入r3所指地址中。
【c代码37行】
同33行,从内存地址0x0800 0180拷贝数据40010c00到r3中
【c代码38行】
同34行,这里也对应3条指令:
【c代码40行】
和33行不同的是,这里分了两条指令:
笔者认为前面是编译器优化了。根据arm指令采用流水线机制,当前执行地址a的指令,同时已经在对下一条指令进行译码同时已经在读取下下一条指令:pc = a +4 (thumb/thumb2指令集)。因此前面类似的代码被优化了。
接下来就进入循环中。
后面就移植在死循环中,不断操作gpio的亮灭。
【c代码45行】
这里是将b0设置为1,和34行类似。
【c代码47行】
这里将进入延时函数。
进入延时函数:
nop是字节对齐,减少指令的内存访问次数。首先将变量d保存到r0,然后将r0赋给r1,接着是r0自减1,紧接着是r1与0比较,如果r1等于0,则会返回,否则,又从头开始,值得注意的是,这里先比较,然后r0才自减的。
为了进一步说明,可以看--d的汇编代码。
这里就是相当于r1先减1,然后再比较的。
【c代码50行】
这行代码对应一下指令,很简单。
5 总结在前面使用keil进行了反汇编,也对相应的c代码进行了分析。我们看到的反汇编代码如下:
根据反汇编的代码,可将其对应到flash,在flash上的内容如下表所示:
地址flash****内容
0x08000000 00000000
0x08000004 08000009
0x08000008 f8dfd004
0x0800000c f000f80c
… …
最后总结下点灯的流程:
第一步:设置栈 :cpu会从0x08000000读取值,用来设置sp。
第二步:跳转 :cpu从0x08000004得到地址值,根据它的bit0切换为arm状态或thumb状态,然后跳转。
__第三步:__对于cortex m3/m4,它只支持thumb状态,所以0x08000004上的值bit0必定是1,0x08000004上的值 = reset_handler + 1。从reset_handler继续执行。
第四步 :然后进入到主函数中执行相应c代码。

明基i707评测 性价比很高沉浸式视听体验
新海宜拟5亿建设2.05万吨碳酸锂及氢氧化锂加工厂
一款由LM139构成的方波发生电路的设计与实现
北京30家医院引进人脸识别技术,严查医闹和号贩子
雷军:要在三年投入50亿,铸就新零售铁军!
Cortex-M反编译入门
ups零地电压高的原因及解决办法 ups零地电压如何消除
诺基亚携手Telenor和Telia打造出了基于端到端5G技术的无线网络
解读BIG.Little技术:ARM为何选择八核处理器?
寒武纪科技的股东都有谁_寒武纪科技十大股东
带触摸屏的电动牙刷你见过吗
魅族pro7什么时候降价,双节过后降至2499元
数据中心的应急备用电源可靠性或将受微生物污染损害
低噪声,稳定型电源技术-适合有源天线系统
华为Mate30 Pro 5G和首款5G折叠屏手机Mate X开售
晶圆代工成熟制程大降价,IC设计厂松一口气
电容怎样做成电池用
嵌入式Linux操作系统的广泛应用分析
关于皮带秤称重传感器的安装注意事项
洗碗机到底有没有用