一生一芯学习记录(二):PA1 nemu代码导读
PA0是配环境和获取源码,教你如何使用一些简单的linux命令和工具,也非常重要,但是本文不赘述。
拿到一个工程首先就是要读源码,比如从main函数一步一步看工程到底在干什么事情。(nemu不是从main开始的,但是不知道这个不耽误你理解nemu,这个在pa3.1有涉及)
那我这里顺便带着大伙读一读nemu源码。
nemu-main.c
#include <common.h>
void init_monitor(int, char *[]);
void am_init_monitor();
void engine_start();
int is_exit_status_bad();
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();
}
#ifdef CONFIG_TARGET_AM
这个东西是当你在am启动nemu的时候会定义的一个宏,理解nemu操作不需要这个,所以可以省略,所以一开始进入main之后就到了init_monitor(argc, argv)
的函数了。
void init_monitor(int argc, char *argv[]) {
/* Perform some global initialization. */
/* Parse arguments.通过getopt_long传进来的参数决定后面的行为 */
parse_args(argc, argv);
/* parse elf file*/
//printf("%s!!",elf_file);
#ifdef CONFIG_FTRACE
parse_elf(elf_file);
#endif
// parse_elf(elf_file);
/* Set random seed. */
init_rand();
/* Open the log file. */
init_log(log_file);
/* Initialize memory. */
init_mem();
/* Initialize devices. */
IFDEF(CONFIG_DEVICE, init_device());//如果定义了device,那就初始化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);
// printf("diff_so_file = %s\n",diff_so_file);
// printf("img_size = %ld\n",img_size);
/* Initialize the simple debugger. */
init_sdb();
IFDEF(CONFIG_ITRACE, init_disasm());
/*parse ftrace*/
/* Display welcome message. */
welcome();
}
就是一些初始化的,比如传参处理,加载日志文件,内存,isa,ftrace处理,启动处理等等。
首先我们来看一下parse_args(argc, argv)
函数。
static int parse_args(int argc, char *argv[]) {
const struct option table[] = {
{"batch" , no_argument , NULL, 'b'},
{"log" , required_argument, NULL, 'l'},
{"diff" , required_argument, NULL, 'd'},
{"port" , required_argument, NULL, 'p'},
{"ftrace" , required_argument, NULL, 'f'},
{"help" , no_argument , NULL, 'h'},
{0 , 0 , NULL, 0 },
};
int o;
while ( (o = getopt_long(argc, argv, "-bhl:d:p:f:e:", table, NULL)) != -1) {
switch (o) {
case 'b': sdb_set_batch_mode(); break;
case 'p': sscanf(optarg, "%d", &difftest_port); break;
case 'l': log_file = optarg; break;
case 'f': elf_file = optarg; break;
case 'd': diff_so_file = optarg; break;
case 1: img_file = optarg; return 0;
default:
printf("Usage: %s [OPTION...] IMAGE [args]\n\n", argv[0]);
printf("\t-b,--batch run with batch mode\n");
printf("\t-l,--log=FILE output log to FILE\n");
printf("\t-f,--ftrace=ELF_FILE ftrace ELF to log\n");
printf("\t-d,--diff=REF_SO run DiffTest with reference REF_SO\n");
printf("\t-p,--port=PORT run DiffTest with port PORT\n");
printf("\n");
exit(0);
}
}
return 0;
}
首先我们看到一个getopt_long
,看到不认识的函数首先懒得查的话那就网上随便搜一下,或者是man getopt_long
,你就能看到他给了一个example。
#include <getopt.h>
#include <stdio.h> /* for printf */
#include <stdlib.h> /* for exit */
int main(int argc, char *argv[])
{
int c;
int digit_optind = 0;
while (1) {
int this_option_optind = optind ? optind : 1;
int option_index = 0;
static struct option long_options[] = {
{"add", required_argument, 0, 0 },
{"append", no_argument, 0, 0 },
{"delete", required_argument, 0, 0 },
{"verbose", no_argument, 0, 0 },
{"create", required_argument, 0, 'c'},
{"file", required_argument, 0, 0 },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "abc:d:012",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
printf("option %s", long_options[option_index].name);
if (optarg)
printf(" with arg %s", optarg);
printf("\n");
break;
case '0':
case '1':
case '2':
if (digit_optind != 0 && digit_optind != this_option_optind)
printf("digits occur in two different argv-elements.\n");
digit_optind = this_option_optind;
printf("option %c\n", c);
break;
case 'a':
printf("option a\n");
break;
case 'b':
printf("option b\n");
break;
case 'c':
printf("option c with value '%s'\n", optarg);
break;
case 'd':
printf("option d with value '%s'\n", optarg);
break;
case '?':
break;
default:
printf("?? getopt returned character code 0%o ??\n", c);
}
}
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc)
printf("%s ", argv[optind++]);
printf("\n");
}
exit(EXIT_SUCCESS);
}
长得可以非常相似,所以挺好理解的。 getopt_long()
是用来处理命令行参数的,不过规模大一点的程序一般第一步就是处理命令行参数,类似的函数是getopt
,前者是用来处理长命令,后者是用于处理短命令。
比如getopt
用来处理命令行输入的 -l
,getopt_long()
用来处理命令行输入的--log
,因此你读懂makefile之后就可以在对应的位置加上参数以达到任务了。
注意 getopt_long
的第三个参数是 -bhl:d:p:f:e:
,开头的 - 字符很关键,
当遇到非选项参数(即不以 - 开头的参数)
时,返回 1
,然后不再解析参数,因为后续已经return 0
了。
当遇到定义的选项(如-b -l)
那就正常返回选项字符。
parse_elf
是后续C阶段的内容,这里可以先忽略,解析elf文件。
init_rand();
生成随机数种子。
init_mem()
初始化内存空间。
init_device()
初始化设备,注册mmio和初始化键盘等各种外设。
init_difftest
初始化difftest,讲img文件传输给spike。
init_sdb()
初始化sdb(simple debug)。
init_disasm()
根据有无ITRACE选项决定是否将你的二进制文件反汇编回去,方便你看。
welcome()
输出一些基本信息,是否开启trace,建立时间,客户端信息等等。
此时init_monitor
结束,开始engine_start
了,然后进入到里面发现除了之前那个CONFIG_TARGET_AM
,这个我们不会进行里面的函数,那么就只剩下sdb_mainloop
了。
那就到了我们nemu的第一个必做题了,欲知后事如何,请听下回分解。