为什么这个 assemble 代码会出现 Segmentation Fault?

Why this assemble code occur Segmentation Fault?

为什么这段代码给出段错误? (它由 Intel Assemble 语法编码)

结果是这样的

return value : 80






[Dump]
eax : 0x00000012
ebx : 0x00000004
ecx : 0x00000400
edx : 0x4010a980
Segmentation fault (core dumped)

我想我有足够的代码来防止分段错误。 每个函数都有序言和结尾来维护堆栈内存。

但是,出现了Segmentation Fault错误。

[补充说明] 如果我删除代码 'mov ebx, 4',则分段错误已被删除。 (但结果不合我意)

extern printf

segment .data
dumpmsg db      10,10,10,10,10,10,'[Dump]',10,'eax : 0x%0.8x',10,'ebx : 0x%0.8x',10,'ecx : 0x%0.8x',10,'edx : 0x%0.8x',10,00
msg     db      'return value : %d', 10, 00

segment .bss

segment .text
global  main
main:
        push    ebp
        mov     ebp,    esp

        push    20
        call    pig
        add     esp,    4

        push    eax
        push    msg
        call    printf

        call    print_x
        leave
        ret

pig:
        push    ebp
        mov     ebp,    esp

        mov     eax,    [ebp+8]
        mov     ebx,    4
        mul     ebx

        leave
        ret

print_x:
        push    ebp
        mov     ebp,    esp

        push    edx
        push    ecx
        push    ebx
        push    eax
        push    dumpmsg
        call    printf

        leave
        ret

If I remove the code 'mov ebx, 4', then segmentation fault error had been removed. (But the result is not fit to my intention)

EBX 寄存器在几乎所有 x86 调用约定中都是被调用者保存的。这意味着叶函数不能破坏它的值。如果它需要使用EBX,它必须在函数的顶部保存它的原始值并在最后恢复它。这通常通过 PUSH+POP 指令来完成,以将寄存器的值保存在堆栈上。

您的 pig 函数正在使用 mov ebx, 4 指令破坏 EBX,但它 没有 注意保存和恢复其原始价值。这违反了调用约定,这很重要,因为您正在与 C 代码进行互操作。

修复很简单:

pig:
    push    ebp
    mov     ebp,    esp
    push    ebx

    mov     eax,    [ebp+12]
    mov     ebx,    4
    mul     ebx

    pop     ebx
    leave
    ret

或者,只需使用 ECX 寄存器来保存除数,因为这个是调用者保存的(因此可以自由破坏):

pig:
    push    ebp
    mov     ebp,    esp

    mov     eax,    [ebp+12]
    mov     ecx,    4
    mul     ecx

    leave
    ret

Every function has prologue and epilogue to maintain stack memory.

这真的没有必要。 pig 函数根本不使用堆栈,除了检索参数,因此您可以将其简单地写为:

pig:
    mov     eax,    [esp+4]
    mov     ecx,    4
    mul     ecx
    ret

通过直接使用堆栈指针 (ESP),基本指针 (EBP) 的使用可以类似地从其他函数中删除,即使是那些使用堆栈的函数。这与编译器为释放 EBP 作为附加寄存器并保存 prologue/epilogue 膨胀而进行的优化相同。但是如果你更愿意这样做,那么除了性能之外它不会损害任何东西。

重要的是您要遵循调用约定。 EAXEDXECX 寄存器可以自由破坏叶函数内部;其余的都需要显式保存并恢复为原始值。当您在堆栈上分配 space 时,您需要确保释放它。你这样做的方式是灵活的,无论是 sub esp, xxadd esp, xx 还是你的序言......结尾。