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

news/2025/10/10 22:29:41/文章来源:https://www.cnblogs.com/yuweijie/p/19129481

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

指令执行的踪迹 - itrace
首先写好一个环形缓冲区的代码,把反汇编的字符串存到环形缓冲区中,然后执行完代码在打印出来。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define IRINGBUF_SIZE 16
#define LOGBUF_SIZE 128// 定义环形缓冲区结构体
typedef struct {char buffer[IRINGBUF_SIZE][LOGBUF_SIZE]; // 每个环形缓冲器的格子里面放一个字符串int head;int count;
} CircularBuffer;void initBuffer(CircularBuffer *cb){//strcpy(cb->buffer , (char *)malloc(sizeof(char) * IRINGBUF_SIZE * LOGBUF_SIZE));cb->head = 0;cb->count = 0;
}void enqueue(CircularBuffer *cb, const char *logbuf) {strncpy(cb->buffer[cb->head], logbuf, LOGBUF_SIZE - 1);cb->buffer[cb->head][LOGBUF_SIZE - 1] = '\0';cb->head = (cb->head + 1) % IRINGBUF_SIZE; //能直接15+1变成0if (cb->count < IRINGBUF_SIZE) {cb->count++;}
}void printBuffer(CircularBuffer *cb) {if (cb->count == 0) {//printf("缓冲区为空\n");return;}int idx = (cb->head + IRINGBUF_SIZE - cb->count) % IRINGBUF_SIZE;for (int i = 0; i < cb->count; i++) {if(i == cb->count-1){ printf("->%s\n", cb->buffer[(idx + i) % IRINGBUF_SIZE]);}else{printf("  %s\n", cb->buffer[(idx + i) % IRINGBUF_SIZE]);}}
}
static void execute(uint64_t n) {Decode s;initBuffer(&cb); // 初始化环形缓冲区,大小为BUFFER_SIZEfor (;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());}/*条件编译宏,如果CONFIG_DEVICE被定义,则调用device_update函数,如果 CONFIG_DEVICE 没有被定义,这一行什么都不会生成(等价于被注释掉)。*/printBuffer(&cb);
}

内存访问的踪迹 - mtrace
只需要在paddr_read()和paddr_write()中进行记录即可.

//读物理地址
word_t paddr_read(paddr_t addr, int len) {//printf("进来了\n"); if (likely(in_pmem(addr))) { //printf("进来了\n"); IFDEF(CONFIG_MTRACE, Log("read in address = " FMT_PADDR ", len = %d\n", addr, len));return pmem_read(addr, len);}IFDEF(CONFIG_DEVICE, return mmio_read(addr, len));//printf("");out_of_bound(addr);return 0;
}//写物理地址
void paddr_write(paddr_t addr, int len, word_t data) {if (likely(in_pmem(addr))) { pmem_write(addr, len, data);IFDEF(CONFIG_MTRACE, Log("write in address = " FMT_PADDR ", len = %d, data = " FMT_WORD "\n", addr, len, data));return; }IFDEF(CONFIG_DEVICE, mmio_write(addr, len, data); return);out_of_bound(addr);//Log("weiwei");
}

FTRACE
首先要知道ftrace是用来追踪程序执行过程中的函数调用和返回的。函数的调用和返回一般要使用jaljalr这两条指令,然后去看下反汇编,发现call行为发生在当rd=1时候的jal中和当rd=1的或者rd=0,imm=0的jalr中,return发生在当inst=0x00008067中。

  INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal    , J, R(rd) = s->pc + 4;s->dnpc = s->pc + imm;IFDEF(CONFIG_FTRACE, {if (rd == 1) {call_trace(s->pc, s->dnpc);}}));INSTPAT("??????? ????? ????? 000 ????? 11001 11", jalr   , I, R(rd) = s->pc + 4;s->dnpc = (src1 + imm) & (~1);IFDEF(CONFIG_FTRACE,{if (s->isa.inst == 0x00008067)ret_trace(s->pc);else if (rd == 1) {call_trace(s->pc, s->dnpc);} else if (rd == 0 && imm == 0) {call_trace(s->pc, s->dnpc);}}));

然后你需要传输elf文件给nemu
可以通过parse_args()函数来实现这一功能。
首先定义一个elf文件

static char *elf_file = NULL;void sdb_set_batch_mode(); //批处理模式static char *log_file = NULL;
static char *diff_so_file = NULL;
static char *img_file = NULL;
static int difftest_port = 1234;

然后在parse_args()函数中传入参数

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);}}

除此之外需要在AM的Makefile中写入参数如下:
NEMUFLAGS += -f $(IMAGE).elf

现在elf算是正确传入nemu中了。

于是现在需要处理elf文件了,初始化一下elf文件,采用之前KCONFIIG中宏定义的方式

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_FTRACEparse_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();
}
#else // CONFIG_TARGET_AM
static long load_img() {1extern char bin_start, bin_end;size_t size = &bin_end - &bin_start;Log("img size = %ld", size);memcpy(guest_to_host(RESET_VECTOR), &bin_start, size);return size;
}

这里先将ftrace.c的代码贴出

//#include <device/map.h>
#include <fcntl.h>
#include <elf.h>
#include <unistd.h>
#include <common.h>typedef struct SymbolEntry {char name[128];	//函数名unsigned char info;     //ELF符号类型信息paddr_t address;       //函数起始地址word_t size;        //函数大小
} SymbolEntry;static SymbolEntry* sym_entrys = NULL;
static uint32_t sym_num = 0;
static uint32_t call_depth = 0;
static uint32_t trace_func_call_flag = 0;void init_symtab_entrys(FILE *elf_file) {if (elf_file == NULL) assert(0);Elf32_Ehdr ehdr;int result = fread(&ehdr, sizeof(Elf32_Ehdr), 1, elf_file);assert(&ehdr != NULL && result == 1);// 检查 ELF 魔数 16进制打开所有的elf文件前四个必须是这四个if (ehdr.e_ident[0] != 0x7F ||ehdr.e_ident[1] != 'E' ||ehdr.e_ident[2] != 'L' ||ehdr.e_ident[3] != 'F') {printf("Not a ELF file\n");exit(0);}Elf32_Shdr *shdrs = malloc(sizeof(Elf32_Shdr) * ehdr.e_shnum);//申请节头表的内存空间assert(shdrs != 0);result = fseek(elf_file, ehdr.e_shoff, SEEK_SET); //根据文件的开头和偏移跳转到段表assert(result == 0);result = fread(shdrs, sizeof(Elf32_Shdr), ehdr.e_shnum, elf_file);//从文件中读取shnum个节头,每个节点的大小是sizeof elfshdrassert(result != 0);//遍历节头表,查找符号表和字符串表,用偏移赋值给他Elf32_Shdr *symtab = NULL;Elf32_Shdr *strtab = NULL;for (int i = 0; i < ehdr.e_shnum; i++) {if (shdrs[i].sh_type == SHT_SYMTAB) {symtab = shdrs + i;  }if (shdrs[i].sh_type == SHT_STRTAB) {strtab = shdrs + i;  }}assert(symtab != NULL);//计算符号表中条目数量 shsize是符号表的大小   shentsize是每个符号条目的大小//两者相除得到符号表中包含的符号总数量,赋值给全局变量symnum//获得符号表在ELF文件中的偏移量uint32_t entry_num = symtab->sh_size / symtab->sh_entsize;sym_num = entry_num;	uint32_t offset = symtab->sh_offset; //符号数据在文件的起始位置的偏移量多少,用于后面进来读取具体内容。//把符号表的内容读取到symbol tables中,从 ELF 文件中读取 entry_num 个符号,存入 symbol_tables 数组中。Elf32_Sym *symbol_tables = malloc(sizeof(Elf32_Sym) * entry_num);result = fseek(elf_file, offset, SEEK_SET);assert(result == 0);result = fread(symbol_tables, sizeof(Elf32_Sym), entry_num, elf_file);assert(result != 0);// 初始化自定义符号表sym_entrys = malloc(sizeof(SymbolEntry) * entry_num);char *str = malloc(strtab -> sh_size);int str_result = fseek(elf_file, strtab -> sh_offset, SEEK_SET);assert(str_result == 0);str_result = fread(str, 1, strtab -> sh_size, elf_file);assert(str_result != 0);assert(str != NULL);//把strtab中的str解析出来。for (int i = 0; i < entry_num; i++) {strcpy(sym_entrys[i].name, str + symbol_tables[i].st_name);sym_entrys[i].info = symbol_tables[i].st_info;sym_entrys[i].address = (paddr_t) symbol_tables[i].st_value;sym_entrys[i].size = (word_t) symbol_tables[i].st_size;}free(shdrs);free(symbol_tables);free(str);
}void parse_elf(const char *elf_file) {if (elf_file == NULL) {return;}	Log("The elf file is %s\n", elf_file);trace_func_call_flag = 1;FILE *file = fopen(elf_file, "rb");assert(file != NULL);init_symtab_entrys(file);
}char *get_function_name_by_addres(paddr_t addr) {for (int i = 0; i < sym_num; i++) {if (ELF32_ST_TYPE(sym_entrys[i].info) == STT_FUNC) {if (addr >= sym_entrys[i].address && addr < (sym_entrys[i].size + sym_entrys[i].address)) {return sym_entrys[i].name;}}}return NULL;
}void call_trace(paddr_t pc, paddr_t target) {if (trace_func_call_flag == 0) return;++call_depth;char *name  = get_function_name_by_addres(target);Log(FMT_PADDR ":%*scall [%s@" FMT_PADDR "]\n", pc, call_depth , "", name, target);
}void ret_trace(paddr_t pc) {if (trace_func_call_flag == 0) return;char *name = get_function_name_by_addres(pc);Log(FMT_PADDR ":%*sret [%s]\n",pc, call_depth , "", name);--call_depth;
}

重点来看一下这个init_symtab_entrys函数。
第一行把elf文件拖进来,如果不存在那就assert。
然后定义一个Elf32_Ehdr的变量ehdr并且把传进来的elf文件的文件头结构体传进来,重要字段包括

  1. e_ident:魔数和文件类型标识
  2. e_shnum:头节表项大小和数量
  3. 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[i].info = symbol_tables[i].st_info; // st_info
sym_entrys[i].address = (paddr_t)symbol_tables[i].st_value; // st_value
sym_entrys[i].size = (word_t)symbol_tables[i].st_size; // st_size
名字:str + symbol_tables[i].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的地址找到符号表的名字。

char *get_function_name_by_addres(paddr_t addr) {for (int i = 0; i < sym_num; i++) {if (ELF32_ST_TYPE(sym_entrys[i].info) == STT_FUNC) {if (addr >= sym_entrys[i].address && addr < (sym_entrys[i].size + sym_entrys[i].address)) {return sym_entrys[i].name;}}}return NULL;
}

随后在jal和jalr中调用call和ret就行。

最后在KCONFIG中像之前一样定义config_ftrace,在monitor.c中宏定义if是否使用这个ftrace即可。

还是非常的困难的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/934449.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验报告3(使用单链表简单实现图书管理系统)

一、实验目的: 使用单链表实现案例2.3的图书管理系统,要求实现查找、插入、删除和计数功能。要求包含主函数,用c语言或者c++实现。 二、实验仪器或设备: 操作系统:Windows11 编程环境:Dev-cpp 5.11 三、算法总体…

【黑马python】2.Python 字符串

参考链接黑马-2.Python 字符串 08-字符串的三种定义方式tbd

FineReport自定义登录系统技术 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

实验报告2(简单实现图书馆管理系统)

一、实验目的:、 实现书上图书馆管理系统 (1) 主函数 (2) 修改:根据指定的ISBN,修改图书的价格 (3) 排序:将图书按照价格由低到高进行排序。 (4) 计数:统计文件中的图书数量 要求:用c语…

实验报告1(switch语句,二维数组)

一、实验目的: 熟练使用switch语句 熟练使用二维数组 二、实验仪器或设备: 操作系统:Windows11 编程环境:Dev-cpp 5.11 三、算法总体设计 (1)项目一:运输公司对用户计算运费 用到的算法的目的:计算并输出基于给…

【实现自己的 kafka!】kafka 的关键概念

kafka 的诞生 现在是在 2000 年代后期,你的名字叫做 Jay Kreps,你就职于 LinkedIn 公司。 LinkedIn 作为社交网络平台,用户规模和数据量现在快速增长,同时内部存在多种数据传递和处理需求,比如用户行为跟踪、日志…

12. 对话框

一、对话框对话框窗口是一个用来完成简单任务或者和用户进行临时交互的顶层窗口,通常用于输入信息、确认信息或者提示信息。Qt Quick 提供了一系列的标准对话框,如 FileDialog、ColorDialog、MessageDialog、FontDia…

2024ICPC区域赛香港站

define时间:#define int long long #define ind long double #define yes cout << "Yes" #define no cout << "No" #define pii pair<long long, long long> #define all(x) (…

AI产品经理要了解的算法有哪些?

中世纪拉丁语“algorismus”指的是用印度数字进行四个基本数学运算——加法,减法,乘法和除法的程序和捷径。后来,术语“算法”被人们用作表示任何逐步的逻辑过程,并成为计算逻辑的核心。 算法的历史可以分为三个阶…

一位印度小哥逆袭成为谷歌数据科学家的心路历程 - 教程

一位印度小哥逆袭成为谷歌数据科学家的心路历程 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&q…

基于selenium的网页自动搜索

第一节 通过简单的百度网页打开学习selenium库的基本功能。1 from selenium import webdriver2 from selenium.webdriver.chrome.service import Service3 from selenium.webdriver.chrome.options import Options4 fr…

MacOS Nginx

查看是否安装:brew info nginx 安装:brew install nginx 卸载:brew uninstall nginx 查看版本:nginx -v 安装目录:/opt/homebrew/Cellar/nginx/1.29.0 (27 files, 2.5MB) 根目录:Docroot is: /opt/homebrew/var/…

缓存的击穿、雪崩、穿透在你项目中的场景是什么

在我们的 OJ 平台中,为了保护数据库、提升响应速度,我设计了一套缓存防护体系: 缓存穿透: 针对恶意请求或不存在的题目 ID,我们用布隆过滤器提前过滤掉无效请求,误判率控制在 0.13% 以下,保护数据库不被大量无效…

[WC2021] 表达式求值

给定一个式子,包含 >,<,? 或者 \([0,m)\) 中的一个数字。其中每个数字代表一个数。 > 代表返回两边的最大值,< 代表返回两边的最小值,? 表示你要在上文的两个符号中选择一个符号替换它。 假设有 \(…

Set集合

无索引 Hashset主注意: LinkedHashset: 存取有顺序其余和hashset一样

JAVA - LinkedList 与 ArrayList 区别和 LinkedList 的四大接口解析

什么是 LinkedListLinkedList 就像一个火车车厢队列。每个“车厢”里装着一个数据(元素),而且每个车厢都知道:自己前面是哪节车厢(previous),自己后面是哪节车厢(next),所以它是一种 “链式结构”。 不像 Ar…

苍穹外卖第三天(Swagger、@RequestParam和@RequestBody的使用场景、@PostMapping和@RequestMapping的区别、对象属性拷贝、@Insert注解)

一、Swagger Swagger是一个用于生成、描述、文档化可视化API的工具(框架)。直接使用Swagger会比较繁琐,所以我们用到了Knife4j框架,它对Swagger进行了封装,简化了相应的操作。 1、Knife4j的使用方式: (1)导入K…

Git 多账号管理

# 新建空白文件夹 mkdir <YOUR PROJECT> # 初始化仓库 git init # 配置当前仓库账号 git config user.name "<YOUR NAME>" git config user.email "<YOUR EMAIL>" # 给当前账号…

完整教程:一文读懂费用分析:定义、分类与成本费用区别

完整教程:一文读懂费用分析:定义、分类与成本费用区别2025-10-10 21:48 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; …

Hyper Server 2019安装I226-V网卡驱动

背景:Hyper-V Server 2019 安装完提示找不到活动的网络适配器 网卡型号:Intel I226-V 以下内容以Hyper-V Server 2019和Intel I226-V网卡为例,其他系统版本(NT6以上)和同系列网卡,操作大同小异,可参考进行。 由…