日期: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)
$(bootblock)

    $(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)
$(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)

    @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)

    @$(OBJCOPY) -S -O binary $(call objfile,bootblock)
$(call outfile,bootblock)

    @$(call totarget,sign)
$(call outfile,bootblock)
$(bootblock)

 

$(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)
$(LDFLAGS) -T tools/kernel.ld -o $@
$(KOBJS)

    @$(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)非空,那么将返回
%.c %.S

 

再看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个晚上了。

最新文章

  1. ASP.NET Web API 2框架揭秘
  2. jQuery基础 -- 如何判断页面元素存在与否
  3. cf.VK CUP 2015.C.Name Quest(贪心)
  4. Frequent values &amp;&amp; Ping pong
  5. 基于.NET平台的分布式应用程序的研究
  6. 决策树及其python实现
  7. Mysql权限控制 - 允许用户远程连接(转载)
  8. 深入理解ajax系列第三篇——头部信息
  9. 如何在jenkins的maven项目中,用mvn命令行指定findbugs的黑名单规则文件
  10. VueJs 权限管理
  11. Reason的介绍和搭建Reason开发环境
  12. Response.End ,Response.Redirect、Server.Transfer 引发 “正在中止线程”异常的问题
  13. if 语句
  14. 根据wsdl,apache cxf的wsdl2java工具生成客户端、服务端代码
  15. ScrollView在布局中的作用
  16. 在SQLSERVER中如何检测一个字符串中是否包含另一个字符串
  17. 教你如何编写、保存与运行 Python 程序
  18. 批量上传文件到HDFS的Shell脚本
  19. [转载]java开发实现word在线编辑及流转
  20. LeetCode--Reverse Linked List(Java)

热门文章

  1. netty4初步使用
  2. [SQL]查询最新的数据
  3. python学习 day08 (3月13日)----函数
  4. 2018.12.12 codeforces 938E. Max History(组合数学)
  5. 使用express框架和mongoose在MongoDB更新数据
  6. Win7 VS2015环境编译cegui-0.8.5
  7. c++关键字volatile的作用
  8. VSCode 设置侧边栏字体大小;Visual Studio Code改变侧边栏大小
  9. BZOJ 1029 [JSOI2007]建筑抢修 (贪心 + 优先队列)
  10. 2014年的最后一个程序,却成为了2015年的第一个bug