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个晚上了。
UCore-Lab1的更多相关文章
- 《ucore lab1》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 练习1:理解通过make生成执行文件的过程 详见<ucore lab1 exercise1>实验报告 练习2:使用qemu执行并调试 ...
- ucore lab1 bootloader学习笔记
---恢复内容开始--- 开机流程回忆 以Intel 80386为例,计算机加电后,CPU从物理地址0xFFFFFFF0(由初始化的CS:EIP确定,此时CS和IP的值分别是0xF000和0xFFF0 ...
- 《ucore lab1 exercise5》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 题目:实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_s ...
- 《ucore lab1 exercise3》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 题目:分析bootloader进入保护模式的过程 BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader.请分 ...
- 《ucore lab1 exercise2》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 题目:使用qemu执行并调试lab1中的软件 为了熟悉使用qemu和gdb进行的调试工作,我们进行如下的小练习: 从CPU加电后执行的第一条指令 ...
- 《ucore lab1 exercise1》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 题目:理解通过make生成执行文件的过程 列出本实验各练习中对应的OS原理的知识点,并说明本实验中的实现部分如何对应和体现了原理中的基本概念和关 ...
- ucore操作系统学习笔记(一) ucore lab1系统启动流程分析
一.ucore操作系统介绍 操作系统作为一个基础系统软件,对下控制硬件(cpu.内存.磁盘网卡等外设),屏蔽了底层复杂多样的硬件差异:对上则提供封装良好的应用程序接口,简化应用程序开发者的使用难度.站 ...
- Ucore lab1实验报告
练习一 Makefile 1.1 OS镜像文件ucore.img 是如何一步步生成的? + cc kern/init/init.c + cc kern/libs/readline.c + cc ker ...
- ucore lab1练习2 qemu+gdb 不能协作调试的问题make lab1-mon
本练习是qemu结合gdb调试,但是我做实验的时候并不能像视频输入make lab1-mon那样顺利调试,期间有各种error,后来我找到原因,请看解决方法. 请先把ucore_lab文件删除,以下全 ...
- 《ucore lab1 exercise4》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 题目:分析bootloader加载ELF格式的OS的过程 通过阅读bootmain.c,了解bootloader如何加载ELF文件.通过分析源代 ...
随机推荐
- Android NDK定位.so文件crash代码位置
参考:http://blog.csdn.net/xyang81/article/details/42319789 问题: QRD8926_110202平台的Browser必现报错.(去年的项 ...
- HBuilder中ios打包
参考:http://ask.dcloud.net.cn/article/152 在ios端钥匙串双击(教程上是双击)导入证书时候,可能会报错,直接把证书文件拖入到keychain的登录里就解决了. 1 ...
- 数的划分(NOIP2001&水题测试2017082401)
题目链接:数的划分 这题直接搜索就行了.给代码,思路没什么好讲的,要讲的放在代码后面: #include<bits/stdc++.h> using namespace std; int d ...
- centos7 sqoop 1 搭建笔记
1.require : java环境,hadoop,hive ,mysql2.下载解压sqoop13.设置环境变量 export SQOOP_HOME=/data/spark/bin/sqoop ex ...
- 【转】【MySQL】时间类型存储格式选择
一 前言 昨天在给开发同学做数据库设计规范分享的时候,讲到时间字段常用的有三个选择datetime.timestamp.int,应该使用什么类型的合适?本文通过三种类型的各个维度来分析,声明:本文 ...
- OpenCV(2):视频
播放AVI视频 #include<iostream> #include<opencv2/core/core.hpp> #include<opencv2/highgui/h ...
- Java的背景、影响及前景
一.背景 詹姆斯·高斯林出生于加拿大,是一位计算机编程天才.在卡内基·梅隆大学攻读计算机博士学位时,他编写了多处理器版本的Unix操作系统,是JAVA编程语言的创始人. 高斯林生于1955年,已婚,育 ...
- 系统当前时间system.currenttimemillis与new Date().getTime() 区别
system.currenttimemillis //取到毫秒数,并且执行效率高 new Date().getTime()没他精确
- react创建项目报错unexpected end of json while parsing near xxx
报这个错,执行下面的命令,然后重新创建项目就可以. npm cache clean --force
- oracle compile 编译无效对象
原博主:http://blog.csdn.net/tianlesoftware/article/details/4843600 Applies to: Oracle Server - Enterpri ...