汇编语言中的分段错误

时间:2012-09-26 15:06:54

标签: linux assembly x86

我正在学习AT& T x86汇编语言。我正在尝试编写一个取整数n的汇编程序,然后返回结果(n / 2 + n / 3 + n / 4)。这就是我所做的:

.text
.global _start
_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax
    mov $0, %esi
    movl $4, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $3, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $2, %ebp
    div %ebp
    addl %eax, %esi
    movl %esi, %eax
    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ret

问题是我遇到了分段错误。问题在哪里?

5 个答案:

答案 0 :(得分:8)

我认为代码在这里失败了:

_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax

所以,你push $24(4个字节)然后是call profit,它会推动eip并跳转到profit。然后,将eip的值转换为ebx,将值$24转换为eax

然后,最后,如果jg end分支到end:,则堆栈将不会保留有效的返回地址,ret将失败。你可能也需要pushl %ebx

    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ; `pushl %ebx` is needed here!
    ret

答案 1 :(得分:2)

  1. 你使用ecx而没有明确地初始化它(我不确定Linux在进程开始时是否会保证ecx的状态 - 如果没有,它看起来像0按规则)
  2. 当程序在过程结束时接受jg end跳转时,返回地址不再在堆栈上,因此ret会将控制转移到某个垃圾地址。

答案 2 :(得分:2)

您似乎没有正确执行函数调用。您需要阅读并理解x86 ABI(32-bit64-bit),特别是“调用约定”部分。

此外,这不是您的直接问题,但是:不要写_start,写main就好像这是一个C程序。当你开始做一些更复杂的事情时,你会希望C库可用,这意味着你必须让它自己初始化。相关地,进行自己的系统调用;调用C库中的包装器。这将使您免受内核界面中的低级更改,确保errno可用,等等。

答案 3 :(得分:2)

您的问题是您从堆栈中弹出退货地址,当您分支到结束时,您不会将其恢复。快速解决方法是在其中添加push %ebx

您应该做的是修改您的程序,以便正确使用调用约定。在Linux中,调用函数应该从堆栈中清除参数,因此您的过程应该将它们保留在原来的位置。

而不是这样做以获取参数然后稍后恢复返回地址

popl %ebx
popl %eax

您应该这样做,并将返回地址和参数保留在

movl 4(%esp), %eax

并删除将返回地址推回堆栈的代码。然后你应该添加

subl $4, %esp

调用过程后从堆栈中删除参数。如果您希望能够从其他语言调用汇编过程,请务必正确遵循此约定。

答案 4 :(得分:1)

在你看到利润之前,我认为你有一个单一的pushl,然后利润做的第一件事是做两个popl指令。我希望这会弹出你压入堆栈的值以及返回代码,这样你的ret就不会工作了。

推送和弹出应该是相同的次数。

调用将返回地址压入堆栈。