nemu_main.c
int main(int argc, char *argv[]) {
/* Initialize the monitor. */
#ifdef CONFIG_TARGET_AM
am_init_monitor();
#else
init_monitor(argc, argv);
#endif
/* Start engine. */
engine_start();
return is_exit_status_bad();
}
先从 nemu_main.c 开始,宏观上来说,主要用来验证 CPU 模型正确性,主要流程为:
首先,进行初始化init_monitor(argc, argv);
然后,加载程序,执行指令。
最后,结束程序。
init_monitor(argc, argv);
时如何初始化的?
程序开始运行先打印 welcome(); 的信息。
static char *log_file = NULL; # 保存日志文件路径
static char *diff_so_file = NULL; # 保存差分测试动态库的路径
static char *img_file = NULL; # 保存镜像文件路径
static int difftest_port = 1234; # 设置差分测试使用的端口号(默认值为 1234)
oad_img();的作用是将fp指向的镜像文件,读取到 guest_to_host(RESET_VECTOR) 【待续】指向的内存中,然后返回镜像的大小。如下:
static long load_img() {
if (img_file == NULL) {
Log("No image is given. Use the default build-in image.");
return 4096;
}
FILE *fp = fopen(img_file, "rb"); # 用只读模式打开镜像文件,并将指针保存为fp
Assert(fp, "Can not open '%s'", img_file);
fseek(fp, 0, SEEK_END); # 将fp指针指向镜像文件的末尾
long size = ftell(fp); # 保存镜像的大小到size变量中
Log("The image is %s, size = %ld", img_file, size); # 输出镜像的大小与路径
fseek(fp, 0, SEEK_SET); # 将fp指针移动回镜像的首地址
int ret = fread(guest_to_host(RESET_VECTOR), size, 1, fp);
# 将镜像文件从fp指向的地址移动到guest_to_host(RESET_VECTOR)指向的地址
assert(ret == 1);
fclose(fp); # 关闭打开的镜像,释放指针
return size; # 返回镜像的尺寸
}
parse_args(); 用于解析命令行参数,有两部分首先定义参数表,其次对命令进行解析。
- -b 或 --batch,调用 sdb_set_batch_mode() 函数来启用批处理模式
- -p 或 --port,使用 sscanf 将 optarg(选项的参数,即端口号)解析为整数,并存储在 difftest_port 中
- -l 或 --log,将 optarg(选项的参数,即日志文件路径)存储在 log_file 中
- -d 或 --diff,将 optarg(选项的参数,即参考共享库文件路径)存储在 diff_so_file 中
这里命令解析的函数。
接着又定义了初始化 monitor的函数。
void init_monitor(int argc, char *argv[]) {
/* Perform some global initialization. */
/* Parse arguments. */
parse_args(argc, argv);
/* Set random seed. */
init_rand();
/* Open the log file. */
init_log(log_file);
/* Initialize memory. */
init_mem();
/* Initialize devices. */
IFDEF(CONFIG_DEVICE, init_device());
/* Perform ISA dependent initialization. */
init_isa();
/* Load the image to memory. This will overwrite the built-in image. */
long img_size = load_img();
/* Initialize differential testing. */
init_difftest(diff_so_file, img_size, difftest_port);
/* Initialize the simple debugger. */
init_sdb();
#ifndef CONFIG_ISA_loongarch32r
IFDEF(CONFIG_ITRACE, init_disasm(
MUXDEF(CONFIG_ISA_x86, "i686",
MUXDEF(CONFIG_ISA_mips32, "mipsel",
MUXDEF(CONFIG_ISA_riscv,
MUXDEF(CONFIG_RV64, "riscv64",
"riscv32"),
"bad"))) "-pc-linux-gnu"
));
#endif
/* Display welcome message. */
welcome();
}
分别是:读取指令、初始化随机数、初始化日志、初始化内存、初始化外设、初始化指令集、初始化差分测试、初始化简单调试器、根据不同的指令集选择不同的反汇编程序。最后打印welcome信息。
初始化日志:该函数决定日志输出的位置,如果传入了日志文件路径,则日志信息写入该文件;否则,默认输出到终端窗口。
初始化内存:该函数根据不同的编译选项,动态或静态地初始化物理内存。如果启用了CONFIG_PMEM_MALLOC,则通过 malloc() 分配内存;否则,物理内存是通过静态数组分配的。函数还可以随机化内存内容(如果启用了 CONFIG_MEM_RANDOM),并记录内存地址范围。
初始化指令集:将内置的镜像复制到物理内存中的重置向量地址,将内置镜像数据加载到模拟器的虚拟物理内存中;再用restart() 函数,初始化和重置整个虚拟计算机系统,NEMU被重置到一个已知的初始状态。
engine_start();
这个函数启动了sdb_mainloop();函数,是一个调试器循环,从用户输入中解析命令并执行相应的处理函数。
首先是:
if (is_batch_mode) {
cmd_c(NULL);
return;
}
如果将全局变量 is_batch_mode 设置为 true,表明调试器应该在批处理模式下运行。批处理模式意味着调试器会自动运行一些命令,而不需要等待用户输入。
此代码 is_batch_mode = false;
for (char *str; (str = rl_gets()) != NULL; ) {
char *str_end = str + strlen(str);
char *cmd = strtok(str, " ");
if (cmd == NULL) { continue; }
char *args = cmd + strlen(cmd) + 1;
if (args >= str_end) {
args = NULL;
}
for循环用来读取用户输入的指令
首先初始化一个str指针,再用rl_gets()读取用户输入的命令,返回指针赋值给str,str储存的就是输入指令首地址的指针。
str_end 储存的是输入指令末尾地址的指针。strlen(str) 不包括末尾的 “/0” 。
char *cmd = strtok(str, " ");
将命令以 “空格” 分成一个个小子串,cmd保存了第一个子串的指针。
char *args = cmd + strlen(cmd) + 1;
cmd 是第一个子串的指针,args 是 cmd 对应的参数的首指针。当 args ≥ str_end 时,就代表整个命令已经读完,让 args 指向空指针。
一部分
static struct {
const char *name;
const char *description;
int (*handler) (char *);
} cmd_table [] = {
{ "help", "Display information about all supported commands", cmd_help },
{ "c" , "Continue the execution of the program" , cmd_c },
{ "q" , "Exit NEMU" , cmd_q },
};
----------------------------------------------------------------------------
二部分
static int cmd_c(char *args) {
cpu_exec(-1);
return 0;
}
static int cmd_q(char *args) {
return -1;
}
-----------------------------------------------------------------------------
三部分
int i;
for (i = 0; i < NR_CMD; i ++) {
if (strcmp(cmd, cmd_table[i].name) == 0) {
if (cmd_table[i].handler(args) < 0) { return; }
break;
}
}
if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); }
for循环遍历 cmd_table 中的每一个命令,使用 strcmp 比较输入的命令 cmd 和命令表中第 i 个命令的名字 cmd_table[i].name。如果相等,说明找到了匹配的命令。如果遍历了所有命令都没有找到匹配项,则打印出 Unknown command 'cmd',表示输入的命令未识别。
如果找到了匹配的命令,执行 cmd_table[i].handler(args),即执行 cmd_c、 cmd_q 或者 cmd_help其中 handler 是处理该命令的函数,args 是命令的参数。
cmd_q 或者 cmd_help 不做分析,重点看 cmd_c。