反编译入门
我们在写单片机裸机程序时,在主函数之前,会有一段启动代码,而启动代码是用汇编写的,有些朋友可能看到汇编头都大了,当时要想深入研究底层架构,这快硬骨头就必须去啃。
汇编:汇编文件转换为目标文件(里面是机器码)。
反汇编:可执行文件(目标文件,里面是机器码),转换为汇编文件。
关于汇编的基础知识,请看笔者以前的文章。
今天笔者以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 handler Reset_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.pdf4反汇编代码全解析
进入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代码
同样演毛泽东主席,唐国强和古月放在一起看,差别就出来了特型演员的存在,或许是许多影视作品当中的灵魂所在。而在这其中,最为家喻户晓的,还是毛主席的特型演员。他们将毛主席的音容笑貌用自己的方式演绎在屏幕当中,让我们这些后人得以一瞻伟人的风
服务群众没有终点!原标题服务群众没有终点!(主题)国家奥体中心服务全民健身的故事(副题)中国体育报记者林剑中办国办印发的关于构建更高水平的全民健身公共服务体系的意见明确提出,推动更多竞技体育成果全民
中超2武汉长江,多拉多侵犯裁判将遭重罚北京时间8月21日,中超第14轮的较量中,武汉长江坐镇主场对阵河南嵩山龙门。开场127秒,张华峻世界波为武汉长江打破僵局,第10分钟,胡人天的进球让武汉长江扩大了领先优势,第15分
湖北的区划调整,1个专区曾管辖武汉大部,如今只有7个县市区湖北地处我国中部,有承东启西沟通南北的重要作用。全省总面积18。59万平方公里,常住人口5830万人。从地理地貌上看,湖北东西北三面环山,中间为平原,山地和丘陵的面积占比为78。从
价值6000元茶叶遗忘餐桌被拿走,警方电话告知已受案,各方热议属侵占还是盗窃,失主该不该较真?酒店用餐遗忘一袋好茶,调取监控找到拿走茶叶男子,当事人王律师认为对方涉嫌盗窃,警方认为是侵占行为,案件引发各方热议。离开餐厅折返价值6000元茶叶被坐对面眼镜男拿走8月20日,律师
又崩了!10万人爆仓中国基金报安曼好久没有关注比特币了!基金君刚刷到一个网友的帖子,其朋友拿100万炒币,去年赚了30万。结果遇到了今年的大跌之后,直接没了100万。基金君赶紧去看了一眼比特币,不看不
为啥更多的人会选择右面的妹子?哈哈哈对性别产生了质疑今天结婚看着多少还是有点紧张了啊每个轮子都有自己的想法这是真的白啊不得不说我真的羡慕了只要思想不滑坡方法总比困难多有这样的媳妇你一定要好好珍惜啊看得出来都是很开心的样子看到你这表情
突发!公交全部停运!又一地宣布非必要不离开中国基金报安曼综合自央视新闻等这个周末,到底能不能开学话题重上热搜。基金君带大家来一起关心一下疫情。非必要不离溪公交全部停运27日午间,辽宁本溪市新冠肺炎疫情防控指挥部发布关于平山
外媒土耳其和芬兰瑞典举行常设联合机制首次会议据新加坡联合早报网站27日报道,土耳其芬兰瑞典常设联合机制首次会议当地时间26日在芬兰南部城市万塔举行,与会方一致同意今年秋季继续举行专家级会议。芬兰外交部说,土耳其芬兰和瑞典三国
一对老夫妻的悔悟退休后,一个错误的决定,让晚年生活大打折扣导语论起现在老年人的养老生活,十个人中就有九个是令我们无比羡慕的。为什么这么说呢,因为当下的老年人几乎都是退休老人,他们不用工作也不愁没钱花,每个月领的退休金,更是和我们这些年轻人
22022羽毛球世锦赛女单半决赛,陈雨菲对阵戴资颖,最终,陈雨菲先输一局后连赢两局21霸气逆转,强势晋级决赛,戴资颖又一次在大赛中拉胯(在之前,戴资颖不止一次在大赛拉胯)。国羽一姐陈