产品战略专家梁宁确认出席AICon北京站,分享AI时代下的商业逻辑与产品需求 了解详情
写点什么

应用程序热补丁(三):完整的设计与实现

  • 2017-04-20
  • 本文字数:4126 字

    阅读完需:约 14 分钟

前言

在前两篇文章介绍了应用程序热补丁的关键技术:

  • 修复运行时进程的函数
  • 加载热补丁到进程中
  • 自动生成热补丁等等

这些是组成应用程序热补丁技术框架的关键部分,但是在生产环境中使用热补丁技术还需要考虑适应现代软件的属性、热补丁的安全性、以及在运营中对热补丁的管理等等。

通过介绍 UCloud 应用程序热补丁框架的设计理念和框架中各个组件,我们会解决以下实践中遇到的问题:

  • 热补丁的管理(加载、卸载、激活、回滚热补丁等)
  • 打入热补丁时的安全检查(简单说是什么时候打入热补丁是安全的)
  • 热补丁对多线程的支持等等。

应用程序热补丁的意义介绍 UCloud 应用程序热补丁框架之前,首先介绍一下我们为什么研发和使用热补丁技术。

目前主流的热补丁技术,例如 Ksplice、kpatch、kGraft、以及后来的 livepatch 等都是特别针对 Linux 内核的热补丁技术,可以在不重启系统的情况下,修复内核缺陷。我们一般称为内核热补丁。

UCloud 使用了内核热补丁修复了若干内核问题,避免了重启系统导致的服务中断,保证了操作系统本身的可用性。

在此基础上,我们的下一个目标提高是核心组件中的单点的可用性。例如虚拟化的核心组件 QEMU,虽然作为单点程序运行,但是可用性的要求和内核是一致的。虽然 QEMU 本身支持在线迁移,可以迁移客户的虚拟机到新版本的 QEMU 上,但是迁移本身比较笨重。在迁移过程中会牵扯多个模块,例如网络、存储等,同时迁移时间和虚拟机的 downtime、break time 在运营上也会带来挑战。

对比 QEMU 通过在线迁移升级,使用热补丁修复极快,并且对虚拟机周边环境没有依赖,可以对用户的虚拟机做到静默升级。由于热补丁本身的天然属性,热补丁更适用于代码改动较小的修复(例如安全漏洞),而在线迁移升级比较适用于大版本的升级。

在 UCloud 我们通过热补丁修复了若干次 QEMU 的缺陷和安全漏洞,极大提高了可用性和安全性。因此我们认为对于代码改动较小的问题时,热补丁是一个完美的解决方案。

为什么要自研应用程序热补丁技术?答案也很简单,我们无法找到一个实用并且易用的应用程序热补丁技术,同时也由于我们已经在内核热补丁领域的具有一定的积累,所以决定敢为人先、自研应用程序热补丁技术。

设计理念提出需求

介绍设计理念之前,首先应该提出应用程序热补丁在 UCloud 云平台的需求:

  • 应用程序热补丁的适用场景和内核热补丁是一致的,目的是修复缺陷,而不是增加功能和升级版本。所以应用程序热补丁必须允许函数级别上的修改(不论是本地函数还是全局函数)。
  • 应用程序热补丁必须是安全的,也就是打入热补丁的前后进程的状态必须一致,热补丁只会操作修改的函数,不会影响进程的正常运行。
  • 应用程序热补丁必须支持云平台环境中现代软件具有的特性,比如说 Linux x86_64、多线程等等。
  • 热补丁必须由工具来构建,也必须要由工具来管理(加载卸载等)。
  • 热补丁必须同时支持回滚,同时支持一个进程多次热补丁修复。
  • 降低热补丁运营的难度。

我们针对这些需求,设计出如下的应用程序热补丁框架。

设计思路

  • 支持修复函数级别的、并且可以自动化生成热补丁的工具。
  • 支持多线程、热补丁安全检查、多热补丁状态管理的热补丁加载工具。
  • 运行中的应用程序应记录热补丁的信息和状态,可供外部工具查询。

或者简单来说,我们要做到,拿到源码和 patch 就能通过工具自动生成热补丁,热补丁可以安全的打入运行的多线程应用程序中(不会引起程序的错乱和崩溃),并且支持打入多个热补丁。打入后的热补丁可以被回滚取消,可以查询当前应用程序中热补丁的状态和信息。

框架组件

这个框架的设计我们通过以下组件实现:

Creator

负责通过 patch 和源码自动化生成热补丁。

支持函数级别修复,不论本地函数还是全局函数。

Loader

负责加载热补丁到目标进程中,也负责管理热补丁(类似于客户端程序)。

目标进程支持 Linux x86_64、多线程等。

支持热补丁安全检查。

支持对热补丁状态的操作(例如加载、卸载、激活、回滚查询等等)。

Core Runtime

负责记录多个热补丁的状态和信息,同时提供热补丁通用操作。

作为热补丁模块的通用运行时框架被 Loader 加载到目标进程中。

Loader 对热补丁状态的操作最终由 Core Runtime 在目标进程空间中执行。

热补丁(补丁本身)

负责提供修复后的替换代码和额外信息。

被 Loader 加载到目标进程中,注册自己的信息到 Core Runtime

在激活后使用自身包含的替换代码代替有问题的函数。

组件之间协作如下图所示,Creator 工具根据程序源码和 patch 生成热补丁模块,然后 Loader 将热补丁模块加载到目标进程 Process 的地址空间里,最后热补丁和通用运行时 Core Runtime 一起完成热修复。

实现方法

接下来分别讲各个组件的实现:

Creator

基于对多种内核热补丁技术的理解,我们认为应用程序的热补丁也是可以通过工具自动生成的。虽然相比内核,应用程序的格式更加复杂、编译链接的过程也更不固定,但是自动生成热补丁应该是可行的。

我们知道,编译源代码之后会生成目标文件,将单个或多个目标文件链接可以生成可执行文件。目标文件和可执行文件都是 ELF 格式(Executable and Linkable Format)。ELF 是一种标准且通用的文件格式,Linux 上的可执行文件、目标文件、库、core dump 都是 ELF。

Creator 工具根据 ELF 标准的格式,解析修复前后的目标文件,找到前后不同的函数,提取出差异(包括改变和新增的函数),连同差异本身的属性信息,生成一个动态链接库格式的热补丁。如下图所示:

之前的文章介绍过二进制比较生成热补丁替换代码,这里不再赘述。

Loader

Loader 工具作为一个客户端程序,操作目标进程 Process,包括热补丁的加载、激活、回滚、卸载、查看等。如下所示:

Loader 利用了 ptrace 调用。Loader 通过 ptrace 可以对 Process 的内存、寄存器进行读写,改变 Process 的运行状态,也可以捕获 Process 的信号。这样 Loader 可以停止 Process 的运行,并根据 AMD64 ABI 对内存和寄存器进行修改,使 Process 执行 dlopen 等函数,加载热补丁到 Process 的地址空间中,或者执行其他热补丁的操作。热补丁被加载之后,在 /proc/pid/maps 文件中可以看到。

Loader 如何利用 ptrace 加载热补丁在之前的文章中有详细描述,不再赘述。

Loader 随后会停止 Process 所有的线程,准备激活热补丁,此时 Loader 需要进行一致性检查,也就是查看热补丁的激活对当前的所有线程来讲是否安全。需要检查的是热补丁的需要替换的函数是否在线程当前函数调用栈上,如果在调用栈上,说明现在是不安全的,激活热补丁不能保证一致性。

在停止所有线程的时候,首先需要得到全部的线程信息,可以通过 libthread_db 库与进程中 libc 进行交互得到线程的信息,也可以通过 /proc/pid/tasks/ 目录从内核中直接得到线程的信息。在停止所有线程之后,需要再次获取所有线程信息,查看是否有新增线程,如果有需要停止新增线程。重复以上动作直到没有新线程出现。

Core Runtime

在一个进程的生命周期中,可能需要多次热补丁修复,同时多个热补丁也会使用一些通用的功能,因此需要一个通用的核心模块来提供通用功能,并且记录进程中每个热补丁的信息。这个通用模块作为一个动态链接库,我们叫做 Core Runtime。

Loader 在加载热补丁时,首先需要加载 Core Runtime 到进程的地址空间,对进程而言,Core Runtime 只需要加载一次。

在热补丁被加载到进程的地址空间后,通过构造函数,首先向 Core Runtime 提供自己的信息,注册到 Core Runtime 里,然后将热补丁中差异函数的需要重定向的部分手动计算重定向。在激活热补丁的时候,Core Runtime 会根据热补丁注册时得到的信息,保存旧函数,并把旧函数入口位置替换成跳转到新的函数的机器码,完成热修复。如下所示:

在回滚热补丁的时候,Core Runtime 把旧函数入口位置恢复,完成回滚。

Core Runtime 同时可以管理多个热补丁,以热补丁的名字作为 ID,区分不同的热补丁,记录必要的信息。

如下所示:

热补丁(补丁本身)

这里的热补丁指的是狭义上的作为动态链接库被 Loader 加载到目标进程 Process 中的热补丁。

热补丁由 Creator 产生,包含了替换代码和一些动态信息(比如新旧函数的地址、大小、重定向信息等)。热补丁被加载后,包含的函数和变量就存在于目标进程的地址空间中。热补丁激活以后,所有对老函数的访问,都会重定向到热补丁地址范围内的新函数。

如下所示:

总结

Creator、Loader、Core Runtime、热补丁这四者构成了 UCloud 热补丁技术框架,这四个组件相辅相成,互相协作完成热补丁。

Creator 负责生成热补丁,Loader 负责热补丁的进程外管理(包括加载、卸载、激活、回滚热补丁等),Core Runtime 负责热补丁的进程内管理(记录热补丁、备份旧函数、恢复旧函数等)。虽然是四个组件,但是都必须遵守同一个热补丁规格标准,这样才能共同完成热补丁的工作。

通过这个框架,极大降低了我们制作热补丁、打入热补丁和运营热补丁的难度。

例如,一个 QEMU 安全漏洞修复的流程可以简化为:

  1. Creator 通过 QEMU 源码和漏洞修复 patch 生成热补丁。
  2. 热补丁被 Loader 打入正在运行的应用程序中(加载并且激活)。
  3. (可选)对运行中的应用程序查询热补丁的状态和信息。
  4. (可选)对已经打入的热补丁进行回滚和卸载。

值得指出的是,目前不是全部 patch 都可以自动生成热补丁,原因是极少部分由于程序修改复杂,但是可以通过手动修改 patch 简化代码或者简化逻辑做到可以自动生成热补丁。大约 90% 的 patch 在无需修改的情况下都能自动生成热补丁。

在一些特定场景下,我们通过第一篇文章(《应用程序热补丁(一):几行代码构造免重启修复补丁》)中介绍的热补丁技术手动编写热补丁即可,无需使用复杂的自动生成热补丁技术。

另外,目前 UCloud 应用程序热补丁技术支持 Linux C 语言程序,但对于其他编译型语言解决思路基本一致(例如 C++ 等)。

在 UCloud,我们利用应用程序热补丁修复了若干紧急安全漏洞和缺陷,在关键时刻迅速解决问题,相比于传统的软件升级方式,解决问题更加及时。

希望通过一系列的文章填补目前应用程序热补丁的空白部分,使更多人了解热补丁的技术原理,让热补丁技术给更多人带来更多的价值。


感谢孟夕对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-04-20 19:003200

评论

发布
暂无评论
发现更多内容

宽表为什么横行?

王磊

软件测试/测试开发 | Selenium多浏览器处理

测试人

软件测试 自动化测试 测试开发 selenium web测试

2022Q4手机银行运营亮点:“新版本迭代潮”叠加“个人养老金账户争夺战”

易观分析

金融 银行 经济

2023-02-14:魔物了占领若干据点,这些据点被若干条道路相连接, roads[i] = [x, y] 表示编号 x、y 的两个据点通过一条道路连接。 现在勇者要将按照以下原则将这些据点逐一夺回:

福大大架构师每日一题

算法 rust 福大大

基于文心大模型套件ERNIEKit实现文本匹配算法,模块化方便应用落地

汀丶人工智能

自然语言处理 nlp 2月月更 2月日更 文本匹配算法

明晚 8 点直播!OpenCloudOS 中的海光国密算法分析

OpenCloudOS

Linux

2023年第一季度汽车行业行情预测分析

不脱发的程序猿

汽车电子 2023年第一季汽车行业分析

面试官:如果 MySQL 数据库中的数据丢失,有哪些补救的办法呢?

做梦都在改BUG

Java MySQL 数据库

瑞萨RH850 CS+环境下设置堆和栈空间

不脱发的程序猿

嵌入式 汽车电子 MCU RH850 瑞萨IDE

Java 发展史

kcodez

Java 后端

Java Map操作解锁新姿势

派大星

ChatGPT入门案例|商务智能对话客服(一)| 社区征文

TiAmo

AI ChatGPT

记一次SpringBoot启动优化实践

做梦都在改BUG

Java spring Spring Boot

中国工商银行签约易观千帆,夯实数字基石,助力用户价值增长

易观分析

金融 银行

Three.js 进阶之旅:物理效果-碰撞和声音 💥

dragonir

CSS JavaScript html 前端 three.js

Redis高级数据结构Stream和HyperLogLog

做梦都在改BUG

Java redis stream HyperLogLog

5 步带你入门 GaussDB (DWS) 的 GDS 导入导出

华为云开发者联盟

数据库 华为云 企业号 2 月 PK 榜 华为云开发者联盟

OKR之剑·实战篇06:OKR致胜法宝-氛围&业绩双轮驱动(下)

vivo互联网技术

团队管理 OKR

怎样快速地迁移 MySQL 中的数据?

做梦都在改BUG

Java MySQL 数据库

从实战出发,聊聊缓存数据库一致性

做梦都在改BUG

Java 数据库 缓存 一致性

飞桨框架v2.4 API新升级!全面支持稀疏计算、图学习、语音处理等任务

飞桨PaddlePaddle

paddle API 飞桨

面试官:你来谈一下Synchronized-轻量级锁

做梦都在改BUG

Java synchronized 轻量级锁

入门数据分析师的最强秘籍,都在这4本书里!

博文视点Broadview

面试官:分库分表,真的有必要吗?

做梦都在改BUG

Java 分库分表

分布式事务解决方案

Java 分布式事务 事务

【Redis 故障排查】「连接失败问题排查和解决」带你总体分析CPU及内存的使用率高问题排查指南及方案

洛神灬殇

redis 性能调优 缓存服务 2月日更

智能汽车商业化、产业化演进及投资机会分析

不脱发的程序猿

汽车电子 智能汽车商业化 汽车行业投资机会分析

用这4招优雅的实现Spring Boot 异步线程间数据传递

小小怪下士

Java spring 程序员 springboot

实现一个简单的Database10(译文)

GreatSQL

sqlite myslq greatsql greatsql社区

微服务 SpringBoot 整合 Redis GEO 实现附近商户功能

做梦都在改BUG

Java redis 微服务 Spring Boot

进击中的 Zebec 生态,Web2 与 Web3 世界的连接器

西柚子

应用程序热补丁(三):完整的设计与实现_语言 & 开发_王超_InfoQ精选文章