编译器会修改你的代码

在这里插入图片描述

当你编写好程序,编译得到HEX文件,并将HEX下载到MCU中运行。请问MCU中运行的机器代码是100%根据你写的程序转换出来的吗?
你可能不知道,MCU中运行的机器代码已经被偷偷修改了。本文将深入研究我们的代码哪些地方被修改了,然后找到改变我们代码的幕后黑手。

测试环境
软件:keil
硬件:stm32f103

1.反汇编

查看MCU运行的二进制机器代码几乎是不可能完成的任务,那我们是不是没有途径查看查看二进制机器代码?当然不是,我们可以利用反汇编(disassembly)的方法

将机器代码(二进制代码)转换回人类可读的汇编语言指令的文件,这种转换过程被称为反汇编(disassembly)
反汇编可以生成一个反汇编文件,反汇编文件是包含汇编语言指令的文本文件。这些指令与原始机器代码在功能上等价,但更易于人类阅读和理解。

生成反汇编文件通常涉及以下几个步骤和工具:

1、获取二进制文件,比如一个可执行文件.exe、.elf、.o等。
2、选择合适的反汇编工具,比如objdump、IDA Pro、Ghidra。
3、使用选择的反汇编工具对二进制文件执行反汇编。

反汇编文件通常包含以下内容
汇编指令:这是文件的主要部分,包含了从二进制文件反汇编得到的汇编指令。
地址信息:每条汇编指令通常都会附带一个内存地址,表示该指令在原始二进制文件中的位置。
注释和符号:一些反汇编工具可能会尝试解析和恢复原始代码中的符号信息(如函数名、变量名等),并将其作为注释添加到汇编代码中。
节(Section)信息:二进制文件通常被划分为不同的节(如代码节、数据节等),反汇编文件可能会包含这些节的边界和属性信息。

本文我们将使用反汇编的方法得到反汇编文件,通过分析反汇编文件,来检查我们的机器码是不是被修改了

2.keil生成反汇编

由于我们使用的KEIL集成开发软件内部包含了反编译工具,因此我们只需要在KEIL软件使用fromelf --text 指令,就可以生成反汇编文件。
在这里插入图片描述

操作步骤如下:

1、打开Keil工程,点击“Options for Target”,在“Options for
Target”对话框中,选择“User”选项卡。

在这里插入图片描述

2、然后勾选“After Build/Rebuild”下的“Run #1”或类似的选项。在“Run #1”的命令框中,输入以下fromelf
命令来生成反汇编文件。

fromelf --text -a -c --output=name1.dis  name2.axf

其中name1是生成反汇编文件的名字
其中name2是生成的axf文件的名字(包括文件路径)
在这里插入图片描述

3、在“Options for Target”对话框中,找到“Linker”选项卡。 查看Linker control string中的axf文件名称(包括文件路径):…\Output\demo.axf

在这里插入图片描述

4、回到“User”选项卡。在“Run #1”的命令框中,修改fromelf 命令

fromelf --text -a -c --output=..\Output\name1.dis  ..\Output\demo.axf

(其中…\Output\根据自己工程输出文件路径可定,不同人的工程配置可能不一样)

配置完成后编译工程,就得到了name1.dis反汇编文件,我们可以用记事本或者Notepad++打开.dis反汇编文件(作者更推荐使用Notepad++工具)。

3.增加代码

3.1启动

接下来通过分析反汇编文件,来检查机器码的内容

为了方便查看,将反汇编文件的后缀名改为.asm,此时用Notepad++工具查看反汇编文件时,会用不同的颜色显示不同格式的内容。

在这里插入图片描述

打开反汇编文件,程序空间起始(0x08000000被映射为0x00000000)的第一个32位数为SP的初始值,第二个32位数为PC的初始值(跳转地址)。从反汇编文件可知0x080001e1为跳转地址。

在这里插入图片描述

在反汇编文件中找到0x080001e1地址附近,复位处理函数Reset_Handler被执行。
在这里插入图片描述

问题:细心的网友可能会发现PC的装载值是0x080001e1,为什么Reset_Handler函数的地址为0x080001e0
由于crotex-m3内核的MCU运行在Thumb状态下,一些指令更新PC值时,需要将新PC置的LSB置1以表示Thumb状态,否则就会导致错误异常,所以向量表中的每个数值必须将LSB置1。

3.2 scatterload

在Reset_Handler中跳转到0x8000131(实际地址为0x8000130),找到地址0x8000130附近,程序调用了__scatterload函数。
在这里插入图片描述

问题来了:在你的程序中有调到到__scatterload函数吗?
实际上,即使你搜索软件工程中的所有文件,都不可能找到scatterload函数,那么scatterload的作用是什么?scatterload函数是如何被添加到机器码中的?
在这里插入图片描述

Scatterload是分散加载函数
分散加载是ARM开发中一种重要的内存管理和映像文件生成方法,通过scatter文件(分散加载描述文件)来指定代码和数据在内存中的分布。
在程序执行过程中,__scatterload函数负责将RW/RO输出段从装载域地址复制到运行域地址,并完成ZI运行域的初始化工作。
在这里插入图片描述

代码的编程过程如下,在这个过程中的链接环节,链接器将分散加载函数添加到了机器码中。所以机器码已经被编译器修改,增加了scatterload函数到机器码
在这里插入图片描述

我们分析scatterload函数,执行如下指令后

LDR      r4,[pc,#24] 
LDR      r0,[r4,#0xc]
ORR      r3,r0,#1
BLX      r3
跳转到08000a4a(LSB置1)

在这里插入图片描述

3.3 decompress

查看08000a4a代码,执行了一个__decompress解压函数。同样的,即使你搜索软件工程中的所有文件,都不可能找到__decompress函数。和scatterload函数一样,__decompress解压函数也是编译器加到机器码中的

在这里插入图片描述

decompress是用于数据解压,既然有数据解压,那么肯定对应有数据压缩。在《compiler_reference_guide》可以找到关于压缩数据的介绍。根据手册可知:编译器(ARMCC)使用LZ77算法数据压缩
在这里插入图片描述
在这里插入图片描述

从《compiler_user_guide》手册中可以找到系统启动初始化流程图如下。
在这里插入图片描述

根据启动初始化流程图可知,处理器在启动后执行了cpoy/decompress RW data ,其中cpoy由scatterload函数完成,decompress功能由decompress函数完成。

由此可见,MCU运行的机器码并不是100%由你写的程序转换而来,编译器还在你不知情的情况下增加了一些代码到机器码中
在这里插入图片描述

4.减少代码

4.1 消失的函数

代码中定义了delay_1和delay_2两个延时函数。
在这里插入图片描述

在反汇编文件中搜索“delay_”,理论上来说应该搜索到“delay_1”和“delay_2”,但是只搜索到了“delay_2”。明明写了两个延时函数,为什么在机器码中只有一个?
在这里插入图片描述

重复未原来在软件中,虽然定义了两个延时函数,但是只有delay_2函数被调用过,而delay_1函数则没有被使用过。在这种情况下,为了节省MCU空间,编译器不会将没有使用的函数编译到机器码中。

在这里插入图片描述

4.2 消失的变量

代码中定义了test_buff1和test_buff2两个变量。
在这里插入图片描述

在反汇编文件中查找“test_buff”,找到了“test_buff1”和“test_buff2”,但是由于test_buff1没有在程序中使用过,test_buff1的地址设置为了0x00000000,就等效于编译没有将这个变量编译到机器码中(反汇编文件中可以看到有其未使用的变量地址也设置成了0x00000000)
在这里插入图片描述

由此可见,MCU运行的机器码并不是100%由你写的程序转换而来,编译器还在你不知情的情况下在机器码中删减了一些代码。
在这里插入图片描述

5.修改代码

代码中定义了一个test_fun函数。
在这里插入图片描述

在反汇编文件中查找“test_fun”,找到了test_fun函数。但是机器码并没有源代码中abc这3个变量的赋值和运算。仔细查看test_fun函数,不管输入的参数值是多少,返回值都是0,而且函数也不会产生其它“副作用”,可以认为这是一个无用的函数。
很显然,编译器也很聪明,编译检测到了这个无用函数,在生成机器码的时候将其它无用的逻辑全部省略,直接让函数返回0 。

在这里插入图片描述

由此可见,MCU运行的机器码并不是100%由你写的程序转换而来,编译器还在你不知情的情况下修改了一些代码。

在这里插入图片描述

6.幕后大佬的工作

MCU运行的机器码并不是100%由你写的程序转换而来,有一个幕后大佬,在你不知情的情况下已经大刀阔斧了修改了你的代码
这个幕后大佬就是编译器,它会对你的代码做如下操作:

1、增加代码
2、删减代码
3、修改代码

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liyinuo2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值