C语言 用于x86_64 Linux系统的GCC调用约定



我写了一个最小的函数来测试我是否可以调用/link C和x86_64汇编代码。

这是main.c

#include <stdio.h>
extern int test(int);
int main(int argc, char* argv[])
{
int a = 10;

int b = test(a);
printf("b=%dn", b);
return 0;
}
这是我的test.asm
section .text
global test
test:
mov ebx,2
add eax,ebx
ret

我用这个脚本构建了一个可执行文件

#!/usr/bin/env bash
nasm -f elf64 test.asm -o test.o
gcc -c main.c -o main.o
gcc main.o test.o -o a.out

我写test.asm没有任何真正的线索我在做什么。然后我离开去读了一些书,现在我不明白我的代码是如何工作的,因为我已经说服自己它不应该是这样的。

以下是我认为这行不通的一系列原因:

  • 我不保存或恢复基本指针(设置堆栈帧)。我真的不明白为什么需要这样做,但是我看过的每个例子都是这样做的。Linux系统上gcc编译器的调用约定应该是通过堆栈传递参数。这里我假设使用eaxebx传递参数。我认为那是不对的。
  • ret可能期望从某处获取返回地址。我很确定我没有提供这个。
  • 可能还有其他我不知道的原因。

我所写的产生正确的输出是完全偶然的吗?

我对这个完全陌生。虽然我听说过一些x86概念,但这是我第一次真正尝试编写一些概念。要从哪里开始吗?

编辑:为了将来参考,这里有一个更正的代码
test:
; save old base pointer
push rbp        ; sub rsp, 8; mov [rsp] rbp
mov rbp, rsp    ; mov rbp, rsp ;; rbp = rsp
; initializes new stack frame
add rdi, 2      ; add 2 to the first argument passed to this function
mov rax, rdi    ; return value passed via rax
; did not allocate any local variables, nothing to add to
; stack pointer
; the stack pointer is unchanged
pop rbp         ; restore old base pointer
ret             ; pop the return address off the stack and jump
; call and ret modify or save the rip instruction pointer

我不保存或恢复基指针(设置堆栈帧)。实际上我不明白为什么需要这样做,但是我看过的每个例子都是这样做的。

不需要。试着用-O3编译一些C代码,你会发现它不会发生。

Linux系统上gcc编译器的调用约定应该是通过堆栈传递参数。这里我假设使用eax和ebx传递参数。我认为那是不对的。

那部分只是侥幸才起作用的。程序集碰巧也按照您编译的方式将10放在eax中,但不能保证这种情况总是会发生。再一次,用-O3编译,它就不会了。

ret可能期望从某处获取返回地址。我很确定我没有提供这个。

这部分很好。返回地址由调用方提供。当你的函数被输入时,它总是在堆栈的顶端。

可能还有其他我不知道的原因。

是的,还有一个:ebx是呼叫保存的,但你正在破坏它。如果调用函数(或堆栈中它上面的任何东西)使用了它,那么这会破坏它。

我所写的产生正确的输出是完全偶然的吗?

是的,因为上面的第二和第四点。

作为参考,下面是gcc-O0(默认优化级别)和-O3的C代码生成的程序集的比较:https://godbolt.org/z/7P13fbb1a

最新更新