你会打补丁吗?
上个月揶揄室友内裤上打了个补丁,他嬉皮笑脸的跟我说,“这有啥,新三年旧三年缝缝补补又三年”。“缝缝补补又三年"这句俏皮话放在程序员的世界里,也再合适不过。代码世界中,打补丁不仅是一种日常操作,更是一种技术艺术。就像给衣服补上一个精致的补丁可以延续它的寿命,代码补丁(patch)则是为了修复漏洞、优化性能或引入新功能,使系统在不断演进中保持稳定和高效。
根据补丁所作用的对象,这里简单地将其分为源码补丁和二进制补丁,前者针对软件的源代码,后者则针对编译后的二进制文件。
0.源码补丁
源码补丁通常以文本形式存在,描述了原始代码和修改后代码之间的差异,常用的格式是diff或unified diff,以.diff或.patch文件保存。由于是文本文件,开发者可以直观地查看修改内容,可读性高,同时便于审查和共享代码更改。
下面介绍几种目前业界主流的源码补丁工具或方案。
Quilt
Quilt是一个用于管理大量补丁的工具,通过跟踪每个补丁所做的更改来管理补丁集。补丁可以被应用、撤销、更新等。其核心理念是,你的主要输出是补丁。
quilt --help
Usage: quilt [--trace[=verbose]] [--quiltrc=XX] command [-h] ...
quilt --version
Commands are:
add fold mail refresh snapshot
annotate fork new remove top
applied graph next rename unapplied
delete grep patches revert upgrade
diff header pop series
edit import previous setup
files init push shell
Global options:
--trace
Runs the command in bash trace mode (-x). For internal debugging.
--quiltrc file
Use the specified configuration file instead of ~/.quiltrc (or
/etc/quilt.quiltrc if ~/.quiltrc does not exist). See the pdf
documentation for details about its possible contents. The
special value "-" causes quilt not to read any configuration
file.
--version
Print the version number and exit immediately.
使用样例
- step0. 创建一个新的补丁,并将其插入到最顶部的补丁之后。
quilt new 0000_add_running_banner.patch - step1. 将一个或多个需要修改或新建的文件添加到最顶部的补丁或指定的补丁中。
quilt add main.c - setp2. 增加或修改代码,完成对漏洞的修复、新功能的引入工作。
- setp3. 刷新指定的补丁,默认情况下刷新最顶部的补丁。补丁文件中实际补丁内容之前的文档将被保留。
quilt refresh
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define DEFAULT_CREDS "test"
// patch1: perform login check
bool perform_login_check(const char* credentials){
bool valid_creds = false;
if(strcmp(credentials, DEFAULT_CREDS) == 0)
valid_creds = true;
done:
return valid_creds;
}
int main(int argc, char** argv){
int ret = -1;
printf("welcome, to use quilt!\r\n"); // patch0: add running banner
if(argc <= 1) goto done;
if(!perform_login_check(argv[1])) goto done;
ret = 0;
done:
return ret;
}
最后总结一些常用的quilt命令,有一些见名知意的命令不再赘述,一些目前还未使用过的命令以后补充:
- series命令,可以输出当前代码中所有已存在补丁
- applied|unapplied命令,显示应用未应用的补丁
- push|pop命令,支持让补丁类似栈的操作
- diff命令,支持显示当前patch中指定文件与之前的差异
- files命令,显示指定patch中修改的文件
- graph命令,输出已应用补丁的依赖关系(
quilt graph --all | dot -Tpng > patchs.png) - top|next|previous命令,显示当前patch栈的关系,前后patch
Diff&Patch
在diff这个工具中,我们虚拟一个更具体的样例。假设你下载了jq-1.0和jq-1.2的源码压缩包,想要使用diff工具比较这两个版本的源码差异。
diff -rNpu jq-jq-1.0 jq-jq-1.2 > jq-jq-1.0.patch
- 参数-r,标识递归的使用diff命令
- -N参数,表示将不存在的文件看作空文件比较
- -p参数,在差异中显示函数名或代码块上下文
- 参数-u,使用unified format显示差异,将原始文件和目标文件的内容集中在一起,包含上下文行,更适合阅读
现在将jq-1.0的源码升级到jq-1.2版本,只需要执行如下命令:
patch -p0 < ./jq-jq-1.0.patch
Git
Git提供了两种补丁方案,一是用git diff生成的UNIX标准补丁.diff文件,二是git format-patch生成的Git专用.patch文件。.diff文件只是记录文件改变的内容,不带有commit记录信息,多个commit可以合并成一个diff文件。.patch文件带有记录文件改变的内容,带有commit记录信息,每个commit对应一个patch文件。
-
创建patch
创建某次(commit_sha1_id)提交之前的number次提交的patch,git format-patch ${commit_sha1_id} -${number},当number为1时表示某个提交的patch。如果打包两次提交之间的所有提交,使用命令git format-patch ${commit_sha1_id}..${commit sha1 id}或者git diff类似形式 -
转发patch
git config命令配置smtp服务器,使用git send-email --to recipient ./${patch}发送patch 也可以使用git format-patch的邮件参数直接发送patch给指定的代码审核或者开发人员。 收到邮件后可以使用mail工具保存邮件(save命令),或者使用mutt工具选中指定邮件通过管道应用补丁。
[sendemail]
smtpServer = mail.diyao.me
smtpUser = admin@diyao.me
smtpPass = secret
smtpEncryption = tls
- 打入patch
使用git apply命令打入patch之前,可以使用–check参数检查能否打入patch
1.二进制补丁
Bsdiff&Bspatch
bsdiff是一种生成二进制文件之间差异补丁的工具,而bspatch是一个应用这些补丁的工具。它们非常适合处理小差异的大文件。适用于小差异,频繁更新,增量发布
继续使用前面例子中的源文件,修改提示的banner信息中的!号改为.号,编译出可执行文件版本v0、v1。可以观察到发布的二进制补丁a.out.v0.patch大小仅197字节。 相比于整个可执行文件,这种差异补丁在网络带宽受限的情况下,能够显著节省传输成本和时间。
gcc -g -O0 main.c -o a.out.v0
gcc -g -O0 main.c -o a.out.v1
bsdiff a.out.v0 a.out.v1 a.out.v0.patch // 制作二进制a.out.v0的patch文件
bspatch a.out.v0 a.out.v1 a.out.v0.patch // 补丁应用
ls -alht
总用量 204K
drwxrwxr-x 2 peter peter 4.0K 12月 23 13:25 .
drwxrwxrwt 51 root root 148K 12月 23 13:25 ..
-rw-rw-r-- 1 peter peter 197 12月 23 13:22 a.out.v0.patch
-rwxrwxr-x 1 peter peter 20K 12月 23 13:21 a.out.v1
-rw-rw-r-- 1 peter peter 597 12月 23 13:21 main.c
-rwxrwxr-x 1 peter peter 20K 12月 23 13:21 a.out.v0
Xdelta
相比与bsdiff在创建补丁时,采用基于字节匹配的算法,适用于较小差异的场景。 基于分块的算法的xdelta,适用于大范围的文件差异,特别是在处理文件变化较大的情况时,有更高的效率和压缩率。适中或大,适合大差异文件,支持多种压缩格式,更复杂场景。
xdelta delta a.out.v0 a.out.v1 a.out.v0.patch
xdelta patch a.out.v0.patch a.out.v0 delta a.out.v1
ls -alht
总用量 204K
drwxrwxr-x 2 peter peter 4.0K 12月 23 13:34 .
-rw-rw-r-- 1 peter peter 235 12月 23 13:34 a.out.v0.patch
drwxrwxrwt 51 root root 148K 12月 23 13:33 ..
-rwxrwxr-x 1 peter peter 20K 12月 23 13:21 a.out.v1
-rw-rw-r-- 1 peter peter 597 12月 23 13:21 main.c
-rwxrwxr-x 1 peter peter 20K 12月 23 13:21 a.out.v0