这周把 ipc、socket 网络编程、文件操作大概看了下,写了一部分练习代码。ipc 看的时间比较多,主要是结合 wrk 看了一下他的内核实现吧。也简单学习了一下 linux 上的 namespace、cgroup 机制。

编译器的项目写完了,下一个应该会写一个基于 ebpf 的内存泄露检测工具,我看训练营也有基于 LD_PRELOAD 的内存泄露和死锁的检测工具,我看能不能整合一下。

要做内存泄露、死锁检测,他涉及到的一个核心就是执行流的重定向。在重定向到我们写的代码后,做记录、判断等操作。

哪具体如何重定向呢,有下面几个流派:
没有标注的都是 ring3 可以实现的。

  • inline hook:在目标函数中写 jmp,跳转到自己写的函数。这种破坏性比较强,不好管理,需要写一个跳板来保存跳转入口。需要保存破坏的指令,这就需要集成反汇编引擎来判断破坏的指令的字节码长度。比较臃肿,且稳定性差。
  • 异常 hook:调试器常用的 hook,包括 ebpf 也在使用。分为以下几类:
    • 0xcc 替换目标的机器码,进入 idt[3] 处理例程,内核派发异常,内核/用户调试器接受。
    • 硬件断点,监控对指定地址的 rwx 操作,触发 idt[0]。好处是代码层面的无痕,但是寄存器数量是有限的。
    • 设置页面属性,然后 veh 接管,进行 hook 逻辑。
  • dll/so 的替换:windows 上的一些 dll 劫持啊,dll 转发。linux 上的 LD_PRELOAD:预加载动态库,覆盖函数实现。
  • Pte hook (ring0):重建一套页表指向 hook 点,把他从其他进程的地址空间中隔离出来。这种是用来 hook 内核函数,防止 patchguard 扫描到内核修改,触发 bugcheck 蓝屏的。
  • 虚拟化 (ring -1):
    • Efer hook:设置 efer 寄存器的 SCE 位为 0,这个时候进行系统调用就会触发 #UD 未定义行为异常,然后在 hybervisor 中接管这个异常,来达到 hook 系统调用的效果。
    • EPT hook:guest virtual addr -> guest physical addr -> host physical addr。会准备两个页,一个页是原来的函数所在的页面,一个是你的跳板页,原来的函数所在的页面去除执行权限,他读取的时候还是原来的页面,但是执行的时候,会触发异常 vmexit 到 hybervisor 中,然后修改这个页面的 pfn,指向 hook 页,在执行完 hook 页中的跳转指令后,pfn 替换回来。
      后面的这些都是 windows 上做游戏可能会用到的,内存泄露检测工具肯定不会用到。

哪 linux 常用的检测工具的原理是什么呢?
Valgrind:按照基本块为单位,把机器码翻译为中间代码,然后插桩,然后再编译为机器码,把编译结果保存为缓存。
llvm 里面的检测(Ascan 等):编译期插桩,需要源码。
heaptrack:LD_PRELOAD。

也看了一些语言的 gc,cpython 的引用计数,go 的三色算法这些的,相比于 python 跑在虚拟机上,可以完全掌控程序的运行,go 的 gc 就需要更加复杂,庞大的代码,打包到可执行文件中。