ksplice 理解执行过程

2900阅读 0评论2014-12-25 mildrengong
分类:LINUX

ksplice-create用于创建补丁
从命令行获得用户命令,如果输入错误就会打印help信息。
ksplice更换版本有3种途径:
1)patch文件
2)diffext指定新文件的后缀
3)使用git指定新的标记

如果没有指定orig_config_dir(指定config的目录),则默认路径为
"$linuxtree/ksplice"
ksplice还要求config目录下还要有System.map文件。

最好在config目录下再创建一个build目录,用于分离二进制。

如果config目录下存在flags文件,则读入此文件的内容作为kbuild_flags。

ksplice每次制作补丁包都会产生一个随机数kid.

有一些内核System.map的符号地址与运行中的符号地址有一个偏移量。
所以现在通过查找printk的地址,来与运行中内核的printk地址进行比较。
来找出此偏移量。

根据配置变量,组织make命令:
make -rR
如果定义了jobs
-jn
如果定义了verbose level
V=1 否则 -s

make_ksplice变量:
@make -f $datadir/Makefile.ksplice @kbuild_flags
如果定义了build_modules
KSPLICE_BUILD_MODULES=1

如果linuxtree目录下不存在.config,则把config_dir中的.config copy到linuxtree下。

进行revert:
如果一进行过制作补丁,现在要先恢复原始代码。
my @revert_flags = ("KSPLICE_MODE=revert");
revert_orig()
    find出.KSPLICE_presrc的文件,回复原始文件。
执行
@make_ksplice, @revert_flags
执行上执行的命令是:
make -rR -f Makefile.ksplice KSPLICE_MODE=revert

我们在看看Makefile.ksplice文件:
Makefile.ksplice默认目标是__ksplice
__ksplice: $(ksplice-deps) $(ksplice-dirs)
    @:
此目标只是依赖两个dirs,没有具体的执行命令。说明所有的动作都是在依赖中执行的。

如何调用到内核的Makefile的呢?
  1. include $(srctree)/scripts/Makefile.build
  2. ksplice-deps += $(vmlinux-dirs)
  3. ksplice-deps += $(subdir-ym) $(always)
  4. ksplice-deps += __build
  5. ksplice-deps += $(ksplice-revert-deps)

  6. ksplice-revert-dirs = $(vmlinux-alldirs:%=_ksplice_%)
  7. ksplice-dirs += $(ksplice-revert-dirs)
  8. ksplice-dirs += $(subdir-ymn:%=_ksplice_%)


对于ksplice-dirs的命令:
$(ksplice-dirs):
    $(Q)$(MAKE) $(build)=$(@:_ksplice_%=%)
其中
build := -f $(ksplice-makefile) obj
所以命令展开就是:
make -f /usr/local/share/ksplice/Makefile.ksplice obj=arch/x86/crypto
又再次进入makefile.ksplice,这次传入了obj

revert的作用就是把.ksplice_pre的文件执行cmd_ksplice-revert

对revert的cmd
  1. cmd_ksplice-revert = touch -r ksplice-revert-stamp $(@:_ksplice-revert_%=%); \
  2. mv $(@:_ksplice-revert_%=%) $(@:_ksplice-revert_%.KSPLICE_pre=%)

如果MODE 等于revert
include $(srctree)/scripts/Makefile.clean
这是会根据
ksplice-clean-files
把kslipse生成的文件clean掉。

看看实际例子revert都做了些什么:
  1. REVERT net/ipv6/ip6_tunnel.o
  2.   REVERT net/ipv6/sit.o
  3.   SNAP net/ipv6/ip6_tunnel.o
  4.   SNAP net/ipv6/sit.o
  5.   CLEAN net/ipv6/ip6_tunnel.o.KSPLICE_new_code
  6.   CLEAN net/ipv6/sit.o.KSPLICE_new_code
  7.   CLEAN net/ipv6/ip6_tunnel.o.KSPLICE_old_code
  8.   CLEAN net/ipv6/sit.o.KSPLICE_old_code

SNAP
revert完毕后就开始进入snap阶段。
也很简单,snap执行的命令和revert类似,只是KSPLICE_MODE不同。
make_ksplice, KSPLICE_MODE=snap

  1. ifeq ($(KSPLICE_MODE),snap)
  2.     $(obj)/%.o.KSPLICE: $(obj)/%.o FORCE
  3.     $(if $(strip $(wildcard $<.KSPLICE_pre) $(filter $<,$?)), \
  4.         $(call cmd,ksplice-snap))
  5. else
  6.     $(obj)/%.o.KSPLICE: $(obj)/%.o
  7.     $(call cmd,ksplice-diff)
  8. endif

首先.o.KSPLICE依赖.o,生成完.o后,调用ksplice-obj snap来生成.o.KSPLICE
/usr/local/share/ksplice/ksplice-obj.pl snap init/main.o.KSPLICE

snap的工作就是生成一个.o.KSPLICE空文件。empty_diff生成空文件。
所以之前理解错了,.o.KSPLICE并不是把.o做一个备份用的。
应该只是把.o.KSPLICE作为一个标志位,只是为了后续diff阶段,
如果有不同的.o就会把.o.KSPLICE中写入1,最后遍历所有.o.KSPLICE,哪些为1就知道哪些有差异了。
如果是我这样理解,实际上就没有必要snap阶段了。直接diff就可以了,
毕竟diff的时候如果两个文件相同则只会生成empty_diff.
除了标志位的作用,还有就是作为目标驱动。
$(obj)/%.o.KSPLICE: $(obj)/%.o
    $(call cmd,ksplice-diff)
无论snap还是diff都是要创建目标.o.KSPLICE但是动作不一样。
并且snap是FORCE,但diff不是强制的。
所以关键点就在这里,
因为打了patch后,就会重新生成patch对应的.o,此时依赖条件更新了,就会执行diff命令。


那么如果没有备份,那么diff的时候是如何比较的呢?
其实diff比较的是.o.KSPLICE_pre 和 新编译的.o
.o.KSPLICE_pre又是哪来的呢?
从do_diff的实现来看,在diff之前,KSPLICE_pre应该已经生成了。
生成KSPLICE_pre的命令只有
cmd_ksplice-cow = cp -a $@ $@.KSPLICE_pre

revert和snap就差一个mode,为什么一个会编译一个不会编译呢?
就在依赖条件上,snap要依赖.o


接下来编译ksplice 的内核代码部分
cp kmodsrc到一个临时目录。
执行
  1. @make_kmodsrc = (@make, "-C", $kernel_headers_dir, "M=$kmodsrc", "KSPLICE_KID=$kid", "KSPLICE_VERSION=0.9.9", "map_printk=$map_printk")

编译内核模块。
然后再执行make module_install

patch
runval_infile($patchfile, "patch", @patch_opt, "-bz", ".KSPLICE_presrc");
-bz的意思:
-b备份原始文件
-z是用.KSPLICE_presrc为后缀备份原始文件。

打上补丁后,执行
make_ksplice KSPLICE_MODE=diff

看看实际例子中diff的动作:
  COW     net/ipv6/sit.o
  CC [M]  net/ipv6/sit.o
  DIFF    net/ipv6/sit.o
如何执行到COW的呢?
虽然我不知道如何执行的,但是猜测它的作用是在.o执行动作之前,
检测是否需要执行命令,如果需要更新,则执行cow,把.o cp为.o.KSPLICE_pre。
所以ksplice-cow-check的作用就是检测目标是否需要更新。


snap和diff又非常像
    cmd_ksplice-snap = $(ksplice-script) snap $@
    cmd_ksplice-diff = $(ksplice-script) diff $@
查看do_diff代码
cmp .o.KSPLICE_pre 和 新编译的.o,如果相同,那不做什么。
如果要有不同,则调用
  1. runval("$libexecdir/ksplice-objmanip", $obj, "$obj.KSPLICE_new_code", "keep-new-code", "$obj.KSPLICE_pre", $ENV{KSPLICE_KID});
  2. runval("$libexecdir/ksplice-objmanip", $obj_pre, "$obj.KSPLICE_old_code", "keep-old-code");

接下来开始处理模块编译
runstr(qw(find -name *.KSPLICE* ! ( -name *.KSPLICE -empty ) ! -name .*.KSPLICE.cmd -print0))
找出所有*.KSPLICE*非空的文件。
读入文件的内容保存到@modules中。
例子:
在sit.mod.KSPLICE中内容是:
net/ipv6/sit

  1. cmd_ksplice-mod = echo $(<:.o.KSPLICE=) > $@; cp -a $< $(<:.KSPLICE=.KSPLICE_new_code) $(<:.KSPLICE=.KSPLICE_old_code) $(KSPLICE_KMODSRC)/
  2. rule_ksplice-mod = if [ -s $< ]; then $(echo-cmd) $(cmd_$(1)); fi
  3. ksplice-deps += $(ksplice-modnames:%=$(KSPLICE_KMODSRC)/%.mod.KSPLICE)
  4. $(KSPLICE_KMODSRC)/%.mod.KSPLICE: $(obj)/%.o.KSPLICE
  5.     $(Q)$(call rule_ksplice-mod,ksplice-mod)

从对mod的处理可以看出,.mod.KSPLICE直接在KSPLICE_KMODSRC中生成。
这是在diff后执行MOD,首先判断-s $<,看.KSPLICE为非空,表示有不同。然后把$< 写入 $@中。
如net/ipv6/sit.o.KSPLICE 取net/ipv6/sit > sit.mod.KSPLICE
并且把sit.o.KSPLICE,sit.o.KSPLICE_new_code,sit.o.KSPLICE_old_code cp 到KMODSRC目录中。
也就是说在diff阶段,new和old.o就应该生成了。
do_diff中调用objmanip来生成的new和old。

  1. runval(@make_ksplice, "KSPLICE_MODE=modinst", "MODLIB=$tmpdir/modules", "INSTALL_MOD_STRIP=1", "modules=@modulepaths");

在makefile.ksplice中,对modinst的处理
ksplice_modinst:
    $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
这里的Makefile.modinst和Makefile.modpost都是内核script中的Makefile

standalone 的作用是是否把ksplice编译成模块,因为可能kernel中已经自带了ksplice内核模块。

在create中分别调用了两次make_kmodsrc,
第一次编译出ksplice.ko
第二次传入参数KSPLICE_MODULES=@modules 生成new.ko和old.ko
我们来看看kmodsrc中的Makefile.
第一次编译的是KSPLICE_CORE
KSPLICE_CORE = ksplice-$(KSPLICE_KID)
obj-m += $(KSPLICE_CORE).o
实际上最终编译成ksplice-kid.ko,还是依靠obj-m的方法编译的。

第二次编译的时候传入modules,同时KSPLICE_SKIP_CORE=1,表示不编译ksplice.ko
  1. ifneq ($(KSPLICE_MODULES),)
  2.     $(foreach mod,$(KSPLICE_MODULES),$(obj)/new-code-$(target).o): $(obj)/%.o: $(src)/new_code_loader.c FORCE
  3.         $(call if_changed_rule,cc_o_c)
  4.     $(foreach mod,$(KSPLICE_MODULES),$(obj)/old-code-$(target).o): $(obj)/%.o: $(src)/old_code_loader.c FORCE
  5.         $(call if_changed_rule,cc_o_c)
  6. endif

但是一上来先要定义一些变量
  1. $(foreach mod,$(KSPLICE_MODULES),$(eval $(ksplice-mod-vars)))

  2. define ksplice-mod-vars
  3.     obj-m += $(KSPLICE)-new.o $(KSPLICE)-old.o
  4.     $(KSPLICE)-new-objs = $(ksplice-new-code-objs)
  5.     $(KSPLICE)-old-objs = $(ksplice-old-code-objs)
  6. 其中KSPLICE定义为
  7. comma ?= ,
  8. name-fix ?= $(subst $(comma),_,$(subst -,_,$(1)))
  9. target = $(call name-fix,$(mod))
  10. KSPLICE_MID = $(KSPLICE_KID)_$(target)
  11. KSPLICE = ksplice-$(KSPLICE_MID)
例如:
target = sit
KSPLICE = ksplice-mbel2vr2_sit

所以我们从ojb-m的值能看出最终需要生成new.ko和old.ko。
以new为例:
  1. $(KSPLICE)-new-objs = $(ksplice-new-code-objs)
  2. ksplice-new-code-objs = new-code-$(target).o collect-new-code-$(mod).o

new.ko由new-code-mod.o和collect-new-code-$(mod).o组成。

new-code-mod.o 的命令:
  1. $(foreach mod,$(KSPLICE_MODULES),$(obj)/new-code-$(target).o): $(obj)/%.o: $(src)/new_code_loader.c FORCE
  2.                                  $(call if_changed_rule,cc_o_c)

collect-new-code-$(mod).o的命令:
  1. $(obj)/collect-new-code-%.o: $(obj)/%.o.KSPLICE_new_code $(obj)/ksplice.lds FORCE
  2.     $(call if_changed,ksplice-collect)

  3. cmd_ksplice-collect = \
  4.     $(ksplice-script) finalize $< $<.final $* && \
  5.     $(LD) --script=$(obj)/ksplice.lds -r -o $@ $<.final

collect的命令是调用 do_finalize 生成mod.final, 再结合ksplice.lds 生成collect-new-code-mod.o

实例看看两次模块编译时的动作:
 
  1. CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/new-code-ip6_tunnel.o
  2. LDS /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice.lds
  3. COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-new-code-ip6_tunnel.o
  4. CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/old-code-ip6_tunnel.o
  5. COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-old-code-ip6_tunnel.o
  6. CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/new-code-sit.o
  7. COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-new-code-sit.o
  8. CC [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/old-code-sit.o
  9. COLLECT /tmp/ksplice-tmp-NdqKBs/kmodsrc/collect-old-code-sit.o
  10. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-new.o
  11. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-old.o
  12. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-new.o
  13. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-old.o
  14. CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-new.mod.o
  15. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-new.ko
  16. CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-old.mod.o
  17. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_ip6_tunnel-old.ko
  18. CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-new.mod.o
  19. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-new.ko
  20. CC /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-old.mod.o
  21. LD [M] /tmp/ksplice-tmp-NdqKBs/kmodsrc/ksplice-y7ifi61j_sit-old.ko

现在代码大体逻辑还算清楚了,但如何做到不用编译内核只做内核模块的编译呢?
ksplice制作内核补丁使用的是内核kbuild的makefile,但是ncore使用的是自己的makefile。
所以我必须找找制作内核模块补丁执行的命令,并且不能使用kbuild的方法。
我大体猜测,流程是:
先编译一遍原始代码
在编译patch后的代码
比较两次的结果,直接做出patch。
现在不明白的是,比较的是.o文件,难道最后生成的就是根据这些.o的差异生成的补丁吗?
没有必要生成对应的.ko吗?
如果是这样那应该就与是不是模块没有关系了,可能多个模块的patch都会生成一个hotupdate。
这个过程还需要进一步研究。
在lfs的基础上,最好手动执行命令,来完成hotupdate的制作。
目前来说System.map还是非常重要的,不光是用于找map_printk,直接定位的时候都需要用到System.map
而且如果多个内核发生变化,就会分别生成对应的.ko。

我还有疑问了,如果模块B中调用了模块A的函数, 那么如何定位地址呢?
通过实践,此种升级可以成功。

apply的过程:
先insmod ksplice.ko
然后insmod module_new.ko
然后insmod module_old.ko
copy patch 到/var/runn下
升级成功
然后rmmod module_old.ko


好像只要有ld的地方就会调用combine
再来看看do_combine函数,有两个参数输入,
第一个是链接结果的文件名,
第二个参数是组成链接结果的所有的.o文件列表。

ksplice-objs的作用
把.o后边添加.KSPLICE


用到的perl知识:
1) perl中=~的意思是模式匹配,所以
my ($obj) = $out =~ /^(.*)\.KSPLICE$/ or die; 的意思是
注意()中的内容是捕获的内容,所以就是匹配*.KSPLICE,并且把文件的名字保存给obj。

2)unlink 删除文件

3)glob 返回通配符匹配的文件名列表。

4)next 相当于C语言中的continue

5)unless 相当于not if,就是当条件为false时执行block。

6)open fd '>' "filename"
打开一个文件用于写入,文件句柄是fd

7)文件test
-e 文件或目录存在
-z 文件为0
-s 文件或目录存在并且size不为0
-d 是目录
-f 是文件
-l 是链接

上一篇:一、Netfilter简介
下一篇:Xen半虚拟化下,IO共享环、事件通道、授权表之间的联系和区别