libcare Hello World测试例
Posted rtoax
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了libcare Hello World测试例相关的知识,希望对你有一定的参考价值。
本文从网络转载。
- GitHub Libcare:https://github.com/paboldin/libcare
- Libcare HelloWorld测试例:https://gitee.com/openeuler/docs/blob/stable2-20.03_LTS_SP1/docs/zh/docs/Virtualization/LibcarePlus.md
1. LibcarePlus
1.1. 概述
LibcarePlus 是一个用户态进程热补丁框架,可以在不重启进程的情况下对 Linux 系统上运行的目标进程进行热补丁操作。热补丁可以应用于 CVE 漏洞修复,也可以应用于不中断应用服务的紧急 bug 修复。
1.2. 软硬件要求
在 openEuler 上使用 LibcarePlus,需要满足一定的软硬件要求:
-
当前仅支持 x86 体系架构。
-
LibcarePlus 可以在任何支持安装 libunwind、 elfutils 以及 binutils 的 Linux 发行版系统上运行。但目前仅在 openEuler 20.03 LTS SP1 版本进行了验证。
1.3. 注意事项和约束
使用 LibcarePlus,需遵循以下热补丁规范和约束:
- 仅支持对 C 语言编写的代码,不支持汇编语言等。
- 仅支持用户态程序,不支持动态库打补丁。
- 代码文件名必须符合 C 语言标识符命名规范:由字母(A-Z,a-z)、数字 (0-9)、下划线“_”组成;并且首字符不能是数字,但可以是字母或者下划线;不能包含“-”、“$”等特殊符号。
- 不支持增量补丁,即必须卸载原有补丁才能加载第二个补丁。
- 不支持补丁自动加载。
- 不支持补丁查询。
- 被打热补丁的目标函数的出参和入参不能增加和删除。
- 静态函数补丁受限于系统中能找到该函数的符号表。
- 动态库热补丁只能对调用这个动态库的进程打补丁。
- 以下场景不支持热补丁:
- 死循环函数、不退出函数、inline 函数、初始化函数、NMI 中断处理函数
- 替换全局变量
- 修改头文件
- 数据结构成员变化(新增、删除、修改)
- 动态库、静态函数、静态变量
- 修改全局变量、TLS 变量、RCU 变量
- 修改包含 LINE , FILE 等gcc编译宏的 C 文件
- 修改 intel 矢量汇编指令
1.4. 安装 LibcarePlus
1.4.1. 安装软件依赖
LibcarePlus 运行依赖于 libunwind、 elfutils 和 binutils,在配置了 yum 源的 openEuler 系统上,可以参考如下命令安装 LibcarePlus 的依赖软件。
$ sudo yum install -y binutils elfutils elfutils-libelf-devel libunwind-devel
1.4.1.1. 安装 LibcarePlus
$ yum install LibcarePlus -y
查看安装是否成功:
$ libcare-ctl -help
usage: libcare-ctl [options] <cmd> [args]
Options:
-v - verbose mode
-h - this message
Commands:
patch - apply patch to a user-space process
unpatch- unapply patch from a user-space process
info - show info on applied patches
server - listen on a unix socket for commands
1.5. 制作 LibcarePlus 热补丁
1.5.1. 概述
LibcarePlus 支持如下方式制作热补丁:
- 手动制作
- 通过脚本制作
手动制作热补丁的过程繁琐,对于代码量较大的工程,例如QEMU,手动制作热补丁极其困难。建议使用 LibcarePlus 自带脚本一键式地生成热补丁文件。
1.5.1.1. 手动制作
本节以原文件 foo.c 和补丁文件 bar.c 为例,给出手动制作热补丁的指导。
-
准备 C 语言编写的原文件和补丁文件。例如原文件 foo.c 和补丁文件 bar.c 。
点击展开 foo.c
点击展开 bar.c// foo.c #include <stdio.h> #include <time.h> void print_hello(void) { printf("Hello world!\\n"); } int main(void) { while (1) { print_hello(); sleep(1); } }
// bar.c #include <stdio.h> #include <time.h> void print_hello(void) { printf("Hello world %s!\\n", "being patched"); } int main(void) { while (1) { print_hello(); sleep(1); } }
-
编译得到原文件和补丁文件的汇编文件 foo.s 和 bar.s,参考命令如下:
$ gcc -S foo.c $ gcc -S bar.c $ ls bar.c bar.s foo.c foo.s
-
使用 kpatch_gensrc 对比 foo.s 和 bar.s 差异,生成包含原文件的汇编内容和差异内容的 foobar.s,参考命令如下:
$ sed -i 's/bar.c/foo.c/' bar.s $ kpatch_gensrc --os=rhel6 -i foo.s -i bar.s -o foobar.s --force-global
由于 kpatch_gensrc 默认对同一 C 语言原文件进行对比,所以对比前需要使用 sed 命令将补丁汇编文件 bar.s 中的 bar.c 改为原文件名称 foo.c。随后调用 kpatch_gensrc,指定输入文件为 foo.s 与 bar.s,输出文件为 foobar.s。
-
编译原文件的汇编文件 foo.s 和生成的汇编文件 foobar.s,得到可执行文件 foo 和 foobar,参考命令如下:
$ gcc -o foo foo.s $ gcc -o foobar foobar.s -Wl,-q
-
利用 kpatch_strip 去除可执行程序 foo 和 foobar 的相同内容,保留制作热补丁所需要的内容。
$ kpatch_strip --strip foobar foobar.stripped $ kpatch_strip --rel-fixup foo foobar.stripped $ strip --strip-unneeded foobar.stripped $ kpatch_strip --undo-link foo foobar.stripped
上述命令中的各参数含义为:
- –strip 用于去除 foobar 中对于补丁制作无用的 section;
- –rel-fixup 用于修复补丁内所访问的变量以及函数的地址;
- strip --strip-unneeded 用于去除对于热补丁重定位操作无用的符号信息;
- –undo-link 用于将补丁内符号的地址从绝对地址更改为相对地址。
-
制作热补丁文件。
通过以上操作,已经得到了热补丁制作所需的主要内容。接下来需要使用 kpatch_make 将原可执行文件的 Build ID 以及 kpatch_strip 的输出文件 foobar.stripped 作为参数传递给 kpatch_make,最终生成热补丁文件,参考命令如下:
$ str=$(readelf -n foo | grep 'Build ID') $ substr=${str##* } $ kpatch_make -b $substr foobar.stripped -o foo.kpatch $ ls bar.c bar.s foo foobar foobar.s foobar.stripped foo.c foo.kpatch foo.s
至此,就得到了最终的热补丁文件 foo.kpatch。
1.5.1.2. 通过脚本制作
本节介绍如何利用 LibcarePlus 自带的 libcare-patch-make 脚本制作热补丁文件,仍以原文件 foo.c 和补丁文件 bar.c 为例。
-
利用 diff 命令生成 foo.c 和 bar.c 的对比文件,命令如下所示:
$ diff -up foo.c bar.c > foo.patch
foo.patch 文件内容如下所示:
点击展开 foo.patch--- foo.c 2020-12-09 15:39:51.159632075 +0800 +++ bar.c 2020-12-09 15:40:03.818632220 +0800 @@ -1,10 +1,10 @@ -// foo.c +// bar.c #include <stdio.h> #include <time.h> void i_m_being_patched(void) { - printf("i'm unpatched!\\n"); + printf("you patched my %s\\n", "tralala"); } int main(void)
-
编写编译 foo.c 的 MakeFile 文件,具体如下所示:
点击展开 MakeFileall: foo foo: foo.c $(CC) -o $@ $< clean: rm -f foo install: foo mkdir $$DESTDIR || : cp foo $$DESTDIR
-
编写好 MakeFile 之后,直接调用 libcare-patch-make 即可。若 libcare-patch-make 询问选择哪个文件进行打补丁操作,输入原文件名即可,具体如下所示:
$ libcare-patch-make --clean foo.patch rm -f foo BUILDING ORIGINAL CODE /usr/local/bin/libcare-cc -o foo foo.c INSTALLING ORIGINAL OBJECTS INTO /libcareplus/test/lpmake mkdir $DESTDIR || : cp foo $DESTDIR applying foo.patch... can't find file to patch at input line 3 Perhaps you used the wrong -p or --strip option? The text leading up to this was: -------------------------- |--- foo.c 2020-12-10 09:43:04.445375845 +0800 |+++ bar.c 2020-12-10 09:48:36.778379648 +0800 -------------------------- File to patch: foo.c patching file foo.c BUILDING PATCHED CODE /usr/local/bin/libcare-cc -o foo foo.c INSTALLING PATCHED OBJECTS INTO /libcareplus/test/.lpmaketmp/patched mkdir $DESTDIR || : cp foo $DESTDIR MAKING PATCHES Fixing up relocation printf@@GLIBC_2.2.5+fffffffffffffffc Fixing up relocation print_hello+0 patch for /libcareplus/test/lpmake/foo is in /libcareplus/test/patchroot/700297b7bc56a11e1d5a6fb564c2a5bc5b282082.kpatch
执行成功之后,输出显示:热补丁文件位于当前目录的 patchroot 目录下,可执行文件则在 lpmake 目录下。脚本生成的热补丁文件默认是采用 Build ID 作为热补丁文件的文件名。
1.6. 应用 LibcarePlus 热补丁
本节以原文件 foo.c 和补丁文件 bar.c 为例,介绍 LibcarePlus 热补丁的应用指导。
1.6.1. 前期准备
应用 LibcarePlus 热补丁之前,需要提前准备好原可执行程序 foo、以及热补丁文件 foo.kpatch。
1.6.2. 加载热补丁
本节介绍应用 LibcarePlus 热补丁的具体流程。
-
首先在第一个 shell 窗口运行需要打补丁的可执行程序,如下所示:
$ ./lpmake/foo Hello world! Hello world! Hello world!
-
随后在第二个 shell 窗口运行 libcare-ctl 应用热补丁,命令如下所示:
$ libcare-ctl -v patch -p $(pidof foo) ./foo.kpatch
若此时热补丁应用成功,第二个 shell 窗口会有如下输出:
1 patch hunk(s) have been successfully applied to PID '10999'
而第一个 shell 窗口内运行的目标进程则会出现如下输出:
Hello world! Hello world! Hello world being patched! Hello world being patched!
1.6.3. 卸载热补丁
本节介绍卸载 LibcarePlus 热补丁的具体流程。
-
在第二个 shell 窗口执行如下命令:
$ libcare-ctl unpatch -p $(pidof foo)
此时若热补丁卸载成功,第二个 shell 窗口会有如下输出:
1 patch hunk(s) were successfully cancelled from PID '10999'
-
第一个 shell 窗口内运行的目标进程则会出现如下输出:
Hello world being patched! Hello world being patched! Hello world! Hello world!
将热补丁应用于项目
正如上面提到的补丁应用,只是一个简单的补丁,当我们的项目比较复杂的情况下,如何应用热补丁呢。那么就需要用到libcare提供的脚本libcare-patch-make
,它可以根据补丁patch文本文件直接生成热补丁,这是非常有用的。
在脚本libcare-patch-make
中有:
# 修改编译器到 libcare 库
export IS_LIBCARE_CC=y
export CC=$KPATCH_PATH/libcare-cc
export CXX=$CC
这也就说明了,在我们的项目中,编译器应该使用环境变量($(CC)
),而不应该使用编译器名称(如gcc
,icc
),如下:
当我们要使用libcare进行热补丁修复时,应该在Make file中采用下面的编译方法:
$(CC) -c master/main.c
而不是
gcc -c master/main.c
因为,这在脚本libcare-patch-make
运行过程中,不会识别到gcc
。
构建复杂的项目
生成补丁
应用热补丁
以上是关于libcare Hello World测试例的主要内容,如果未能解决你的问题,请参考以下文章
TDD测试驱动开发:Hello, YOU -- 通过简单的 hello_test.go 程序学会编写测试,学习使用常量来优化并根据需要重构(谁说hello world就只是 hello world?)