运行时劫持——在不动你代码的前提下"接管"算子
系列三 · 第 10 篇
前面都在说工具"能查出什么",这篇换个视角,讲它"凭什么能插手"。你明明没把工具代码塞进算子里,它却能在算子运行时耳聪目明,越界、竞争一堆细节尽收眼底。秘密在于一个老牌的 Linux 机制:LD_PRELOAD。
LD_PRELOAD 的作用,是允许你"顶替"动态库里的某些函数。具体到昇腾:算子在 host 侧干活时,要调用 runtime 提供的一堆接口——申请内存、拉起 kernel、同步任务、拷贝数据。msSanitizer 提供了一个同名同型的钩子库,把其中几个关键接口"中途截住":先记录下这次调用要干嘛,再转手交给真正的 runtime 函数去执行。对算子来说一切如常、浑然不觉;对工具来说,每一次内存分配、每一次 kernel 启动,都在眼皮底下过了账。
这里有个精巧的小装置值得说说。截住了接口,总得知道"真身"在哪、怎么继续调它吧?工具用了一个叫 VallinaSymbol("原生态符号"之意)的东西,加载时把真正的 runtime 库打开、取出原函数指针存好。于是每个钩子的标准动作就成了:记一笔 → 调原函数 → 该干嘛干嘛。这也是"先上报、再放行"的由来。
被截住的关键接口分几层。最要紧的是 rtKernelLaunch 这一族——它们是启动算子的入口,工具正是在这一刻、算子真正开跑之前完成插桩的(下一篇细讲)。再往上,有 halMemAlloc、halMemFree 这类 HAL 层内存接口,以及 aclrtMalloc、aclrtFree 这类 ACL 层接口——截住它们,内存的分配释放、读写拷贝就有了记录。前面讲过的堆块台账、越界区间表,原始素材全从这里来。
剩下的工程问题是通信:工具主体和用户算子是两个进程,记录的事件怎么传回来?答案是 Unix Domain Socket——本机进程间聊天的标准通道。被劫持的算子进程当客户端,工具当服务端,一条 socket 把设备信息、kernel 信息、内存事件源源不断地递回去。多卡并发时,每张卡各连一条,服务端每个连接开一个线程,各收各的、互不打架。
下一篇,就顺着 rtKernelLaunch 这个口子,看看工具如何在算子开跑前"现场改二进制"——那是动态插桩,整套工具里最"玩操作"的一环。
- 点赞
- 收藏
- 关注作者
评论(0)