找回密码
 立即注册
首页 业界区 业界 一生一芯学习:基础设施(2)

一生一芯学习:基础设施(2)

毋峻舷 昨天 23:05
指令执行的踪迹 - itrace
首先写好一个环形缓冲区的代码,把反汇编的字符串存到环形缓冲区中,然后执行完代码在打印出来。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <stdbool.h>
  4. #include <string.h>
  5. #define IRINGBUF_SIZE 16
  6. #define LOGBUF_SIZE 128
  7. // 定义环形缓冲区结构体
  8. typedef struct {
  9.     char buffer[IRINGBUF_SIZE][LOGBUF_SIZE]; // 每个环形缓冲器的格子里面放一个字符串
  10.     int head;
  11.     int count;
  12. } CircularBuffer;
  13. void initBuffer(CircularBuffer *cb){
  14.     //strcpy(cb->buffer , (char *)malloc(sizeof(char) * IRINGBUF_SIZE * LOGBUF_SIZE));
  15.     cb->head = 0;
  16.     cb->count = 0;
  17. }
  18. void enqueue(CircularBuffer *cb, const char *logbuf) {
  19.     strncpy(cb->buffer[cb->head], logbuf, LOGBUF_SIZE - 1);
  20.     cb->buffer[cb->head][LOGBUF_SIZE - 1] = '\0';
  21.     cb->head = (cb->head + 1) % IRINGBUF_SIZE; //能直接15+1变成0
  22.     if (cb->count < IRINGBUF_SIZE) {
  23.         cb->count++;
  24.     }
  25. }
  26. void printBuffer(CircularBuffer *cb) {
  27.     if (cb->count == 0) {
  28.         //printf("缓冲区为空\n");
  29.         return;
  30.     }
  31.     int idx = (cb->head + IRINGBUF_SIZE - cb->count) % IRINGBUF_SIZE;
  32.     for (int i = 0; i < cb->count; i++) {
  33.         if(i == cb->count-1){
  34.         printf("->%s\n", cb->buffer[(idx + i) % IRINGBUF_SIZE]);
  35.         }
  36.         else{
  37.         printf("  %s\n", cb->buffer[(idx + i) % IRINGBUF_SIZE]);
  38.         }
  39.     }
  40. }
复制代码
  1. static void execute(uint64_t n) {
  2.   Decode s;
  3.   initBuffer(&cb); // 初始化环形缓冲区,大小为BUFFER_SIZE
  4.   for (;n > 0; n --) {
  5.     exec_once(&s, cpu.pc);
  6.     g_nr_guest_inst ++;
  7.     trace_and_difftest(&s, cpu.pc);
  8.     if (nemu_state.state != NEMU_RUNNING) {break;}
  9.     IFDEF(CONFIG_DEVICE, device_update());
  10.   }/*条件编译宏,如果CONFIG_DEVICE被定义,则调用device_update函数,如果 CONFIG_DEVICE 没有被定义,
  11.   这一行什么都不会生成(等价于被注释掉)。*/
  12.   printBuffer(&cb);
  13. }
复制代码
内存访问的踪迹 - mtrace
只需要在paddr_read()和paddr_write()中进行记录即可.
  1. //读物理地址
  2. word_t paddr_read(paddr_t addr, int len) {
  3.   //printf("进来了\n");
  4.   if (likely(in_pmem(addr))) {
  5.     //printf("进来了\n");
  6.     IFDEF(CONFIG_MTRACE, Log("read in address = " FMT_PADDR ", len = %d\n", addr, len));
  7.     return pmem_read(addr, len);
  8.   }
  9.   IFDEF(CONFIG_DEVICE, return mmio_read(addr, len));
  10.   //printf("");
  11.   out_of_bound(addr);
  12.   return 0;
  13. }
  14. //写物理地址
  15. void paddr_write(paddr_t addr, int len, word_t data) {
  16.   if (likely(in_pmem(addr))) {
  17.     pmem_write(addr, len, data);
  18.     IFDEF(CONFIG_MTRACE, Log("write in address = " FMT_PADDR ", len = %d, data = " FMT_WORD "\n", addr, len, data));
  19.     return; }
  20.   
  21.   IFDEF(CONFIG_DEVICE, mmio_write(addr, len, data); return);
  22.   out_of_bound(addr);
  23.   //Log("weiwei");
  24. }
复制代码
FTRACE
首先要知道ftrace是用来追踪程序执行过程中的函数调用和返回的。函数的调用和返回一般要使用jal和jalr这两条指令,然后去看下反汇编,发现call行为发生在当rd=1时候的jal中和当rd=1的或者rd=0,imm=0的jalr中,return发生在当inst=0x00008067中。
  1.   INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal    , J, R(rd) = s->pc + 4;
  2.    s->dnpc = s->pc + imm;
  3.    IFDEF(CONFIG_FTRACE, {
  4.     if (rd == 1) {
  5.         call_trace(s->pc, s->dnpc);
  6.     }})
  7.    );
  8.   INSTPAT("??????? ????? ????? 000 ????? 11001 11", jalr   , I, R(rd) = s->pc + 4;
  9.    s->dnpc = (src1 + imm) & (~1);
  10.    IFDEF(CONFIG_FTRACE,{
  11.     if (s->isa.inst == 0x00008067)
  12.         ret_trace(s->pc);
  13.     else if (rd == 1) {call_trace(s->pc, s->dnpc);}
  14.     else if (rd == 0 && imm == 0) {call_trace(s->pc, s->dnpc);}
  15.    })
  16.    );
复制代码
然后你需要传输elf文件给nemu
可以通过parse_args()函数来实现这一功能。
首先定义一个elf文件
  1. static char *elf_file = NULL;
  2. void sdb_set_batch_mode(); //批处理模式
  3. static char *log_file = NULL;
  4. static char *diff_so_file = NULL;
  5. static char *img_file = NULL;
  6. static int difftest_port = 1234;
复制代码
然后在parse_args()函数中传入参数
  1. static int parse_args(int argc, char *argv[]) {
  2.   const struct option table[] = {
  3.     {"batch"    , no_argument      , NULL, 'b'},
  4.     {"log"      , required_argument, NULL, 'l'},
  5.     {"diff"     , required_argument, NULL, 'd'},
  6.     {"port"     , required_argument, NULL, 'p'},
  7.     {"ftrace"   , required_argument, NULL, 'f'},
  8.     {"help"     , no_argument      , NULL, 'h'},
  9.     {0          , 0                , NULL,  0 },
  10.   };
  11.   int o;
  12.   while ( (o = getopt_long(argc, argv, "-bhl:d:p:f:e:", table, NULL)) != -1) {
  13.     switch (o) {
  14.       case 'b': sdb_set_batch_mode(); break;
  15.       case 'p': sscanf(optarg, "%d", &difftest_port); break;
  16.       case 'l': log_file = optarg; break;
  17.       case 'f': elf_file = optarg; break;
  18.       case 'd': diff_so_file = optarg; break;
  19.       case 1: img_file = optarg; return 0;
  20.       default:
  21.         printf("Usage: %s [OPTION...] IMAGE [args]\n\n", argv[0]);
  22.         printf("\t-b,--batch              run with batch mode\n");
  23.         printf("\t-l,--log=FILE           output log to FILE\n");
  24.         printf("\t-f,--ftrace=ELF_FILE    ftrace ELF to log\n");
  25.         printf("\t-d,--diff=REF_SO        run DiffTest with reference REF_SO\n");
  26.         printf("\t-p,--port=PORT          run DiffTest with port PORT\n");
  27.         printf("\n");
  28.         exit(0);
  29.     }
  30.   }
复制代码
除此之外需要在AM的Makefile中写入参数如下:
NEMUFLAGS += -f $(IMAGE).elf
现在elf算是正确传入nemu中了。
于是现在需要处理elf文件了,初始化一下elf文件,采用之前KCONFIIG中宏定义的方式
  1. void init_monitor(int argc, char *argv[]) {
  2.   /* Perform some global initialization. */
  3.   /* Parse arguments.通过getopt_long传进来的参数决定后面的行为 */
  4.   parse_args(argc, argv);
  5.   /* parse elf file*/
  6.   //printf("%s!!",elf_file);
  7.   #ifdef CONFIG_FTRACE
  8.                 parse_elf(elf_file);
  9.   #endif
  10. // parse_elf(elf_file);
  11.   /* Set random seed. */
  12.   init_rand();
  13.   /* Open the log file. */
  14.   init_log(log_file);
  15.   /* Initialize memory. */
  16.   init_mem();
  17.   /* Initialize devices. */
  18.   IFDEF(CONFIG_DEVICE, init_device());//如果定义了device,那就初始化device,晚点看。
  19.   /* Perform ISA dependent initialization. */
  20.   init_isa();
  21.   /* Load the image to memory. This will overwrite the built-in image. */
  22.   long img_size = load_img();
  23.   /* Initialize differential testing. */
  24.   init_difftest(diff_so_file, img_size, difftest_port);
  25.   // printf("diff_so_file = %s\n",diff_so_file);
  26.   // printf("img_size = %ld\n",img_size);
  27.   /* Initialize the simple debugger. */
  28.   init_sdb();
  29.   IFDEF(CONFIG_ITRACE, init_disasm());
  30.   /*parse ftrace*/
  31.   /* Display welcome message. */
  32.   welcome();
  33. }
  34. #else // CONFIG_TARGET_AM
  35. static long load_img() {1
  36.   extern char bin_start, bin_end;
  37.   size_t size = &bin_end - &bin_start;
  38.   Log("img size = %ld", size);
  39.   memcpy(guest_to_host(RESET_VECTOR), &bin_start, size);
  40.   return size;
  41. }
复制代码
这里先将ftrace.c的代码贴出
  1. //#include <device/map.h>
  2. #include <fcntl.h>
  3. #include <elf.h>
  4. #include <unistd.h>
  5. #include <common.h>
  6. typedef struct SymbolEntry {
  7.         char name[128];        //函数名
  8.         unsigned char info;     //ELF符号类型信息
  9.         paddr_t address;       //函数起始地址
  10.         word_t size;        //函数大小
  11. } SymbolEntry;
  12. static SymbolEntry* sym_entrys = NULL;
  13. static uint32_t sym_num = 0;
  14. static uint32_t call_depth = 0;
  15. static uint32_t trace_func_call_flag = 0;
  16. void init_symtab_entrys(FILE *elf_file) {
  17.         if (elf_file == NULL) assert(0);
  18.         Elf32_Ehdr ehdr;
  19.         int result = fread(&ehdr, sizeof(Elf32_Ehdr), 1, elf_file);
  20.         assert(&ehdr != NULL && result == 1);
  21.     // 检查 ELF 魔数 16进制打开所有的elf文件前四个必须是这四个
  22.     if (ehdr.e_ident[0] != 0x7F ||
  23.         ehdr.e_ident[1] != 'E' ||
  24.         ehdr.e_ident[2] != 'L' ||
  25.         ehdr.e_ident[3] != 'F') {
  26.         printf("Not a ELF file\n");
  27.         exit(0);
  28.     }
  29.         Elf32_Shdr *shdrs = malloc(sizeof(Elf32_Shdr) * ehdr.e_shnum);//申请节头表的内存空间
  30.     assert(shdrs != 0);
  31.         result = fseek(elf_file, ehdr.e_shoff, SEEK_SET); //根据文件的开头和偏移跳转到段表
  32.         assert(result == 0);
  33.         result = fread(shdrs, sizeof(Elf32_Shdr), ehdr.e_shnum, elf_file);//从文件中读取shnum个节头,每个节点的大小是sizeof elfshdr
  34.         assert(result != 0);
  35.     //遍历节头表,查找符号表和字符串表,用偏移赋值给他
  36.         Elf32_Shdr *symtab = NULL;
  37.     Elf32_Shdr *strtab = NULL;
  38.         for (int i = 0; i < ehdr.e_shnum; i++) {
  39.                 if (shdrs[i].sh_type == SHT_SYMTAB) {
  40.                         symtab = shdrs + i;  
  41.             }
  42.         if (shdrs[i].sh_type == SHT_STRTAB) {
  43.                         strtab = shdrs + i;  
  44.             }
  45.   }
  46.         assert(symtab != NULL);
  47.         //计算符号表中条目数量 shsize是符号表的大小   shentsize是每个符号条目的大小
  48.     //两者相除得到符号表中包含的符号总数量,赋值给全局变量symnum
  49.     //获得符号表在ELF文件中的偏移量
  50.         uint32_t entry_num = symtab->sh_size / symtab->sh_entsize;
  51.         sym_num = entry_num;       
  52.         uint32_t offset = symtab->sh_offset; //符号数据在文件的起始位置的偏移量多少,用于后面进来读取具体内容。
  53.         //把符号表的内容读取到symbol tables中,从 ELF 文件中读取 entry_num 个符号,存入 symbol_tables 数组中。
  54.         Elf32_Sym *symbol_tables = malloc(sizeof(Elf32_Sym) * entry_num);
  55.         result = fseek(elf_file, offset, SEEK_SET);
  56.         assert(result == 0);
  57.         result = fread(symbol_tables, sizeof(Elf32_Sym), entry_num, elf_file);
  58.         assert(result != 0);
  59.         // 初始化自定义符号表
  60.         sym_entrys = malloc(sizeof(SymbolEntry) * entry_num);
  61.     char *str = malloc(strtab -> sh_size);
  62.     int str_result = fseek(elf_file, strtab -> sh_offset, SEEK_SET);
  63.     assert(str_result == 0);
  64.     str_result = fread(str, 1, strtab -> sh_size, elf_file);
  65.     assert(str_result != 0);
  66.         assert(str != NULL);
  67.     //把strtab中的str解析出来。
  68.         for (int i = 0; i < entry_num; i++) {
  69.                 strcpy(sym_entrys[i].name, str + symbol_tables[i].st_name);
  70.                 sym_entrys[i].info = symbol_tables[i].st_info;
  71.                 sym_entrys[i].address = (paddr_t) symbol_tables[i].st_value;
  72.                 sym_entrys[i].size = (word_t) symbol_tables[i].st_size;
  73.         }
  74.         
  75.         free(shdrs);
  76.         free(symbol_tables);
  77.         free(str);
  78. }
  79. void parse_elf(const char *elf_file) {
  80.         if (elf_file == NULL) {
  81.                 return;
  82.         }       
  83.         Log("The elf file is %s\n", elf_file);
  84.         trace_func_call_flag = 1;
  85.         FILE *file = fopen(elf_file, "rb");
  86.         assert(file != NULL);
  87.         init_symtab_entrys(file);
  88. }
  89. char *get_function_name_by_addres(paddr_t addr) {
  90.         for (int i = 0; i < sym_num; i++) {
  91.                 if (ELF32_ST_TYPE(sym_entrys[i].info) == STT_FUNC) {
  92.                         if (addr >= sym_entrys[i].address && addr <
  93.                 (sym_entrys[i].size + sym_entrys[i].address)) {
  94.                                 return sym_entrys[i].name;
  95.                         }
  96.                 }
  97.         }
  98.         return NULL;
  99. }
  100. void call_trace(paddr_t pc, paddr_t target) {
  101.         if (trace_func_call_flag == 0) return;
  102.         ++call_depth;
  103.         char *name  = get_function_name_by_addres(target);
  104.         Log(FMT_PADDR ":%*scall [%s@" FMT_PADDR "]\n", pc, call_depth , "", name, target);
  105. }
  106. void ret_trace(paddr_t pc) {
  107.         if (trace_func_call_flag == 0) return;
  108.         char *name = get_function_name_by_addres(pc);
  109.         Log(FMT_PADDR ":%*sret [%s]\n",pc, call_depth , "", name);
  110.         --call_depth;
  111. }
复制代码
重点来看一下这个init_symtab_entrys函数。
第一行把elf文件拖进来,如果不存在那就assert。
然后定义一个Elf32_Ehdr的变量ehdr并且把传进来的elf文件的文件头结构体传进来,重要字段包括

  • e_ident:魔数和文件类型标识
  • e_shnum:头节表项大小和数量
  • e_shoff:头节表偏移(用于定位符号表等)
首先根据ELF头中的e_shnum分配一块数组,用来存放所有节头。
根据ehdr.e_shoff的偏移和elf_file的开头跳转到段表。
并冲文件中读取到ehdr.e_shnum个节头。
随后遍历节头表并且寻找里面的符号包和字符串表。
symtab->sh_size是符号表的大小
symtab->sh_entsize是每个符号条目的大小
而这俩相除可以得到符号表条目的数量。
然后获得符号数据在文件的起始位置的偏移量。
然后把符号表的内容读取到symbol_tables中,
sym_entrys = malloc(sizeof(SymbolEntry) * entry_num);
为 entry_num 个符号分配一个自定义符号条目数组
然后把所需的name info address存进去
大概流程总结一下:
1.用fopen打开ELF文件并读取ELF头(Elf32_Ehdr),校验一下ELF的魔数。
2.根据 ELF 头的 e_shoff/e_shnum,定位并读入所有节头表(Elf32_Shdr 数组)。
3.在节头表中找到符号表节(sh_type == SHT_SYMTAB),记下:
符号节偏移 sh_offset、大小 sh_size、每项大小 sh_entsize。
正确做法:用 symtab->sh_link 找到“与符号表关联的字符串表节索引”,然后取出该字符串表节作为符号名表(不要随便用第一个 SHT_STRTAB)。
4.计算条目数:entry_num = sh_size / sh_entsize,分配数组读取所有 Elf32_Sym 条目(fseek→fread)。
5.读出字符串表:fseek 到字符串表的 sh_offset,分配缓冲区并 fread 整个字符串表数据。
6.遍历每个符号条目,提取并保存:
sym_entrys.info = symbol_tables.st_info; // st_info
sym_entrys.address = (paddr_t)symbol_tables.st_value; // st_value
sym_entrys.size = (word_t)symbol_tables.st_size; // st_size
名字:str + symbol_tables.st_name(先检查 st_name < strtab->sh_size,再拷贝,避免越界,使用 strncpy)
可选过滤:只处理 ELF32_ST_TYPE(st_info) == STT_FUNC(或根据需要过滤局部/全局符号)
7.释放临时缓冲(节头表、symbol_tables、字符串表),但保留 sym_entrys 与 sym_num 供运行时查询。
8.注意事项:检查返回值、避免越界、处理 32/64 位差异、注意字节序与 sh_entsize 是否与 sizeof(Elf32_Sym) 匹配。
初始化完elf文件把重要信息放到自定义符号表中后根据pc的地址找到符号表的名字。
  1. char *get_function_name_by_addres(paddr_t addr) {
  2.         for (int i = 0; i < sym_num; i++) {
  3.                 if (ELF32_ST_TYPE(sym_entrys[i].info) == STT_FUNC) {
  4.                         if (addr >= sym_entrys[i].address && addr <
  5.                 (sym_entrys[i].size + sym_entrys[i].address)) {
  6.                                 return sym_entrys[i].name;
  7.                         }
  8.                 }
  9.         }
  10.         return NULL;
  11. }
复制代码
随后在jal和jalr中调用call和ret就行。
最后在KCONFIG中像之前一样定义config_ftrace,在monitor.c中宏定义if是否使用这个ftrace即可。
还是非常的困难的。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册