【问题标题】:Segmentation Fault in Assembly Language汇编语言中的分段错误
【发布时间】:2012-09-26 15:06:54
【问题描述】:

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

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

【问题讨论】:

  • 您是否尝试使用调试器单步执行代码以查看发生了什么?
  • AT&T 分支到处理器? (-:
  • tripleee:“AT&T 语法”指的是上面的汇编语法,最初用于 SysV Unix。它将操作数大小作为后缀放在指令中,使用“%”标记寄存器名称,并将目标寄存器放在列表的末尾。它是 linux 上的 GNU 汇编器支持的语法。它与 PC 行业更常用的“Intel”语法完全不同。
  • 如果您要学习英特尔汇编语言,请使用英特尔语法。在一个体面的世界里,你称之为 AT&T 语法的可憎行为永远不会存在。

标签: linux assembly x86


【解决方案1】:

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

_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

【讨论】:

  • 解释的详细程度很好。不错。
  • 正常且更高效的解决方案是将返回地址留在堆栈上,并使用mov 4(%esp), %eax 访问第一个arg。 (或者如果你先推别的东西,它的任何偏移量)。如果您使用 EBP 制作传统的堆栈框架,则无论其他推送如何,您都可以以 8(%ebp) 访问第一个 arg。
【解决方案2】:

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

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

【讨论】:

  • 问题是关于 assembly 语言中的分段错误,而不是关于在 C 中使用 inline assebly 或如何使用包装器进行系统调用。 _start 在这里完全有效,这是一个纯汇编程序。
【解决方案3】:
  1. 您使用 ecx 却没有显式初始化它(我不确定 Linux 是否会在进程启动时保证 ecx 的状态 - 如果不是按照规则,实际上看起来就像是 0
  2. 当程序在接近过程结束时进行jg end 跳转时,返回地址不再在堆栈上,因此ret 会将控制权转移到某个垃圾地址。

【讨论】:

    【解决方案4】:

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

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

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

    popl %ebx
    popl %eax
    

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

    movl 4(%esp), %eax
    

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

    subl $4, %esp
    

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

    【讨论】:

      【解决方案5】:

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

      push 和 pop 的次数应该相同。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多