从 C 程序调用 x86 汇编函数时出现段错误

Segmentation fault when calling x86 Assembly function from C program

我正在编写一个调用 x86 汇编函数的 C 程序,该函数将两个数字相加。下面是我的C程序的内容(CallAssemblyFromC.c):

#include <stdio.h>
#include <stdlib.h>

int addition(int a, int b);

int main(void) {
    int sum = addition(3, 4);
    printf("%d", sum);
    return EXIT_SUCCESS;
}

下面是汇编函数的代码(我的想法是从头开始编写栈帧序言和结语,我添加了注释来解释我的代码逻辑)(addition.s):

.text

# Here, we define a function addition
.global addition
addition:
    # Prologue:
    # Push the current EBP (base pointer) to the stack, so that we
    # can reset the EBP to its original state after the function's
    # execution
    push %ebp
    # Move the EBP (base pointer) to the current position of the ESP
    # register
    movl %esp, %ebp

    # Read in the parameters of the addition function
    # addition(a, b)
    #
    # Since we are pushing to the stack, we need to obtain the parameters
    # in reverse order:
    # EBP (return address) | EBP + 4 (return value) | EBP + 8 (b) | EBP + 4 (a)
    #
    # Utilize advanced indexing in order to obtain the parameters, and
    # store them in the CPU's registers
    movzbl 8(%ebp), %ebx
    movzbl 12(%ebp), %ecx

    # Clear the EAX register to store the sum
    xorl %eax, %eax
    # Add the values into the section of memory storing the return value
    addl %ebx, %eax
    addl %ecx, %eax

我收到一个分段错误,考虑到我认为我是根据 x86 调用约定分配内存(e.x。将正确的内存部分分配给函数的参数),这似乎很奇怪。此外,如果你们有任何解决方案,如果您能提供一些关于如何调试嵌入 C 的汇编程序的建议,我们将不胜感激(我一直在使用 GDB 调试器,但它只是指向 C 的行发生分段错误的程序而不是汇编程序中的行。

你正在破坏这里的堆栈:

movb %al, 4(%ebp)

到return的值,简单的放到eax中。还有为什么你需要清除eax?这是低效的,因为您可以将第一个值直接加载到 eax 中,然后再添加到它。

另外EBX要用的话一定要存起来,反正也用不着。

  1. 您的函数没有结尾。您需要恢复 %ebp 并将堆栈弹出回原来的位置,然后 ret。如果您的代码中确实缺少它,那么这就解释了您的段错误:CPU 将继续执行内存中代码结束后碰巧出现的任何垃圾。

  2. 您破坏(即覆盖)本应被调用方保存的 %ebx 寄存器。 (你提到遵循 x86 调用约定,但你似乎错过了那个细节。)在你修复第一个段错误之后,这将是你下一个段错误的原因。如果你使用%ebx,你需要保存和恢复它,例如push %ebx 在序言之后,pop %ebx 在结语之前。但在这种情况下,最好重写您的代码,以免根本不使用它;见下文。

  3. movzbl 从内存中加载一个 8 位值并将其零扩展到 32 位寄存器中。这里的参数是 int 所以它们已经是 32 位了,所以简单的 movl 是正确的。就目前而言,对于任何负数或大于 255 的参数,您的函数都会给出错误的结果。

  4. 您使用了不必要数量的寄存器。您可以将加法的第一个操作数直接移动到 %eax 中,而不是将其放入 %ebx 中并将其加到零。在 x86 上,在相加之前不需要将两个操作数都放入寄存器;算术指令具有 mem, reg 形式,其中一个操作数可以直接从内存中加载。使用这种方法,我们不需要 %eax 本身以外的任何寄存器,特别是我们不必再担心 %ebx

我会写:

.text

# Here, we define a function addition
.global addition
addition:
    # Prologue:
    push %ebp
    movl %esp, %ebp

    # load first argument
    movl 8(%ebp), %eax 
    # add second argument
    addl 12(%ebp), %eax

    # epilogue
    movl %ebp, %esp  # redundant since we haven't touched esp, but will be needed in more complex functions 
    pop %ebp
    ret

事实上,您根本不需要此函数的堆栈框架,但我理解您是否出于教育价值而想要包含它。但是如果你省略它,函数可以简化为

.text
.global addition
addition:
    movl 4(%esp), %eax
    addl 8(%esp), %eax
    ret