我们在参与某些开源项目的过程当中,经常会遇到漏洞之类的问题,需要我们打补丁解决。尤其是 Linux
源码,源码代码量较多,在修改完内核并发布新内核的时候,基本采用补丁的方式进行发布,而不是将整个内核打包发布
我们使用补丁发布有很多好处
注:博主本人并没有给 linux 提过补丁,本文仅为学习提交补丁的记录,希望对大家有帮助,勿喷
如果了解这方面的佬,也欢迎评论区交流
我们以经典的 linux 0.11
版本源码为例,假如说我们当前目录下有两个源码文件夹,未经修改的源码文件夹为 linux-0.11
,以及修改完毕的补丁源码 linux-0.11-new
注:linux 0.11 是早期的 Linux 内核版本,发布于 1991 年,相当古老,仅供学习使用,但是工程上已经没有使用价值,最新版内核可以到下方的源码库寻找
linux 源码库:torvalds/linux: Linux kernel source tree (github.com)
linux 0.11 源码库:karottc/linux-0.11: the source code of linux-0.11 for study linux kernel (github.com)
我们可以拉取两份 linux 0.11
源码,其中一份保持原样,另外一份做出相应修改,并且重命名文件夹为 linux-0.11-new
注:我这里拉取源码时使用参数
--exclude=.git
排除 .git 文件夹,防止后续打补丁干扰。如果 git 版本过低,没有该功能,可以先克隆,再删除文件夹
git clone --exclude=.git [email protected]:karottc/linux-0.11.git
或者是
git clone [email protected]:karottc/linux-0.11.git
git clone [email protected]:karottc/linux-0.11.git ./linux-0.11-new
rm -rf linux-0.11/.git
rm -rf linux-0.11-new/.git
然后使用下面的 diff
命令,输出原始源码和修改之后的源码的文件区别,并重定向输出到 linux.patch
补丁文件中
sudo diff -uprN linux-0.11/ linux-0.11-new/ > linux.patch
注:
.patch
为约定俗称的补丁文件扩展名,尽量遵守规范,除此之外名字可以随便起
参数解释:
-u
: 生成统一格式的差异输出,通常用于生成补丁文件。-p
: 在差异输出中显示更多的上下文信息,以方便阅读。-r
: 对目录进行递归比较,而不仅仅比较单个文件。-N
: 当比较的文件是空文件时,也显示差异信息。我们可以直接在当前目录下执行如下打补丁命令
patch -p0 < linux.patch
注:-p 参数代表忽略哪级文件路径,0 标识去掉全路径,1 标识去掉第一层路径
或者是进入未经修改的 linux 源码根目录下执行如下打补丁命令
patch -p1 < ../linux.patch
注:这里的重定向符号大家注意不要写反,我开始就写反了,然后执行命令后系统就会卡死,不会报错,但是也不会终止
我这里简单作为示例,仅修改了 README.md
文件
举例解释 -p
参数,比如说 patch 文件片段如下
--- old/modules/pcitable Mon Sep 27 11:03:56 1999
+++ new/modules/pcitable Tue Dec 19 20:05:41 2000
如果使用参数 -p0,那就表示从当前目录找一个叫做 old 的文件夹,再在它下面寻找 modules/pcitable 文件来执行 patch 操作
而如果使用参数 -p1,那就表示忽略第一层目录(即不管 old),从当前目录寻找 modules 的文件夹,再在它下面找 pcitable
最后我们可能出现冲突,通常是因为原始文件已经被修改过,这时我们只需要手动解决这些冲突,然后重新执行补丁即可
注:但是建议最好不要手动修改原版代码,不然后续改比较麻烦
我们可以执行如下命令撤销补丁
patch -Rp0 < linux.patch
或者是进入未修改源码根目录,然后执行如下命令
patch -Rp1 < ../linux.patch
上面是对于整个文件夹打补丁,下面将会讲解如何对单个文件打补丁
使用单独文件依次打补丁,可以更加有效的验证补丁正确性,方便后续进行功能测试
比如说我们当前目录有原文件 a.c
,修改之后的文件 b.c
,我们可以使用如下命令生成补丁文件
diff -u a.c b.c > test.patch
# 使用补丁
patch a.c < test.patch
# 撤销补丁
patch -RE < test.patch
当我们在开发自己项目的过程中,可能会只制作大量补丁,管理这些数量庞大的补丁会非常耗费时间
所以 linux 内核开发者 Andrew Morton 开发出 quilt 补丁管理工具,以帮助我们更好的管理补丁
官方相关文档
本章将会简单讲解 quilt 使用流程,更多详细信息可以查看上方官方文档
只要我们在源代码树里使用了 quilt 命令,quilt 就会在源代码树的根目录建立两个特殊目录:patches 和.pc
patches 文件夹下为管理的补丁文件
.pc 文件夹下保存着其内部工作状态
可以使用如下命令安装
sudo apt-get install quilt
新建补丁文件
quilt new xxx.patch
补丁文件关联修改文件,关联后即可对文件进行修改,如要关联多个文件,重复添加然后修改即可
quilt add file
查看补丁是否正确
quilt diff
保存补丁,之后补丁文件会保存在 patches
quilt refresh
git 提供了两种补丁方案
git diff 生成的文件不含有 commit 信息,可以指定文件生成 diff,也可以指定单个 commit, 多个 commit 生成
git format-patch 生成的 .patch 文件 含有 commmit 信息,一个 commit 对应一个 patch 文件
注:本章将会简单讲解 git 补丁流程,如果您需要更详细的文档说明,博主会将在每小章开头附上 git 官网文档链接,供您参考
官方文档:Git - git-diff Documentation (git-scm.com)
制作补丁命令如下
# 单独文件补丁
git diff Test.java > test.patch
# 所有文件补丁
git diff > test.patch
指定 commit id
制作补丁
git diff [commit sha1 id] [commit sha1 id]> test.patch
官方文档:Git - git-format-patch Documentation (git-scm.com)
制作当前分支超前于指定分支提交的补丁
注:命令示例即为制作超前于 master 分支的补丁
git format-patch -M master
制作某次提交以后的补丁
git format-patch [commit id]
某两次提交之间补丁
git format-patch [commit sha1 id]..[commit sha1 id]
检查补丁文件
git apply --stat xxx.patch
检查能否应用成功
git apply --check xxx.patch
使用补丁
git am --signoff < xxx.patch
但是可能会出现我们上面提到的冲突,导致使用补丁失败,出现如下错误
$ git am PATCH
Applying: PACTH DESCRIPTION
error: patch failed: file.c:137
error: file.c: patch does not apply
error: patch failed: Makefile:24
error: libavfilter/Makefile: patch does not apply
Patch failed at 0001 PATCH DESCRIPTION
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".
使用下面命令自动合并不冲突代码,保留冲突部分
执行后会生成后缀为 .rej
的文件,保存没有合并进去的部分的内容,可以参考这个进行冲突解决
解决完冲突后删除后缀为 .rej 的文件,然后提交代码到源码库即可
git apply --reject xxxx.patch