一、实现目标
用汇编实现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...