一、實現目標
用彙編實現C庫函數的調用,即:當給定函數名和參數時,可以實現該函數的調用。
二、問題描述
在實現C解譯器時,解析函數調用語句,例如:strlen( "linxr" ); 那麼,如何去調用strlen函數?
首先,可以得到參數列表arg_listk,然後用如下形式的代碼去實現調用stlen函數:
if( strcmp( token, "strlen" ) == 0 ){
strlen( arg_list[0] );
}
else if( ... ){
}
...
[問題]這樣子,C的庫函數大致有幾百個,那麼這個代碼就會變得沒完沒了了。
三、解決問題
根據上述的問題。我想到了一種方法,那就是用統一的入口來調用C庫函數。函數的原型暫訂為:
int ccall( void *fapp, var_t *arg_list );
fapp: 函數地址
arg_list: 參數列表,var_t是我定義的變數類型,這裡不詳述。
要調用ccall函數,首先必須做兩件事:
1. 必鬚根據函數名得到相應的函數指標。
現在我暫時用一個列表來保持所有C庫函數的指標,可以這麼定義:
struct cpp_t{
char *fname;
char *fapp;
}cpp_table[100] = {
"strlen", (char*)strlen,
"strcpy", (char*)strcpy,
....
}
根據函數名尋找函數指標,可以遍曆這個表實現,但是效率比較低。所有我用了雜湊表實現,這裡不詳述。
2. 必鬚根據函數調用語句,得到一個參數列表。怎麼實現就是文法解析的問題了。:)
四、ccall函數實現
如果你明白C的函數調用規則,那麼這個問題將是相當的簡單。這裡簡單的描述一下:
例如,調用 printf( "%d %d", 1, 2 );
C語言的調用規則(即__cdecl)是這樣的:
push 2
push 1
push 0x123456 ;設0x123456是字串"%d %d"的地址
call 0xA0B1C2 ;設0xA0B1C2是printf的地址
add esp, 12 ;這裡要自己實現棧的平衡
那麼事情就簡單了,我們只要讓C調用彙編就能實現這個ccall函數。
下面的彙編是用masm編譯的。編譯命令:Ml /c /coff /Zi /Fl ccall.asm
[ccall.asm代碼]
title ccall
.386
.model flat,c
.data?
arg_num dword 0
arg_tab dword 100 dup(0)
arg_tye dword 100 dup(0)
fun_ptr dword 0
.code
push_arg_ini proc
mov arg_num, 0
ret
push_arg_ini endp
push_arg proc, arg : sdword, tye : sdword
mov eax, arg_num
mov ebx, offset arg_tab
mov ecx, arg
mov [ebx+eax*4], ecx
mov ebx, offset arg_tye
mov ecx, tye
mov [ebx+eax*4], ecx
add eax, 1
mov arg_num, eax
ret
push_arg endp
push_fun proc, fun : sdword
mov eax,fun
mov fun_ptr, eax
ret
push_fun endp
i_fun_call proc
local count : dword
xor eax, eax
mov ebx, offset arg_tab
mov ecx, offset arg_tye
mov count, 0
.while eax < arg_num
mov edx, [ecx+eax*4]
;浮點型入棧;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.if edx
fld dword ptr [ebx+eax*4]
sub esp,8
fstp qword ptr [esp]
.elseif
push [ebx+eax*4]
.endif
add eax, 1
add count, 4
.endw
mov eax, fun_ptr
call eax
add esp, count
ret
i_fun_call endp
f_fun_call proc
local count : dword
xor eax, eax
mov ebx, offset arg_tab
mov ecx, offset arg_tye
mov count, 0
.while eax < arg_num
mov edx, [ecx+eax*4]
;浮點型入棧;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.if edx
fld dword ptr [ebx+eax*4]
sub esp,8
fstp qword ptr [esp]
.elseif
push [ebx+eax*4]
.endif
add eax, 1
add count, 4
.endw
mov eax, fun_ptr
call eax
add esp, count
ret
f_fun_call endp
end
在C中聲明這些函數:
int __cdecl push_arg_ini();
int __cdecl push_arg( int arg, int tye );
int __cdecl push_fun( int fun );
int __cdecl i_fun_call();
double __cdecl f_fun_call();
這樣,在C中,如果要調用strlen函數,就可以這樣調用函數:
push_arg_ini();
push_arg((int)"linxr",0); //這裡的第二個參數是參數類型,因為浮點型的入棧方式和整形不一樣
push_fun((int)strlen);
i_fun_call(); //f_fun_call用於返回double類型
其實調用任意的C庫函數都可以這麼實現,所有我們就很容易定義實現上述的ccall函數了。
int ccall( void *fapp, var_t *arg_list )
{
int i;
push_arg_ini();
for( i=0; i<arg_num; i++ ){
push_arg( arg_list[i].value, arg_list[i].type );
}
push_fun((int)fapp);
return i_fun_call();
}
同樣可以實現返回double類型的ccall...