编辑
2025-03-21
工作知识
0
请注意,本文编写于 77 天前,最后修改于 77 天前,其中某些信息可能已经过时。

目录

浅谈程序跑飞
跑飞的代码
思考
关于x30寄存器
关于ret指令
演示
qemu运行rtems
挂gdb
盯住x30寄存器
确认一下x0-x30常规寄存器
修复

浅谈程序跑飞

我们原来最早调试51的时候,碰到程序跑飞了就不管了,那个时候认为程序跑飞是概率事件,所以没有考虑其跑飞的真正原因。最近发现aarch64架构的bl指令,非常容易导致程序跑飞。本文复现这种跑飞的情况,用来说明一个非常常见的跑飞现象,并且给出解决思路。 同样,思考以前调试51的时候,跑飞很可能是芯片的一些特性导致的问题。只怪当时知识尚浅,无法理解。

跑飞的代码

本文以rtems上的汇编为例,当然可以在linux中复现,本文就不在linux重复实验了,问题一样会出现,如下是代码diff

diff --git a/bsps/aarch64/shared/start/start.S b/bsps/aarch64/shared/start/start.S index 0a89d85035..04e2b1a8fa 100644 --- a/bsps/aarch64/shared/start/start.S +++ b/bsps/aarch64/shared/start/start.S @@ -43,6 +43,18 @@ .globl _start .section ".bsp_start_text", "ax" +kylin_call: + mov x0, xzr + ret + +kylin_out_of_control: + mov x0, xzr + bl kylin_call + mov x0, xzr + ret + /* Start entry */ _start: @@ -341,6 +353,8 @@ _start: /* Branch to start hook 1 */ bl bsp_start_hook_1 + bl kylin_out_of_control + /* Branch to boot card */ mov x0, #0 bl boot_card

上面的代码我定义了一个kylin_out_of_contrl标签,通过bl来跳转此标签,然后在kylin_out_of_control中,添加了一个没有意义的指令来代替其他指令,没有意义的指令是将x0清空。然后调用kylin_call标签,在kylin_call中继续将x0清空后返回到kylin_out_of_control,然后kylin_out_of_control内继续调用x0清空来代替其他指令,最后返回。

思考

根据我们对汇编的理解,上面的代码完全正常,bl是跳转指令,ret返回指令,mov移动指令。每个指令简单易懂,这些代码为什么组合会跑飞呢。可以思考一下

关于x30寄存器

为了理解这个问题,我们需要了解x30寄存器。在aapcs中我们可以知道x30作为lr寄存器,这个寄存器保存的是上一个函数调用pc值的+4,也就是 last_pc + 4 的值。这里出现跑飞的原因是x30寄存器不正常了。

那么我们可以思考,为什么x30会不正常?

关于ret指令

我们知道ret指令是返回指令,那么有一个问题,ret指令它会操作系统寄存器吗(x0-x30寄存器)?
aarch64的定义上,ret没有提到会操作寄存器,我们看看ret的说明如下:

image.png

根据上面的信息,我们知道ret默认返回x30寄存器。完全没问题啊,不会操作任何寄存器

那么问题就出来了,ret不会操作寄存器,但是我们bl进入的时候,会根据aapce记录x30寄存器为原pc+4。也就是会改写x30寄存器。

发现没有,x30寄存器会被aapcs改写,但是不会在ret还原。所以真相是:

如果bl出现嵌套,那么两次ret时,最外层的函数的x30和内层函数的x30相等,那么程序就一直在这里打转。于是出现了跑飞现象

演示

上面文字介绍不是很直观,下面开始演示

qemu运行rtems

如下

qemu-system-aarch64 -no-reboot -nographic -serial mon:stdio -machine xlnx-zcu102 -m 4096 -smp 4 -kernel build/aarch64/zynqmp_qemu/testsuites/libtests/kylinos.exe -s -S

挂gdb

gdb开始监测

aarch64-rtems6-gdb build/aarch64/zynqmp_qemu/testsuites/libtests/malloctest.exe

挂上断点

(gdb) b kylin_out_of_control Breakpoint 1 at 0x18008: file ../../../bsps/aarch64/shared/start/start.S, line 51. (gdb) c Continuing. Thread 1 hit Breakpoint 1, kylin_out_of_control () at ../../../bsps/aarch64/shared/start/start.S:51 51 mov x0, xzr (gdb) l 46 kylin_call: 47 mov x0, xzr 48 ret 49 50 kylin_out_of_control: 51 mov x0, xzr 52 //mov x19, x30 53 bl kylin_call 54 //mov x30, x19 55 mov x0, xzr

盯住x30寄存器

此时x30寄存器为

(gdb) x/x $x30 0x180e4 <_start+204>

这里还是一切正常,x30正好是_start+200 + 4

此时进入kylin_call,如下

(gdb) x/x $x30 0x18010 <kylin_out_of_control+8>:

然后我们第一个ret返回,如下

(gdb) s kylin_out_of_control () at ../../../bsps/aarch64/shared/start/start.S:55 55 mov x0, xzr (gdb) x/x $x30 0x18010 <kylin_out_of_control+8>: 0xaa1f03e0 (gdb) disassemble Dump of assembler code for function kylin_out_of_control: 0x0000000000018008 <+0>: mov x0, xzr 0x000000000001800c <+4>: bl 0x18000 <bsp_vector_table_begin> => 0x0000000000018010 <+8>: mov x0, xzr 0x0000000000018014 <+12>: ret End of assembler dump.

可以看到在kylin_out_of_control中了,但是异常出现了,kylin_out_of_control函数中的x30寄存器并不是我们认为的_start+204,而是kylin_out_of_control+8

按照代码逻辑,即将在0x0000000000018014处调用ret,而这个ret默认找的x30是kylin_out_of_control+8。程序会死循环了。也就是我们说的跑飞了。

确认一下x0-x30常规寄存器

为了说明这个问题,我们需要看看两次bl和ret是否是bl修改了x30,而ret不会修改任何寄存器。信息如下

第一个bl,进入kylin_out_of_control时,寄存器如下

x1 0x0 0 x2 0xffffffffffffffe8 -24 x3 0x1030c0 1061056 x4 0x103128 1061160 x5 0x4 4 x6 0x4 4 x7 0xf 15 x8 0x1c 28 x9 0x4 4 x10 0x3c0 960 x11 0x3fffffff 1073741823 x12 0x0 0 x13 0x8000000000 549755813888 x14 0x40000000 1073741824 x15 0x200000 2097152 x16 0x0 0 x17 0x0 0 x18 0x0 0 x19 0x0 0 x20 0x0 0 x21 0x0 0 x22 0x0 0 x23 0x0 0 x24 0x0 0 x25 0x0 0 x26 0x0 0 x27 0x0 0 x28 0x0 0 x29 0x0 0 x30 0x180e4 98532

然后再一个bl进入kylin_call函数,我们查看x0-x30如下

x0 0x0 0 x1 0x0 0 x2 0xffffffffffffffe8 -24 x3 0x1030c0 1061056 x4 0x103128 1061160 x5 0x4 4 x6 0x4 4 x7 0xf 15 x8 0x1c 28 x9 0x4 4 x10 0x3c0 960 x11 0x3fffffff 1073741823 x12 0x0 0 x13 0x8000000000 549755813888 x14 0x40000000 1073741824 x15 0x200000 2097152 x16 0x0 0 x17 0x0 0 x18 0x0 0 x19 0x0 0 x20 0x0 0 x21 0x0 0 x22 0x0 0 x23 0x0 0 x24 0x0 0 x25 0x0 0 x26 0x0 0 x27 0x0 0 x28 0x0 0 x29 0x0 0 x30 0x18010 98320

发现没有,bl寄存器会修改x30寄存器。

然后我们看第一个ret后的寄存器,此时应该在函数kylin_call的ret后,也就是kylin_out_of_control中

x0 0x0 0 x1 0x0 0 x2 0xffffffffffffffe8 -24 x3 0x1030c0 1061056 x4 0x103128 1061160 x5 0x4 4 x6 0x4 4 x7 0xf 15 x8 0x1c 28 x9 0x4 4 x10 0x3c0 960 x11 0x3fffffff 1073741823 x12 0x0 0 x13 0x8000000000 549755813888 x14 0x40000000 1073741824 x15 0x200000 2097152 x16 0x0 0 x17 0x0 0 x18 0x0 0 x19 0x0 0 x20 0x0 0 x21 0x0 0 x22 0x0 0 x23 0x0 0 x24 0x0 0 x25 0x0 0 x26 0x0 0 x27 0x0 0 x28 0x0 0 x29 0x0 0 x30 0x18010 98320

可以发现,此时寄存器和bl后的寄存器完全一致,x30没有发生改变

然后查看第二次ret的寄存器信息

x0 0x0 0 x1 0x0 0 x2 0xffffffffffffffe8 -24 x3 0x1030c0 1061056 x4 0x103128 1061160 x5 0x4 4 x6 0x4 4 x7 0xf 15 x8 0x1c 28 x9 0x4 4 x10 0x3c0 960 x11 0x3fffffff 1073741823 x12 0x0 0 x13 0x8000000000 549755813888 x14 0x40000000 1073741824 x15 0x200000 2097152 x16 0x0 0 x17 0x0 0 x18 0x0 0 x19 0x0 0 x20 0x0 0 x21 0x0 0 x22 0x0 0 x23 0x0 0 x24 0x0 0 x25 0x0 0 x26 0x0 0 x27 0x0 0 x28 0x0 0 x29 0x0 0 x30 0x18010 98320

完全没有变化,而且代码回到了bl后的地址上。详细如下

(gdb) disassemble Dump of assembler code for function kylin_out_of_control: 0x0000000000018008 <+0>: mov x0, xzr 0x000000000001800c <+4>: bl 0x18000 <bsp_vector_table_begin> => 0x0000000000018010 <+8>: mov x0, xzr 0x0000000000018014 <+12>: ret End of assembler dump. (gdb) s 56 ret (gdb) disassemble Dump of assembler code for function kylin_out_of_control: 0x0000000000018008 <+0>: mov x0, xzr 0x000000000001800c <+4>: bl 0x18000 <bsp_vector_table_begin> 0x0000000000018010 <+8>: mov x0, xzr => 0x0000000000018014 <+12>: ret End of assembler dump. (gdb) s 55 mov x0, xzr (gdb) disassemble Dump of assembler code for function kylin_out_of_control: 0x0000000000018008 <+0>: mov x0, xzr 0x000000000001800c <+4>: bl 0x18000 <bsp_vector_table_begin> => 0x0000000000018010 <+8>: mov x0, xzr 0x0000000000018014 <+12>: ret End of assembler dump.

修复

修复这个问题很简单,我们可以利用x19这种aapcs约定临时存储寄存器来保存x30,如下

diff --git a/bsps/aarch64/shared/start/start.S b/bsps/aarch64/shared/start/start.S index 0a89d85035..f284cf2baa 100644 --- a/bsps/aarch64/shared/start/start.S +++ b/bsps/aarch64/shared/start/start.S @@ -43,6 +43,17 @@ .globl _start .section ".bsp_start_text", "ax" +kylin_call: + mov x0, xzr + ret + +kylin_out_of_control: + mov x19, x30 + bl kylin_call + mov x30, x19 + mov x0, xzr + ret + /* Start entry */ _start: @@ -341,6 +352,8 @@ _start: /* Branch to start hook 1 */ bl bsp_start_hook_1 + bl kylin_out_of_control + /* Branch to boot card */ mov x0, #0 bl boot_card

此时运行代码,系统不再跑飞了。