目录
1.makefile总结
以下内容参考《跟我一起写makefile》,仅作个人学习总结。
make 命令执行时,需要一个 Makefile 文件,以告诉 make 命令需要怎么样的去编译和链接程序。
1.1makefile基本规则
target...:prerequisites...
command
target: 目标文件,Objectfile,执行文件,或者标签
prerequisites:生成target所需文件或目标
command:make执行的命令(任意shell命令)
1.1.1makefile变量
Makefile 中的变量其实就是 C/C++中的宏。
objects = main.0 kbd.o display.o insert.o #all .o file
edit: $(objects)
cc -o edit $(objects)
1.1.2make自动推导
GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必 要去在每一个[.o]文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导 命令。 只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make 找到whatever.o,那么 whatever.c,就会是 whatever.o 的依赖文件,并且 cc -c whatever.c 也会被推导出来。
1.1.3清空目标文件
所有生成的文件,都应当可以被清空,这有利于重编译和保持文件清洁。
.PHONY:clean
clean:
rm edit $(objects)
1.2makefile综述
makefile主要包含五个东西:显式规则,隐式规则,变量定义,文件指示和注释
1.2.1引用其他makefile
makefile使用include关键字可以把别的makefile包含进来
include <filename>
filename 可以是当前操作系统 Shell 的文件模式(可以保含路径和通配符) 在 include 前面可以有一些空字符,但是绝不能是[Tab]键开始。make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置。
如果文件都没有指定绝对路径或是相对路径的话, make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:
1."-I"或"--include-dir"所指定目录
2.<prefix>/include中
1.2.2makefile工作方式
GNU 的 make 工作时的执行步骤入下:
1、读入所有的 Makefile。
2、读入被 include 的其它 Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
1.3makefile规则书写
targets : prerequisites
command
或是这样:
targets : prerequisites ; command
command
1.3.1通配符的使用
~/:当前用户$HOME目录
*.c:表示所有后缀为c的文件
clean:
rm -f *.o
1.3.2文件搜寻
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是定义一个路径,让 make 在自动去找。
VPATH
VPATH = src:../headers
make 会优先在当前的目录中去找寻依赖文件和目标文件,找不到时会去VPATH中寻找,如上所示会按照"src"和"../headers"顺序去搜索。目录由“冒号”分隔。
关键字vpath
1、vpath 为符合模式的文件指定搜索目录。
vpath <pattern> <directories>
2、vpath 清除符合模式的文件的搜索目录。
vpath <pattern>
3、vpath 清除所有已被设置好了的文件搜索目录。
vpath
vapth 使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符, 例如,“%.h”表示所有以“.h”结尾的文件。
vpath %.h ../headers
该语句表示,要求 make 在“../headers”目录下搜索所有以“.h”结尾的文件。(如果 某文件在当前目录没有找到的话)
1.3.3伪目标
“伪目标”并不是一个文件,只是一个标签, 由于“伪目标”不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。
伪目标最好不要和文件名重名,但可以用特殊标记".PHONY"显式指明伪目标,不管是否有该文件,该目标都是伪目标。
.PHONY: clean
clean:
rm *.o temp
伪目标可以指定依赖,如下所示可以实现“一键三连”
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
伪目标也可以成为依赖,如下所示可以实现“一键清空”
PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
1.3.4多目标静态模式
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern 是指明了 targets 的模式,也就是的目标集模式。
prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
命令中的“$<”和“$@”是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)
当文件众多时,利用简单的“静态模式规则”可以高效的完成规则编写。
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter %.o,$(files))表示调用 Makefile 的 filter 函数,过滤“$filter”集,只要其 中模式为“%.o”的内容。
1.4命令书写
每条命令的开头必须以[Tab]键开头,在命令行之间中的空格或是空行会被忽略。
make 的 命 令 默 认 是 被 “/bin/sh”——UNIX 的标准 Shell 解释执行的。
“#”是注释符,其后的本行字符都被注释。
1.4.1显示命令
通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在 命令行前,那么,这个命令将不被 make 显示出来。
echo git checkout ysyx-tracer
@echo git checkout ysyx-tracer
make执行时,加入参数"-n"或"--just-print",那么将只显示命令,不执行命令,可以利用这个功能调试Makefile。
而 make 参数“-s”或“--slient”则是全面禁止命令的显示。
1.4.2 命令执行
当依赖目标新于目标时,也就是当规则的目标需要被更新时,make 会一条一条的执行 其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使 用分号分隔这两条命令。
exec: cd /home/hchen; pwd
1.4.3命令出错
忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号“-” (在 Tab 键之后),标记为不管命令出不出错都认为是成功的。
clean:
-rm -f *.o
给make 加上“-i”或是“--ignore-errors”参数,那么,Makefile 中所有命令都会忽略错误。
如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。
参数“-k”或是“--keep-going”的作用是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。
1.4.4定义命令包
如果 Makefile 中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一 个变量。定义这种命令序列的语法以“define”开始,以“endef”结束。
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
foo.c : foo.y
$(run-yacc)
1.5使用变量
在 Makefile 中的定义的变量,就像是 C/C++语言中的宏一样,他代表了一个文本字串,在 Makefile 中执行的时候其会自动原模原样地展开在所使用的地方。其与 C/C++所不同的是,你可以在 Makefile 中改变其值。在 Makefile 中,变量可以使用在“目标”,“依赖目标”,“命令”或是 Makefile 的其它部分中。
1.5.1变量基础
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
1.5.2变量中的变量
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
1.5.3变量值的替换
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以 “a”字串“结尾”的“a”替换成“b”字串。
foo := a.o b.o c.o
bar := $(foo:.o=.c)
1.5.4追加变量值
objects = main.o foo.o bar.o utils.o
objects += another.o
如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=” 会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符。
1.6条件判断
使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
<conditional-directive>
<text-if-true>
endif
其中<conditional-directive>表示条件关键字,共有四个:"ifeq","ifneq","ifdef","ifndef"。
1.ifeq
ifeq (<arg1>, <arg2>)
比较参数“arg1”和“arg2”值是否相同,相同为真。
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
这个示例中使用了“strip”函数,如果这个函数的返回值是空( Empty )那么就生效。
2.ifneq
ifneq (<arg1>, <arg2>)
和ifeq类似,“arg1”和“arg2”不同时为真。
3.ifdef
ifdef <variable-name>
如果变量 的值非空,那到表达式为真。否则,表达式为假。当然,同样可以是一个函数的返回值。ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。
4.ifndef
ifndef <variable-name>
在<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)而注释符“#”同样也是安全的。“else”和“endif”也 一样,只要不是以[Tab]键开始就行了。
1.7使用函数
在Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。
1.7.1函数调用语法
$(<function> <arguments>)
function是函数名,arguments是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
1.7.2字符串处理函数
1.subset
$(subst <from>,<to>,<text>)
把字串<text>中的<from>字符串替换成<to>,并返回替换过后的字符串。
2.patsubsat
$(patsubst <pattern>,<replacement>,<text>)
3.strip
$(strip <string>)
去掉<string>字串中开头和结尾的空字符,并返回字串。
4.findstring
$(findstring <find>,<in>)
在字串<in>中查找<find>字串,如果找到返回<find>,否则返回空字符串
5.filter
$(filter <pattern...>,<text>)
以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词,并返回符合模式<pattern>的字串。可以有多个模式。
6.filter-out
$(filter-out <pattern...>,<text>)
7.sort
$(sort <list>)
8.word
$(word <n>,<text>)
9.wordlist
$(wordlist <s>,<e>,<text>)
10.words
$(words <text>)
11.firstword
$(firstword <text>)
12.组合使用举例:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
1.7.3文件名操作函数
1.dir
$(dir <names...>)
2.notdir
$(notdir <names...>)
3.suffix
$(suffix <names...>)
4.basename
$(basename <names...>)
5.addsuffix
$(addsuffix <suffix>,<names...>)
6.addprefix
$(addprefix <prefix>,<names...>)
7. join
$(join <list1>,<list2>)
1.7.4foreach函数
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)是foreach函数的返回值。
names := a b c d
files := $(foreach n,$(names),$(n).o)
#将返回“a.o b.o c.o d.o”
1.7.5if函数
$(if <condition>,<then-part>)
#或者
$(if <condition>,<then-part>,<else-part>)
1.7.6call函数
类似printf的用法
$(call <expression>,<parm1>,<parm2>,<parm3>...)
reverse = $(1) $(2)
foo = $(call reverse,a,b)
foo的返回值将是“a,b”,参数顺序可以自定
1.7.7shell函数
contents := $(shell cat foo)
files := $(shell echo *.c)
1.8make的运行
make 命令执行后有三个退出码:
0 - 表示成功执行。
1 - 如果 make 运行时出现任何错误,其返回 1。
2 - 如果你使用了 make 的“-q”选项,并且 make 使得一些目标不需要更新,那么返回 2。
1.8.1检查规则
-n 打印命令单步执行。
-t 将目标文件时间更新,但不更改目标文件。
-q 寻找目标。目标存在将不输出信息,目标不存在会打印出错信息。
-W <file> 指定一个文件,make会根据规则推导依赖于这个文件的命令,配合-n使用可以查看依赖该文件发生的规则命令。
1.8.2make参数
-B 将所有目标都更新。
-C 指定读取makefile的目录。
-qp 只输出信息不执行。
-p -f /dev/null 输出makefile文件的文件名和行号。
1.9隐含规则
“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的[.o]文件。
1.9.1使用隐含规则
foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
例子中,make的隐含规则会自动推导foo.o bar.o的依赖目标和生成命令。 即省略一下的内容,可以由make隐含规则自动推导。
foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
1.9.2隐含规则使用变量
编译C程序的隐含规则的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默认的编译命令是“cc”,如果把变量“$(CC)”重定义成“gcc”,把变量“$(CFLAGS)”重定义成“-g”,那么,隐含规则中的命令全部会以“gcc –c -g $(CPPFLAGS)”的样子来执行了。
1.关于命令的变量
CC:C语言编译程序。默认命令是“cc”。
CXX:C++语言编译程序。默认命令是“g++”。
2.关于命令参数的变量
如果没有指明其默认值,那么其默认值都是空。
CFLAGS:C语言编译器参数。
CXXFLAGS:C++语言编译器参数。
CPPFLAGS:C预处理器参数。
LDFLAGS:链接器参数。
1.9.3模式规则
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
这个例子中,会把所有所有[.c]文件都编译成[.o]文件。
1.9.4★自动化变量★
所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
$@:表示规则中的目标文件集。
$%:仅当目标是函数库文件中,表示规则中的目标成员名。
$<:依赖目标中的第一个目标名字。
$?:所有比目标新的依赖目标的集合。以空格分隔。
2.NEMU项目构建
2.1 NEMU-Makefile
ifeq ($(wildcard $(NEMU_HOME)/src/nemu-main.c),)
$(error NEMU_HOME=$(NEMU_HOME) is not a NEMU repo)
endif
这两行代码用于检查 NEMU_HOME 变量是否为有效的 NEMU 项目目录。具体来说,通过 wildcard 命令查找 $(NEMU_HOME)/src/nemu-main.c 文件是否存在,如果不存在,则通过 error 命令打印错误信息。
# Include variables and rules generated by menuconfig
-include $(NEMU_HOME)/include/config/auto.conf
-include $(NEMU_HOME)/include/config/auto.conf.cmd
这两行代码用于引入 menuconfig 生成的配置变量和规则。menuconfig 是一个配置工具,可以通过图形界面方便地配置项目的编译选项,并生成自动化脚本。
remove_quote = $(patsubst "%",%,$(1))
# Extract variabls from menuconfig
GUEST_ISA ?= $(call remove_quote,$(CONFIG_ISA))
ENGINE ?= $(call remove_quote,$(CONFIG_ENGINE))
NAME = $(GUEST_ISA)-nemu-$(ENGINE)
这段代码用于设置 GUEST_ISA
和 ENGINE
变量,分别基于 CONFIG_ISA
和 CONFIG_ENGINE
变量。 remove_quote
函数用于去除可能存在的引号。然后,NAME
变量被设置为 GUEST_ISA
和 ENGINE
的组合,中间用短横线连接。例如,如果 GUEST_ISA
设置为 x86
,ENGINE
设置为 qemu
,则 NAME
将设置为 x86-nemu-qemu
。
?=
运算符用于为变量设置默认值。在这种情况下,如果 GUEST_ISA
和 ENGINE
变量尚未设置,它们将被设置为 CONFIG_ISA
和 CONFIG_ENGINE
变量的值,而不是保留原来的值。如果 GUEST_ISA
和 ENGINE
变量已经设置,则它们的值不会更改。这使得 Makefile 可以在这些变量没有指定其他值的情况下使用默认值。
?=
和 +=
都是 make 的变量定义运算符。
?=
的意思是,如果变量之前没有被定义过,就将其定义为后面的值,否则,保留之前的值。这个运算符可以用来定义默认值。+=
的意思是将变量的值和后面的值进行拼接。这个运算符可以用来往变量添加额外的值。
# Include all filelist.mk to merge file lists
FILELIST_MK = $(shell find ./src -name "filelist.mk")
include $(FILELIST_MK)
# Filter out directories and files in blacklist to obtain the final set of source files
DIRS-BLACKLIST-y += $(DIRS-BLACKLIST)
SRCS-BLACKLIST-y += $(SRCS-BLACKLIST) $(shell find $(DIRS-BLACKLIST-y) -name "*.c")
SRCS-y += $(shell find $(DIRS-y) -name "*.c")
SRCS = $(filter-out $(SRCS-BLACKLIST-y),$(SRCS-y))
这一段代码用于提取源文件列表。
首先,它会查找所有名为 filelist.mk
的文件,并将它们包含到当前文件中。filelist.mk
文件中包含了一些变量,用于指定要编译的源文件。
然后,它会定义一些黑名单变量,用于指定不需要编译的目录和文件。它会先找出所有在黑名单中的目录,并将所有在这些目录中的 .c
文件都加入黑名单。
最后,它会找出所有需要编译的目录,并从中提取所有 .c
文件,最终将它们筛送出黑名单中的文件,得到最终的源文件列表。
在这段代码中,筛选文件列表的过程使用了 filter-out
函数。这个函数会将第二个参数中不在第一个参数中出现过的项目返回。
# Extract compiler and options from menuconfig
CC = $(call remove_quote,$(CONFIG_CC))
CFLAGS_BUILD += $(call remove_quote,$(CONFIG_CC_OPT))
CFLAGS_BUILD += $(if $(CONFIG_CC_LTO),-flto,)
CFLAGS_BUILD += $(if $(CONFIG_CC_DEBUG),-Og -ggdb3,)
CFLAGS_BUILD += $(if $(CONFIG_CC_ASAN),-fsanitize=address,)
CFLAGS_TRACE += -DITRACE_COND=$(if $(CONFIG_ITRACE_COND),$(call remove_quote,$(CONFIG_ITRACE_COND)),true)
CFLAGS += $(CFLAGS_BUILD) $(CFLAGS_TRACE) -D__GUEST_ISA__=$(GUEST_ISA)
LDFLAGS += $(CFLAGS_BUILD)
这段代码用于从菜单配置中提取编译器和选项,并设置相应的编译器变量。它首先设置 CC
变量为 CONFIG_CC
的值,并使用 remove_quote
函数去除可能存在的引号。然后,它检查了菜单配置中的各种选项,并根据它们来设置 CFLAGS_BUILD
和 CFLAGS_TRACE
变量。最后,它将这些变量用于设置 CFLAGS
和 LDFLAGS
变量,以便在编译时使用它们。这些变量包含了编译器和选项,用于编译程序。
# Include rules for menuconfig
include $(NEMU_HOME)/scripts/config.mk
ifdef CONFIG_TARGET_AM
include $(AM_HOME)/Makefile
LINKAGE += $(ARCHIVES)
else
# Include rules to build NEMU
include $(NEMU_HOME)/scripts/native.mk
endif
这段代码包括了其他 Makefile 中的规则,用于构建 NEMU
程序。include $(NEMU_HOME)/scripts/config.mk
语句包括了 config.mk
Makefile 中的规则,用于处理菜单配置选项。ifdef
语句检查 CONFIG_TARGET_AM
变量是否已定义。如果是,则执行 include $(AM_HOME)/Makefile
语句,它包括了 AM_HOME
目录中的 Makefile
中的规则。然后将 LINKAGE
变量附加上 ARCHIVES
变量。如果 CONFIG_TARGET_AM
未定义,则执行 include $(NEMU_HOME)/scripts/native.mk
语句,它包括了 native.mk
Makefile 中的规则,用于构建 NEMU
程序。这些规则用于编译程序并链接所有必要的对象文件,以创建最终的可执行文件。
3.AM项目构建
3.1AM-Makefile
AM的框架代码提供了一种可读性更强的markdown版本,可以通过运行指令“make html”获得。
按照官方提供的markdown模式,Makefile文件总共有6个部分。
3.1.1基础设置和检查
### Default to create a bare-metal kernel image
ifeq ($(MAKECMDGOALS),)
MAKECMDGOALS = image
.DEFAULT_GOAL = image
endif
创建裸机内核映像。MAKECMDGOALS为空时,设置为image。
### Override checks when `make clean/clean-all/html`
ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)
利用findstring函数检查参数MAKECMDGOALS中是否包含clean,clean-all,html,如果则返回值不为空,ifeq为假,后续内容不执行。
### Check: environment variable `$ARCH` must be in the supported list
ARCHS = $(basename $(notdir $(shell ls $(AM_HOME)/scripts/*.mk)))
ifeq ($(filter $(ARCHS), $(ARCH)), )
$(error Expected $$ARCH in {$(ARCHS)}, Got "$(ARCH)")
endif
通过shell指令获得$(AM_HOME)/scripts/目录下所有[.mk]文件,用notdir函数去掉目录部分,用basename函数去掉后缀,环境变量ARCHS即为所有mk文件的文件名合集。再利用filter函数,如果ARCH变量不在ARCHS列表中,将报error。
(ARCH即为编译时所使用的指令集架构,包含了native,spike,(x86/mips32/riscv32/riscv64)-nemu,riscv64-npc,(x86/x86_64)-qemu)
### Extract instruction set architecture (`ISA`) and platform from `$ARCH`. Example: `ARCH=x86_64-qemu -> ISA=x86_64; PLATFORM=qemu`
ARCH_SPLIT = $(subst -, ,$(ARCH))
ISA = $(word 1,$(ARCH_SPLIT))
PLATFORM = $(word 2,$(ARCH_SPLIT))
提取出ARCH中的各个关键字。首先用subst函数将ARCH中所有 '-' 替换成空格。再使用word函数,将架构和平台分别给到变量ISA和PLATFORM。(根据注释中给出的例子可以很直观了解)
3.1.2一般编译目标
### Create the destination directory (`build/$ARCH`)
WORK_DIR = $(shell pwd)
DST_DIR = $(WORK_DIR)/build/$(ARCH)
$(shell mkdir -p $(DST_DIR))
shell命令pwd为打印当前目录,将目录传入参数WORK_DIR,在当前目录下创建build/$(ARCH)作为目标目录,再调用shell命令mkdir创建名为DST_DIR的目录。
### Compilation targets (a binary image or archive)
IMAGE_REL = build/$(NAME)-$(ARCH)
IMAGE = $(abspath $(IMAGE_REL))
ARCHIVE = $(WORK_DIR)/build/$(NAME)-$(ARCH).a
abspath函数将IMAGE_REL中的各路径转换成绝对路径,并将转换后的结果返回。
### Collect the files to be linked: object files (`.o`) and libraries (`.a`)
OBJS = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS))))
LIBS := $(sort $(LIBS) am klib) # lazy evaluation ("=") causes infinite recursions
LINKAGE = $(OBJS) \
$(addsuffix -$(ARCH).a, $(join \
$(addsuffix /build/, $(addprefix $(AM_HOME)/, $(LIBS))), \
$(LIBS) ))
将变量SRCS里所有文件后缀去掉,使用addsuffix函数添加[.d]后缀。sort函数将LIBS内容重新排序。
在变量LINKAGE中,首先用addprefix、addsuffix函数为变量LIBS添加前后缀。再用join函数将两个字串连接,再添加后缀ARCH.a
3.1.3常规编译标志
### (Cross) compilers, e.g., mips-linux-gnu-g++
AS = $(CROSS_COMPILE)gcc
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
LD = $(CROSS_COMPILE)ld
OBJDUMP = $(CROSS_COMPILE)objdump
OBJCOPY = $(CROSS_COMPILE)objcopy
READELF = $(CROSS_COMPILE)readelf
交叉编译参数选项。
### Compilation flags
INC_PATH += $(WORK_DIR)/include $(addsuffix /include/, $(addprefix $(AM_HOME)/, $(LIBS)))
INCFLAGS += $(addprefix -I, $(INC_PATH))
CFLAGS += -O2 -MMD -Wall -Werror $(INCFLAGS) \
-D__ISA__=\"$(ISA)\" -D__ISA_$(shell echo $(ISA) | tr a-z A-Z)__ \
-D__ARCH__=$(ARCH) -D__ARCH_$(shell echo $(ARCH) | tr a-z A-Z | tr - _) \
-D__PLATFORM__=$(PLATFORM) -D__PLATFORM_$(shell echo $(PLATFORM) | tr a-z A-Z | tr - _) \
-DARCH_H=\"arch/$(ARCH).h\" \
-fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector \
-Wno-main -U_FORTIFY_SOURCE
CXXFLAGS += $(CFLAGS) -ffreestanding -fno-rtti -fno-exceptions
ASFLAGS += -MMD $(INCFLAGS)
tr函数为shell命令行中字符串替换函数,"tr a-z A-Z"即为将所有小写替换为大写字符。
3.1.4特定架构配置
-include $(AM_HOME)/scripts/$(ARCH).mk
引用scripts中的makefile文件,‘-’的作用是,当所要包含的文件不存在时不提示错误同时make也不会退出继续进行。
### Fall back to native gcc/binutils if there is no cross compiler
ifeq ($(wildcard $(shell which $(CC))),)
$(info # $(CC) not found; fall back to default gcc and binutils)
CROSS_COMPILE :=
endif
检查交叉编译器,如果没有将跳转回native。
3.1.5编译选项
### Rule (compile): a single `.c` -> `.o` (gcc)
$(DST_DIR)/%.o: %.c
@mkdir -p $(dir $@) && echo + CC $<
@$(CC) -std=gnu11 $(CFLAGS) -c -o $@ $(realpath $<)
&&是左边返回0(执行成功)再执行右边,和编程语言的逻辑且比较像。
||是左边返回非0(执行失败)再执行右边,和编程语言的逻辑或比较像。
3.2riscv64-nemu.mk(ARCH)
include $(AM_HOME)/scripts/isa/riscv64.mk
include $(AM_HOME)/scripts/platform/nemu.mk
CFLAGS += -DISA_H=\"riscv/riscv.h\"
AM_SRCS += riscv/nemu/start.S \
riscv/nemu/cte.c \
riscv/nemu/trap.S \
riscv/nemu/vme.c
AM-Makefile中会调用该文件。
3.3riscv64.mk(ISA)
CROSS_COMPILE := riscv64-linux-gnu-
COMMON_FLAGS := -fno-pic -march=rv64g -mcmodel=medany -mstrict-align
CFLAGS += $(COMMON_FLAGS) -static
ASFLAGS += $(COMMON_FLAGS) -O0
LDFLAGS += -melf64lriscv
3.4nemu.mk(PLATFORM)
AM_SRCS := platform/nemu/trm.c \
platform/nemu/ioe/ioe.c \
platform/nemu/ioe/timer.c \
platform/nemu/ioe/input.c \
platform/nemu/ioe/gpu.c \
platform/nemu/ioe/audio.c \
platform/nemu/ioe/disk.c \
platform/nemu/mpe.c
CFLAGS += -fdata-sections -ffunction-sections
LDFLAGS += -T $(AM_HOME)/scripts/linker.ld \
--defsym=_pmem_start=0x80000000 --defsym=_entry_offset=0x0
LDFLAGS += --gc-sections -e _start
NEMUFLAGS += -l $(shell dirname $(IMAGE).elf)/nemu-log.txt
CFLAGS += -DMAINARGS=\"$(mainargs)\"
CFLAGS += -I$(AM_HOME)/am/src/platform/nemu/include
.PHONY: $(AM_HOME)/am/src/platform/nemu/trm.c
image: $(IMAGE).elf
@$(OBJDUMP) -d $(IMAGE).elf > $(IMAGE).txt
@echo + OBJCOPY "->" $(IMAGE_REL).bin
@$(OBJCOPY) -S --set-section-flags .bss=alloc,contents -O binary $(IMAGE).elf $(IMAGE).bin
run: image
$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) run ARGS="$(NEMUFLAGS)" IMG=$(IMAGE).bin
gdb: image
$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) gdb ARGS="$(NEMUFLAGS)" IMG=$(IMAGE).bin