Valgrind工作原理简介

Valgrind是一款用于构建内存调试、内存泄露检测以及性能分析的软件开发框架,它是一个由来自全世界的开发者组织合作开发得到的开源软件。他的初始作者Julian Seward在2006年获得第二届Google-O’Reilly开源代码奖。Valgrind这个名字取自北欧神话中英灵殿的入口。

官方首页上这个骑士与恶龙的绘画由伦敦画家 Rupert Lees 创作。灵感来自于神话传说 St George and the Dragon 。某日,圣乔治到利比亚去,当地沼泽中的一只恶龙(一说鳄鱼)在水泉旁边筑巢,这水泉是Silene城唯一的水源,市民为了取水,每天都要把两头绵羊献祭给恶龙。 到后来,绵羊都吃完了,只好用活人来替代,每天抽签决定何人应选派作牺牲。 有一天,国王的女儿被抽中,国王也没有办法,悲痛欲绝。当少女走近,正要被恶龙吞吃时,圣乔治在这时赶到,提起利矛对抗恶龙,并用腰带把它束缚住,牵到城里当众杀死,救出了公主。

在软件开发过程中我们有时需要追踪程序执行、诊断错误、衡量性能等需求。如果有源代码的话可以添加相应代码,没有源代码时这个方法就行不通了。前者可以对源代码分析,后者只能分析二进制了。我的理解是,dynamic Binary Instrumentation (DBI)强调对二进制执行文件的追踪与信息收集,dynamic Binary Analysis (DBA)强调对收集信息的分析,valgrind是一个DBI框架,使用它可以很方便构建一个DBA工具。

Valgrind作者认为当时的DBI框架都把注意力放在了性能检测上,对程序的质量(原文capabilities)并没有太注意。所以设计了一个新的DBI框架,目标是可以使用它开发重型DBA工具。

Valgrind的重点是shadow values技术,该技术要求对所有的寄存器和使用到的内存做shadow(自己维护一份)。因此使用valgrind开发出来的工具一般跑的都比较慢。为了实现shadow values需要框架实现以下4个部分:

  • Shadow State
  • 提供 shadow registers (例如 integer、FP、SIMD)
  • 提供 shadow memory
  • 读写操作

9个具体功能:

  • instrument read/write instructions
  • instrument read/write system calls
  • Allocation and deallocation operations
  • instrument start-up allocations
  • instrument system call (de)allocations
  • instrument stack (de)allocations
  • instrument heap (de)allocations
  • Transparent execution, but with extra output
  • extra output

核心思想就是要把寄存器和内存中的东西自己维护一份,并且在任何情况下都可以安全正确地使用,同时记录程序的所有操作,在不影响程序执行结果前提下,输出有用的信息。使用shadow values技术的DBI框架都使用不同方式实现了上述全部功能或部分功能。

valgrind结构上分为core和tool,不同的tool具有不同的功能。比较特别的是,valgrind tool都包含core的静态链接,虽然有点浪费空间,但可以简化某些事情。当我们在调用valgrind时,实际上启动的只是一个解析命令参数的启动器,由这个启动器启动具体的tool。

为了实现上述功能,valgrind会利用dynamic binary re-compilation把测试程序(client程序)的机器码解析到VEX中间语言。VEX IR是valgrind开发者专门设计给DBI使用的中间语言,是一种RISC like的语言。目前VEX IR从valgrind分离出去成libVEX了。libVEX采用execution-driven的方式用just-in-time技术动态地把机器码转换为IR,如果发生了某些tool感兴趣的事件,就会hook tool的函数,tool会插入一些分析代码,再把这些代码转换为机器码,存储到code cache中,以便再需要的时候执行。

Machine Code --> IR --> IR --> Machine Code

        ^        ^      ^
        |        |      |
    translate    |      |
                 |      |
            instrument  |
                        |
                     translate  

valgrind启动后,core、tool和client都在一个进程中,共用一个地址空间。core首先会初始化必要的组件,然后载入client,建立client的stack,完成后会要求tool初始化自己,tool完成剩余部分的初始化,这样tool就具有了控制权,开始转换client程式。从某种意义上说,valgrind执行的都是加工后的client程序的代码。

DBI framework 有两种基本的方式可以表示code和进行 instrumentation:

  • disassemble-and-resynthesise (D&R)。
    Valgrind 使用这种把machine code先转成IR,IR会通过加入更IR来instrument。IR最后转回machine code执行,原本的code对guest state的所有影响都必须明确地转成IR,因为最后执行的是纯粹由IR转成的machine code。
  • copy-and-annotate (C&A)。instructions会被逐字地复制(除了一些 control flow 改变)
    每个instruction都加上注解描述其影响(annotate),利用这些描述来帮助做instrumentation
    通过给每条指令添加一个额外的data structure (DynamoRIO)
    通过提供相应的获取指令相关信息的API (Intel Pin)
    这些添加的注解可以指导进行相应的instrument,并且不影响原来的native code的执行效果。

Ref