trace实现原理与应用

项目工程

Posted by Bruce Lee on 2024-08-20

关于我

欢迎来到我的博客!这里汇集了我对编程和技术的洞见和总结。本站内容分为几个主要类别,涵盖从具体技术实现到编程理念的广泛话题。

主要内容分类

  • 项目工程:深入探讨技术的实现细节和理解。
  • C/C++:围绕C/C++语言的技术点和编程技巧进行详细总结。
  • 程序员哲学:分享程序员在职业生涯中应该具备的哲学理念和思考方式。

想要了解更多具体内容,您可以访问文章分类页面。

联系我

如果您有任何问题或想要交流,欢迎通过关于页面与我联系。

感谢您的阅读和支持,希望我的博客能为您的技术旅程带来帮助!


mtrace实现原理

所有的访存指令,都要输出其访问的地址.文档中给出在paddr_read和paddr_write中写代码,进行记录.

其实不然,我认为无需进行这样的多余操作:只需要在in_pmem函数中实现即可:
原因:首先所有的访问内存的指令都会调用paddr_read和paddr_write函数,这两个函数都会调用in_pmem函数进行安全边界检查,只需要在in_pmem函数中实现相应的功能就可以了.指令的读和写的信息没有必要输出,因为我们有itrace功能,出现了访问错误,输出访问的地址,加上对应的itrace输出的指令,我们就知道是读或者写了.而且大部分出错应该是写.所以没有必要为了多了一个读/写信息,要在两个函数中实现大部分是相同的代码,造成代码冗余/重复.

关于CONFIG_MTRACE的开启.

在Kconfig中,看到了ITRACE以及ITRACE_COND的选项,然后依赖关系关键字depends on

应该ITRACE_COND的后面添加相应的MTRACE.并且依赖TRACE宏,默认这个是关闭的.

关于如何/何时获取内存范围检查信息.

逻辑上应该在执行指令之前获取,看了monitor的代码,我认为最适合的就是在welcome函数中进行编写代码来获取.这里输出了关于trace宏的相关信息,我们应该在这里输出关于Mtrace的相关信息,信息类似,应该放在一起.

在welcome中寄信ifdef代码块.关键变量memory_left/right是要在paddr.h中使用的.
所以这两个变量进行全局的条件定义.

in_pmem函数是一个内联函数,其本身就是一个很简短的函数,内联是完全合理的.

但是按照上述理论,这个函数不能编程内联函数了.

其static属性,是完全可以的

特殊的:

不管是指令的行为中存在访存,如果击中了内存跟踪范围,那么会输出相应的信息.

那么ifetch函数对指令的抓取,也会触发这个跟踪.

考虑到如果要消除,指令读取的信息,inst_fetch函数调用vaddr_ifetch函数,这个函数值直接对vaddr_ifetch函数的封装

vaddr_ifetch是直接对paddr_read函数的封装.那么无解了,除非在vaddr_ifetch中进行相应的实现,并且所有的具体代码中是调用vaddr_read/write进行间接调用paddr_read/write,但是在之前的一些实现中,有些代码,是直接调用了paddr_read/write函数

ftrace实现原理

成本分析:
计算每一条指令地址的击中目标范围检查.每条指令的执行成本几乎翻了3倍.

而且根据后续分析,这里需要依赖itrace,在iringbuf中记录着当前执行的指令的地址,二进制串,反汇编结果.我们用不上mtrace,因为mtrace主要是判断和输出,只有两个memory_left/right全局变量用于判断用的.

所以CONFIG_FTRACE 依赖于CONFIG_ITRACE

call的原理
根据上述分析的,itrace依然会被触发(指令访存),那么每一条指令的地址,我们是动态记录了在iringbuf中.每一条执行后的指令,我们就提取对应地址,然后与FUNC字段的地址和地址+value进行比较,如果在里面,表示正在调用这个函数.

从哪里取地址?

如果在trace_and_diff函数中,跟在CONFIG_ITRACE的代码块之后进行条件编译CONFIG_FTRACE,然后从iringbuf中的前10个字节提取内存地址的字符串.

这个方案采取,不是完全依据理想化,规整化的角度来选择的.在从elf文件中获取的一系列关键信息其实就是数值.所以我们应该在exec_once函数中,直接将pc值提取出来,做一系列的处理.这样子做是否合理.合理,因为在exec_once中,也有CONFIG_ITRACE的条件编译,我们在ITRACE的处理后,再处理我们的FTRACE.这样不仅提升了trace的效率,减低了些许开销,也减小了代码量(不用将字符串转为数值).

在exec_once中,要做的工作,就是将pc与已经处理好的一系列FUNC地址进行比较,记录,然后输出.

比较用于确定是否到了某个函数的内部,记录用于排除掉重复的指令在函数内部,然后被输出call,输出就在符合条件的时候,并且是第一次到这个函数内部,就输出.

所以比较,比较记载的所有FUNC地址和其地址+size值

记录,用于记录这个函数是否被调用.

当一个pc值等于一个FUNC中的地址值时,此时一定是调用函数:包括递归调用.

这句话的另一层意思就是,如果一个跳转指令,跳到了FUNC的首地址,那么这个跳转指令一定是一个调用函数的语句的汇编?

答案是这样,可以说在RISCV32中是这样的,因为我观察了多个汇编代码,以及多个函数调用,循环跳转代码.

最甚至的是,编写了一个带有__attribute__((optimize(“O0”))),定义一个全局变量,在这个函数的第一句就是这样的:

1
2
3
here:	p = p + 1;
if (p < 5)
goto here;

即使是这样的代码,依然不会直接跳转到函数的首地址开始执行.

因为调用函数,是要切换上下文的,是要保存系列寄存器的状态的.而只有在调用函数,中断等上下文环境变换的时候,才有这些操作.

所以,只要是pc值等于FUNC中的Address值时,就是call.直接输出对应的name.

关于返回,就是回到调用函数的地方.

这里的意思就是,如果一个函数不是返回到调用地方,那么就不会有ret.这样的函数跳转到别的地方了,函数即使执行完毕,那么我们也不应该输出ret信息.

而是如果跳转了别的函数入口,继续输出call信息,

这样下来,每一个函数都会被call信息输出,但不是每一个函数都有一个ret信息输出.这样我们更能看清一个函数的行为.

综上:决策就是,在exec_once中进行范围判断,如果pc值等于首地址值,直接call,如果遇见ret指令,就直接ret信息.

ret信息的输出条件:
根据上述,如果离开了这个函数的范围,并且进入到了另一个函数的范围,并且不是另一个函数的首地址,那么就是ret信息.

再综上:
判断pc在某个FUNC范围内.
判断pc是否是首地址.
pc正在的某个FUNC内,此FUNC的记录为1.如果pc离开了FUNC,进入到了另一个FUNC,如果是首地址,输出cal信息,并且此新FUNC记录为1,原记录为0.如果不是首地址,那么输出ret信息,并且标记回来的FUNC记录为1,原记录为0.如果pc跳转到了该FUNC内,并且不是首地址-不管,如果是首地址,那么就是递归,那么call信息.并且不用修改记录值.

记录值是为了表示,pc在哪里,如果一直在为1的FUNC里面,说明没有发生什么(除上述),如果来到了为0的FUNC里面,说明了发生了什么(call或者ret),因为这里并不去识别指令是jal或者jalr,而是根据记录值和上述逻辑进行判断.

这些就是在exec_once函数中干的事.

对于FUNC信息的整理,需要在更早的时候进行,而且还需要关于makefile文件的编译进行改写,需要增加对elf库的链接.同时也要增加menuconfig中的ftrace选项.

现在Kconfig中实现对应的ftrace,然后在makefile中进行条件语句,如果CONFIG_FTRACE存在,那么就将LDFLAGS+=-lelf,并且将NEMUFLAGS += -f $(shell dirname $(IMAGE).elf)/$(NAME).elf

然后在parse_args中,添加f指令

现在的问题就是:在Kconfig中并且用make menuconfig选中FTRACE,在makefile中进行条件语句,是没有用的.menuconfig中的定义,在makefile中是用不了的

所有menuconfig中的用于c代码的编写,那么在nemu.md中再次进行手动添加!

首先c源代码中对与ftrace的相关代码依赖menuconfig中的CONFIG_FTRACE宏进行条件编译.

在编译过程中的一系列参数,-f,以及ld的参数是需要手动定义条件.

现在再来回看这个makefile,对于makefile的组织架构和功能分区进一步的理解.

首先nemu的编译,是依照GNU/Linux的编译环境进行编译的,因为nemu是模拟器,是架设在真实机器的,这是好理解的.所以我们在关于链接elf库的时候(-lelf),是需要在nemu中的makefile中进行的.对于一些ARGS,我们需要在am中的makefile添加,因为我们的-f参数以及elf文件都是在am中,而且也只要在am中的程序,才能用的上ftrace功能.

而am是架设在nemu上的,所以我们也建立了关于nemu的目标机器的运行时环境,有些基础东西需要我们自己实现.

这次是实现了自动的文件导入,通过在makefile中进行的.

上述的添加代码有些许偏差,

更改:将symtable相关变量改为malloc申请内存.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !