赞
踩
C语言编译后,在可执行文件中会有 函数名信息
。如果想要动态调用一个C函数,首先需要 根据函数名找到这个函数地址 ,然后根据函数地址进行调用。
动态链接器已经提供一个 API:dlsym()
,可以通过函数名字拿到函数地址:
void test() {
printf("testFunc");
}
int main() {
void (*funcPointer)() = dlsym(RTLD_DEFAULT, "test");
funcPointer();
return 0;
}
从上面代码中可以看出,test方法是没有返回值和参数的。所以funcPointer只能在指向参数和返回值都是空的函数时才能正确调用到。
对于有返回值和有参数的C函数,需要指明参数和返回值类型才能使用。
int testFunc(int n, int m) { printf("testFunc"); return 1; } int main() { // ① 表示正确定义了函数参数/返回值类型的函数指针 int (*funcPointer)(int, int) = dlsym(RTLD_DEFAULT, "testFunc"); funcPointer(1, 2); // ② 表示没有正确定义参数/返回值类型的函数指针 void (*funcPointer)() = dlsym(RTLD_DEFAULT, "testFunc"); funcPointer(1, 2); //error return 0; }
不同的函数都有不同的参数和返回值类型,也就没办法通过一个万能的函数指针去支持所有函数的动态调用,必须要让函数的参数/返回值类型都对应上才能调用。因为函数的调用方和被调用方会遵循一种约定:调用惯例(Calling Convention)。
当然我们也不是没有应对方法,我们可以将所有参数设置为void* 指针,然后强行
将所有类型参数转化为void* 调用,就像这样:
typedef uint64_t (*easycall_func_t)(void); typedef uint64_t (*easycall_func_null_t)(void); typedef uint64_t (*easycall_func_args_t)(void*,...); int easycall_call_function(easycall_func_info_t easycall_func_info) { int i=0 ; uint64_t fun_ret; easycall_func_t func=NULL,temp; void *arg[10]; easycall_func_null_t temp_func0; easycall_func_args_t temp_func1; for(i = 0 ; i< g_easycall_lib_info_table.lib_index ; i++ ) { temp=easycall_find_symbols(g_easycall_lib_info_table.lib_table[i],easycall_func_info.name); if(temp!=NULL)func = temp; } if(func==NULL){ ECALL_PRINT_LN("don't find such func"); return -1; } for(i=0 ; i<easycall_func_info.pera_nums;i++) { if(easycall_func_info.pera_type[i] == EASYCALL_PERA_INTER) arg[i] = (void*)easycall_func_info.pera[i].arg_inter; else{ arg[i] = (void*)easycall_func_info.pera[i].arg_str; } } switch (easycall_func_info.pera_nums) { case 0: temp_func0=(easycall_func_null_t)func; fun_ret=temp_func0(); break; case 1: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0]); break; case 2: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1]); break; case 3: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2]); break; case 4: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2],arg[3]); break; case 5: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2],arg[3],arg[4]); break; case 6: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5]); break; case 7: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6]); break; case 8: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7]); break; case 9: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7],arg[8]); break; case 10: temp_func1=(easycall_func_args_t)func; fun_ret=temp_func1(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7],arg[8],arg[9]); break; default: ECALL_PRINT_LN("too much perameteas"); return -1; } ECALL_PRINT_LN("%s return: 0x%lx(%ld)",easycall_func_info.name,(uint64_t)fun_ret,(uint64_t)fun_ret); return 0; }
但是检测的调用也是会出现很多问题的。
高级编程语言的函数在调用时,需要约定好参数的传递顺序、传递方式,栈维护的方式,名字修饰。这种函数调用者和被调用者对函数如何调用的约定,就叫作调用惯例(Calling Convention)。高级语言编译时,会生成遵循调用惯例的汇编代码。
参数传递方式
调用函数时,参数可以选择使用栈或者使用寄存器进行传递
参数传递顺序
参数压栈的顺序可以从左到右也可以从右到左
栈维护方式
函数调用后参数从栈弹出可以由调用方完成,也可以由被调用方完成
在日常工作中,比较少接触到这个概念。因为编译器已经帮我们完成了这一工作,我们只需要遵循正确的语法规则即可,编译器会根据不同的架构生成对应的汇编代码,从而确保函数调用约定的正确性。
函数调用者和被调用者需要遵循这同一套约定,上述②,就是函数本身遵循了这个约定,而调用者没有遵守,导致调用出错。
以上面例子简单分析下,如果按①那样正确的定义方式定义funcPointer,然后调用它,这里编译成汇编后,在调用处会有相应指令把参数 n,m 的值 1 和 2 入栈(这里是举例),然后跳过去 testFunc()函数实体执行,这个函数执行时,按约定它知道n,m两个参数值已经在栈上,就可以取出来使用了:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82a7oLXS-1670083113664)(image/C语言runtime调用技术/1667789573132.png)]
而如果按②那样定义,编译后这里不会把参数 n,m 的值 1 和 2 入栈,因为这里编译器把它当成了没有参数和没有返回值的函数,也就不需要进行参数入栈的操作,然后在 testFunc()函数实体里按约定去栈上取参数时就会发现栈上本来应该存参数 n 和 m 的地方并没有数据,或者是其他错误的数据,导致调用出错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GxsQRlIO-1670083113664)(image/C语言runtime调用技术/1667789630625.png)]
也就是说如果需要动态调用任意 C 函数,有一种笨方案就是事先准备好任意参数类型/参数个数/返回值类型 排列组合的 C 函数指针,让最终的汇编把所有情况都准备好,最后调用时通过 switch 去找到正确的那个去执行就可以了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。