- 1、原创力文档(book118)网站文档一经付费(服务费),不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。。
- 2、本站所有内容均由合作方或网友上传,本站不对文档的完整性、权威性及其观点立场正确性做任何保证或承诺!文档内容仅供研究参考,付费前请自行鉴别。如您付费,意味着您自己接受本站规则且自行承担风险,本站不退款、不进行额外附加服务;查看《如何避免下载的几个坑》。如果您已付费下载过本站文档,您可以点击 这里二次下载。
- 3、如文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“版权申诉”(推荐),也可以打举报电话:400-050-0827(电话支持时间:9:00-18:30)。
- 4、该文档为VIP文档,如果想要下载,成为VIP会员后,下载免费。
- 5、成为VIP后,下载本文档将扣除1次下载权益。下载后,不支持退款、换文档。如有疑问请联系我们。
- 6、成为VIP后,您将拥有八大权益,权益包括:VIP文档下载权益、阅读免打扰、文档格式转换、高级专利检索、专属身份标志、高级客服、多端互通、版权登记。
- 7、VIP文档为合作方或网友上传,每下载1次, 网站将根据用户上传文档的质量评分、类型等,对文档贡献者给予高额补贴、流量扶持。如果你也想贡献VIP文档。上传文档
查看更多
应用程序热补丁(二): 自动生成热补丁在上篇文章中,我们介绍了应用程序热补丁技术的基本原理,同时实现了一个简单的热补丁。但是无法对本地函数打热补丁,同时手动编写热补丁比较麻烦、非常复杂且容易出错。?为了解决这些问题,本文将会介绍一种自动生成应用程序热补丁技术,可以生成应用程序和动态链接库中任意函数的热补丁。自动生成热补丁是利用热补丁生成工具,对现有的源代码和补丁文件进行处理,自动输出热补丁的技术。?我们知道,热补丁的基本原理是新函数替换旧函数,也就是完整的函数替换。补丁中可能包含对一个或多个函数的修改,这些被修改的函数都会被替换掉。上一篇文章介绍过,热补丁首先把新函数放入目标进程的内存中,然后修改旧函数的入口使之跳转到新函数。?自动生成热补丁中较主要的部分是自动生成新函数的二进制代码,也称为是替换代码。在生成替换代码时,主要由以下两部分组成:1.自动生成替换代码;2.解析替换代码中使用的符号。接下来会对以上部分进行详细的介绍。在介绍之前,必须假设系统环境是 Linux X86/X86_64,应用程序是 C 语言编译链接的 ELF 格式可执行文件,并且拥有原始程序的源代码。动机与挑战为了生成替换代码,首先需要知道代码修复之后,哪些函数发生了改变,然后根据这些改变,生成替换代码。使用一种二进制比较的方法,通过比较原始程序和修复后程序的二进制,提取出生成替换代码所需的全部信息。我们选择对目标文件(object file)的级别进行前后比较。这样做的好处是显而易见的:首先,任何源代码的改变都会在目标文件的二进制代码中显示出来。举个例子:头文件 .h 文件中函数的参数如果从int变成了 long long,调用这个函数的 .c 文件由于隐含的类型转换并不发生改变。如果前后代码对比发生在源代码级别,那么预处理之后也不能发现前后 .c 文件发生了改变。?其次,我们不需要处理语言级别的特性,比如 inline 关键字、隐含类型转换、宏等。这些语言相关的特性可能随着语言的发展会愈发复杂,并且我们也不希望热补丁局限于某种语言。C 语言、C++、汇编都是我们希望可以处理的语言。?我们只需要关心代码的二进制表达。在目标文件的级别上,二进制代码的组成信息是较完整的,所以在此进行前后代码比较也是较合理的。所以,生成替换代码的思路是——通过比较前后编译的目标文件,提取出差异部分,组成替换代码。?比较目标文件也面临很多挑战,主要在于如何提取出发生改变的函数。举个例子:由于目标文件中默认所有的函数代码都会放在 .text 段,同时 .text 段中的相对地址跳转都是相对于 .text 段的。也就是说,如果某个函数发生了改变,就算是一行代码的改变,后面的相对地址跳转也很可能会发生改变(由于符号位置发生了改变)。解决办法我们对目标应用程序代码和修复后代码分别编译,逐一比较两次编译产生的若干个目标文件。如图所示:首先,我们编译原始源代码,保留所有中间过程中产生的目标文件;然后,我们对源代码打上修复补丁,再次编译,构建系统(make)一般只会编译改变的源文件,保留新生成的目标文件。如果没有构建系统或者构建系统不如期工作,可以保留所有的目标文件;我们比较新生成的目标文件和对应的原始代码编译出来的目标文件,提取出差异部分,组成替换代码,生成热补丁。在比较过程中,我们希望可以做到在函数级别上进行比较,这样就只需要提取出发生改变的函数;同时也希望生成的替换代码是与地址无关的,因为替换代码可能被加载到任意的内存地址。?GCC 编译器提供了 -ffunciton-sections 和 -fdata-section 的编译选项,作用是把函数和变量放入目标文件中独立的段(每个函数代码都由独立的段来表示)。这样编译出的函数代码都是与地址无关的、更加通用的二进制,从而提取到被加载到内存中任意位置运行的替换代码。?在对前后目标文件进行比较的时候,我们选择在 ELF 段的级别进行比较(也就是函数的级别,因为函数都在自己独立的段中),目标文件是 ELF 文件,遵守通用的标准。通过解析目标文件的 ELF Header,找到段的开头(Section Header)由此找到所有的段。这里我们建议使用一些 ELF 解析库,如 libelf、libbfd 等。?逐一比较前后目标文件中表示代码的段(段的内容就是函数的代码),找到发生改变的函数:首先,比较段的大小,如果大小不同,说明函数发生了改变;?接着,对段的内容进行比较,如果某个字节不同,而且字节不属于重定向的一部分,说明函数发生了改变。如果是重定向,检查重定向计算之后的指令内容是否相同,如果不同,说明函数发生了改变(引用了与之前不同的函数或者变量);如果段的大小和段的内容都没有改变,说明函数没有改变。通过比较前后目标文件,我们可以找到发生了改变的段。
文档评论(0)