nemu、am项目构建

nemu

首先研究 nemu 的构建方式。

直接看 nemu 的 Makefile(~/ysyx-workbench/nemu/Makefile):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#***************************************************************************************
# Copyright (c) 2014-2022 Zihao Yu, Nanjing University
#
# NEMU is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#
# See the Mulan PSL v2 for more details.
#**************************************************************************************/

# Sanity check
# 环境检查
# 条件的检查:ifeq 判断 $(wildcard $(NEMU_HOME)/src/nemu-main.c) 的结果是否为空。空结果表示 nemu-main.c 文件在指定目录下不存在。
ifeq ($(wildcard $(NEMU_HOME)/src/nemu-main.c),)
$(error NEMU_HOME=$(NEMU_HOME) is not a NEMU repo)
endif

# Include variables and rules generated by menuconfig
# 导入一些配置文件(自动生成)
-include $(NEMU_HOME)/include/config/auto.conf
-include $(NEMU_HOME)/include/config/auto.conf.cmd

# 定义函数:去掉上双引号""
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)

# Include all filelist.mk to merge file lists
FILELIST_MK = $(shell find -L ./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 -L $(DIRS-BLACKLIST-y) -name "*.c")
SRCS-y += $(shell find -L $(DIRS-y) -name "*.c")
SRCS = $(filter-out $(SRCS-BLACKLIST-y),$(SRCS-y))

# Extract compiler and options from menuconfig
CC = $(call remove_quote,$(CONFIG_CC))
CFLAGS_BUILD += -g
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) -DCALL_BATCH_MODE
LDFLAGS += $(CFLAGS_BUILD)

# 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 做的事情就是定义了一些变量,甚至设置了一些宏,然后还包含了一些Makeifile文件:

  • include $(NEMU_HOME)/scripts/config.mk
  • include $(NEMU_HOME)/scripts/native.mk

继续分析$(NEMU_HOME)/scripts/config.mk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#***************************************************************************************
# Copyright (c) 2014-2022 Zihao Yu, Nanjing University
#
# NEMU is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#
# See the Mulan PSL v2 for more details.
#**************************************************************************************/

COLOR_RED := $(shell echo "\033[1;31m")
COLOR_END := $(shell echo "\033[0m")

ifeq ($(wildcard .config),)
$(warning $(COLOR_RED)Warning: .config does not exists!$(COLOR_END))
$(warning $(COLOR_RED)To build the project, first run 'make menuconfig'.$(COLOR_END))
endif

Q := @
KCONFIG_PATH := $(NEMU_HOME)/tools/kconfig
FIXDEP_PATH := $(NEMU_HOME)/tools/fixdep
Kconfig := $(NEMU_HOME)/Kconfig
rm-distclean += include/generated include/config .config .config.old
silent := -s

CONF := $(KCONFIG_PATH)/build/conf
MCONF := $(KCONFIG_PATH)/build/mconf
FIXDEP := $(FIXDEP_PATH)/build/fixdep

$(CONF):
$(Q)$(MAKE) $(silent) -C $(KCONFIG_PATH) NAME=conf

$(MCONF):
$(Q)$(MAKE) $(silent) -C $(KCONFIG_PATH) NAME=mconf

$(FIXDEP):
$(Q)$(MAKE) $(silent) -C $(FIXDEP_PATH)

menuconfig: $(MCONF) $(CONF) $(FIXDEP)
$(Q)$(MCONF) $(Kconfig)
$(Q)$(CONF) $(silent) --syncconfig $(Kconfig)

savedefconfig: $(CONF)
$(Q)$< $(silent) --$@=configs/defconfig $(Kconfig)

%defconfig: $(CONF) $(FIXDEP)
$(Q)$< $(silent) --defconfig=configs/$@ $(Kconfig)
$(Q)$< $(silent) --syncconfig $(Kconfig)

.PHONY: menuconfig savedefconfig defconfig

# Help text used by make help
help:
@echo ' menuconfig - Update current config utilising a menu based program'
@echo ' savedefconfig - Save current config as configs/defconfig (minimal config)'

distclean: clean
-@rm -rf $(rm-distclean)

.PHONY: help distclean

define call_fixdep
@$(FIXDEP) $(1) $(2) unused > $(1).tmp
@mv $(1).tmp $(1)
endef

这个Makefile为NEMU项目提供了一个完整的构建和配置管理框架,支持配置文件的生成和管理,自动化构建工具的编译,以及项目的清理工作。这有助于项目维护者和用户方便地管理和构建项目,同时确保配置的一致性和项目的可重建性。

继续分析$(NEMU_HOME)/scripts/native.mk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#***************************************************************************************
# Copyright (c) 2014-2022 Zihao Yu, Nanjing University
#
# NEMU is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#
# See the Mulan PSL v2 for more details.
#**************************************************************************************/

-include $(NEMU_HOME)/../Makefile
include $(NEMU_HOME)/scripts/build.mk

include $(NEMU_HOME)/tools/difftest.mk

compile_git:
$(call git_commit, "compile NEMU")
$(BINARY):: compile_git

# Some convenient rules
# 使用override确保即使ARGS通过命令行被设置,这条追加操作也会生效,从而保证$(ARGS_DIFF)中的额外参数被包含在内。
override ARGS ?= --log=$(BUILD_DIR)/nemu-log.txt
override ARGS += $(ARGS_DIFF)

# Command to execute NEMU
# 可以手动指定 img
IMG ?=
NEMU_EXEC := $(BINARY) $(ARGS) $(IMG)

run-env: $(BINARY) $(DIFF_REF_SO)

run: run-env
$(call git_commit, "run NEMU")
$(NEMU_EXEC)

gdb: run-env
$(call git_commit, "gdb NEMU")
gdb -s $(BINARY) --args $(NEMU_EXEC)

clean-tools = $(dir $(shell find ./tools -maxdepth 2 -mindepth 2 -name "Makefile"))
$(clean-tools):
-@$(MAKE) -s -C $@ clean
clean-tools: $(clean-tools)
clean-all: clean distclean clean-tools

.PHONY: run gdb run-env clean-tools clean-all $(clean-tools)

可以看到,这个文件是真正构建 nemu 的文件,但是它依然包含了一下文件:

  • -include $(NEMU_HOME)/../Makefile
  • include $(NEMU_HOME)/scripts/build.mk
  • include $(NEMU_HOME)/tools/difftest.mk

继续分析$(NEMU_HOME)/../Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
...
...
# DO NOT modify the following code!!!

TRACER = tracer-ysyx
GITFLAGS = -q --author='$(TRACER) <tracer@ysyx.org>' --no-verify --allow-empty

YSYX_HOME = $(NEMU_HOME)/..
WORK_BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
WORK_INDEX = $(YSYX_HOME)/.git/index.$(WORK_BRANCH)
TRACER_BRANCH = $(TRACER)

LOCK_DIR = $(YSYX_HOME)/.git/

# prototype: git_soft_checkout(branch)
define git_soft_checkout
git checkout --detach -q && git reset --soft $(1) -q -- && git checkout $(1) -q --
endef

# prototype: git_commit(msg)
define git_commit
-@flock $(LOCK_DIR) $(MAKE) -C $(YSYX_HOME) .git_commit MSG='$(1)'
-@sync $(LOCK_DIR)
endef

.git_commit:
-@while (test -e .git/index.lock); do sleep 0.1; done; `# wait for other git instances`
-@git branch $(TRACER_BRANCH) -q 2>/dev/null || true `# create tracer branch if not existent`
-@cp -a .git/index $(WORK_INDEX) `# backup git index`
-@$(call git_soft_checkout, $(TRACER_BRANCH)) `# switch to tracer branch`
-@git add . -A --ignore-errors `# add files to commit`
-@(echo "> $(MSG)" && echo $(STUID) $(STUNAME) && uname -a && uptime `# generate commit msg`) \
| git commit -F - $(GITFLAGS) `# commit changes in tracer branch`
-@$(call git_soft_checkout, $(WORK_BRANCH)) `# switch to work branch`
-@mv $(WORK_INDEX) .git/index `# restore git index`

.clean_index:
rm -f $(WORK_INDEX)

_default:
@echo "Please run 'make' under subprojects."

.PHONY: .git_commit .clean_index _default

这个文件定义了函数git_commit、git_soft_checkout。其中commit通过一系列复杂的Git操作支持一个环境,它为代码提交提供了一个自动化的环境,包括自动添加学生的身份信息、系统信息和运行时间到提交信息中。

继续分析:$(NEMU_HOME)/scripts/build.mk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
.DEFAULT_GOAL = app

# Add necessary options if the target is a shared library
ifeq ($(SHARE),1)
SO = -so
CFLAGS += -fPIC -fvisibility=hidden
LDFLAGS += -shared -fPIC
endif

WORK_DIR = $(shell pwd)
BUILD_DIR = $(WORK_DIR)/build

INC_PATH := $(WORK_DIR)/include $(INC_PATH)
OBJ_DIR = $(BUILD_DIR)/obj-$(NAME)$(SO)
BINARY = $(BUILD_DIR)/$(NAME)$(SO)

# Compilation flags
ifeq ($(CC),clang)
CXX := clang++
else
CXX := g++
endif
LD := $(CXX)
INCLUDES = $(addprefix -I, $(INC_PATH))
CFLAGS := -O2 -MMD -Wall -Werror $(INCLUDES) $(CFLAGS)
LDFLAGS := -O2 $(LDFLAGS)

OBJS = $(SRCS:%.c=$(OBJ_DIR)/%.o) $(CXXSRC:%.cc=$(OBJ_DIR)/%.o)

# Compilation patterns
$(OBJ_DIR)/%.o: %.c
@echo + CC $<
@mkdir -p $(dir $@)
@$(CC) $(CFLAGS) -c -o $@ $<
$(call call_fixdep, $(@:.o=.d), $@)

$(OBJ_DIR)/%.o: %.cc
@echo + CXX $<
@mkdir -p $(dir $@)
@$(CXX) $(CFLAGS) $(CXXFLAGS) -c -o $@ $<
$(call call_fixdep, $(@:.o=.d), $@)

# Depencies
-include $(OBJS:.o=.d)

# Some convenient rules

.PHONY: app clean

app: $(BINARY)

$(BINARY):: $(OBJS) $(ARCHIVES)
@echo + LD $@
@$(LD) -o $@ $(OBJS) $(LDFLAGS) $(ARCHIVES) $(LIBS)

clean:
-rm -rf $(BUILD_DIR)

这个Makefile提供了一个完整的构建系统,支持从源代码到最终可执行文件或库的整个编译和链接过程,同时具备条件编译和依赖管理功能。

至于$(NEMU_HOME)/tools/difftest.mk,就不看了,因为这是后面的内容。

因此以上整体包含以下几个文件:

  • ~/ysyx-workbench/nemu/Makefile
    • $(NEMU_HOME)/scripts/config.mk
    • $(NEMU_HOME)/scripts/native.mk
      • $(NEMU_HOME)/../Makefile
      • $(NEMU_HOME)/scripts/build.mk
      • $(NEMU_HOME)/tools/difftest.mk

以上文件最终都会被包含到~/ysyx-workbench/nemu/Makefile 中。

这是一个非常重要的伪目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ifeq ($(wildcard .config),)
$(warning $(COLOR_RED)Warning: .config does not exists!$(COLOR_END))
$(warning $(COLOR_RED)To build the project, first run 'make menuconfig'.$(COLOR_END))
endif

Q := @
KCONFIG_PATH := $(NEMU_HOME)/tools/kconfig
FIXDEP_PATH := $(NEMU_HOME)/tools/fixdep
Kconfig := $(NEMU_HOME)/Kconfig
rm-distclean += include/generated include/config .config .config.old
silent := -s

CONF := $(KCONFIG_PATH)/build/conf
MCONF := $(KCONFIG_PATH)/build/mconf
FIXDEP := $(FIXDEP_PATH)/build/fixdep

$(CONF):
$(Q)$(MAKE) $(silent) -C $(KCONFIG_PATH) NAME=conf

$(MCONF):
$(Q)$(MAKE) $(silent) -C $(KCONFIG_PATH) NAME=mconf

$(FIXDEP):
$(Q)$(MAKE) $(silent) -C $(FIXDEP_PATH)

menuconfig: $(MCONF) $(CONF) $(FIXDEP)
$(Q)$(MCONF) $(Kconfig)
$(Q)$(CONF) $(silent) --syncconfig $(Kconfig)

从这里看出,这个为目标依赖于$(MCONF)、$(CONF)、$(FIXDEP),因此在 make menuconfig 的时候,会有以下操作:

1
2
$(Q)$(MCONF) $(Kconfig)
$(Q)$(CONF) $(silent) --syncconfig $(Kconfig)

此时mconf将会解析nemu/Kconfig中的描述, 以菜单树的形式展示各种配置选项, 供开发者进行选择;

运行命令conf –syncconfig nemu/Kconfig, 此时conf将会解析nemu/Kconfig中的描述, 并读取选择结果nemu/.config, 结合两者来生成如下文件:

  • 可以被包含到C代码中的宏定义(nemu/include/generated/autoconf.h), 这些宏的名称都是形如CONFIG_xxx的形式
  • 可以被包含到Makefile中的变量定义(nemu/include/config/auto.conf)
  • 可以被包含到Makefile中的, 和”配置描述文件”相关的依赖规则(nemu/include/config/auto.conf.cmd), 为了阅读代码, 我们可以不必关心它
  • 通过时间戳来维护配置选项变化的目录树nemu/include/config/, 它会配合另一个工具nemu/tools/fixdep来使用, 用于在更新配置选项后节省不必要的文件编译, 为了阅读代码, 我们可以不必关心它。

换句话说,我们首先得定义Kconfig,这是最关键的文件,它会被解析且配合我们的选择生成的 nemu/.config 文件,生成nemu/include/config/auto.conf以及可以被包含到Makefile中的, 和”配置描述文件”相关的依赖规则 nemu/include/config/auto.conf.cmd。

因此,我们生成后,只需要关注包含到Makefile中的:

1
2
3
# Include variables and rules generated by menuconfig
-include $(NEMU_HOME)/include/config/auto.conf
-include $(NEMU_HOME)/include/config/auto.conf.cmd

比如auto.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#
# Automatically generated file; DO NOT EDIT.
# NEMU Configuration Menu
#
CONFIG_DIFFTEST_REF_NAME="none"
CONFIG_ENGINE="interpreter"
CONFIG_PC_RESET_OFFSET=0
CONFIG_TARGET_NATIVE_ELF=y
CONFIG_MSIZE=0x8000000
CONFIG_CC_O2=y
CONFIG_MODE_SYSTEM=y
CONFIG_MEM_RANDOM=y
CONFIG_ITRACE=y
CONFIG_ISA_riscv=y
CONFIG_TRACE_END=10000
CONFIG_MBASE=0x80000000
CONFIG_TIMER_GETTIMEOFDAY=y
CONFIG_ENGINE_INTERPRETER=y
CONFIG_CC_OPT="-O2"
CONFIG_RT_CHECK=y
CONFIG_ITRACE_COND="true"
CONFIG_PMEM_MALLOC=y
CONFIG_CC="gcc"
CONFIG_DIFFTEST_REF_PATH="none"
CONFIG_TRACE_START=0
CONFIG_CC_GCC=y
CONFIG_TRACE=y
CONFIG_ISA="riscv32"

由于该文件被首先包含,因此后面包含的其它文件,都可以引用该变量。

run

1
2
3
4
5
6
7
# Command to execute NEMU
IMG ?=
NEMU_EXEC := $(BINARY) $(ARGS) $(IMG)
run-env: $(BINARY) $(DIFF_REF_SO)
run: run-env
$(call git_commit, "run NEMU")
$(NEMU_EXEC)

可以看到,如果没有设置 IMG 的变量,那么 IMG 就是用默认值空。

通过打印信息:

1
2
3
4
5
6
run: run-env
@echo $(NEMU_EXEC)
@echo "over"
$(call git_commit, "run NEMU")
$(NEMU_EXEC)

输出:

1
2
3
4
5
6
make run
make[1]: Entering directory '/home/luyoung/ysyx-workbench'
make[1]: Leaving directory '/home/luyoung/ysyx-workbench'
/home/luyoung/ysyx-workbench/nemu/build/riscv32-nemu-interpreter --log=/home/luyoung/ysyx-workbench/nemu/build/nemu-log.txt
over
...

可以看到 $(BINARY): /home/luyoung/ysyx-workbench/nemu/build/riscv32-nemu-interpreter、$(ARGS):–log=/home/luyoung/ysyx-workbench/nemu/build/nemu-log.txt $(IMG):。

就是这样构建riscv32-nemu-interpreter的。

因此,以上文件大致就是这样组织的:

am

接着看 am-kernels,这个工程将会构建镜像,并且引用 nemu 来执行镜像,看起来比 nemu 复杂一点。

首先直接看 am-kernels/tests/cpu-tests/Makefile ,它是我们执行程序的开始。

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

.PHONY: all run gdb clean latest $(ALL)

RESULT = .result
$(shell > $(RESULT))

COLOR_RED = \033[1;31m
COLOR_GREEN = \033[1;32m
COLOR_NONE = \033[0m

ALL = $(basename $(notdir $(shell find tests/. -name "*.c")))

all: $(addprefix Makefile., $(ALL))
@echo "test list [$(words $(ALL)) item(s)]:" $(ALL)

$(ALL): %: Makefile.%

Makefile.%: tests/%.c latest
@/bin/echo -e "NAME = $*\nSRCS = $<\ninclude $${AM_HOME}/Makefile" > $@
@if make -s -f $@ ARCH=$(ARCH) $(MAKECMDGOALS); then \
printf "[%14s] $(COLOR_GREEN)PASS$(COLOR_NONE)\n" $* >> $(RESULT); \
else \
printf "[%14s] $(COLOR_RED)***FAIL***$(COLOR_NONE)\n" $* >> $(RESULT); \
fi
-@rm -f Makefile.$*

run: all
@cat $(RESULT)
@rm $(RESULT)

gdb: all

clean:
rm -rf Makefile.* build/

latest:


如果 ALL 没有制定的话,它会找到这个文件夹中的所有文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
find tests/. -name "*.c"
tests/./max.c
tests/./load-store.c
tests/./quick-sort.c
tests/./leap-year.c
tests/./add.c
tests/./mul-longlong.c
tests/./goldbach.c
tests/./matrix-mul.c
tests/./mersenne.c
tests/./bit.c
tests/./movsx.c
tests/./hello-str.c
tests/./add-longlong.c
tests/./wanshu.c
tests/./bubble-sort.c
tests/./crc32.c
tests/./switch.c
tests/./sub-longlong.c
tests/./string.c
tests/./shift.c
tests/./select-sort.c
tests/./mov-c.c
tests/./pascal.c
tests/./unalign.c
tests/./dummy.c
tests/./shuixianhua.c
tests/./recursion.c
tests/./min3.c
tests/./fact.c
tests/./fib.c
tests/./to-lower-case.c
tests/./div.c
tests/./prime.c
tests/./sum.c
tests/./if-else.c

然后去掉路径、扩展名,这样的设置通常用于构建项目中需要编译多个源文件的情况,ALL 变量提供了一个便于其他 Makefile 规则引用的目标列表。

接着会创建一系列 Makefile.%,然后编译这个文件(以ALL=bit 为例):

1
2
3
4
NAME = bit
SRCS = tests/bit.c
include /home/luyoung/ysyx-workbench/abstract-machine/Makefile

可以看到这个文件依然包含了一个 Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# Makefile for AbstractMachine Kernels and Libraries

### *Get a more readable version of this Makefile* by `make html` (requires python-markdown)
html:
cat Makefile | sed 's/^\([^#]\)/ \1/g' | markdown_py > Makefile.html
.PHONY: html

## 1. Basic Setup and Checks

### Default to create a bare-metal kernel image
ifeq ($(MAKECMDGOALS),)
MAKECMDGOALS = image
.DEFAULT_GOAL = image
endif

### Override checks when `make clean/clean-all/html`
ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)

### Print build info message
$(info # Building $(NAME)-$(MAKECMDGOALS) [$(ARCH)])

### Check: environment variable `$AM_HOME` looks sane
ifeq ($(wildcard $(AM_HOME)/am/include/am.h),)
$(error $$AM_HOME must be an AbstractMachine repo)
endif

### 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

### 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))

### Check if there is something to build
ifeq ($(flavor SRCS), undefined)
$(error Nothing to build)
endif

### Checks end here
endif

## 2. General Compilation Targets

### Create the destination directory (`build/$ARCH`)
WORK_DIR = $(shell pwd)
DST_DIR = $(WORK_DIR)/build/$(ARCH)
$(shell mkdir -p $(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

### 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) ))

## 3. General Compilation Flags

### (Cross) compilers, e.g., mips-linux-gnu-g++
AS = $(CROSS_COMPILE)gcc
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
LD = $(CROSS_COMPILE)ld
AR = $(CROSS_COMPILE)ar
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))

ARCH_H := arch/$(ARCH).h
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_H)\" \
-fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector \
-Wno-main -U_FORTIFY_SOURCE -fvisibility=hidden
CXXFLAGS += $(CFLAGS) -ffreestanding -fno-rtti -fno-exceptions
ASFLAGS += -MMD $(INCFLAGS)
LDFLAGS += -z noexecstack

## 4. Arch-Specific Configurations

### Paste in arch-specific configurations (e.g., from `scripts/x86_64-qemu.mk`)
-include $(AM_HOME)/scripts/$(ARCH).mk

### 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

## 5. Compilation Rules

### Rule (compile): a single `.c` -> `.o` (gcc)
$(DST_DIR)/%.o: %.c
@mkdir -p $(dir $@) && echo + CC $<
@$(CC) -std=gnu11 $(CFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.cc` -> `.o` (g++)
$(DST_DIR)/%.o: %.cc
@mkdir -p $(dir $@) && echo + CXX $<
@$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.cpp` -> `.o` (g++)
$(DST_DIR)/%.o: %.cpp
@mkdir -p $(dir $@) && echo + CXX $<
@$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.S` -> `.o` (gcc, which preprocesses and calls as)
$(DST_DIR)/%.o: %.S
@mkdir -p $(dir $@) && echo + AS $<
@$(AS) $(ASFLAGS) -c -o $@ $(realpath $<)

### Rule (recursive make): build a dependent library (am, klib, ...)
$(LIBS): %:
@$(MAKE) -s -C $(AM_HOME)/$* archive

### Rule (link): objects (`*.o`) and libraries (`*.a`) -> `IMAGE.elf`, the final ELF binary to be packed into image (ld)
$(IMAGE).elf: $(OBJS) $(LIBS)
@echo + LD "->" $(IMAGE_REL).elf
@$(LD) $(LDFLAGS) -o $(IMAGE).elf --start-group $(LINKAGE) --end-group

### Rule (archive): objects (`*.o`) -> `ARCHIVE.a` (ar)
$(ARCHIVE): $(OBJS)
@echo + AR "->" $(shell realpath $@ --relative-to .)
@$(AR) rcs $(ARCHIVE) $(OBJS)

### Rule (`#include` dependencies): paste in `.d` files generated by gcc on `-MMD`
-include $(addprefix $(DST_DIR)/, $(addsuffix .d, $(basename $(SRCS))))

## 6. Miscellaneous

### Build order control
image: image-dep
archive: $(ARCHIVE)
image-dep: $(OBJS) $(LIBS)
@echo \# Creating image [$(ARCH)]
.PHONY: image image-dep archive run $(LIBS)

### Clean a single project (remove `build/`)
clean:
rm -rf Makefile.html $(WORK_DIR)/build/
.PHONY: clean

### Clean all sub-projects within depth 2 (and ignore errors)
CLEAN_ALL = $(dir $(shell find . -mindepth 2 -name Makefile))
clean-all: $(CLEAN_ALL) clean
$(CLEAN_ALL):
-@$(MAKE) -s -C $@ clean
.PHONY: clean-all $(CLEAN_ALL)

通过 Makefile 可以看到:

  • 如果MAKECMDGOALS 没有被定义,那么MAKECMDGOALS=image。

  • 如果MAKECMDGOALS 不为一些特殊目标,比如 clean:

    • 打印信息,比如# Building bit-run [riscv32-nemu]
    • 环境检查
    • 检查 ARCH 是否在列表中
    • 提取 ARCH
    • 检查被编译目标
  • 设置工作目录WORK_DIR: /am-kernels/tests/cpu-tests/

  • 设置目标目录DST_DIR: /am-kernels/tests/cpu-tests/build/riscv-nemu/

  • 设置编译目标IMAGE_REL: build/bit-riscv32-nemu

  • 设置镜像IMAGE: /home/luyoung/ysyx-workbench/am-kernels/tests/cpu-tests/build/bit-riscv32-nemu(这里使用的绝对路径)

  • 收集要被连接的文件:

    • OBJS: /am-kernels/tests/cpu-tests/build/riscv-nemu/bit.o
    • LIBS: am klib
    • LINKAGE: /am-kernels/tests/cpu-tests/build/riscv-nemu/bit.o /home/luyoung/ysyx-workbench/abstract-machine/am/build/am-riscv32-nemu.a /home/luyoung/ysyx-workbench/abstract-machine/kilb/build/klib-riscv32-nemu.a
  • 交叉编译变量

  • 编译标志:

    • INC_PATH: /am-kernels/tests/cpu-tests/include /home/luyoung/ysyx-workbench/abstract-machine/am/include/ /home/luyoung/ysyx-workbench/abstract-machine/klib/include/
    • INCFLAGS: -I$(INC_PATH)
    • ARCH_H: arch/riscv32-nemu.h
    • CFLAGS
    • CXXFLAGS
    • ASFLAGS
    • LDFLAGS

接着又导入了一个非常重要的可包含文件:/home/luyoung/ysyx-workbench/abstract-machine/scripts/riscv32-nemu.mk:

1
2
3
4
5
6
7
8
9
10
11
12
include $(AM_HOME)/scripts/isa/riscv.mk
include $(AM_HOME)/scripts/platform/nemu.mk
CFLAGS += -DISA_H=\"riscv/riscv.h\"
COMMON_CFLAGS += -march=rv32im_zicsr -mabi=ilp32 # overwrite
LDFLAGS += -melf32lriscv # overwrite

AM_SRCS += riscv/nemu/start.S \
riscv/nemu/cte.c \
riscv/nemu/trap.S \
riscv/nemu/vme.c


这里又包含了两个可导入文件:

1
2
3
4
5
6
7
8
CROSS_COMPILE := riscv64-linux-gnu-
COMMON_CFLAGS := -fno-pic -march=rv64g -mcmodel=medany -mstrict-align
CFLAGS += $(COMMON_CFLAGS) -static
ASFLAGS += $(COMMON_CFLAGS) -O0
LDFLAGS += -melf64lriscv

# overwrite ARCH_H defined in $(AM_HOME)/Makefile
ARCH_H := arch/riscv.h

它补充了CROSS_COMPILE的定义(注意延迟赋值和立即赋值的区别)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

这个文件在编译目标文件.bin,然后继续编译 nemu,通过 nemu 运行.bin

再回到am 的 Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
### Build order control
image: image-dep
archive: $(ARCHIVE)
image-dep: $(OBJS) $(LIBS)
@echo \# Creating image [$(ARCH)]
.PHONY: image image-dep archive run $(LIBS)

### Clean a single project (remove `build/`)
clean:
rm -rf Makefile.html $(WORK_DIR)/build/
.PHONY: clean

### Clean all sub-projects within depth 2 (and ignore errors)
CLEAN_ALL = $(dir $(shell find . -mindepth 2 -name Makefile))
clean-all: $(CLEAN_ALL) clean
$(CLEAN_ALL):
-@$(MAKE) -s -C $@ clean
.PHONY: clean-all $(CLEAN_ALL)

这里在引入目标 image 之后,确定了一下依赖关系。

到这里事情就比较清晰了:nemu负责构建模拟器,am负责构建镜像文件,并且强大的am可以适配不同的isa和platform。而从运行层次上来说,nemu负责跟host打交道,am负责跟platform(在这里是nemu)打交道。


nemu、am项目构建
http://blog.luliang.online/2024/08/23/nemu_am项目构建/
作者
Luyoung
发布于
2024年8月23日
许可协议