简体   繁体   English

从 C 程序调用 x86 程序集 function 时出现分段错误

[英]Segmentation fault when calling x86 Assembly function from C program

I am writing a C program that calls an x86 Assembly function which adds two numbers.我正在编写一个 C 程序,它调用一个 x86 程序集 function,它添加了两个数字。 Below are the contents of my C program ( CallAssemblyFromC.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;
}

Below is the code of the Assembly function (my idea is to code from scratch the stack frame prologue and epilogue, I have added comments to explain the logic of my code) ( addition.s ):下面是程序集 function 的代码(我的想法是从头开始编写堆栈框架序言和结语,我添加了注释来解释我的代码的逻辑)( 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

I am getting a segmentation fault error, which seems strange considering that I think I am allocating memory in accordance with the x86 calling conventions (ex allocating the correct memory sections to the function's parameters).我收到一个分段错误,考虑到我认为我正在根据 x86 调用约定分配 memory(例如将正确的 memory 部分分配给函数的参数),这似乎很奇怪。 Furthermore, if any of you have a solution, it would be greatly appreciated if you could provide some advice as to how to debug an Assembly program embedded with C (I have been using the GDB debugger but it simply points to the line of the C program where the segmentation fault happens instead of the line in the Assembly program).此外,如果你们有解决方案,如果您能提供一些关于如何调试嵌入 C 的汇编程序的建议,我们将不胜感激(我一直在使用 GDB 调试器,但它只是指向 C 的行发生分段错误的程序而不是汇编程序中的行)。

  1. Your function has no epilogue.您的 function 没有结语。 You need to restore %ebp and pop the stack back to where it was, and then ret .您需要恢复%ebp并将堆栈弹出回到原来的位置,然后ret If that's really missing from your code, then that explains your segfault: the CPU will go on executing whatever garbage happens to be after the end of your code in memory.如果您的代码中确实缺少它,那么这就解释了您的段错误:CPU 将 go 执行 memory 中代码结束后恰好出现的任何垃圾。

  2. You clobber (ie overwrite) the %ebx register which is supposed to be callee-saved.您破坏(即覆盖)应该被调用者保存的%ebx寄存器。 (You mention following the x86 calling conventions, but you seem to have missed that detail.) That would be the cause of your next segfault, after you fixed the first one. (你提到遵循 x86 调用约定,但你似乎错过了那个细节。)在你修复第一个段错误之后,这将是你下一个段错误的原因。 If you use %ebx , you need to save and restore it, eg with push %ebx after your prologue and pop %ebx before your epilogue.如果您使用%ebx ,则需要保存和恢复它,例如在序言之后使用push %ebx并在结语之前使用pop %ebx But in this case it is better to rewrite your code so as not to use it at all;但在这种情况下,最好重写您的代码,以免根本不使用它; see below.见下文。

  3. movzbl loads an 8-bit value from memory and zero-extends it into a 32-bit register. movzbl从 memory 加载一个 8 位值并将其零扩展到一个 32 位寄存器中。 Here the parameters are int so they are already 32 bits, so plain movl is correct.这里的参数是int所以它们已经是 32 位了,所以普通的movl是正确的。 As it stands your function would give incorrect results for any arguments which are negative or larger than 255.就目前而言,您的 function 会为任何负数或大于 255 的 arguments 给出不正确的结果。

  4. You're using an unnecessary number of registers.您正在使用不必要数量的寄存器。 You could move the first operand for the addition directly into %eax rather than putting it into %ebx and adding it to zero.您可以将加法的第一个操作数直接移动到%eax中,而不是将其放入%ebx中并将其加到零。 And on x86 it is not necessary to get both operands into registers before adding;而在 x86 上,在添加之前不需要将两个操作数都放入寄存器; arithmetic instructions have a mem, reg form where one operand can be loaded directly from memory. With this approach we don't need any registers other than %eax itself, and in particular we don't have to worry about %ebx anymore.算术指令有一个mem, reg形式,其中一个操作数可以直接从 memory 加载。使用这种方法,除了%eax本身,我们不需要任何寄存器,特别是我们不必再担心%ebx了。

I would write:我会写:

.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

In fact, you don't need a stack frame for this function at all, though I understand if you want to include it for educational value.事实上,您根本不需要这个 function 的堆栈框架,但我理解您是否想将其包含在教育价值中。 But if you omit it, the function can be reduced to但如果省略它,则 function 可以简化为

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

You are corrupting the stacke here:你在这里破坏堆栈:

movb %al, 4(%ebp)

To return the value, simply put it in eax.要返回值,只需将其放入 eax 即可。 Also why do you need to clear eax?还有为什么你需要清除eax? that's inefficient as you can load the first value directly into eax and then add to it.这是低效的,因为您可以将第一个值直接加载到 eax 中,然后再添加到它。

Also EBX must be saved if you intend to use it, but you don't really need it anyway.如果您打算使用 EBX,则必须保存它,但实际上您并不需要它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM