• 用汇编实现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...

  • 相关阅读:
    [清华集训2016]温暖会指引我们前行——LCT+最大生成树
    BZOJ1415[Noi2005]聪聪和可可——记忆化搜索+期望dp
    NOIP2018游记
    BZOJ4832[Lydsy1704月赛]抵制克苏恩——期望DP
    BZOJ1906树上的蚂蚁&BZOJ3700发展城市——RMQ求LCA+树链的交
    BZOJ2822[AHOI2012]树屋阶梯——卡特兰数+高精度
    BZOJ4001[TJOI2015]概率论——卡特兰数
    BZOJ1805[Ioi2007]Sail船帆——线段树+贪心
    [IOI2018]排座位——线段树
    BZOJ3718[PA2014]Parking——树状数组
  • 原文地址:https://www.cnblogs.com/linxr/p/1961316.html
Copyright © 2020-2023  润新知