为什么armcpu在遇到ltorg创建的错误汇编指令时可以运行?

Why arm cpu can run when meet the error assembly instruction created by ltorg?

我正在学习 arm 汇编语言,使用 qemu vexpress-a9 作为虚拟 arm cpu 和 GNU assemble。这是我的代码:

... @ 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中assemble、link、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

我们可以看到,.ltorg之前的ldr指令执行了两次。为什么ram 0x01111111中的数据在cpu中执行的命令在第22行是ldr r10, =0x1111111?我以为程序会在第 22 行损坏。

0x60010120: 0xe59fa004 0xe59fc004 0xe59f5004 0x01111111
0x60010130: 0x02222222 0x03333333 0xe59fb004 0xe59fb004
0x60010140: 0xeafffffe 0x04444444 0x05555555 0x00000000

从你的问题来看,所有的答案都是肯定的吗?也许这是一个 GDB 问题,而不是一个 SO 问题?

您要求 assembler 通过在代码中放置一个 ltorg 来插入混合在代码中的数据。它按照你的要求做了。

不确定您是否理解 none 是 运行 时间,它是所有 assemble/link 时间,提供的代码在 运行 时间没有魔法发生。

正如您从 ARM 文档中了解到的,当解释为指令时,这些数据项的条件字段是 0b0000 或 EQ,如果设置了 z 标志,则执行,现在我们必须假设 z 标志的状态是一种方式或另一个。

如果未设置 zflag,那么它将简单地通过作为指令插入的数据,并找到进入无限循环的方式。

当你 运行 没有单步执行时发生了什么?

如果你想看看 gnu binutils 认为指令是什么那么

.word 0x01111111

.inst 0x01111111

取决于您的工具版本,assemble,然后取消assemble。

正如评论中所指出的那样,gnu 在这里挣扎并且不一定正确(armv4 到 armv7 都表明这不是 TST,第 12 位)。应谨慎查看所有工具链的所有目标的所有 disassembler 输出,永远不要假设它们是完全正确的。对于可变长度指令集,它们通常是错误的,并且该站点上有太多问题与此相关。同样,相信调试器的反汇编或单步执行时会发生什么。同样是一个指令集模拟器。

在这种情况下,你把未知数和风险叠加在一起,尽可能糟糕。所以这里的期望是从这个实验中什么也得不到,当然任何人的结果 运行 无论是在模拟上还是在可能 运行 的许多特定核心之一上, 也没有给我们留下真正的结果,因为结果是预期的,或者至少当你从一个目标切换到另一个目标(一个核心或 sim)时可能会改变。

gdb 恰好在正确的地址看到正确数量的指令。可能是由于工具层问题的组合(我对 gdb 的使用为零,因为对于所有调试器,它们产生的问题多于解决的问题,我发现它们没用,可以使用更可靠的调试方法)它打印出错误对于说明的反汇编,您没有转储寄存器以查看它在做什么。您没有将标志转储到 0x01111111 或任何这些数据值中,以查看我们是否希望模拟执行或跳过这些指令。然而,它会在第一个数据池执行后或至少尽管 gdb 损坏表明它一直在继续运行,但它会在应有的位置上运行。 (例如,它是否遇到了未定义的指令处理程序?)。即使你做了这些事情,它真正有什么价值?您的目标是在这里对 qemu 核心进行验证测试吗?如果是这样,那么您试图让 qemu 匹配实际硬件的结果是什么,未定义指令的预期结果是什么等等?

gnu binutils 认为这些数据值是针对寄存器和常量的 testeq、eoreq 和 teqeq,因此没有 b运行ches 或代码流改变执行与否,您可以检查标志和寄存器以查看它们是否正在做某事。第一个 tsteq 如果这甚至是 sim 认为的指令(相对于未定义的指令),则可以更改标志以更改是否执行以下指令。

很多年前,我对进入 Whosebug 的调试器毫无用处,从那以后我所看到的一切,我肯定对 gdb 毫无用处,大多数时候我看到人们在这里使用它,它导致更多问题多于解决方案。因此,如果这是导致混淆的对 gdb 的简单信任,那么答案是不要相信 gdb,假设它几乎不起作用,它只是一个工具。就像用锤子把螺丝钉进去一样。

这似乎不是 Whosebug 问题,而是双重问题,首先是 gdb bugzilla 站点错误报告,其次是 qemu 模拟器报告。

gdb 可以按原样询问,我会把它放在 50/50,他们只是说你故意写了错误的代码,所以我们不感兴趣。即使您明确将 .words 放在那里,我也会将其设置为 50/50,他们不会费心回答。真正的问题是为什么该工具与二进制文件中的地址和内容不匹配(gdb 似乎没有使用 objdumps 资源并且似乎产生比 objdump 更糟糕的反汇编(根据我在此站点的经验))。

在去找 qemu 人员之前,您应该弄清楚您使用的是哪个 sim,并查看他们的指令解码,看看他们是否忽略了注释中指示的位。然后问题将是这个和这个以及这个 ARM ARM 表示这是一个未定义的指令,但您将其视为 X...我希望这种问题有超过 50% 的机会得到真正的回应。也许我们会在未来 10 年的某个时候修复它(我认为我的 gcc 和 clang 错误需要那么长时间才能有人真正修复)。

祝那些有这个问题的网站好运,如果你在这个 SO 问题关闭之前得到肯定的回应 and/or 删除,那么请 post 将这些错误报告的结果发送给 SO。

很快,你就走运了...恰好 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:看来我第一个关于buggy emulator的假设毕竟是错误的。


PPS: `llvm-objdump` 是 'less parsy',并且不会将内存池转换为指令,这在这种情况下有点烦人。
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