assembly - 为什么arm cpu遇到ltorg创建的错误汇编指令可以运行?
问题描述
我正在学习 arm 汇编语言,使用 qemu vexpress-a9 作为虚拟 arm cpu,使用 GNU 作为汇编语言。这是我的代码:
... @ some vector table code
.section .text
Reset_Handler: @ 0x60010120
@ldr sp, = SRAM_BASE
ldr r10, =0x1111111 @ I know it is 0x01111111
ldr r12, =0x2222222
ldr r5, =0x3333333
.ltorg
ldr r11, =0x4444444
ldr r11, =0x5555555
stop:
b stop
在 qemu 中组装、链接、objcopy 和运行后,我得到了 .bin 文件并从 ram 地址 0x60010120 开始。
@ This is the result of gdb command x/20x 0x60010120!!!
0x60010120: 0xe59fa004 0xe59fc004 0xe59f5004 0x01111111
0x60010130: 0x02222222 0x03333333 0xe59fb004 0xe59fb004
0x60010140: 0xeafffffe 0x04444444 0x05555555 0x00000000
地址从 0x6001012C 到 0x60010134 的数据是我在代码中设置的数字。我认为程序会在 0x6001012C 处损坏。它不是指令而是数据。
然而,程序以stop: b stop
指令结束。我从 Reset_Handler 走出来。从 gdb 得到的结果让我很困惑。
(gdb) ni
_Reset () at startup.s:8
8 b Reset_Handler
(gdb) ni
SRAM_BASE () at startup.s:22
22 ldr r10, =0x1111111
(gdb) i r pc
pc 0x60010120 0x60010120 <SRAM_BASE>
(gdb) ni
23 ldr r12, =0x2222222
(gdb) i r pc
pc 0x60010124 0x60010124 <SRAM_BASE+4>
(gdb) ni
24 ldr r5, =0x3333333
(gdb) i r pc
pc 0x60010128 0x60010128 <SRAM_BASE+8>
(gdb) ni
22 ldr r10, =0x1111111
(gdb) i r pc
pc 0x6001012c 0x6001012c <SRAM_BASE+12>
(gdb) ni
23 ldr r12, =0x2222222
(gdb) i r pc
pc 0x60010130 0x60010130 <SRAM_BASE+16>
(gdb) ni
24 ldr r5, =0x3333333
(gdb) i r pc
pc 0x60010134 0x60010134 <SRAM_BASE+20>
(gdb) ni
26 ldr r11, =0x4444444
(gdb) i r pc
pc 0x60010138 0x60010138 <SRAM_BASE+24>
(gdb) ni
27 ldr r11, =0x5555555
(gdb) i r pc
pc 0x6001013c 0x6001013c <SRAM_BASE+28>
(gdb) ni
stop () at startup.s:29
29 b stop
正如我们所见,ldr 指令.ltorg
执行了两次。为什么 ram 中的数据是 0x01111111 而在 cpu 中执行的命令却ldr r10, =0x1111111
在第 22 行?我认为程序会在第 22 行损坏。
解决方案
很快,你会得到好运......这恰好0x01111111 0x02222222 0x03333333
是有效的指令。
现在让我们详细说明。我在 ARMv7(Cortex-A9、SoC Zynq-7000)上运行以下代码。
void test_so() __attribute__((naked));
void test_so()
{
asm volatile
(
"ldr r0, =0x1111111 \n\t"
"ldr r1, =0x2222222 \n\t"
"ldr r2, =0x3333333 \n\t"
".ltorg \n\t"
"add r3, r0, r1 \n\t"
// crash it
"mov r3, #0 \n\t"
"ldr r3, [r3] \n\t"
:::"memory", "r0", "r1", "r2", "r3"
);
}
...
printf("test start.\n");
test_so();
printf("test end.\n");
test_so
使用 GNU objdump 进行反汇编
01a281b4 <test_so()>:
1a281b4: e59f0004 ldr r0, [pc, #4] ; 1a281c0 <test_so()+0xc>
1a281b8: e59f1004 ldr r1, [pc, #4] ; 1a281c4 <test_so()+0x10>
1a281bc: e59f2004 ldr r2, [pc, #4] ; 1a281c8 <test_so()+0x14>
1a281c0: 01111111 tsteq r1, r1, lsl r1
1a281c4: 02222222 eoreq r2, r2, #536870914 ; 0x20000002
1a281c8: 03333333 teqeq r3, #-872415232 ; 0xcc000000
1a281cc: e0803001 add r3, r0, r1
1a281d0: e3a03000 mov r3, #0, 0
1a281d4: e5933000 ldr r3, [r3]
如您所见objdump
,实际上将内存池中的值显示为指令。
故意崩溃执行此代码的结果是
test start.
...
Type: Data Abort
...
---
r0: 0x1111111
r1: 0x2222222
r2: 0x3333333
r3: 0x0
...
r13(sp): 0x149b8
r14(lr): 0x1a28200
r15(pc): 0x1a281d4 <-- address of Instruction causing a crash
---
因此 CPU 执行了有问题的指令(它们是内存池中的数据)并按计划崩溃
1a281d4: e5933000 ldr r3, [r3]
(空解引用,r3
为零)
为了获得额外的乐趣,让我们使用以下代码中止未定义的指令
asm volatile
(
"ldr r0, =0x1111111 \n\t"
"ldr r1, =0x2222222 \n\t"
"ldr r2, =0x3333333 \n\t"
".ltorg \n\t"
"add r3, r0, r1 \n\t"
// crash it
"udf #1 \n\t" <-- undefined instruction
:::"memory", "r0", "r1", "r2", "r3"
);
反汇编几乎相同,除了空解引用被未定义指令替换udf
01a281b4 <test_so()>:
1a281b4: e59f0004 ldr r0, [pc, #4] ; 1a281c0 <test_so()+0xc>
1a281b8: e59f1004 ldr r1, [pc, #4] ; 1a281c4 <test_so()+0x10>
1a281bc: e59f2004 ldr r2, [pc, #4] ; 1a281c8 <test_so()+0x14>
1a281c0: 01111111 tsteq r1, r1, lsl r1
1a281c4: 02222222 eoreq r2, r2, #536870914 ; 0x20000002
1a281c8: 03333333 teqeq r3, #-872415232 ; 0xcc000000
1a281cc: e0803001 add r3, r0, r1
1a281d0: e7f000f1 udf #1
运行此代码会崩溃
test start.
...
Type: Undefined Instruction Abort
...
---
r0: 0x1111111
r1: 0x2222222
r2: 0x3333333
r3: 0x3333333
...
r13(sp): 0x149b8
r14(lr): 0x1a281fc
r15(pc): 0x1a281d0 <-- address of Instruction causing a crash
---
所以在这种情况下,这是一个真正的指令中止
1a281d0: e7f000f1 udf #1
PS:看来我对错误模拟器的第一个假设毕竟是错误的。
PPS: `llvm-objdump` 虽然是“不那么解析”,并且不会将内存池转换为指令,这在这种情况下有点烦人。
test_so():
1a281b4: 04 00 9f e5 ldr r0, [pc, #4]
1a281b8: 04 10 9f e5 ldr r1, [pc, #4]
1a281bc: 04 20 9f e5 ldr r2, [pc, #4]
$d.4:
1a281c0: 11 11 11 01 .word 0x01111111
1a281c4: 22 22 22 02 .word 0x02222222
1a281c8: 33 33 33 03 .word 0x03333333
$a.5:
1a281cc: 01 30 80 e0 add r3, r0, r1
1a281d0: f1 00 f0 e7 udf #1
推荐阅读
- vue.js - 在创建的数据 api 完成加载之前运行的挂载钩子
- c# - 使用 INotifyPropertyChanged 与类似 WinForms 的事件处理程序有什么优势?
- php - 我的 PHP 脚本不断抛出“内存不足”
- pandas - 使用替代方法对分组进行排序时保持顺序?
- bash - for循环中的远程服务器数组参数
- mongodb - 我需要按字段对嵌套集合进行排序,并按文档字段切片和分组输出结果
- caching - Elixir 进程占用过多内存
- c# - 如何使用可编写脚本的对象创建动态变量系统
- java - Tomcat作为Windows服务如何传递Java属性
- javascript - 如果对象是通过引用复制的,为什么 JavaScript 垃圾回收会这样工作?