C語言中調用匯編函數

除了內聯彙編以外,還有一種途徑可以把彙編代碼整合到C/C語言中,C/C語言可以直接調用匯編函數,把輸入值傳遞給函數,然後從函數獲得輸出值。

如果希望彙編語言函數和C/C++程序一起工作,就必須顯示地遵守C樣式的函數格式,也就是說所有輸入變量都必須從堆棧讀取,並且大多數輸入值都返回到EAX嫁寄存器中。在彙編函數代碼中,C樣式函數對於可以修改哪些寄存器和函數必須保留哪些寄存器有著特定的規則。

如果必須保留的寄存器被修改了,那麼必須恢復寄存器的原始值,否則在執行返回發出調用的C程序會出現不可預料的後果。在函數中使用通用寄存器必須謹慎。下表列出寄存器在函數中的狀態:

寄存器
狀態 
eax
用於保存輸出值,但是可能在函數返回之前被修改
ebx
用於指向全局偏移表,必須保留
ecx
在函數中可用
edx
在函數中可用
ebp
C程序使用它作為堆棧基址指針;必須保留 
esp
C程序使用它作為堆棧基址指針;必須保留 
edi
C程序使用它作為堆棧基址指針;必須保留 
esi
C程序使用它作為局部寄存器;必須保留
ST(0)
保存浮點輸出值 
ST(1)-ST(7)
在函數中可用 

被調用的函數必須保留ebx、edi、esi、esp寄存器,這要求在執行函數代碼之前把寄存器的值壓入堆棧,並且在函數準備返回調用程序時把他們彈出堆棧。 1 C函數調用的彙編語言函數的基本模板如下:

.section .text
.type func, @function
func:
pushl %ebp
movl %esp, %ebp
subl $12, %esp #為函數局部變量保留堆棧空間,可以保存3個4字節的數據值。在函數中,相對於ebp寄存器引用局部變量。
pushl %edi
pushl %esi
pushl %ebx

# here is function code

popl %ebx
popl %esi
popl %edi
movl %ebp, %esp
popl %ebp
ret

該模板可以用於C/C++函數使用的所有彙編語言函數。 在把C文件和包含彙編函數的S文件編譯可執行文件時,需要把所有文件包含在編譯器命令行中,如下:

$ gcc -o app cfile.c asmfun.s

最終編譯器輸出單一可執行文件app,不過這樣不會生成中間文件,所以我們還可以使用單獨的彙編語言目標文件編譯程序。如下:

$ as -o asmfunc.o asmfunc.s
$ gcc -o app cfile.c asmfun.o

對於大型程序使用許多文件時,可以編寫Makefile文件來自動完成編譯連接工作。 如下:

# Makefile for linux as

CFLAGS= -Wall -g
ASFLAGS= -gstabs

SRC_BIN=target_bin

SRC_C=$(wildcard *.c)
SRC_S=$(wildcard *.s)

SRC_OBJ=$(SRC_C:.c=.o)
SRC_OBJ+=$(SRC_S:.s=.o)

all: $(SRC_BIN)

$(SRC_BIN): $(SRC_OBJ)
 $(CC) -o $@ $(SRC_OBJ)

clean:
 $(RM) $(SRC_OBJ) $(SRC_BIN) *~
.PHONY:
 all clean

下面是彙編語言函數和使用該函數的C程序示例,函數接收二個整數輸入參數,求它們的和,然後把結果返回到eax寄存器中:

# add.s
.type add, @function
.globl add
add:
    pushl %ebp        # 因為該函數不影響ebx,edi、esi寄存器,所以開頭和結尾沒有包含它們。
    movl %esp, %ebp
    movl 8(%ebp), %eax
    addl 12(%ebp), %eax
    movl %ebp, %esp
    popl %ebp
    ret

c文件:

include <stdio.h>
int add(int, int);
int main(int argc, const char *argv[])
{
    int ret;
    ret = add(7, 11);
    printf("The return value is %d.\n", ret);
    return 0;
}

make及執行結果輸出如下:

$ make
cc -Wall -g   -c -o main.o main.c
as -gstabs  -o add.o add.s
cc -o target_bin main.o add.o
$ ./target_bin
The return value is 18.
$
```
在C函數調用之前,每個輸入值都要存放在堆棧中,在使用超過一個輸入值時,必須按照順序把它們存放進堆棧,C函數後面的參數先進棧,前面的參數後進棧。由於堆棧是向下增長的,所以在彙編函數中位8(%ebp)引用第一個輸入值,位置12(%ebp)引用第二個輸入值。

书籍推荐