本专栏总结王利涛《C语言嵌入式Linux高级编程》第二期课程
文章目录
一、伪指令
1)什么是伪指令
- 为了编程方便定义的伪指令,并不是ARM指令集中的指令。
- 编译时会将这些伪指令等效为一条或多条机器指令。
4)四条伪指令
- ADR R0, LOOP; 将LOOP标号处的地址放入R0。ARR是小范围的地址读取伪指令,该指令将基于PC的地址或基于寄存器的地址值读取到寄存器中。(反汇编:汇编翻译成类似的语句 ADD R0, PC, #0X1234)。
- ADRL: 中等范围的地址读取伪指令。范围在[0, 64k]。
- LDR: 大范围的地址读取伪指令,范围在[0, 4G]。 常用来加载外部设备的寄存器地址,参数有等于号,LDR R0, =0X30008000
- NOP: 空操作伪指令,常用来延时,被翻译成 MOV R0, R0
二、LDR,MOV和LDR伪指令的区别
1)指令格式
- LDR伪指令的指令格式为
- LDR R0, =0X3000800; 有“=”表示伪指令,常数0x30008000写入R0
- LDR R0, =LOOP; 将标号LOOP所代表的内存地址写入R0
- LDR指令(非伪指令)通常用于寄存器间接寻址
- LDR R0, [R1]; 将R1值作为内存地址,取该内存地址内容,存入R0
- LDR R0, LOOP; 将标号LOOP代表的内存地址处的内容,存入R0
- MOV 通常用于寄存器间传输数据
- MOV R0, R1; 将寄存器R1的值,存入R0
- MOV R0, #20; 将立即数20,存入R0
2) 用途
- ARM 是 RISC结构,不能直接操作内存数据,要通过LDR/STD指令在内存和寄存器之间搬运数据;
- MOV用来在寄存器之间搬运数据;
- LDR伪指令常用来加载一个32位立即数或地址到指定寄存器。
三、思考
1)有了MOV指令和LDR指令,为什么还要使用LDR伪指令?
- 为什么不能 LDR R0, 0X30008000
- 在32位ARM中,外设寄存器地址是一个32位的地址。
- ARM指令通常是 :32位 = 操作码 + 操作数 的thumb 16位指令
如果 0x30008000作为操作数,肯定存不进 ARM指令里,此时,需要通过伪指令,一条伪指令可以等效成多条指令。
四、LDR伪指令
1)转换为MOV 指令形式
- 当立即数效于 8bit数范围时
- LDR R0, =200
- 相当于 MOV R0, #200
2)转换为LDR指令 + 文字池形式
LDR R0, =0X30008000
LDR R0,[PC, #offset]
...
DCD 0X30008000
- PC的值是当前指令的地址,然后计算得到DCD处的偏移地址,从而找到DCD的地址,最后[ ]间接读0x30008000, 送入R0。
- DCD它是定义一个数在内存中,编译器是知道这条指令的存在,从而可以计算出偏移数,同时,当前运行地址 =PC-8(由于ARM是采用多级流水线)。
- 在内存中定义一个字的存储单元,将32bit地址 0x30008000 存放在存储单元中,该存储单元通常叫做 文字池(literal poll)。
- 算出该存储单元到LDR伪指令之间的偏移offset,即可用相对寻址,将这个地址值送到R0寄存器。
五、ADR伪指令
1) 小范围地址读取的伪指令
- 将基于PC相对偏移的地址值读到寄存器中。
- 相对地址,做到代码与位置无关,即不管基地址是多少,只关系它的偏移量。
- 实例
ADR R0, _start
...
_start
b _start
①采用相对地址,ADR伪指令当前地址+标号_start 与 ADR伪指令的偏移量。
②ADR当前指令地址是已知的 (PC -8)
③偏移量:offset = _start - (PC - 8)
④该指令被翻译成一条合适的指令:
ADD R0, PC, #offset (范围不超过8bit, 不然不够放,然后采用其它语句)
六、LDR和ADR比较
1)相同点
- 都是ARM 伪指令,加载一个地址到指定寄存器。
2)不同点
- LDR伪指令通过被翻译为 LDR 或 MOV指令, 而ADR 伪指令通常被翻译为 ADD或SUB指令。
- 用途上,LDR主要用来操作外设寄存器。ADR主要通过相对寻址,生成与位置无关的代码,只要各标号相对位置不变,就可以做到位置无关。
- 实现上, LDR使用绝对地址, 而ADR使用相对地址。
- 地址范围:LDR为 [0, 4G], 但ADR要求标号必须在同一个段中,地址偏移范围:地址对齐[0, 1024], 未对齐[0, 4096]。
- 位置无关代码经常被运用到 uboot启动和动态库 -fPIC