替换程序中的特定函数
JavaScript中的函数概念与使用方法 #生活知识# #编程教程#
问题描述解决方案示例代码构建方法小结参考资料问题描述
修改或替换现有程序中的实现函数是一种非常常见的需求,尤其是,在不能得到源码的情况下应该如何解决这一问题?
这里我们将问题描述为我们有main程序代码,会调用文件A中的函数A,函数A会调用文件B中的函数B(两文件中都不一定只有一个函数)。我们需要替换函数B为一个指定的函数C,如何实现?
以下以C/C++语言为例,提供不同条件下的解决方案,所有测试在类UNIX平台下使用GCC编译器完成。
解决方案
当无法获得X源码时,称为没有X,根据条件或许需要X所在的静态库文件来实现替换方案。
在一篇文章【1】中针对Windows,Unix,OS X三个系统平台分别提出了实用的解决方案,这里只关注Unix平台下的方案。文中提供的方法为链接时通过链接器支持的--wrap选项进行函数替换和运行时通过LD_PRELOAD环境变量预先加载修改过的函数生成的动态库。这篇文章提供的方法最大的优点就是在没有A且没有B的条件下完全可用,并且两种方法分别在链接时或运行时作用。
需要注意的是,使用--wrap选项的方法,对于C++使用会有一定的限制,要求函数B必须基于C实现,因此C++中需要使用extern "C"定义替换函数。而且因为C++使用了名字修饰(name mangling)技术用来处理命名空间,在使用g++编译B.c后,函数名B作为符号名会发生变化,可以通过nm <program name>查看原函数修饰后的名字,在此处示例中得到函数B的名字为_Z1Bv,这样还需要正确修改替换函数的函数名以及链接选项--wrap的值【2】。具体内容可见示例中main_g++_wrap的构建过程。
还有一个很好的方法,就是将目标(.o)文件或静态库文件(.a)中函数B对应的符号定义为弱符号【3】,同样可以在没有A且没有B的条件下实现。
很多相关的方法在这个问题【4】下有很好的讨论,还有一些参考资料很有帮助【6】【7】【8】。以下对几种方法进行总结,所有的方法实现均提供了示例程序。
为了确保运行时替换方法中可执行程序main_dynamic能够加载当前目录下动态库,需要设置库路径环境变量。export LD_LIBRARY_PATH = "/your/current/path"
已知条件 附加条件 方法描述 可执行程序 没有A,没有B 链接时替换,需要函数B的静态库 ld支持的--wrap=symbol选项,支持对系统函数进行封装替换【5】加选项后实际调用为带__wrap_前缀的目标函数,通过__real_前缀可以调用原函数
通过-Wl,--wrap=B或-Xlinker --wrap=B参数生成可执行文件
对于C++的代码有不同的要求,详见②的构建过程 ①main_gcc_wrap
②main_g++_wrap 链接时替换,需要函数B的静态库 通过objcopy --weaken-symbol=B可以设置原函数B为弱符号 ③main_objcopy 链接时替换,需要函数B的静态库,文件B中只有函数B 通过ar删去静态库中函数B所在的目标文件,使用目标函数的文件重新编译生成。有限制,由于没有B源码,最好文件B中只有函数B ④main_extract 运行时替换,通过动态库调用函数B 通过设置LD_PRELOAD环境变量以更高优先级调用同名函数 ⑤main_dynamic 有A,没有B 编译时替换,替换函数B为函数C 编译文件A时加上-D"B()=C()"编译选项,替换函数名 ⑥main_C 没有A,有B 编译时替换,将原函数B定义为弱符号 编译文件B时加上-D"B()=__attribute__((weak))B()"编译选项,修改原函数B为弱符号,优先加载同名的目标函数B ⑦main_weaken 链接时替换,需要函数B的静态库 通过ar删去静态库中函数B所在的目标文件,修改函数B,重新编译生成 ⑧main_replace
示例代码
对于各种解决方案,提供了示例代码。
wrap_test.zip
构建方法
通过Makefile可以生成9个可执行程序:
main未替换函数B的原程序; main_gcc_wrap--wrap方法通过gcc编译生成的程序; main_g++_wrap--wrap方法通过g++编译生成的程序; main_objcopy使用objcopy设置弱符号的方法生成的程序; main_extract通过ar替换目标文件生成的程序; main_dynamic通过设置LD_PRELOAD环境变量实现的运行时替换程序,此程序在本地运行需要自行设置LD_PRELOAD环境变量,通过export LD_PRELOAD=""可以恢复环境变量; main_C通过定义宏替换调用函数名生成的程序; main_weaken通过定义函数B为弱符号,替换函数进行覆盖生成的程序; main_replace直接修改函数B,通过ar替换原先的目标文件生成的程序。cc = gcc cxx = g++ gcc_wrap := -Wl,--wrap=B g++_wrap := -Wl,--wrap=_Z1Bv src := main.c A.c B.c cc_obj := main.o A.o B.o cxx_obj := main.oxx A.oxx B.oxx slib := libabc.a dlib := libabc.so target := main main_gcc_wrap main_g++_wrap main_objcopy \ main_extract main_dynamic main_C main_weaken main_replace all : clean ${target} del %.o : %.c${cc} -c $< -o $@ %.oxx : %.c${cxx} -c $< -o $@ # original program main : ${cc_obj}ar cr ${slib} ${cc_obj}${cc} ${slib} -o $@rm -f ${slib} # gcc wrap main_gcc_wrap : C_gcc_wrap.c ${cc_obj}ar cr ${slib} ${cc_obj}${cc} ${gcc_wrap} $< ${slib} -o $@rm -f ${slib} # g++ wrap main_g++_wrap : C_g++_wrap.c ${cxx_obj}ar cr ${slib} ${cxx_obj}${cxx} ${g++_wrap} $< ${slib} -o $@rm -f ${slib} # objcopy main_objcopy : C_objcopy.c ${cc_obj}ar cr ${slib} ${cc_obj}objcopy ${slib} --weaken-symbol=B ${slib}${cc} $< ${slib} -o $@rm -f ${slib} # extract main_extract : C_extract.c ${cc_obj}ar cr ${slib} ${cc_obj}ar d ${slib} B.o${cc} $< ${slib} -o $@rm -f ${slib} # run-time main_dynamic : C_dynamic.c ${src}${cc} ${src} -fPIC -shared -o ${dlib}${cc} $< -fPIC -shared -o libC.so${cc} -L. -labc -o $@ # 在脚本中设置的环境变量不能作用于当前shell # 需要在外部设置环境变量后,运行# export LD_PRELOAD="./libC.so"; \# ./main_dynamic; # macro definition main_C : C.c ${cc_obj}${cc} -c A.c -D"B()=C()"ar cr ${slib} ${cc_obj}${cc} $< ${slib} -o $@${cc} -c A.crm -f ${slib} # weaken main_weaken : C_weaken.c ${cc_obj}${cc} -c B.c -D"B()=__attribute__((weak))B()"ar cr ${slib} ${cc_obj}${cc} $< ${slib} -o $@${cc} -c B.crm -f ${slib} # replace main_replace : C_replace.c ${cc_obj}ar cr ${slib} ${cc_obj}ar d ${slib} B.o${cc} $< ${slib} -o $@rm -f ${slib} del :rm -f *.o *.oxx clean :rm -f *.o *.oxx *.a *.so ${target}
小结
以上多种方法总体上可归类为四种:
通过改名实现替换。实现方法包括链接器支持的--wrap选项,需要特别注意C++项目中符号名的变化;也可通过编译时定义替换函数名; 通过弱符号实现替换。可以使用objcopy设定弱符号属性或在编译中定义将原函数替换为弱符号属性的函数; 通过环境变量实现运行时替换。通过设置LD_PRELOAD优先调用目标函数; 通过修改原文件实现替换。参考资料
Myers, D. S., & Bazinet, A. L. (2004). Intercepting arbitrary functions on Windows, UNIX, and Macintosh OS X platforms. Center for Bioinformatics and Computational Biology, Institute for Advanced Computer Studies, University of Maryland, Tech. Rep. Wrapping C++ functions with GNU linker - Stack Overflow linker - GNU gcc_ld - wrapping a call to symbol with caller and callee defined in the same object file - Stack Overflow Override a function call in C - Stack Overflow ld - Options - binutils docs GCC中通过--wrap选项使用包装函数_网络资源是无限的-CSDN博客 使用ld的wrap选项替换已有库函数_leolinux的专栏-CSDN博客 LD_PRELOAD作用_chen_jianjian的专栏-CSDN博客网址:替换程序中的特定函数 https://klqsh.com/news/view/139673
相关内容
Linux环境下,在不停止程序的情况下,更换动态链接库一文搞懂系列——替换动态库,为什么导致运行进程异常
【C语言】C语言 atoi 函数解析
数据转换工具
老公举与爬山男星婚变内幕?函数当替罪羊?大曲线婚内出轨实锤?
excel判断单元格包含指定内容的函数用=IF(COUNTIF(A1,“*内容*”),“0”,“1”)
亲子活动的邀请函
IMDB 电影评论情感分类数据集
欧阳娜娜AI换脸直播翻车:穿帮17处,品牌方怒发律师函?
提升社交圈子数据处理性能的方法及装置与流程