阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

应用程序热补丁(一):几行代码构造免重启修复补丁

  • 2019-11-12
  • 本文字数:2507 字

    阅读完需:约 8 分钟

应用程序热补丁(一):几行代码构造免重启修复补丁

热补丁是一种在程序运行时动态修复内存中代码 bug 的技术。在 UCloud,我们使用内核热补丁和应用程序热补丁(也就是进程热补丁)来在线修复核心业务的缺陷和安全漏洞。


多年来我们使用内核热补丁技术避免了系统重启导致的业务中断、保证了操作系统的可用性。属于核心业务组件的应用程序,尤其是单点的虚拟化组件和有状态的应用程序,同样面对高可用的挑战,每次重启都会导致服务受损。


然而业界并没有成熟可靠的应用程序热补丁方案可以参考,原因在于应用程序热补丁比内核热补丁更加困难和复杂。比如内核对外提供完整的模块加载功能,可以直接加载内核模块形式的热补丁。


而应用程序需要通过外部程序通过一系列对其内存和寄存器的复杂操作来注入动态链接库形式的热补丁;应用程序包含多线程;内核在编译时会被限定使用特定的编译方式,而应用程序的编译方式则更加宽泛;内核的二进制相对简单,而应用程序二进制因为需要链接到多种的动态链接库,本身的结构会更复杂。


经过大量的研究和实践,我们针对应用程序如何免重启修复 bug,自研了一套应用程序热补丁技术而且在 UCloud 内部已经经过数十万台次修复验证。后面通过一系列文章分享其技术实现。本文先介绍一种简单实用的应用程序热补丁技术,不少场景下采用该方法编写几行代码即可免修复应用程序 bug。

原理

一般来说,应用程序热补丁的流程是:首先通过编译器将热补丁源码制作成可加载的动态链接库,然后通过加载程序将热补丁加载到目标进程的地址空间,最后在进行一致性模型检查确认安全的情况下,把原始代码替换成新的代码,完成在线修复的过程。


下面我们分别介绍热补丁本身和热补丁加载程序,热补丁本身是因 patch 而异的,加载程序是通用的。假设我们有热补丁加载程序 Loader、目标进程 T、热补丁 patch.so,目标程序的 func 函数替换为 func_v2。

热补丁

  • 编写热补丁源码,编译成动态链接库的格式的热补丁 patch.so,patch.so 中包含 func 和 func_v2 的信息。

  • 热补丁 patch.so 在被加载程序 Loader 加载到目标进程 T 地址空间的过程中,通过 dlsym 调用找到 func 的地址,并将 func 的入口指令改为可写,同时改变为跳转到 func_v2。

  • 至此,所有对 func 的调用都会被重定向到 func_v2,func_v2 执行完毕后返回,程序继续运行。

  • 如图所示:


热补丁加载程序

  • 加载程序 Loader 找到目标进程 T 的 dlopen 函数入口地址。

  • Loader 通过 ptrace 依附到目标进程 T,Loader 将热补丁的名字放入放入目标进程 T 的堆栈,将 IP 寄存器设置为 dlopen 函数的地址。

  • Loader 使目标进程 T 继续运行。因为 IP 寄存器已经设置为 dlopen 函数的入口,目标进程 T 会调用 dlopen 把热补丁加载到 T 的地址空间中。

  • 如图所示:



了解原理之后,我们一步步实现一种简单的基于 x86_64 的热补丁。(对于需要制作热补丁的同学,只需自己编写 patch.so,而 Loader 是通用的。patch.so 编写可以参考下面的例子,往往只需几行代码做相应替换。)

实现

热补丁

  1. 目标进程 T 执行 dlopen 的过程中,通过预先在热补丁(动态链接库)中写入的 constructor 函数,在加载过程中函数 func_v1 替换函数 func。


热补丁加载程序

1.Loader 得到目标进程 T 地址空间中 dlopen 入口地址


  • dlopen 函数有 libdl 提供,并不是所有的程序都加载 libdl,幸运的是,libc 中提供了同样功能的函数 __libc_dlopen_mode,并且接受的参数和 dlopen 相同。除非特殊情况,所有程序都会加载 libc。所以我们需要找到 __libc_dlopen_mode 在目标进程 T 地址空间中的函数入口地址。

  • 我们知道,不同进程中 libc 会被加载到不同的基地址,但是 libc 中函数的地址相对基地址的偏移是不变的。

  • 通过 Loader 和目标进程 T 的 /proc/pid/maps,我们可以得到 libc 在 Loader 和目标进程 T 中加载的基地址。通过 Loader 运行 dlsym,我们可以得到 Loader 中的__libc_dlopen_mode 的地址。这样我们可以得到目标进程 T 中__libc_dlopen_mode 的地址(Loader_dlopen - Loader_libc + T_libc)。



2.Loader 对目标进程 T 使用 ptrace attach,并保存 T 此时的寄存器信息。



3.将目标进程 T 的 %RIP 指向 dlopen,热补丁的名字的字符串放入堆栈,字符串的地址写入 %rdi,RTLD_NOW 的值写入 %rsi 作为 dlopen 的 flag。同时把 dlopen 返回地址设置为非法地址 0x0(把 0x0 压入栈中),这样 Loader 可以捕获目标进程 T 产生的 SIGSEGV 信号进而重新获得 T 的控制权。



  1. Loader 使目标进程 T 继续运行。当 T 执行完 dlopen 之后,T 产生的 SIGSEGV 信号被 Loader 捕获,Loader 重新获得 T 进程的控制权。



5.Loader 通过读取目标进程 T 此时的 %rax 寄存器得到 dlopen 的返回值,恢复 T 最开始的执行状态,最后释放对 T 的控制。



至此对目标进程 T 的热补丁就完成了,下面我们看一个例子。

验证

假设我们运行 target 程序,每隔一秒打印 Hello 一次:



target 程序由 target 本身和 libold.so 组成,分别代码如下:



编译如下:



我们想要修改 print 函数,变成打印“Goodbye”。我们需要编写热补丁 new.c,并添加新函数和 constructor:



编译:



然后通过加载程序对 target 进程打入热补丁 libnew.so,最后我们对 target 程序打入这个热补丁,观察变化:



我们发现热补丁确实改变了 print 函数,最后通过 gdb 进一步确认,可以看出 print 函数的入口被修改成 48 b8 dc b6 15 a9 c1 7f 00 00 ff e0,与我们的预期相符:


总结

我们介绍了应用程序热补丁的基本原理,实践了一个应用程序热补丁 demo。此类热补丁适用于动态替换共享链接库中的可见函数,可以修复例如 glibc “GHOST 漏洞”(CVE-2015-0235)等等,在 UCloud 我们利用热补丁修复了若干缺陷,在用户没有感知的情况下把 bug 快速及时的修复。这些热补丁修复程序里,绝大多数代码是通用的,只需少数几行做特殊替换。


本文介绍的热补丁技术对于适用的场景非常理想,简单可靠,但存在几个缺点:


  • 手写热补丁代码门槛较高,特别是被修复函数的依赖函数链较长时手写热补丁很容易出错;

  • 无法修复局部函数和局部变量(只能修复全局可见的函数和变量)。


后面的文章我们会介绍如一种更加先进的应用程序热补丁技术。


本文转载自公众号 UCloud 技术(ID:ucloud_tech)。


原文链接:


https://mp.weixin.qq.com/s/BJhpWihFh3UcxMrvaZaOnw


2019-11-12 16:183318

评论

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

面试阿里P6,过关斩将直通2面,结果3面找了个架构师来吊打我?

Java spring 程序员 架构

Activity-的-36-大难点,你会几个?,android游戏开发实践指南

android 程序员 移动开发

Activity的任务栈Task以及启动模式与Intent的Flag详解(经典博文,值得收藏

android 程序员 移动开发

Andriod 网络框架 OkHttp 源码解析,总结一下

android 程序员 移动开发

大势已来!!区块链的真正价值是什么

CECBC

GitLab和Rainbond整合实现一体化开发环境

北京好雨科技有限公司

DevOps gitlab #GitLab gitlab hook rainbond

A010-menu资源,看完老板哭着让我留下来

android 程序员 移动开发

知识中台与区块链助力多源可信数据价值释放

CECBC

想要实现元宇宙,需要哪些技术支撑?

行云创新

技术 云原生 vr 云宇宙 虚拟

Android 12体验!新的黑夜模式、影音格式,详解系列文章

android 程序员 移动开发

分布式服务下,消息中间件改造

kafka 架构 RocketMQ RabbitMQ 中间件

Andorid&Kotlin编译速度原理剖析(上),lambda表达式的作用与好处

android 程序员 移动开发

Android - 在线浏览源码,电话短信相关,文本变化监听器

android 程序员 移动开发

Android - 定位方式,火星坐标系统,一键锁屏,字节Android高工面试

android 程序员 移动开发

云原生:详解|K8s技术栈解析, 一文读懂K8s工作原理

息之

架构 容器 云原生 k8s 集群

Android 6,android网络开发技术实战详解

android 程序员 移动开发

ajax分析 学习(1),android0基础

android 程序员 移动开发

Android 8 通知渠道(Notification Channels),美团移动端开发工程师

android 程序员 移动开发

ajax分析 学习,kotlin构造器

android 程序员 移动开发

Androdid Droid Fu介绍,flutter底部弹窗

android 程序员 移动开发

Android 11 Beta 版正式发布!以及众多面向开发者的重磅更新

android 程序员 移动开发

Android 3年外包工面试笔记,有机会还是要去大厂学习提升

android 程序员 移动开发

2021新鲜面经,蚂蚁内部转岗Android面试分享,深夜思考

android 程序员 移动开发

Activity的生命周期,这可能是目前最全的

android 程序员 移动开发

2021疫情下Android技术人的宅家学习进阶指南!花了大价钱大厂内部买来的学习资料,爱看不看

android 程序员 移动开发

巧用 Redis 数据结构实现亿级数据聚合统计

码哥字节

redis 数据统计 NoSQL 数据库 11月日更

Anaconda详细安装及使用教程,阿里P8大牛亲自教你

android 程序员 移动开发

2021牛转乾坤:新花样玩转Android组件化架构实践,15K-50K的详细Android学习指南

android 程序员 移动开发

Andoird中LiveEventBus的使用——用LiveEventBus替代RxBus

android 程序员 移动开发

Androdid Droid Fu介绍(1),万字Android技术类校招面试题汇总

android 程序员 移动开发

读完这些“Java技术栈”,拿下阿里Offer没问题

Java MySQL spring 程序员 JVM

应用程序热补丁(一):几行代码构造免重启修复补丁_文化 & 方法_王超_InfoQ精选文章