6

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

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

4

5 回答 5

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. 然后将into的值和eipintoebx的值弹出。$24eax

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

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

end:
    mov %ecx, %eax
    ; `pushl %ebx` is needed here!
    ret
于 2012-09-26T15:17:19.547 回答
2
  1. ecx在没有显式初始化的情况下使用它(我不确定 Linux 是否会保证ecx进程启动时的状态 - 如果不是按照规则,看起来就像0在实践中一样)
  2. 当程序jg end接近程序结束时进行跳转时,返回地址不再在堆栈上,因此ret会将控制权转移到某个垃圾地址。
于 2012-09-26T15:17:02.093 回答
2

您似乎没有正确执行函数调用。您需要阅读并理解 x86 ABI(32 位64 位),尤其是“调用约定”部分。

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

于 2012-09-26T15:33:07.273 回答
2

您的问题是您将返回地址从堆栈中弹出,并且当您分支到结束时您不会恢复它。一个快速的解决方法是在push %ebx那里添加。

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

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

popl %ebx
popl %eax

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

movl 4(%esp), %eax

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

subl $4, %esp

在调用过程后从堆栈中删除参数。如果您希望能够从其他语言调用您的汇编过程,那么正确遵循此约定很重要。

于 2012-09-26T16:21:31.330 回答
1

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

push 和 pop 的次数应该相同。

call 将返回地址压入堆栈。

于 2012-09-26T15:17:37.383 回答