makefile 和 qemu

本节导读

为了帮助大家进一步理解我们的项目的链接和编译的过程,这里简要介绍一下 makefile 的内容。

警告

注意,makefile 在整个实验过程中不可修改,否则可能导致 CI 无法通过!

makefile 内部

指定编译使用的工具

TOOLPREFIX = riscv64-unknown-elf-
CC = $(TOOLPREFIX)gcc
AS = $(TOOLPREFIX)gas
LD = $(TOOLPREFIX)ld
OBJCOPY = $(TOOLPREFIX)objcopy
OBJDUMP = $(TOOLPREFIX)objdump
GDB = $(TOOLPREFIX)gdb

这里makefile调用了大家设定好的PATH之中的riscv64工具链。如果没有设置好,那么之后的编译就会因为找不到这些文件而出错。

添加编译flag

CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb
CFLAGS += -MD
CFLAGS += -mcmodel=medany
CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax
CFLAGS += -I.
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)

比较需要注意的是我们设置了警告也会报错,因此大家写代码的时候最好避免 warning 的出现,这是良好的编程习惯。

设置编译目标

# 目录定义
K = os
BUILDDIR = build
# .o 目标的确定,也就是 os 目录下所有的 .c 和 .s 都编译成 .o
C_SRCS = $(wildcard $K/*.c)
AS_SRCS = $(wildcard $K/*.S)
C_OBJS = $(addprefix $(BUILDDIR)/, $(addsuffix .o, $(basename $(C_SRCS))))
AS_OBJS = $(addprefix $(BUILDDIR)/, $(addsuffix .o, $(basename $(AS_SRCS))))
OBJS = $(C_OBJS) $(AS_OBJS)
# kernel 镜像由所有的 .o 按照 kernel.ld 链接而成
$(BUILDDIR)/kernel: $(OBJS) $(K)/kernel.ld
   $(LD) $(LDFLAGS) -T kernel.ld -o kernel $(OBJS)

请同学们自行查阅并了解``wildcard``、addprefixaddsuffix``basename``的意义。

运行 qemu

QEMU = qemu-system-riscv64
QEMUOPTS = \
   -nographic \
   -smp $(CPUS) \
   -machine virt \
   -bios $(BOOTLOADER) \
   -kernel kernel

run: $(BUILDDIR)/kernel
$(QEMU) $(QEMUOPTS)

这里和前面一致。大家不需要太关心qemu的更多细节,我们涉及它的操作已经在makefile和sbi之中处理了。

gdb 调试

# QEMU's gdb stub command line changed in 0.11
QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
   then echo "-gdb tcp::1234"; \
   else echo "-s -p 1234"; fi)

debug: kernel .gdbinit
   $(QEMU) $(QEMUOPTS) -S $(QEMUGDB) &
   sleep 1
   $(GDB)

使用 make debug 来使用 gdb 调试 qemu。程序自身执行的机制和直接 make run 一样。在解析 bootloader 的行为时可以使用 gdb 在其中添加断点来查看对应寄存器和内存的内容。gdb的具体使用方法和汇编课程上一致。不熟悉的同学可以在训练章节查看到可能用到的gdb指令的简单用法,也十分推荐同学们自学一些基础的 gdb 使用方法,掌握 gdb 对本课程帮助很大。

LOG 支持

ifeq ($(LOG), error)
CFLAGS += -D LOG_LEVEL_ERROR
else ifeq ($(LOG), warn)
CFLAGS += -D LOG_LEVEL_WARN
else ifeq ($(LOG), info)
CFLAGS += -D LOG_LEVEL_INFO
else ifeq ($(LOG), debug)
CFLAGS += -D LOG_LEVEL_DEBUG
else ifeq ($(LOG), trace)
CFLAGS += -D LOG_LEVEL_TRACE
endif

我们的 log 等级选择是通过 -D 参数来实现的,这也是大家 make run LOG=xxx 的原理。从这里我们也可以看到 LOG 的可选值。