为什么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
我正在学习 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