| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 嵌入式 -> ARM汇编:MOV/LDR陷阱导致offset out of range -> 正文阅读 |
|
[嵌入式]ARM汇编:MOV/LDR陷阱导致offset out of range |
错误现象先来说一下最近碰到的问题。在编译阶段,编译器报如下错误信息:
看到
问题原因经过一系列测试,调试(说的比较高大上,其实就是不断屏蔽代码来缩小范围😢 ),最后发现导致该问题的罪魁祸首就是下面这行代码:
LDR, MOV回锅说实话,在这之前,我真没有觉得LDR,MOV指令有什么难的(其实是太孤陋寡闻了,还停留在8051时代)。结果就在这上面上当了,今天重头开始学习,回锅肉多炒炒。 首先我们来普及一下这两个命令的作用(虽然可能大家都很熟悉了)。
MOV指令的作用就是在寄存器之间传递数据,或者将立即数拷贝到寄存器里面。而LDR指令类似于C语言里面的指针。我们C语言里面经常使用的将某个内存区域里的数据拷贝到寄存器里面,只能通过LDR来实现,MOV指令是无法完成的。 直接看示例代码:
上面第一行MOV的作用就是把R1寄存器里面的内容拷贝到R0里面。第二行则是将立即数1赋值到R0里面。最后一行LDR的意思是将0X11223344这个地址里面的值拷贝到R0中,注意0X11223344是作为地址,而不是它本身,这不就是C语言里面的指针了么。MOV是没有LDR这种功能的,MOV后面第二个操作数是立即数。那反过来,LDR能不能实现MOV的功能呢?答案是肯定的。并且它还引入另一个概念:LDR伪指令。区别于LDR伪指令,上面我们提到的LDR指令叫加载指令。那么伪指令有什么区别呢? 直接看代码:
看到区别没有?数字前面多了一个 但是,LDR伪指令和MOV在使用上还是有区别的。MOV指令对后面的立即数实际上是有要求的,从ARM官方cortex-m7的手册来看,MOV后面的立即数/常量范围限制是8bits。但是这里并非是说真的只能跟一个小于等于8bits的数据。32bits数据也是可以的,只是说8bits范围内的所有整数都是可以的,但是大于8bits的数据不是所有数据都可以,这里面涉及到另外一个机制:移位。简单来说,如果你的这个32bits的数据可以通过一个8bits数据移位得到,那么这个数据也是可以的。这个过程编译器在编译翻译为机器码的时候自动完成。另外这里稍微提一下另外一个指令,MOVW,这个指令后面可以跟16bits立即数,但是好像没有移位这个机制。具体MOV指令的其他语法以及移位机制的详细原理这里就不讲解了,请看官方文档。这里还是贴一下图。 我们再来看看LDR的语法。 我给大家把重点的地方拎出来翻译一下。重点看expression下面两段话。如果表达式的值在MOV指令范围内,则汇编器直接生成指令。这里所谓的MOV指令范围内,其实就是说的上面描述的MOV指令对立即数的那些限制。重点,重点,重点。如果表达式的值不在MOV指令范围内,则汇编器会将这个立即数放入文字池/字符池(literal pool)并生成程序相关LDR指令,这个指令会从文字池中读这个立即数。pc指针到这个立即数的偏移必须小于4K。你必须保证在4K范围内有一个文字池。 原文 立即数在MOV范围内(假如立即数是255):
立即数不在MOV范围内(假如立即数是0xe000ef38):
上面第一种情况最直接。重点来看看第二种情况。这里的汇编我全部贴出来了,前面0,1,fce,fd0是地址信息,后面是机器码,再后面才是对应的汇编。由于0xe000ef38这个数不符合MOV指令的条件,于是在翻译的时候你看汇编器不是直接去访问这个数了,而是使用偏移的方式去访问,看r2后面的东西,[pc, #4044],也就是pc指针往后偏移4044这么多的地方去取值。当前指令所在地方是0,往后偏移4044+4,注意这里我加了4,因为是pc指针基础上偏移4044,这不就变成4048(fd0)了么,你看最后一行fd0的地方不就是放的原始值么?是不是就是和C语言的指针一样,纯粹是为了让第一行后面那个值变一下以满足MOV的规则。最后fd0这里就是文字池。至于前面fce那里,这里编译器自动填充了两字节,否则就不对齐了(M7里面只有部分指令支持不对齐访问,比如LDR,LDRT,STR等)。 你是不是有个小小的疑问?我们不是再说LDR指令么,为何又是MOV指令的范围?没错,LDR指令和MOV联系非常紧密。当立即数符合MOV指令条件时,其实LDR指令最后是翻译为MOV指令来执行的。如上面第一种情况:
解决方法再继续说说这个文字池,当立即数不符合条件时,解决方法其实官方文档已经给出了。那就是引入文字池,方法就是在合适的地方插入 如下面是官方给的示例:
插入LTORG伪指令的方法在全汇编的使用场景下还是比较方便的,但是如果是在C语言和汇编混合编程的情况下,其实是不太适用的,尤其是大部分都是C语言函数,偶尔插入LDR指令这种情况。因为C语言的函数返回对于编程人员来说都是黑盒子,由编译器帮我们处理的,程序员无法在return后面去添加伪指令,但是条件分支倒是可以,也就是将这个伪指令适用条件语句包裹起来,但是这样需要引入额外的条件变量,程序通过保证这个变量不满足条件,也就不会执行里面的伪指令。这种方法除了引入额外的变量之外,有时候编译器优化可能会把这种条件语句给优化掉。所以也不是百分百能成功的,需要小心检查。 除了上面说的方法外,当然还有第二种方法,根本原因我们找到了,就是那个立即数不合法,当然这里肯定不是让你去该掉这个立即数,要是这样就不用我花这么多时间来敲文字了,我们讨论的情况肯定是这个值不能改。继续哈,不合法,并且下一个文字池偏移超过了4K。那如果我将文字池偏移缩小使其小于4K不久好了么。对的,完全没错,最开始我说的添加一个编译器参数。这个编译器参数就是去修改这个文字池的。默认情况下,如果不加这个参数,文字池是以一个文件为单位的。如下图所示:
如上map文件所示,默认情况下,每个文件,不管你文件里面有多少函数,都是统一编译到一个section(.text)的。很明显,如果你某个文件里面内容比较多,就会导致这个文字池比较大。 编译器为我们提供了一个参数:
看到没有,添加之后就变成以每个函数为单位进行了,section的名字也是:.text.xxx,xxx代表函数名。这样是不是每个section的大小就大大缩小了。这就是为何大部分时候添加这个编译器参数后就可以我们的问题了。这里为何说大部分时候,而不是一定能解决。假如某个函数特别大,大小超过了4K,正好在里面也适用了LDR汇编指令,那么一样会报错。这种情况你还是得通过前面的方法来解决。
|
|
嵌入式 最新文章 |
基于高精度单片机开发红外测温仪方案 |
89C51单片机与DAC0832 |
基于51单片机宠物自动投料喂食器控制系统仿 |
《痞子衡嵌入式半月刊》 第 68 期 |
多思计组实验实验七 简单模型机实验 |
CSC7720 |
启明智显分享| ESP32学习笔记参考--PWM(脉冲 |
STM32初探 |
STM32 总结 |
【STM32】CubeMX例程四---定时器中断(附工 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/28 17:46:29- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |
数据统计 |