Kbuild学习
Kbuild的学习主要从三部分学习的
- 驱动模块的构建
- vmlinux的构建
- bzImage的构建
关于Kbuild,可以参考Kbuild官方文档
关于Makefile构建linux的大体框架,见Makefile框架
须知
下面介绍一些须知
编译调试
debug流程是可以通过verbose开启,如下
1
make V=1 bzImage -j12 2>&1 | tee build.log
更多见make help
ld了解
- -m emulation模拟仿真器链接 -V 可以查看所以支持的仿真器
- -T 指定链接脚本
- –script=scriptfile 使用该脚本代替ld默认链接脚本
- -o output 指定链接结果
- –whole-archive file 将归档文件中的路径的库的符号都链接起来,后面需要跟–no-whole-archive代表界限
objcopy了解
object file(.o文件) copy和转换工具,使用方式为object input output
- -j section 只处理指定的section
- -R remove 移除指定的section
- -O指定输出格式
- -S strip-all 没有debug section,因此没有symbol信息
驱动模块的构建
驱动模块的构建在likely学习扩展部分:驱动模块的构建
vmlinux的构建
背景
在研究模块初始化过程中,发现__initcall__kmod_trace__397_9768_tracer_init_tracefs5所在的section在kernel/trace/trace.o和vmlinux中是不同的
section从.initcall5.init变动到.init.data,因此确定在object file整合到vmlinux时发生了section的整合
1 | ➜ linux git:(master) ✗ objdump -t kernel/trace/trace.o | grep -n "tracer_init_tracefs" |
vmlinux构建过程
Makefile研究编译构建vmlinux的规则,下面是一个总体的框架
graph LR
Makefile --> Kbuild.include -->指明kbuild规则为build=scripts/Makefile.build
Kbuild.include -->定义编译规则函数
定义编译规则函数-->newer-prereqss是否需要更新即依赖比目标新
定义编译规则函数-->cmd_command获取
cmd_command获取-->if_changed_dep
cmd_command获取-->if_changed_rule
Makefile --> arch/x86/Makefile --> head-y -->需要首先链接到vmlinux非built-in.a部分
Makefile --> Kbuild:arch/x86/kernel/Makefile --> extra-y是一些构建vmlinux部分非built-in.a的部分
Makefile -->目标vmlinux-->vmlinux.lds-->descend
目标vmlinux -->KBUILD_VMLINUX_OBJS-->descend
目标vmlinux -->KBUILD_VMLINUX_LIBS-->descend
descend-->Makefile.build-->targets-for-builtin-->通过.lds.S生成.lds -->控制section顺序vmlinux的编译规则如下:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE |
重点是vmlinux-deps
1 | vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS) |
vmlinux.lds
vmlinux.lds依赖descend,descend的行为调用scripts/Makefile.build去编译,默认目标为__build
1 | __build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \ |
依赖为$(targets-for-builtin),其实理解就是内建目标
1 | targets-for-builtin := $(extra-y) |
通过arch/x86/Kbuild指定编译的arch/x86/kernel/Makefile中有如下
1 | extra-y += vmlinux.lds |
因此vmlinux.lds作为依赖先要满足,规则为
1 | # Linker scripts preprocessor (.lds.S -> .lds) |
关于cpp_lds_S和cmd_cpp_lds_S的关系,见cmd_
其实也可以从代码中看出来
1 | cmd = @set -e; $(echo-cmd) $($(quiet)redirect) $(cmd_$(1)) |
如果存在依赖比目标新的情况(即$?)等,那么默认执行$(cmd_and_fixdep)命令
1 | newer-prereqs = $(filter-out $(PHONY),$?) |
因此最后的执行命令为cmd_cpp_lds_S,实际展开为如下
1 | "gcc -E -Wp,-MMD,arch/x86/kernel/.vmlinux.lds.d -nostdinc -I./arch/x86/include -I./arch/x86/include/generated -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/ |
KBUILD_VMLINUX_OBJS
KBUILD_VMLINUX_OBJS为4次增加,下面将分4次结合kbuild/makefiles.html来分析
1 | KBUILD_VMLINUX_OBJS := $(head-y) $(patsubst %/,%/built-in.a, $(core-y)) |
part 1
第一部分$(head-y) $(patsubst %/,%/built-in.a, $(core-y))打印为如下
1 | arch/x86/kernel/head_64.o arch/x86/kernel/head64.o arch/x86/kernel/ebda.o arch/x86/kernel/platform-quirks.o init/built-in.a usr/built-in.a arch/x86/built-in.a kernel/built-in.a certs/built-in.a mm/built-in.a fs/built-in.a ipc/built-in.a security/built-in.a crypto/built-in.a block/built-in.a" |
由两部分组成,head-y和built-in.a部分
head-y在arch/x86/Makefile中有定义,是需要首先链接到vmlinux的部分
1 | head-y := arch/x86/kernel/head_$(BITS).o |
built-in.a是啥?从打印信息来看,每个主目录下$(core-y)都有一个built-in.a(每一个$(obj-y)下也有),这是Kbuild编译$(obj-y)部分之后将object文件归档的,之后会通过链接脚本scripts/link-vmlinux.sh链接到vmlinux
1 | ➜ kernel git:(master) ✗ pwd |
part 2&3
第二部分$(addsuffix built-in.a, $(filter %/, $(libs-y)))和第三部分KBUILD_VMLINUX_OBJS += $(patsubst %/, %/lib.a, $(filter %/, $(libs-y)))打印为
1 | lib/built-in.a arch/x86/lib/built-in.a |
libs-y的组成如下,官方doc介绍,工具将libs-y下的目标打包为库lib.a,并将路径归档为built-in.a
1 | //Makefile |
part 4
第4部分$(patsubst %/,%/built-in.a, $(drivers-y))的打印为
1 | drivers/built-in.a sound/built-in.a net/built-in.a virt/built-in.a arch/x86/pci/built-in.a arch/x86/power/built-in.a arch/x86/video/built-in.a" |
这是一些驱动部分
KBUILD_VMLINUX_LIBS
KBUILD_VMLINUX_LIBS := $(filter-out %/, $(libs-y))
因为libs-y只有lib/和arch/x86/lib/两部分,因此KBUILD_VMLINUX_LIBS为空
链接依赖总结
在链接脚本链接前,需要以下依赖
graph LR
依赖--> vmlinux-deps
vmlinux-deps-->非built-in-->vmlinux.lds
非built-in-->head-y
vmlinux-deps-->built-in
built-in.a-->core-y
built-in.a-->drivers-y
built-in.a-->libs-y
built-in-->built-in.a
built-in-->lib.a-->libs-y整体依赖如下:
1 | arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o arch/x86/kernel/head64.o arch/x86/kernel/ebda.o arch/x86/kernel/platform-quirks.o init/built-in.a usr/built-in.a arch/x86/built-in.a kernel/built-in.a certs/built-in.a mm/built-in.a fs/built-in.a ipc/built-in.a security/built-in.a crypto/built-in.a block/built-in.a lib/built-in.a arch/x86/lib/built-in.a lib/lib.a arch/x86/lib/lib.a drivers/built-in.a sound/built-in.a net/built-in.a virt/built-in.a arch/x86/pci/built-in.a arch/x86/power/built-in.a arch/x86/video/built-in.a " |
脚本链接vmlinux
link-vmlinux.sh描述链接过程如下,其实lib/lib.a已经合并到KBUILD_VMLINUX_OBJS
1 |
|
最后链接执行的语句为:
1 | cmd_link-vmlinux = \ |
实际扩展为:
1 | sh scripts/link-vmlinux.sh "ld" " -m elf_x86_64" "--emit-relocs --discard-none -z max-page-size=0x200000 --build-id=sha1 --orphan-handling=warn"; true |
脚本链接过程
脚本链接过程如下:
0.1通过modpost_link使用ld默认链接生成vmlinux.o所以此时section是没有经过处理的
0.2通过objtool_link将vmlinux.o中的模块symbol导出生成vmlinux.symvers, 见scripts/mod/modpost.c
1.1先vmlinux_link链接通过ld链接归档生成.tmp_vmlinux.kallsyms1,此时包含了所有的section 和section,(但是文档上说__kallsyms是空的, 没有发现除了地址变化外的其他不同)
1.2通过kallsyms生成.tmp_vmlinux.kallsyms1.S,这是一个包含symbol的汇编源代码,可以查看文件scripts/kallsyms.c
1 | nm -n .tmp_vmlinux.kallsyms1 | scripts/kallsyms --all-symbols --absolute-percpu --base-relative >.tmp_vmlinux.kallsyms1.S |
1.3 gcc通过-I选项指定头文件路径进行编译生成.tmp_vmlinux.kallsyms1.o,此时应该包含正确的kallsyms类symbol
2.1vmlinux_link链接通过ld链接.tmp_vmlinux.kallsyms1.o和归档为.tmp_vmlinux.kallsyms2,但由于新增加部分,导致地址发生了偏移
2.2通过kallsyms生成.tmp_vmlinux.kallsyms1.S,新的包含symbol的汇编源代码
2.3 gcc通过-I选项指定头文件路径进行编译生成.tmp_vmlinux.kallsyms2.o
3.1 vmlinux_link链接通过ld`链接.tmp_vmlinux.kallsyms2.o和归档为vmlinux
4.mksysmap vmlinux System.map通过nm提取符号表为System.map
5.sorttable vmlinux进行符号表排序,见scripts/sorttable.c
6.符号表检验机制:将.tmp_vmlinux.kallsyms2生成的符号表.tmp_System.map和System.map做校验
graph TB
scripts/link-vmlinux.sh --> 1.ld默认链接生成vmlinux.o -->2.提取模块symbol生成vmlinux.symvers
1.ld默认链接生成vmlinux.o -->3.提取.modinfo到modules.builtin.modinfo
scripts/link-vmlinux.sh --> 4.生成.tmp_vmlinux.kallsyms1 -->5.生成.tmp_vmlinux.kallsyms1.S
5.生成.tmp_vmlinux.kallsyms1.S -->6.生成.tmp_vmlinux.kallsyms1.o
6.生成.tmp_vmlinux.kallsyms1.o -->7.生成.tmp_vmlinux.kallsyms2
7.生成.tmp_vmlinux.kallsyms2-->8.生成.tmp_vmlinux.kallsyms2.S
8.生成.tmp_vmlinux.kallsyms2.S-->9.生成.tmp_vmlinux.kallsyms2.o
9.生成.tmp_vmlinux.kallsyms2.o -->10.生成vmlinux -->11.生成System.map
7.生成.tmp_vmlinux.kallsyms2 -->12.生成tmp_System.map从链接脚本脚本的打印来看:
1 | ➜ linux git:(master) ✗ make V=1 bzImage -j12 2>&1 | tee build.log |
bzImage的构建
到此时,vmlinux已经构建成功,开始bzImage构建
bzImage构建参考官方boot image构建
bzImage的流程构建框架
bzImage的流程构建框架如下:
graph TB
setup.bin-->bzImage
vmlinux.bin -->bzImage
zoffset.h -->bzImage
setup.elf-->setup.bin
setup.ld -->setup.elf
header.o -->setup.elf
header.S -->header.o
version.o -->setup.elf
version.c -->version.o
compressed/vmlinux.lds--> compressed/vmlinux -->vmlinux.bin
compressed/vmlinux -->zoffset.h
compressed/vmlinux-bin.gz-->piggy.S --> piggo.o -->compressed/vmlinux
kasler.o -->compressed/vmlinux
compressed/vmlinux.bin -->compressed/vmlinux-bin.gz
compressed/vmlinux.relocs -->compressed/vmlinux-bin.gz
vmlinux --> compressed/vmlinux.binbzImage构建时的文件框架
bzImage构建时的Makefile框架是一个动态的包含和调用,如下
graph LR
arch/x86/Makefile -->scripts/Makefile.build
scripts/Makefile.build -->scripts/Kbuild.include
scripts/Makefile.build -->scripts/Makefile.lib
scripts/Makefile.build -->1.arch/x86/boot/Makefile -->scripts/Makefile.build
scripts/Makefile.build -->2.arch/x86/boot/compressed/MakefilebzImage的构建过程
依赖规则为:
1 | //arch/x86/Makefile |
扩展为:
1 | make -f ./scripts/Makefile.build obj=arch/x86/boot arch/x86/boot/bzImage |
而scripts/Makefile.build此时又包含了arch/x86/boot/Makefile
1 | kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) |
定义了bzImage的依赖和cmd行为
1 | quiet_cmd_image = BUILD $@ |
bzImage的主要构成为为setup.bin和vmlinux.bin,构建为arch/x86/boot/tools/build
setup.bin
setup.bin用来引导探测内存等的,依赖setup.elf
1 | OBJCOPYFLAGS_setup.bin := -O binary |
setup.elf是由arch/x86/boot下的header.o等通过ld链接而来
1 | $(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE |
setup.ld是作为链接脚本,指定了内核入口点等重要的内核启动信息
而setup.elf有一些附加信息对内核没有用,所以才二进制导入到setup.bin
vmlinux.bin
vmlinux.bin的构建在arch/x86/boot/compressed/Makefile中描述如下:
1 |
|
vmlinux.bin的依赖为一个被压缩过后的vmlinux
1 | OBJCOPYFLAGS_vmlinux.bin := -O binary -R .note -R .comment -S |
compressed/vmlinux将除了.note和.comment以外的section部分以二进制方式导入到vmlinux.bin中
1 | ➜ boot git:(master) ✗ file vmlinux.bin |
compressed/vmlinux的依赖如下:
1 | $(obj)/compressed/vmlinux: FORCE |
展开为:
1 | make -f ./scripts/Makefile.build obj=arch/x86/boot/compressed arch/x86/boot/compressed/vmlinux |
而此时include的东西根据obj的不同也开始不同,现在相当于变成了include arch/x86/boot/compressed/Makefile
1 | kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) |
因此,compressed/vmlinux的依赖变为如下:之后将这些链接起来,链接脚本为compressed/vmlinux.lds
1 | $(obj)/vmlinux: $(vmlinux-objs-y) $(efi-obj-y) FORCE |
其中包括 compressed/vmlinux.lds和其他的一些库,比如piggy.o和进行内核地址随机化的kaslr.o, piggy.o需要通过如下生成
1 | quiet_cmd_mkpiggy = MKPIGGY $@ |
通过mkpiggy工具处理compressed/vmlinux.bin.gz生成pigggy.S,compressed/vmlinux.bin.gz的依赖如下:
1 | vmlinux.bin.all-y := $(obj)/vmlinux.bin |
在.config中CONFIG_X86_NEED_RELOCS为y,因此需要两个依赖compressed/vmlinux.bin和compressed/vmlinux.relocs
1 | OBJCOPYFLAGS_vmlinux.bin := -R .comment -S |
如上,compressed/vmlinux.bin是vmlinux去掉.comment section和 strip-all(没有 debug secion部分)的二进制数
如下,compressed/vmlinux.relocs是通过relocs处理vmlinux生成的
1 | CMD_RELOCS = arch/x86/tools/relocs |