UCore-Lab1
日期:2019/3/31
内容:makefile分析;
一、"Makefile"分析
1.1 ucore.img
lab1已有的源文件
目录 |
文件 |
boot |
asm.h、bootasm.S、bootmain.c |
tools |
sign.c、vector.c、kernel.ld |
如下相当于makefile的main函数。
# create ucore.img UCOREIMG := $(call totarget,ucore.img)
$(UCOREIMG): $(kernel) $(V)dd if=/dev/zero of=$@ count=10000 $(V)dd if=$(bootblock) of=$@ conv=notrunc $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc
$(call create_target,ucore.img)
第一个dd:向ucore.img文件写入5120000bytes,每个byte都是0 第二个dd:拷贝bootblock到ucore.img,但是不截断ucore.img。(如果没有notrunc则相当于cp bootblock ucore.img) 第三个dd:跳过kernel的第一个block(512bytes)同时不截断ucore.img |
/dev/zero是一个特殊的文件,当你读它的时候,它会提供无限的空字符(NULL, ASCII NUL, 0x00)。
$@表示目标文件$(UCOREIMG)。
V在makefile的第6行定义为@,$(V)相当于给每个命令加了一个@前缀。
为了生成ucore.img,首先需要生成bin/bootblock、bin/kernel。
1.2 bin/bootblock
生成bin/bootblock的makefile部分。
# create bootblock bootfiles = $(call listf_cc,boot) $(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
bootblock = $(call totarget,bootblock)
$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign) @echo + ld $@ $(V)$(LD) @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) @$(OBJCOPY) -S -O binary $(call objfile,bootblock) @$(call totarget,sign)
$(call create_target,bootblock) # -------------------------------------------------------------------
foreach函数: $(bootfiles)展开后是boot/文件夹下的.c和.S文件,即:boot/bootmain.c boot/bootasm.S $(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)这个有点复杂,先留着坑 大致可以推测这个foreach是编译boot下的.c和.S文件的(会输出 + cc boot/bootmain.c )。
bootblock = $(call totarget,bootblock) = bin/bootblock
第一行command:输出 + ld bin/bootblock 第二行command:链接依赖文件,输出文件为obj/bootblock.o 第三行command:利用objdump工具进行反汇编。输入文件是bin/bootblock,输出文件是obj/bootblock.asm 第四行command: >> 利用objcopy工具进行拷贝。输入是obj/bootblock.o >> -S表示去除源文件中的重定位信息和符号信息。 >> -O binary表示输出为原始的二进制文件 >> 输入是obj/bootblock.o >> 输出是obj/bootblock.asm 第五行command: >> bin/sign >> obj/bootblock.out >> bin/bootblock >> 作用:运行sign这个程序,输入是.out,输出是bootblock这个二进制文件。 |
bin/bootblock的依赖是$(call toobj,$(bootfiles)) | $(call totarget,sign)
即:obj/boot/bootmain.o 、 obj/boot/bootasm.o和bin/sign。
生成obj/boot/bootmain.o 和 obj/boot/bootasm.o的语句:
bootfiles = $(call listf_cc,boot) $(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) |
在此处不一行行对cc_compile展开(其实是因为看不懂* _*)。
使用make "V=" > makelog,可以看到执行的命令为:
+ cc boot/bootasm.S gcc -Iboot/ -march=i686 -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o + cc boot/bootmain.c gcc -Iboot/ -march=i686 -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
关键参数: >> ggdb: 生成可供gdb使用的调试信息。这样才能用qemu+gdb来调试bootloader or ucore 位环境的代码。我们用的模拟硬件是32bit的80386,所以ucore也要是32位的软件。 >> gstabs: 生成stabs格式的调试信息。这样要ucore的monitor可以显示出便于开发者阅读的函数调用栈信息。 >> nostdinc: 不使用标准库。标准库是给应用程序用的,我们是编译ucore内核,OS内核是提供服务的,所以所有的服务要自给自足。 >> fno-stack-protector: 不生成用于检测缓冲区溢出的代码。这是for应用程序的,我们是编译内核,ucore内核好像还用不到此功能。 >> Os: 为减小代码大小而进行优化。bootloader最多只能是512 bytes。 >> I<dir>: 添加搜索头文件的路径 >> fno-builtin: 除非用__builtin_前缀,否则不进行builtin函数的优化。 (buildin即是C语言中的内建函数,通过define来实现一个函数的功能) |
生成bin/sign的makefile:
# create 'sign' tools $(call add_files_host,tools/sign.c,sign,sign) $(call create_target_host,sign,sign) |
实际执行的命令为:
+ cc tools/sign.c gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign |
有了上面的obj/boot/bootasm.o和obj/boot/bootmain.o,下面生成obj/bootblock.o。
生成obj/bootblock.obj的makefile第164行:
@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) |
实际执行shell命令:
+ ld bin/bootblock ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
参数解析: >> m <emulation> 模拟为i386上的连接器 >> nostdlib 不使用标准库 >> N 设置代码段和数据段均可读写 >> e <entry> 指定入口 >> Ttext 制定代码段开始位置 |
1.3 bin/kernel
生成kernel的makefile
# create kernel target kernel = $(call totarget,kernel)
$(kernel): tools/kernel.ld
$(kernel): $(KOBJS) @echo + ld $@ $(V)$(LD) @$(OBJDUMP) -S $@ > $(call asmfile,kernel) @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)
$(call create_target,kernel)
# ------------------------------------------------------------------- 参数: >> T <scriptfile> 让连接器使用指定的脚本 |
实际执行shell命令(依赖一目了然,均可通过源文件gcc可得):
+ ld bin/kernel ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o obj/kern/trap/trapentry.o obj/kern/mm/pmm.o obj/libs/string.o obj/libs/printfmt.o |
1.4 总结
生成目标 |
依赖文件和操作步骤 |
bin/ucore.img |
bin/bootblock、bin/kernel bootblock + kernel (dd) =>ucore.img |
bin/bootblock |
obj/boot/bootmain.o 、 obj/boot/bootasm.o和bin/sign (前2个通过源文件编译而来) gootmain.c+bootasm.S (gcc) => bootmain.o+bootasm.o (ld) => obj/bootblock.o (objcopy) => obj/bootblock.out (bin/sign) => bin/bootblock |
bin/sign |
tools/sign.c sign.c => sign.o (gcc) => sign (gcc) |
bin/kernel |
kernel.ld init.o readline.o stdio.o kdebug.o kmonitor.o panic.o clock.o console.o intr.o picirq.o trap.o trapentry.o vectors.o pmm.o printfmt.o string.o kernel.ld (tools下已有) kernel/init/init.c => init.o kernel/libs/* => readline.o + stdio.o kernel/debug/* => kdebug.o + kmonitor.o + panic.o kernel/driver/* => clock.o + console.o + intr.o + picirq.o kernel/trap/* => trap.o + trapentry.o + vectors.o kernel/mm/* => pmm.o libs/* => printfmt.o + string.o |
二、"Makefile"函数
函数定义在lab1/tools/function.mk
2.1 totarget
函数定义
totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) |
其中BINDIR(80行)和SLASH(4行)在Makefile定义。
BINDIR := bin SLASH := / |
作用:给参数$(1)添加bin/前缀,bin/$(1)作为tartget的路径
2.2 create_target
函数定义
create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) |
2.3 list_fcc
list_fcc在Makefile中89行定义。
作用:list出目录$(1)下的.c和.S文件。
参数$(1)是一个目录。 listf_cc = $(call listf,$(1),$(CTYPE)) 行 CTYPE := c S
那么,listf_cc相当于 listf_cc = $(call listf,$(1),c S)
listf在tools/functions.mk # list all files in some directories: (#directories, #types) listf = $(filter $(if $(2),$(addprefix %.,$(2)),%), $(wildcard $(addsuffix $(SLASH)*,$(1))))
先分析if函数 $(if $(2),$(addprefix %.,$(2)),%) 先看addprefix $(addprefix %.,$(2))给参数$(2), 在此处是"c S"加前缀"%.",即"%.c %.S" 那么相当于 $(if c S,%.c %.S,%) 如果$(2)非空,那么将返回
再看wildcard函数 $(wildcard $(addsuffix $(SLASH)*,$(1))) addsuffix给参数$(1)的每个单词加上 /* 后缀 然后wildcard对符合 $(1)/* 规则的文件展开。(换句话说,就是列出$(1)目录下的所有文件)
最后看filter函数 if函数是<pattern>,wildcard是<text> 把$(1)目录下的所有文件以<pattern> = %.c %.S过滤,只剩下.c和.S文件。 |
2.4 cc_compile
cc_compiler定义在functions.mk第79行。
cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) |
有点难,先留坑。
2.5 toobj
见functions.mk第10行。
参数:$(1)是一个文件名。
作用:生成obj目录下的.o文件。
# get .o obj files: (#files[, packet]) toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)), $(addsuffix .o,$(basename $(1))))
OBJDIR := obj
如果无参数$(2): >> $(if $(2),$(2)$(SLASH)) >> $(addsuffix .o,$(basename $(1))) 将返回一个$(1).o文件名 >> toobj = obj/$(1).o
如果有参数$(2): >> $(if $(2),$(2)$(SLASH)) 将返回$(2)/ >> toobj = obj/$(2)/$(1).o |
2.6 objfile
见Makefile第100行。
作用:返回obj/$(1).o
objfile = $(call toobj,$(1)) |
2.7 asmfile
见Makefile第101行。
作用:把obj/$(1).o的后缀.o替换为.asm
asmfile = $(call cgtype,$(call toobj,$(1)),o,asm)
$(call toobj,$(1)) 将返回 obj/$(1).o
那么asmfile将会是 asmfile = $(call cgtype, obj/$(1).o, o, asm)
cgtype = $(patsubst %.$(2),%.$(3),$(1)) 对于文件列表$(1),将$(2)后缀替换为$(3)后缀
那么asmfile将会是 asmfile = obj/$(1).asm |
2.8 outfile
Makefile第102行。
作用:把obj/$(1).o替换为obj/$(1).out
outfile = $(call cgtype,$(call toobj,$(1)),o,out) |
三、make库函数
3.1 if
- 函数格式
$(if <condition>,<then-part> ) $(if <condition>,<then-part>,<else-part> ) |
- 说明
如果<condition>为真(非空字符串),那么<then-part>会是整个函数的返回值。
如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值。此时如果<else-part>没有被定义,那么整个函数返回空字串。
所以,<then-part>和<else-part>只会有一个被计算。
3.2 filter
- 函数格式
$(filter <pattern...>,<text> ) |
- 说明
以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式(用空格隔开)。
返回符合模式<pattern>的字串。
- 示例
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))返回的值是"foo.c bar.c baz.s"
3.3 addprefix
- 函数格式
$(addprefix <prefix>,<names...> ) |
- 说明
把前缀<prefix>加到<names>中的每个单词后面(每个单词用空格隔开)。
3.4 wildcard
Makefile的通配符为*,?,[],与shell使用的是一样的通配符。
Makefile的通配符只有在targets 和prerequisites中展开,在定义变量时是不会展开的,如果想在定义变量时展开通配符,需要使用wildcard函数。
- 函数格式
$(wildcard <str>) |
- 示例
VAR1 := *.c VAR2 := $(wildcard *.c) run : echo $(VAR1) echo $(VAR2)
、3行是make run的结果,2、4行是shell命令的结果。 |
3.5 foreach
- 函数格式
$(foreach <var>,<list>,<text>) |
- 说明
作用:把<list>中的每个单词取出,放在<var>中,再计算<text>表达式,返回<text1> <text2> ... <textn>
foreach中的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不在作用,其作用域只在foreach函数当中。
- 示例
names := 1 2 3 files := $(foreach n, $(names), $(n).o) run : echo $(files) 运行结果 |
3.6 eval
留坑。
3.7 basename
- 函数格式
$(basename NAMES…) |
- 功能
从文件名序列"NAMES…"中取出各个文件名的前缀部分(点号之前的部分)。前缀部分指的是文件名中最后一个点号之前的部分。
如果"NAMES…"中包含没有后缀的文件名,此文件名不改变。如果一个文件名中存在多个点号,则返回值为此文件名的最后一个点号之前的文件名部分。
- 返回值
空格分割的文件名序列"NAMES…"中各个文件的前缀序列。如果文件没有前缀,则返回空字串。
- 示例
CFILES = $(wildcard *.c) BASENAMES = $(basename $(CFILES)) run : @echo $(CFILES) @echo $(BASENAMES)
运行结果 |
3.8 patsubst
- 函数格式
$(patsubst <src pattern>, <dst pattern>, <file list>) |
- 功能
在文件列表中找到所有符合src pattern的文件,然后把所有的src pattern换成dst pattern。
- 返回值
返回替换后的list。
- 示例
SRCFILES = $(wildcard *.c) DSTFILES = $(patsubst %.c, %.o, $(SRCFILES)) run : @echo $(SRCFILES) @echo $(DSTFILES)
运行结果 |
Note
[Note-1] 命令前缀+,-,@
不用前缀:输出执行的命令以及命令执行的结果, 出错的话停止执行
前缀 @:不回显执行的命令, 出错的话停止执行
前缀 -:命令执行有错的话, 忽略错误, 继续执行
例子
V := @ run : $(V)echo before 'rm' $(V)rm test1 test2 $(V)echo after 'rm' 如果有test1和test2 如果test1和test2至少一个没有 如果把所有的@删除,test1和test2都存在 |
[Note-2] $@,$^,$<,$?,$$的区别
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
$$ 访问一个在shell当中定义的变量
示例
make_var = mval EXEC = a.out SRC = 1.c 2.c 3.c $(EXEC) : $(SRC) @echo "echo "\$$@"" @echo $@ @echo "echo "\$$^"" @echo $^ @echo "echo "make_var" " @echo $(make_var) shell_var="sval"; echo $$shell_var
运行结果 |
[Note-3] =,:=,?=,+=区别
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
[Note-4] 依赖中的竖线 |
target : normal-prerequisites | order-only-prerequisites |
心得体会
- 2019/4/2:(牛)(啤)的人写个makefile都让人窒息,光分析生成ucore.img就花了2个晚上了。
最新文章
- ASP.NET Web API 2框架揭秘
- jQuery基础 -- 如何判断页面元素存在与否
- cf.VK CUP 2015.C.Name Quest(贪心)
- Frequent values &;&; Ping pong
- 基于.NET平台的分布式应用程序的研究
- 决策树及其python实现
- Mysql权限控制 - 允许用户远程连接(转载)
- 深入理解ajax系列第三篇——头部信息
- 如何在jenkins的maven项目中,用mvn命令行指定findbugs的黑名单规则文件
- VueJs 权限管理
- Reason的介绍和搭建Reason开发环境
- Response.End ,Response.Redirect、Server.Transfer 引发 “正在中止线程”异常的问题
- if 语句
- 根据wsdl,apache cxf的wsdl2java工具生成客户端、服务端代码
- ScrollView在布局中的作用
- 在SQLSERVER中如何检测一个字符串中是否包含另一个字符串
- 教你如何编写、保存与运行 Python 程序
- 批量上传文件到HDFS的Shell脚本
- [转载]java开发实现word在线编辑及流转
- LeetCode--Reverse Linked List(Java)
热门文章
- netty4初步使用
- [SQL]查询最新的数据
- python学习 day08 (3月13日)----函数
- 2018.12.12 codeforces 938E. Max History(组合数学)
- 使用express框架和mongoose在MongoDB更新数据
- Win7 VS2015环境编译cegui-0.8.5
- c++关键字volatile的作用
- VSCode 设置侧边栏字体大小;Visual Studio Code改变侧边栏大小
- BZOJ 1029 [JSOI2007]建筑抢修 (贪心 + 优先队列)
- 2014年的最后一个程序,却成为了2015年的第一个bug