(注:一、文中实验环境为Ubuntu Linux/GCC;二、具体实现细节未做严谨化处理,只阐述抽象思想;三、这帖子本来是写挺细的,但保存到草稿再打开后就神马都没有了,不知道怎么回事,所以改简述……;四、鉴于实验程序非常复杂庞大,且涉及多文件链接,故在文中给出的代码只是个现编的不完善的示例代码。)
学过C语言的应该都对函数指针有所了解,忘了而且想看下去的请自行翻查课本,自己编码C/C++这些年没少用函数指针,更没少用数组(虽然我更喜欢用指针实现动态数组),但始终没碰过“函数指针数组”,因为一直鲜有这方面需求。今天下午在做一个基于Linux的通信协议相关的实验(C语言实现)时遇到了这样一个问题:手头有多个已经封装好的动态链接库,每个库中又包含多个函数头一致或近似的子函数,且对这些函数的调用有规律可循,即可以通过函数指针数组设计非常便捷灵活的算法。同时问题也就来了:如果用之前的方式手动逐条把各函数的地址赋值给函数指针数组的话,无疑是太累人,因为需要塞进去数组的函数实在是太多太多了。
所以想到了用一个循环搞定它,但各函数编译后的长度相差非常广大而且没有明显规律,关闭栈随机化后的装入地址就已经让人无迹可寻,开启随机化后就更……于是试着在函数头前定义static型变量来做标识,结果无效,做逆向时发现他们全被放到连续的数据段里了,和函数头毛关系都没有,然后改用能定义到代码段里的const,结果发现它们直接被编译器优化成define(巨无语 - -!),最后直接内联汇编向代码段置标识,但还是没能达到预期效果(这个理论上应该可以,可能是我具体实现细节没处理好)。后来灵机一动,铤而走险,用一种狗血到我自己都觉得很不严谨的算法把这个问题解决了,但确实是解决了,无数个函数乖乖归为到指针数组,一切正常。下面列出算法的简单示例:
(从vim里直接粘过来的,一个tab的缩进不知为何就成了这么短……)
#include<stdio.h> int fun1(int a,int b) { return a+b; } int fun2(int a,int b) { return a-b; } int fun3(int a,int b) { return a*b; } int main() { int (*fp[3])(int a,int b); int (*ptr)(int a,int b); int a,b,c,i; int fun_head; char inbuf; fun_head=*(int*)(fun1); printf("fun_head:%d\n",fun_head); ptr=fp[0]=fun1; for(i=1;i<3;i++) { while((*(int*)(++ptr))!=fun_head) { ; } fp[i]=ptr; } printf("input a and b:"); scanf("%d %d",&a,&b); printf("a=%d\tb=%d\n",a,b); for(i=0;i<3;i++) { c=(*fp[i])(a,b); printf("fun[%d]: c=%d\n",i,c); } printf("address of fun1:%d\t%d\n",(int)fp[0],*(int*)(*fp[0])); printf("address of fun2:%d\t%d\n",(int)fp[1],*(int*)(*fp[1])); printf("address of fun3:%d\t%d\n",(int)fp[2],*(int*)(*fp[2])); return 0; }
说明:
32位Ubuntu Linux下的GCC将int型视为4字节(32位),示例代码中只定义了三个子函数:fun1、fun2和fun3。主函数中先将装入内存的fun1函数的含函数头在内的4个字节的信息用fun_head保存,作为对比标识(注:*(int*)(fun1)实际就是先把fun1的地址转化成(int*)型指针,然后再对这个指针进行取值操作)并输出,之后把fun1的指针串联赋给函数指针数组的第一个元素fp[0]和游动指针ptr。之后ptr以1字节为单位递增(实验环境下函数指针程度经sizeof测得为1字节),并在当前指向处取出4字节同标识fun_head做比较,比较结果相同则把当前指针赋入函数指针数组,否则指针继续前移。
理想情况下完成以i为计数器的循环后所有子函数的指针都已依次赋入指针数组fp,这时候通过键盘输入两个数,再通过循环以函数指针数组的形式依次调用这些函数并输出结果来观察前述操作是否成功。最后三行printf输出函数指针数组中三个元素的值(即三个函数的地址)及“函数头”的十进制表示,这个主要是和之前输出的fun_head值做对比。运行结果如下:
(注:这是第一次实验时拍的,只调用了(*fp[0])(),所以输出时只有一个结果,至于读fun_head为什么选int型,我当时的想法是double太长了,怕读取范围超过共同一致的部分,char又太短,怕没有代表性,所以就……总之这个算法还太不严谨(我现在还觉得它很狗血……),而且函数指针数组确实用得不多,只在特殊情况下能派上用场,所以只提供个思路供有兴趣的同学研究学习。)