首页 > 解决方案 > 为什么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 行损坏。

标签: assemblyarmgdbcpu-registers

解决方案


很快,你会得到好运......这恰好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

推荐阅读