Linux静态库和动态库的Hook方法
起因
最近在做一个node项目,需要接入一个公司用于脏字过滤的C模块(tdirty),该模块的主要功能就是启动时读入一个脏字字典到内存中,提供API读取该字典进行脏字判断过滤,并且可以热更新脏字库,但是该库热更新的部署比较复杂,由于年久失修,开发项目组已经不维护改代码了,热更新的功能已经废弃了,我们不得不自己想办法来实现这个功能,我的的项目上线,热更新脏字库的功能是必须的。通过读改C模块的源码,我们发现了它的人更新脏字库的函数,只是没有将该函数在API中暴露出来,因此我们想到了Hook技术来解决这个问题。
说干就干
读过《程序员的自我修养》,但是Hook C 函数还真是第一次做,先找到tdirty的动态链接库libtdirty.so,使用readelf读取libtdirty.so的符号表,找到要调用的函数在动态链接库中偏移地址,动态链接库是运行时加载的,需要运行的时获取动态库加载的基地址,使用linux提供的系统函数dl_iterate_phdr API获取动态库加载的基地址,然后定义一个函数指针指向基地址+偏移地址的地址,即可调用重新加载脏字库的函数了。通过测试,验证了上述方法的有效性,接下来就将tdirty C 模块做成一个node的C模块,供node代码调用,具体过程就不详述了。 上面通过hook动态链接库的方式调用tdirty的reload脏字库的函数,看似已经把问题完全解决了,但是我们在上线部署的时候还是碰到了问题,因为线上要使用libtdirty.so库,需要部署该库并设置相应的查找路径,这增加了运维成本,于是我们决定将tdirty的静态库libtdirty.a直接编译进node的C模块,这样免去部署tdirty动态库的过程。node的C模块实际上就是编译生成一个.node文件,而这个.node文件实际上就是一个.so动态链接文件,因此我们的目标就是把libtdirty.a和功能代码编译成一个.node文件,这里我们使用popen调用readelf命令找到reload函数在编译成的.node文件中的偏移地址,再使用系统函数dl_iterate_phdr找到.node加载后的基地址,就找到了reload函数的函数指针了。
总结
这段时间的工作多忙于业务逻辑,很久没有做一些技术层面的深入研究了,这个东西搞完感觉收获不少,深入理解了动态链接和静态链接以及编译的过程。