当前位置: 首页 > news >正文

PA2.2-基础设施(2)

📚 使用须知

  • 本博客内容仅供学习参考
  • 建议理解思路后独立实现
  • 欢迎交流讨论

task : 基础设施(2)

bug诊断的利器 - 踪迹

指令执行的踪迹 - itrace

// nemu/src/cpu/cpu-exec.c
static void exec_once(Decode *s, vaddr_t pc) {s->pc = pc;s->snpc = pc;isa_exec_once(s);cpu.pc = s->dnpc;
#ifdef CONFIG_ITRACEchar *p = s->logbuf;/* * p += snprintf(p, sizeof(s->logbuf), "0x%08x:", s->pc);* 以十六进制的形式打印出当前pc的值* sizeof(s->logbuf) 写入的最大字符数* such as : 0x80000000:*/p += snprintf(p, sizeof(s->logbuf), FMT_WORD ":", s->pc);/** ilen always = 4*/int ilen = s->snpc - s->pc;int i;uint8_t *inst = (uint8_t *)&s->isa.inst.val;/** 每次以十六进制的形式打印出8bit出来, 因为 uint8_t *inst * all time of print is ilen = 4* such as: 00 00 04 13*/for (i = ilen - 1; i >= 0; i --) {p += snprintf(p, 4, " %02x", inst[i]);}int ilen_max = MUXDEF(CONFIG_ISA_x86, 8, 4);int space_len = ilen_max - ilen;if (space_len < 0) space_len = 0;space_len = space_len * 3 + 1;memset(p, ' ', space_len);p += space_len;void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte);disassemble(p, s->logbuf + sizeof(s->logbuf) - p,MUXDEF(CONFIG_ISA_x86, s->snpc, s->pc), (uint8_t *)&s->isa.inst.val, ilen);#endif
}/**********************************************/
//nemu/src/utils/disasm.cc
/** disassemble(p, s->logbuf + sizeof(s->logbuf) - p,MUXDEF(CONFIG_ISA_x86, s->snpc, s->pc), (uint8_t *)&s->isa.inst.val, ilen);* from nemu/src/cpu/cpu-exec.c* char *str is logbuf which string buf writed to build/nemu-log.txt* int size is the remaining memory of logbuf * uint64_t pc is the pc当前指向的地址(have not +4)* uint8_t *code is 指向指令的指针* int nbyte is 指令的字节长度*/
extern "C" void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte) {MCInst inst;llvm::ArrayRef<uint8_t> arr(code, nbyte);uint64_t dummy_size = 0;gDisassembler->getInstruction(inst, dummy_size, arr, pc, llvm::nulls());std::string s;raw_string_ostream os(s);gIP->printInst(&inst, pc, "", *gSTI, os);int skip = s.find_first_not_of('\t');const char *p = s.c_str() + skip;assert((int)s.length() - skip < size);strcpy(str, p);
}

我们能够看到,在exec_once函数中,每次执行完一条指令,我们就会将这个条指令的地址,指令本身,以及通过disassemble函数得到的指令反汇编保存到s->logbuf

那么s->logbuf是如何写到build/nemu-log中的呢?

// First: 使用Makefile在编译链接的时候传人参数-l(跟我们上面传入参数使得开启批处理模式相同)
// Second: 解析-l的参数 
//nemu/src/monitor/monitor.c
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'},{"help"     , no_argument      , NULL, 'h'},{0          , 0                , NULL,  0 },};int o;while ( (o = getopt_long(argc, argv, "-bhl:d:p:", 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 '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-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;
}//third: 
//nemu/src/cpu/cpu-exec.c
//在execut中执行trace_and_difftest(&s, cpu.pc);
static void execute(uint64_t n) {Decode s;for (;n > 0; n --) {exec_once(&s, cpu.pc);g_nr_guest_inst ++;trace_and_difftest(&s, cpu.pc);if (nemu_state.state != NEMU_RUNNING) break;IFDEF(CONFIG_DEVICE, device_update());}
}static void trace_and_difftest(Decode *_this, vaddr_t dnpc) {
#ifdef CONFIG_ITRACE_CONDif (ITRACE_COND) { log_write("%s\n", _this->logbuf); }
#endifif (g_print_step) { IFDEF(CONFIG_ITRACE, puts(_this->logbuf)); }IFDEF(CONFIG_DIFFTEST, difftest_step(_this->pc, dnpc));IFDEF(CONFIG_WATCHPOINT, checkWatchPoint());
}

trace_and_difftest函数中ITRACE_COND这个宏是通过我们使用gcc -D ITRACE_COND=true 传过来的,源代码中并未定义

-D 选项是 GCC 编译器的一个选项,用于定义预处理器宏。通过 -D 选项,我们可以在编译时为源代码中的宏指定一个值。

log_write("%s\n", _this->logbuf);这条语句的作用便是将我们s->logbuf中的内容写到我们通过-l传入的文件中了

if (g_print_step) { IFDEF(CONFIG_ITRACE, puts(_this->logbuf)); }这条语句是通过g_print_step判断是否要直接打印到终端中,一个例子是当我们再NEMU中使用命令si的时候可以看到我们将s->logbuf的内容打印到终端了

指令环形缓冲区 - iringbuf

task : 实现iringbuf

根据上述内容, 在NEMU中实现iringbuf. 你可以按照自己的喜好来设计输出的格式, 如果你想输出指令的反汇编, 可以参考itrace的相关代码; 如果你不知道应该在什么地方添加什么样的代码, 你就需要RTFSC了.

在哪添加代码?

想想当出现访问物理内存越界的时候是哪里在报错?

//nemu/src/memory/paddr.c
static void out_of_bound(paddr_t addr) {panic("address = " FMT_PADDR " is out of bound of pmem [" FMT_PADDR ", " FMT_PADDR "] at pc = " FMT_WORD,addr, PMEM_LEFT, PMEM_RIGHT, cpu.pc);
}
//nemu/include/debug.h
#define panic(format, ...) Assert(0, format, ## __VA_ARGS__)/* * 看来是通过Assert来实现的报错的,我们不妨看看Assert中的内容*/
//nemu/include/debug.h
#define Assert(cond, format, ...) \do { \if (!(cond)) { \MUXDEF(CONFIG_TARGET_AM, printf(ANSI_FMT(format, ANSI_FG_RED) "\n", ## __VA_ARGS__), \(fflush(stdout), fprintf(stderr, ANSI_FMT(format, ANSI_FG_RED) "\n", ##  __VA_ARGS__))); \IFNDEF(CONFIG_TARGET_AM, extern FILE* log_fp; fflush(log_fp)); \extern void assert_fail_msg(); \assert_fail_msg(); \assert(cond); \} \} while (0)// 出现了个assert_fail_msg()函数,有点眼熟
//nemu/src/cpu/cpu-exec.c
void assert_fail_msg() {isa_reg_display();statistic();
}

看来我们要在assert_fail_msg输出它

img

//nemu/src/cpu/cpu-exec.c
void assert_fail_msg() {isa_reg_display();IFDEF(CONFIG_IRINGTRACE, iringbuf_display());statistic();
}//nemu/src/isa/riscv32/inst.c
int isa_exec_once(Decode *s) {s->isa.inst.val = inst_fetch(&s->snpc, 4);IFDEF(CONFIG_IRINGTRACE, iringbuf_get(*s));return decode_exec(s);
}//nemu/src/utils/trace.c 我自己新建立的文件
#include <common.h>
#include <cpu/decode.h>
#define IRINGBUF_SIZE 16static Decode iringbuf[IRINGBUF_SIZE];
/*The next instruction should be placed at the index in iringbuf*/
static int iringbuf_nextIdx = 0;void iringbuf_get(Decode s){iringbuf[iringbuf_nextIdx++] = s;if (iringbuf_nextIdx >= IRINGBUF_SIZE)iringbuf_nextIdx = 0;
}static void iringbuf_translate(Decode *s){char *p = s->logbuf;p += snprintf(p, sizeof(s->logbuf), FMT_WORD ":", s->pc);int ilen = s->snpc - s->pc;int i;uint8_t *inst = (uint8_t *)&s->isa.inst.val;for (i = ilen - 1; i >= 0; i --) {p += snprintf(p, 4, " %02x", inst[i]);}int ilen_max = MUXDEF(CONFIG_ISA_x86, 8, 4);int space_len = ilen_max - ilen;if (space_len < 0) space_len = 0;space_len = space_len * 3 + 1;memset(p, ' ', space_len);p += space_len;#ifndef CONFIG_ISA_loongarch32rvoid disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte);disassemble(p, s->logbuf + sizeof(s->logbuf) - p,MUXDEF(CONFIG_ISA_x86, s->snpc, s->pc), (uint8_t *)&s->isa.inst.val, ilen);
#elsep[0] = '\0'; // the upstream llvm does not support loongarch32r
#endif
}/** 一般来说, 我们只会关心出错现场前的trace, 在运行一些大程序的时候, 运行前期的trace大多时候没有查看甚至输出的必要. * 一个很自然的想法就是, 我们能不能在客户程序出错(例如访问物理内存越界)的时候输出最近执行的若干条指令呢?* 要实现这个功能其实并不困难, 我们只需要维护一个很简单的数据结构 - 环形缓冲区(ring buffer)即可
*/
void iringbuf_display(){int iringbuf_nowIdx = (iringbuf_nextIdx - 1) < 0 ? 31 : iringbuf_nextIdx - 1; int i;for (i = 0; i < IRINGBUF_SIZE; i++){if (i == iringbuf_nowIdx)printf("%-4s","-->");else printf("%-4s","   ");iringbuf_translate(&iringbuf[i]);printf("%s\n",iringbuf[i].logbuf);}
}

内存访问的踪迹 - mtrace

task : 实现mtrace

这个功能非常简单, 你已经想好如何实现了: 只需要在paddr_read()paddr_write()中进行记录即可. 你可以自行定义mtrace输出的格式.

不过和最后只输出一次的iringbuf不同, 程序一般会执行很多访存指令, 这意味着开启mtrace将会产生大量的输出, 因此最好可以在不需要的时候关闭mtrace. 噢, 那就参考一下itrace的相关实现吧: 尝试在Kconfig和相关文件中添加相应的代码, 使得我们可以通过menuconfig来打开或者关闭mtrace. 另外也可以实现mtrace输出的条件, 例如你可能只会关心某一段内存区间的访问, 有了相关的条件控制功能, mtrace使用起来就更加灵活了.

void mtraceRead_display(paddr_t addr, int len){printf("read address = " FMT_PADDR " at pc = " FMT_WORD " with byte = %d\n",addr, cpu.pc, len);
}void mtraceWrite_display(paddr_t addr, int len, word_t data){printf("write address = " FMT_PADDR " at pc = " FMT_WORD " with byte = %d and data =" FMT_WORD "\n",addr, cpu.pc, len, data);
}

函数调用的踪迹 - ftrace

task : 实现ftrace

根据上述内容, 在NEMU中实现ftrace. 你可以自行决定输出的格式. 你需要注意以下内容:

你需要为NEMU传入一个ELF文件, 你可以通过在`parse_args()`中添加相关代码来实现这一功能
你可能需要在初始化`ftrace`时从ELF文件中读出符号表和字符串表, 供你后续使用
关于如何解析ELF文件, 可以参考`man 5 elf`
如果你选择的是riscv32, 你还需要考虑如何从`jal`和`jalr`指令中正确识别出函数调用指令和函数返回指令

注意, 你不应该通过readelf等工具直接解析ELF文件. 在真实的项目中, 这个方案确实可以解决问题; 但作为一道学习性质的题目, 其目标是让你了解ELF文件的组织结构, 使得将来你在必要的时候(例如在裸机环境中)可以自己从中解析出所需的信息. 如果你通过readelf等工具直接解析ELF文件, 相当于自动放弃训练的机会, 与我们设置这道题目的目的背道而驰.

http://www.jsqmd.com/news/78679/

相关文章:

  • Material Theme UI字体搭配完全指南:打造专属编程视觉体验
  • 【Azure Developer】中国区Azure环境中查看用户账号是否可用(accountEnabled)的操作步骤
  • vcode内置的AI
  • Java后端常用技术选型 |(一)数据库篇 - 详解
  • sql server 事务日志备份异常恢复案例---惜分飞
  • 一名网工运维转型安全渗透工程师的自白,从零基础入门到精通,收藏这一篇就够了!
  • Hetty深色主题实战:提升安全测试效率的视觉优化方案
  • 使用MCP6S22检测导航信号特性测试
  • 【超详细】漏洞挖掘入门教程:零基础从原理到实战,全流程拆解 + 工具清单,精通看这一篇!
  • 完整教程:【029】智能停车计费系统
  • 串口助手终极指南:从零开始掌握Serial Port Utility
  • RulersGuides.js终极指南:快速实现网页精准布局的免费工具
  • 国内可用的免费AI
  • Rust-Prometheus:高性能监控指标的现代化解决方案
  • GitHub Actions下载工件全攻略:从基础到高级应用
  • 利用水凝胶从干旱空气中高效收集水的新技术
  • 从0到1搭建智能分析OBS埋点数据的AI Agent:实战指南
  • 10、深入探索Domino服务器的功能与应用
  • Scrypted:智能家居视频集成的终极解决方案
  • 20、管理邮件服务器:Sendmail 的全面指南
  • F5-TTS模型配置实战:从入门到精通的路径管理艺术
  • 宁波某高端酒店资产数字化:72小时售罄5000分的会员升级
  • 完整教程:微信开发者工具的使用(一)
  • Ruby编程最佳实践
  • 智能化拓客工具真的有用吗?技术架构与实践深度解析
  • 破壁者:授权委托书识别技术如何打通纸质文件与数字系统的鸿沟
  • DICOM医学图像查看器终极指南:从入门到精通
  • 5分钟掌握窗口置顶:让多任务处理效率翻倍的秘密武器
  • 糊涂工具类hutool
  • Discord音乐机器人搭建指南:从零开始的完整部署方案