news 2026/1/12 9:26:00

一生一芯学习:程序,运行时环境与AM(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一生一芯学习:程序,运行时环境与AM(一)

前我们已经跑通了cpu-test和实现了riscv-I型指令所需的42条指令,现在我们已经可以到跑简单程序的地步了,我们也希望运行简单的程序,因此我们需要运行时环境(runtime environment)。

比如现在要结束程序,那我们就要用提前准备好的API如void halt,调用这个halt()就可以来结束运行,那我们怎么实现这个halt呢,来看看源码。

void halt(int code) {

nemu_trap(code);

// should not reach here

while (1);

}

那nemu_trap(code)是什么意思呢,在/ysyx-workbench/abstract-machine/am/src/platform/nemu/include中有这样一段代码,判断所使用的架构并且给出对应架构退出的内联汇编。

#if defined(__ISA_X86__)

# define nemu_trap(code) asm volatile ("int3" : :"a"(code))

#elif defined(__ISA_MIPS32__)

# define nemu_trap(code) asm volatile ("move $v0, %0; sdbbp" : :"r"(code))

#elif defined(__riscv)

# define nemu_trap(code) asm volatile("mv a0, %0; ebreak" : :"r"(code))

#elif defined(__ISA_LOONGARCH32R__)

# define nemu_trap(code) asm volatile("move $a0, %0; break 0" : :"r"(code))

#else

# error unsupported ISA __ISA__

#endif

随便找一个程序来看反汇编,用户程序会以这种形式成为机器码。

80000fdc <halt>:

void halt(int code) {

nemu_trap(code);

80000fdc: 00050513 mv a0,a0

80000fe0: 00100073 ebreak

//里面是一个嵌入汇编语句,宏会把一个识别结束的结束码移动到通用寄存器中

// should not reach here

while (1);

80000fe4: 0000006f j 80000fe4 <halt+0x8>

80000fe8 <_trm_init>:

}

正好对应上了。

此时我很好奇# define nemu_trap(code) asm volatile("mv a0, %0; ebreak" : :"r"(code))的"r"(code)到底指的是什么。

首先明确说明这是一个内联汇编,具体看https://luyoung0001.github.io/2025/02/06/C%E8%AF%AD%E8%A8%80%E5%86%85%E8%81%94%E6%B1%87%E7%BC%96/

指的是操作数和约束,r意思是“通用寄存器”,编译器会为操作数分配一个合适的通用寄存器 (例如 x1 - x31)。

在汇编指令模板中,使用 %0, %1, %2… 来引用操作数。

%0 对应第一个操作数,%1 对应第二个操作数,以此类推。

编译生成一个可以在NEMU的运行时环境上运行的程序的过程大致如下:

gcc将$ISA-nemu的AM实现源文件编译成目标文件, 然后通过ar将这些目标文件作为一个库, 打包成一个归档文件abstract-machine/am/build/am-$ISA-nemu.a

gcc把应用程序源文件(如am-kernels/tests/cpu-tests/tests/dummy.c)编译成目标文件

通过gcc和ar把程序依赖的运行库(如abstract-machine/klib/)也编译并打包成归档文件

根据Makefile文件abstract-machine/scripts/$ISA-nemu.mk中的指示, 让ld根据链接脚本abstract-machine/scripts/linker.ld, 将上述目标文件和归档文件链接成可执行文件。

根据上述链接脚本的指示, 可执行程序重定位后的节从0x100000或0x80000000开始 (取决于_pmem_start和_entry_offset的值), 首先是.text节, 其中又以abstract-machine/am/src/$ISA/nemu/start.S中自定义的entry节开始, 然后接下来是其它目标文件的.text节. 这样, 可执行程序起始处总是放置start.S的代码, 而不是其它代码, 保证客户程序总能从start.S开始正确执行. 链接脚本也定义了其它节(包括.rodata, .data, .bss)的链接顺序, 还定义了一些关于位置信息的符号, 包括每个节的末尾, 栈顶位置, 堆区的起始和末尾.

我们对编译得到的可执行文件的行为进行简单的梳理:

第一条指令从abstract-machine/am/src/$ISA/nemu/start.S开始, 设置好栈顶之后就跳转到abstract-machine/am/src/platform/nemu/trm.c的_trm_init()函数处执行.

在_trm_init()中调用main()函数执行程序的主体功能, main()函数还带一个参数, 目前我们暂时不会用到, 后面我们再介绍它.

从main()函数返回后, 调用halt()结束运行.

有了TRM这个简单的运行时环境, 我们就可以很容易地在上面运行各种"简单"的程序了. 当然, 我们也可以运行"不简单"的程序: 我们可以实现任意复杂的算法, 甚至是各种理论上可计算的问题, 都可以在TRM上解决.

解读一下abstract-machine项目的Makefile

这里的MAKECMDGOALS就是你在命令行make后面带的参数,如果make后面没有参数,就让makecmdgoals等于image,并且让默认目标等于image

ifeq ($(MAKECMDGOALS),)

MAKECMDGOALS = image

.DEFAULT_GOAL = image

endif

检查am环境目录以及ARCH架构是否为预估的riscv32-nemu or riscv32e-npc,再判断是否有正确的.c文件。

### Override checks when `make clean/clean-all/html`

### 清理和生成文档的时候跳过检查

ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)

### Print build info message

$(info # Buildin $(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

#notdir用于去掉文件的绝对路径,只保留文件名。path/nemu.mk ---> nemu.mk

#basename取前缀函数, nemu.mk ---> nemu

ARCHS = $(basename $(notdir $(shell ls $(AM_HOME)/scripts/*.mk)))

#filter函数用于检查 $(ARCH) 是否在 $(ARCHS) 列表中。如果 $(ARCH) 在列表中,返回 $(ARCH)。如果不在,返回空字符串。

#arch如果不在archs中,返回空字符串 ifeq逻辑生效 直接error。

ifeq ($(filter $(ARCHS), $(ARCH)), )

$(error Expected $$ARCH in {$(ARCHS)}, Got "$(ARCH)")

endif

#subst字符串替换函数,$(subst <from>,<to>,<text>),from替换成to在text中 逻辑:将ARCH中的-替换成“空格”

### Extract instruction set architecture (`ISA`) and platform from `$ARCH`. Example: `ARCH=x86_64-qemu -> ISA=x86_64; PLATFORM=qemu`

#word取单词 1 2是第几个单词,逻辑就是isa取split中第一个单词,platform取split中第二个单词。

ARCH_SPLIT = $(subst -, ,$(ARCH))

ISA = $(word 1,$(ARCH_SPLIT))

PLATFORM = $(word 2,$(ARCH_SPLIT))

$(info # Using ISA=$(ISA), PLATFORM=$(PLATFORM))

$(info # SRCS=$(SRCS))

### Check if there is something to build

###flavor是寻找变量函数,当SRCS在makefile及其include中都没有时,函数输出undefined。ifeq就生效执行error

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_rel中的文件变成绝对路径之后返回给IMAGE

IMAGE = $(abspath $(IMAGE_REL))

ARCHIVE = $(WORK_DIR)/build/$(NAME)-$(ARCH).a

### Collect the files to be linked: object files (`.o`) and libraries (`.a`)

#basename取SRCS的前缀 addsuffix给SRCS的后缀加一个.o addprefix给SRCS.o加一个类似路径的前缀。

OBJS = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS))))

#sort是排序函数,将$(LIBS) am klib 按照首字母进行升序,并去除重复的单词。

LIBS := $(sort $(LIBS) am klib) # lazy evaluation ("=") causes infinite recursions

LINKAGE = $(OBJS) # library archives are added by LIB_TEMPLATE below

制定好交叉编译所需的工具链,因为最终的可执行文件是需要在RISCV机器(nemu或者是npc)上运行,因此需要使用RV对应的工具链,如果是loonarch或者mips32也可以指定对应的工具链。

告诉编译器正确的路径如何找到库函数.h

并给予合适的编译选项。

## 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告诉编译器在哪里找到.h文件

#给LIBS加上amhome前缀之后加上include后缀再加上workdir/include,给库生成正确路径

INC_PATH += $(WORK_DIR)/include $(addsuffix /include/, $(addprefix $(AM_HOME)/, $(LIBS)))

#给incpath加上-I前缀

INCFLAGS += $(addprefix -I, $(INC_PATH))

ARCH_H := arch/$(ARCH).h

#-O2:启用中级优化(平衡性能与编译速度)。-MMD:生成依赖文件(.d),用于自动追踪头文件变更。

#-Wall -Werror:启用所有警告并将其视为错误(严格模式)。 $(INCFLAGS):可能包含额外的头文件搜索路径(如 -Iinclude)。

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 $(addprefix -T, $(LDSCRIPTS))

引入对应架构的makefile文件,如果是riscv32-nemu的话那就是引入scripts/riscv32-nemu.mk的makefile

### 4. Arch-Specific Configurations

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

-include $(AM_HOME)/scripts/$(ARCH).mk

这里就是前面所说的编译过程,首先将各种.c .cpp .cc .S编译成.o后放到对应的dstdir中。

随后用ar工具将这些.o文件打包为一个.a静态库,最终根据riscv32-nemu.mk的指示通过链接脚本将上述目标文件和静态库等等变成一个可执行文件。

## 5. Compilation Rules

### Rule (compile): a single `.c` -> `.o` (gcc)

#%.c是依赖的文件,要把.c .cc .cpp .S编译成.o放到dstdir中

$(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 $<)

###把所有的.s .c .cc .cpp文件转换为.o文件放到对应目录

ifeq ($(MAKECMDGOALS),archive)

### Rule (archive): objects (`*.o`) -> `ARCHIVE.a` (ar)

#make archive用ar把所有的.o文件打包成一个.a静态库

#如果不是archive会递归调用每个依赖库如(am klib)的makefile

#把他们也编译成.a静态库,然后在链接的时候把这些.a静态库和.o文件一起链接成最终的elf文件

$(ARCHIVE): $(OBJS)

@echo + AR "->" $(shell realpath $@ --relative-to .)

@$(AR) rcs $@ $^

else

# $(1): library name

define LIB_TEMPLATE =

$$(AM_HOME)/$(1)/build/$(1)-$$(ARCH).a: force

@$$(MAKE) -s -C $$(AM_HOME)/$(1) archive

LINKAGE += $$(AM_HOME)/$(1)/build/$(1)-$$(ARCH).a

endef

### Rule (recursive make): build a dependent library (am, klib, ...)

$(foreach lib, $(LIBS), $(eval $(call LIB_TEMPLATE,$(lib))))

endif

### 最终链接Rule (link): objects (`*.o`) and libraries (`*.a`) -> `IMAGE.elf`, the final ELF binary to be packed into image (ld)

### 把所有的目标文件和库文件连接成最终的ELF image

$(IMAGE).elf: $(LINKAGE) $(LDSCRIPTS)

@echo \# Creating image [$(ARCH)]

@echo + LD "->" $(IMAGE_REL).elf

ifneq ($(filter $(ARCH),native),)

@$(CXX) -o $@ -Wl,--whole-archive $(LINKAGE) -Wl,-no-whole-archive $(LDFLAGS_CXX)

else

@$(LD) $(LDFLAGS) -o $@ --start-group $(LINKAGE) --end-group

endif

### Rule (`#include` dependencies): paste in `.d` files generated by gcc on `-MMD`

-include $(addprefix $(DST_DIR)/, $(addsuffix .d, $(basename $(SRCS))))

makefile的最后是一些依赖和更新,确保有文件修改后makefile能及时更新并进行编译。

## 6. Miscellaneous各种依赖及更新

## .PHONY 保证这些命令每次都能执行,不受文件名影响

### Build order control

image: image-dep

archive: $(ARCHIVE)

image-dep: $(IMAGE).elf

.PHONY: image image-dep archive run

### Force to rebuild a rule

force:

.PHONY: force

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/30 1:12:57

II CZOI Round 7P14081 「CZOI-R7」炸弹游戏

题目描述花火要和你在晖长石号上玩一个游戏&#xff01;规则是这样的&#xff1a;晖长石号可以被视为一个 个点组成的图&#xff0c;初始的时候没有任何边。你可以在这 个点之间连 条无向边&#xff0c;不允许有重边和自环。花火会在这 个点中选出 个点放炸弹。为了不让你在拆炸…

作者头像 李华
网站建设 2025/12/26 1:56:31

【打靶日记】VulNyx 之 Listen

主机发现 ┌──(root㉿xhh)-[~/Desktop/xhh/VluNyx/listen] └─# arp-scan -I eth1 -l192.168.56.151 08:00:27:1b:16:5c PCS Systemtechnik GmbH主机地址为 端口扫描 ┌──(root㉿xhh)-[~/Desktop/xhh/VluNyx/listen] └─# nmap -p- 192.168.56.151 …

作者头像 李华
网站建设 2026/1/9 10:49:13

无人驾驶车辆轨迹跟踪与模型预测控制第二版配套程序整理分享

无人驾驶车辆轨迹跟踪与模型预测控制第二版书中配套程序整理&#xff0c;包括MATLAB simulink模型与Carsim par文件。 一共从第二章到第八章。 已经完全适配Carsim2019与MATLAB2018a以上版本&#xff0c;最好为MATLAB2021a。 包括相关的电子资料。 非常适合学习模型预测控制&am…

作者头像 李华
网站建设 2025/12/17 22:52:29

Cadence 1.8V LDO电路设计:从带隙基准到完整实现

cadance 1.8v LDO电路 cadance virtuoso 设计 模拟电路设计 LDO带隙基准电路设计 带设计报告&#xff08;14页word&#xff09; 基于tsmc18工艺 模拟ic设计 bandgapLDO 1.8v LDO电路 包含工程文件和报告 可以直接打开最近在模拟IC设计的领域里摸爬滚打&#xff0c;深入研究了基…

作者头像 李华