用汇编实现C库函数的调用

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

posted @ 2011-02-22 14:55  linxr  阅读(4212)  评论(0编辑  收藏  举报