从 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要用的话一定要存起来,反正也用不着。
您的函数没有结尾。您需要恢复 %ebp
并将堆栈弹出回原来的位置,然后 ret
。如果您的代码中确实缺少它,那么这就解释了您的段错误:CPU 将继续执行内存中代码结束后碰巧出现的任何垃圾。
您破坏(即覆盖)本应被调用方保存的 %ebx
寄存器。 (你提到遵循 x86 调用约定,但你似乎错过了那个细节。)在你修复第一个段错误之后,这将是你下一个段错误的原因。如果你使用%ebx
,你需要保存和恢复它,例如push %ebx
在序言之后,pop %ebx
在结语之前。但在这种情况下,最好重写您的代码,以免根本不使用它;见下文。
movzbl
从内存中加载一个 8 位值并将其零扩展到 32 位寄存器中。这里的参数是 int
所以它们已经是 32 位了,所以简单的 movl
是正确的。就目前而言,对于任何负数或大于 255 的参数,您的函数都会给出错误的结果。
您使用了不必要数量的寄存器。您可以将加法的第一个操作数直接移动到 %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
我正在编写一个调用 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要用的话一定要存起来,反正也用不着。
您的函数没有结尾。您需要恢复
%ebp
并将堆栈弹出回原来的位置,然后ret
。如果您的代码中确实缺少它,那么这就解释了您的段错误:CPU 将继续执行内存中代码结束后碰巧出现的任何垃圾。您破坏(即覆盖)本应被调用方保存的
%ebx
寄存器。 (你提到遵循 x86 调用约定,但你似乎错过了那个细节。)在你修复第一个段错误之后,这将是你下一个段错误的原因。如果你使用%ebx
,你需要保存和恢复它,例如push %ebx
在序言之后,pop %ebx
在结语之前。但在这种情况下,最好重写您的代码,以免根本不使用它;见下文。movzbl
从内存中加载一个 8 位值并将其零扩展到 32 位寄存器中。这里的参数是int
所以它们已经是 32 位了,所以简单的movl
是正确的。就目前而言,对于任何负数或大于 255 的参数,您的函数都会给出错误的结果。您使用了不必要数量的寄存器。您可以将加法的第一个操作数直接移动到
%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