一生一芯 预学习阶段 NEMU代码学习(1)

 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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值