装配分段故障恢复

Posted

技术标签:

【中文标题】装配分段故障恢复【英文标题】:Assembly Segmentation Fault Ret 【发布时间】:2021-09-22 14:56:03 【问题描述】:

我正在编写一个调用子例程阶乘的程序,然后计算 5 的阶乘。返回后将打印答案。但是,在我尝试运行我的程序后,我得到了一个分段错误。调试器显示错误来自第 41 行,该行仅包含要从子例程返回的 Ret 语句。是什么导致了这种分段错误?

代码如下:

 1 
 2 .data
 3 .text
 4 
 5 .global main
 6 main:
 7 pushq %rbp        #prologue
 8 mov %rsp, %rbp
 9 
10 push $5               #push value on stack for it to be operated
11 call factorial
12 mov -4(%rbp), %rdi    #move answer into %rdi
13 mov $0, %rax          #no vectors
14 call printf
15 
16 mov %rbp, %rsp    #epilogue
17 popq %rbp
18 
19 mov $0, %rdi      #exit of the program
20 call exit
21 
22 factorial:
23 pushq %rbp      #epilogue
24 mov %rsp, %rbp
25 
26 mov 8(%rbp), %rdi     #move value from line 10 into %rdi
27 mov $1, %rax          # move 1 into %rax, here the result will come
28 
29 factorial_loop_start:
30 cmpb $1, (%rdi)       #compare %rdi to 1 to see if loop ends
31 je factorial_end
32 imul %rdi, %rax       #multiply %rdi with %rax and put the answer in %rax
33 dec %rdi              #decrement %rdi and return to start of loop
34 jmp factorial_loop_start
35 
36 factorial_end:
37 mov %rax, 8(%rbp)     #move the result in %rax to the original stack position of
38                       #the value that needs to be processed
39 mov %rbp, %rsp      #epilogue
40 popq %rbp
41 ret

【问题讨论】:

在调试器中单步验证每条指令是否达到预期效果;跟踪堆栈,以及压入堆栈的内容。 call 会压入一个返回地址,所以在调用指令之后,栈顶应该有一个指向第 12 行的指针;还要注意rsp 值。当ret 执行时,同样的rsp 值应该在那里,同样的第12 行代码指针作为堆栈的顶部项目。首先从一个小测试开始,例如 factorial(1) 并确保它有效,然后使用 factorial(2) 进入循环。 您正在使用cmpb 来测试终端值1,但您可能需要cmplcmpq。此外,由于 5 已按值传递,因此您希望将其直接用作值,而不是用作指针。 【参考方案1】:

如果 ret 崩溃,那么要么是 RSP 错误,要么是它弹出到 RIP 的返回地址是错误的。看看RSPret 执行之前指向的内容,与函数入口处的内容。您的函数会覆盖其返回地址。

由于您已经在使用调试器,请查看 RDI 实际指向的位置。

奇怪的是,您在 64 位代码中在堆栈上传递了一个 arg,看起来您从 32 位代码中复制了帧指针的 8 字节偏移量。

在 64 位代码中,每次推送(包括call 推送的返回地址)为 8 个字节。所以你实际上是从8(%rbp) 加载了你的返回地址,我想。 (然后用mov %rax, 8(%rbp)覆盖它。)

这就是为什么 cmpb $1, (%rdi) 在您通过将 () 放在它周围来取消引用 %rdi 以获取 RDI 指向的内存时不会出错的原因。这显然与您使用裸寄存器作为imul 的源操作数的方式不匹配。

如果 RDI 持有你想要的整数,你的代码就会出错。


mov -4(%rbp), %rdi 也是错误的,加载推送的上半部分并重叠到 main 保存的 RBP。如果您修复factorial 中的偏移量,您可以 让该函数更新其堆栈参数,但这是一个奇怪的复杂自定义调用约定。只需让它像普通函数一样返回 %rax 中的值。

我给你正确的代码并不是为你做功课;我故意只是告诉你用调试器在哪里寻找有问题的东西。

【讨论】:

以上是关于装配分段故障恢复的主要内容,如果未能解决你的问题,请参考以下文章

Consul集群故障恢复

Zabbix的故障与恢复邮件设置

WINDOWS故障恢复控制台的命令操作

数据库恢复技术-第三节:故障种类

rabbitMQ故障恢复与故障转移

故障恢复+并发控制