一生一芯学习记录(二):PA1 nemu代码导读

PA0是配环境和获取源码,教你如何使用一些简单的linux命令和工具,也非常重要,但是本文不赘述。

image

拿到一个工程首先就是要读源码,比如从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用来处理命令行输入的 -lgetopt_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的第一个必做题了,欲知后事如何,请听下回分解。

posted on 2025-09-04 10:55  yuweijie0124  阅读(21)  评论(0)    收藏  举报