指令解析——工具是怎么"读懂"一条昇腾指令的
系列三 · 第 12 篇
前面反复提到"探针",它们要记录内存访问,可一条昇腾指令究竟碰了哪块内存、多大范围,工具是怎么知道的?答案藏在一个反直觉的事实里:工具不是靠"反汇编"去猜指令语义,而是让编译器直接把答案递到你手上。
昇腾的算子源码,最终要经过一个叫 CCE(Cube Compute Engine)的编译器,翻译成 NPU 能跑的指令。而上层的算子代码本质是 C++ 风格的内建函数——搬一块数据用 copy_gm_to_cbuf,向量加法用 vadd,同步用 set_flag。msSanitizer 手里正好准备了一批和这些内建函数"同名同形"的探针函数。
关键在于,探针并没有拿着编译器生成的机器码去逐条分析,而是仗着"对同名函数插桩"这件事。编译器在把算子源码翻成机器码时,顺手把每个内建函数的调用点替换成对探针函数的调用,并把原函数的操作数——目标地址、源地址、还有一颗关键的 config 寄存器——原样传进去。于是探针函数就"站"在了每条指令的执行现场,参数里直接带着这条指令要读写的内存信息。
剩下的事,就是解码那颗 config 寄存器。昇腾指令的不少字段是打包塞进一个 64 位配置寄存器里的。比如一条 DMA 拷贝指令,config 里就藏着 burst 次数、burst 长度、源步长、目的步长……探针函数用一堆位移和掩码把这些字段一个个抠出来,拼出"这条指令到底要碰从哪到哪、跨度多少"的完整区间。这样一来,记录一个内存访问,跟填一张"访问了哪些地址"的表单没两样。
所以整个目录下躺着几十个超大的记录解析头文件,动辄几万行——它们不是什么玄学,就是几百条昇腾指令"怎么从参数推出内存范围"的完整配方,每种指令一种,白纸黑字摆在那里。
这套"让编译器帮我插桩、我来解码参数"的思路,比去啃机器码省力也聪明得多。也因为解析的是插桩后的现场参数,它天然和编译工具链深度绑定——这正是这套工具要跟昇腾的 bisheng 编译器、LLVM 工具链坐在同一条船上的原因。
下一篇,我们跳出单个技术点,看看这一堆模块——检测算法、运行时、探针——是怎么被拼装、又怎么并行跑起来的。
- 点赞
- 收藏
- 关注作者
评论(0)