基于QEMU的动态函数调用跟踪

向 勇1 曹睿东1 毛英明2

1(清华大学计算机科学与技术系 北京 100084)2 (北京理工大学计算机学院 北京 100081)

(xyong@csnet4.cs.tsinghua.edu.cn)

摘 要:函数调用一直是Linux内核分析研究领域的重点.获得函数调用信息主要有2种方法:静态分析和动态分析.动态跟踪方法可实时和准确地获取函数调用关系信息,在分析和调试软件程序时有极大的帮助作用.针对现有工具存在跟踪信息不全面、需要编译选项支持等不足,基于开源的QEMU模拟器,设计并实现了支持多种CPU平台的通用动态函数调用跟踪工具,可在x86_32,x86_64,ARM共3种体系架构上动态跟踪包括Linux内核启动过程在内的函数调用和返回信息.该工具在程序运行时截获调用和返回的指令,并记录相关信息,利用此种指令只会在QEMU翻译块的最后一条出现的性质,减少检查指令的数量,提高运行效率;可不依赖源代码,只依据函数符号表进行函数调用关系分析.实验结果表明:跟踪和分析结果与源代码行为一致,相比于S2E提升了分析性能和支持的CPU平台种类,且能更好地扩展至其他平台.

关键词:函数调用;动态跟踪;模拟器;多平台;Linux内核分析

软件由众多函数组织而成,以Linux内核代码为例,已包含了3万多个函数.研究和梳理函数之间的调用关系对软件的开发和测试有极大的帮助,可以为提高程序执行效率提供必要的支持[1],在软件性能研究等方面也有重大意义.

函数调用分析又分为2种方法:

1) 静态分析,即利用源代码,得到函数的信息以及调用关系.但是这样会存在一些问题,比如有的函数是通过指针进行调用的,这在C语言编写的程序中尤为常见,此时静态分析就有些无能为力了[2].还有的函数是针对其他体系结构的,完全不会调用,使得结果中存在冗余.

2) 为了能够得到较为全面且准确的信息,还需要另一种方法——动态分析,即在程序运行的过程中实时记录发生的函数调用.通过动态跟踪产生的结果首先可以得知程序的执行流程,这对于分析、了解程序的架构有很大帮助,以便于快速厘清程序的运行逻辑;其次还能知道哪些函数执行的时间较长、占用的资源较多,可以对其进行针对性的优化,以提高总体的运行速度和程序效率,或是在应用程序安全分析中起到识别的作用[3].

有关函数调用的动态分析是系统研究的传统方向之一,能够得到函数调用关系图的工具有很多,比如GNU binutils系列工具集中的gprof[4],Linux自2.6版本之后已集成在内核中的强大的内核分析工具Ftrace中的Function Tracer函数调用动态跟踪器,以及诊断Linux系统问题的SystemTap软件.或者直接利用GCC编译器的功能在程序中进行插桩,在每次进入和退出函数的时候分别进行记录.

然而这些软件对编译源代码都有一定的要求,例如gprof只能针对应用程序,无法用于内核;Ftrace需要在编译内核时打开相关的编译选项;SystemTap无法进行系统启动早期阶段的跟踪;直接插桩的方法也要重新编译内核,上面这些方法并不能完全满足当前的实际需要.

而使用模拟器技术可以很好地对函数调用关系进行动态跟踪,而对模拟器内部的系统没有影响,不需要对源代码修改或者插桩,也无需对程序重新进行编译,同时可以完整地跟踪虚拟机中运行的操作系统从启动开始的所有函数调用,并获得机器中一些必要的实时状态信息.S2E[5]是基于QEMU[6]模拟器和KLEE开发的符号执行软件,提供了插件接口截获系统运行时的指令,可以通过指令筛选的方法获得函数调用关系.虽然可以利用它较完善地实现需求的功能[7],但是S2E软件本身也存在着一些限制,例如只能运行在x86_64的机器上,同时也只能跟踪x86_32平台,不能在其他平台上工作,而且运行速度也较慢.

本文的目的是不依赖S2E而直接对QEMU进行修改,全面跟踪包括启动阶段在内的操作系统执行过程中的所有函数调用;利用QEMU的跨平台特性,在它支持的多种平台上实现这个功能,包括x86_32,x86_64,ARM,并且能较方便地扩展至其他平台,形成较为通用的方法;支持没有源代码的系统软件跟踪,提高运行的速度和跟踪性能.基本的思路是监控QEMU的行为,当遇到函数调用或返回的指令时将相关信息输出到日志文件之中,按照标准格式对数据进行处理,形成函数调用关系图.选择在线处理方式,即在生成数据的同时,另一个工作进程读取日志并解析,将运行过程流水化,以提高效率.

1 基于QEMU的动态函数调用

1.1 “快速模拟器”QEMU

QEMU是个应用广泛的开源软件,可以作为模拟器来使用,它承担了模拟各种硬件、外设,翻译指令、执行指令等所有的工作.因此所有在虚拟机中要执行的指令,都会由QEMU进行翻译成宿主机可以识别的指令之后再进行.可以通过这个特性截获虚拟机中的函数调用和返回指令,从而得到函数调用信息.同时QEMU还具有仿真速度快和跨平台的特性,可以模拟包括x86,MIPS,ARM,PowerPC等多种CPU架构,这样可以满足扩展至多平台的要求[8].

QEMU采用了动态二进制翻译技术,即在运行过程中将客户机的二进制代码翻译为宿主机的二进制代码,从而在宿主机上执行.QEMU的指令翻译和执行都是以“翻译块”(translation block, TB)为基本单位进行的.所谓的翻译块就是数条连续执行指令的集合,其最后一条指令只能是分支指令如跳转、函数调用等可以修改当前CPU指令地址的指令[9].为了提高执行的速度,QEMU使用了翻译缓存的机制,即将翻译好的宿主机代码存入散列表中再执行,可供之后的反复使用而不是再重新翻译.而考虑到避免在控制代码和客户机代码之间因为经常跳转所带来的开销,QEMU又使用了块链接的机制,即翻译好代码块后就尝试将其与之前的翻译块链接起来,如果之前的翻译块与这个将要执行的翻译块的客户机代码不跨内存分页的话,则在它们之间建立直接跳转,形成链表,这样大部分的跳转是在翻译块之间进行的,它们处于同一个运行时上下文,因此没有返回QEMU代码时所需的切换上下文等复杂的操作[10],如图1所示:

Fig.1 QEMU block chaining
图1 QEMU块链接示意图

QEMU的运行流程是启动后从客户机的第1条代码开始,首先查看客户机当前的指令指针(program counter, PC)对应的翻译块是否在翻译缓存中,如果没有的话则进行翻译,并加入翻译缓存后再执行;否则直接执行缓存好的代码,如此循环,如图2所示:

Fig.2 QEMU execution flow chart
图2 QEMU运行流程图

本文要解决的核心问题是:如何在多种平台下如x86和ARM中动态地获取函数调用与返回的信息,生成函数调用关系数据,保证完整和全面;同时考虑到性能,提高执行速度,减少模拟速度慢导致的软件运行异常.

1.2 设计思路

如1.1节所述,QEMU模拟器使用动态二进制翻译技术,所有指令在执行前都会被翻译,所以可以通过在翻译时识别函数调用和返回指令,对代码块进行标记.在执行这些块之前使用QEMU提供的API获取如时间、读取客户机内存中的进程标识、线程标志等信息,利用QEMU已有的输出日志功能保存到文件中.其中需要额外注意的是信息的完整和全面性问题,QEMU原本的块链接机制会造成其中的一些函数调用由于没有返回到主控代码中而无法输出到日志中,因此需要禁用块链接,每次执行翻译块之后都会返回控制代码中检查是否发生了函数调用或返回,保证能够获取到所有的函数调用关系.

Fig.3 Dynamic function call tracing architecture design
图3 动态函数调用跟踪的结构设计图

本文为了获取函数调用信息,需要进行4部分的工作,其中前3部分是对QEMU进行的修改,以支持多个平台上的动态函数调用跟踪.

1) 利用QEMU已有的日志输出功能,增加跟踪选项,进行信息的输出;这部分是与平台无关的.

2) 根据QEMU块翻译的特点和目标平台CPU指令集的特征,在遇到函数调用和返回指令时对代码块进行标记;这些信息的获取是与平台相关的,平台的指令集不同,需要识别的函数调用和返回指令也不同.

3) 操作系统的进程和线程等语义信息的获取;这也是与平台相关的,并且依赖对操作系统的了解,QEMU针对不同的平台有不同的模拟代码,存储的数据结构也有不同.

4) 根据操作系统内核的符号表,进行函数符号的解析.

本文使用DBCG-RTL[11]工具作为后端对数据进行处理.DBCG-RTL可以对满足工具格式要求的函数调用关系数据进行函数调用与返回的匹配,生成函数调用关系图,比起文字来图片有更清晰、明了的展示效果,也可以让人更直观地观察函数之间的联系.整体的结构设计如图3所示.

1.3 跟踪数据格式

由于需要跟踪多个平台的函数调用,所以应该设计统一的数据格式,同时还要满足DBCG-RTL工具的要求.

首先,最基本的信息包括被调函数的地址.为了得到函数调用的时序关系,还需要当前虚拟机的时间.为了进行函数调用和返回的匹配,还需要当前的进程标识、线程标识和当前的栈顶指针.根据Linux内核的设计,由于分页机制,8 KB内存为2页,而线程的内核栈应该存在于这2页之中,故可以利用栈顶指针将低13 b屏蔽为0的结果来标识当前线程.可以在解析日志时进行处理,假设当前栈顶指针为esp,则线程标识为esp&0xffffe000的值.而区分函数的位置,例如函数是来自内核或者可加载模块,则需要函数所在模块的名称.根据以上的分析,需要直接在虚拟机中获取的函数调用信息格式为“当前虚拟机时间进程标识 当前栈顶指针 被调函数地址 函数所在模块名称”,函数返回信息的格式为“当前虚拟机时间 进程标识 当前栈顶指针”.

具体的输出数据与平台的位数有关,例如32 b平台上,表示“在0xba66171d时刻,发生了函数调用,此时的PID为0x1907000,栈顶地址为c1837fb0,函数的入口地址为c18c696;在0xba66adb5时刻,发生了函数返回,此时的PID为0x1907000,栈顶地址为c1837fb0”,则对应的输出数据如下:

0xba66171d 0x1907000 c1837fb0 c18c6596 kernel

0xba66adb5 0x1907000 c1837fb0

在64 b的平台上,输出数据中的进程标识、当前栈顶指针和被调函数地址的位数由32 b变为64 b.

1.4 标记翻译块类型

如1.1节中所说,每个翻译块的最后一条指令只会是跳转指令,因此可以以翻译块为分析的基本单位.在翻译块的数据结构中增加变量type表示它的类型,并在开始翻译时将当前的翻译块标记为一般块.当遇到函数调用语句时,将这个翻译块标记为发生了函数调用;当遇到返回语句时,将这个翻译块标记为发生了函数返回.

函数调用就是在一个函数在运行中进入另一个函数,执行其代码再返回到主调函数的过程.而被调函数的入口地址就是它第1条指令的地址,所以只要记录函数调用语句下一条被执行指令的地址,也就记录下了被调函数的地址,这个地址可以进一步解析为函数名称、所在文件和行号,方便与源代码进行对照.

当QEMU执行完一个翻译块时,会返回到主控循环中,然后寻找下一个要执行的块.翻译块的数据结构中包含第1条指令的地址即PC值的信息.此时下一个翻译块的PC值就是被调用函数的地址.

在1.1节提到QEMU使用了名为“块链接”的机制,如果QEMU一直在执行翻译缓存而不返回到主控循环中,那么中间产生的一些函数调用和返回就无法被我们捕获.因此如果开启了跟踪函数调用的功能,就要屏蔽块链接机制,使得每执行完一个翻译块,都会回到主控循环中.对每一个翻译块的类型进行判断,如果是函数调用或者返回,则输出相关信息到日志中,就可以跟踪虚拟机中所有的函数调用和返回情况了.这样虽然会带来对性能的影响,但是保证了数据的完整性和全面性.我们评估了修改前后的执行时间,在相同的硬件环境中使用QEMU启动Linux3.5.4内核,原生即启用块链接的情况下耗时3.06 s,禁用块链接则耗时14.23 s,增加了3.65倍,总体的开销与原生的在同一数量级上.

然而在实际中,不同CPU平台上对翻译块分类的判断仍不大相同.x86平台只依据指令就能作出判断,而在ARM平台上还需要参考指令的操作数等具体情况.

因为x86_64指令集是x86指令集的扩展,实现了32 b向64 b的过渡,所以x86_64指令集是可以兼容x86指令集的,在指令标记上它们可以通用,只是数据位数不同,在输出日志时还会稍有区别.以x86汇编为例,函数调用使用CALL指令,函数返回使用RET指令.但需要注意的是同一类型的指令可能对应多个操作码,QEMU也是根据操作码来区分不同的指令,进行相应的翻译操作.

通过查阅Intel指令手册可以得知,CALL指令对应多个不同的操作码,分别是E8,9A,FF,表示相对寻址、间接寻址、直接绝对寻址、立即数寻址等多种不同的寻址方式.所以需要在每一种情况中都增加标记翻译块类型的操作.函数返回使用的RET指令也如此处理,将所有可能出现的情况都覆盖,保证了信息不会丢失.

对于ARM平台,它与x86有很大不同.因为它们根本的体系结构差异较大,ARM基于RISC而x86主要基于CISC,所以在ARM上实现同样的功能可能会需要更多的工作.ARM芯片的特点之一是大量使用寄存器,数量多达37个,如通用寄存器R0~R15,从名字到功能不一定能和x86上的一一对应.另一个特点是指令归整、简单,基本寻址方式有2~3种.ARM的指令集中没有类似x86的CALLRET指令来表示函数调用和返回,因为它们可以认为是跳转的特殊情况.根据ARM程序调用过程规范,子程序跳转一般使用BL或BLX指令.同时,ARM指令集的子函数返回功能是通过BX LR或者出栈语句如POP{R4,PC}来实现的,因为函数返回的效果即是将PC也就是指令指针设定为函数调用的下一条语句.此时需要判断pop指令的操作数中是否包含PC寄存器,如果有则表示起到了函数返回的功能;否则只是一般的出栈操作.因此ARM指令集不像x86那样易于识别和标记,需要对多个指令进行分析,然后根据操作码进行相应的标记.

1.5 获取函数调用相关的CPU状态信息

根据1.3节的数据格式,需要获取当前虚拟机时间、进程标识、当前栈顶指针、函数所在模块名称4项内容.

当前虚拟机的时间可以直接通过QEMU提供的API函数qemu_clock_get_ns来读取,比较方便.

进程标识和栈顶指针在真实的物理机器中一般都保存在寄存器中.在QEMU中维护着一个变量env,它模拟了目标机CPU与平台相关的部分,包括了所有寄存器、标志位的值,代表目标机的当前状态,可以通过读取这个变量来获得寄存器的值.

不同的体系结构中寄存器的配置也不相同.在x86中,可以使用CR3寄存器来标识进程,因为它保存了页目录表的物理内存基地址,每个进程的值都不一样;栈顶指针则由ESP寄存器保存,可以直接读取.在ARM中,页目录表的物理内存基址一般保存在协处理器CP15中的TTBR0寄存器,QEMU中使用变量ttbr0_el1模拟.栈顶指针则由R13寄存器保存.

函数所在模块的名称可以表示函数的来源.如果是内核中的函数,则规定所在模块的名称为kernel.如果函数来自内核可加载模块,则需要读取虚拟机的内存获得模块的名字.这需要依赖操作系统源码进行的分析.Linux在内存中维护着一个存储可加载模块信息的环形链表,每加载一个模块,就会向链表中插入一个新的节点.第1个模块的加载地址是固定的,即内核符号表中变量modules的值.模块的数据结构中含有名字以及加载地址、模块大小等信息.可以通过它们相对于结构体的偏移量来计算变量在内存中的实际地址,然后可以利用QEMU提供的访问客户机内存的API函数cpu_memory_rw_debug()来读取相应地址的内容.这一部分主要与平台的位数有关,它决定了偏移量的大小,也与内核版本有关,不同的版本可能导致数据结构有所变化.

我们可以根据这一特点遍历整个链表,查找每个模块的起始地址和大小,当被调函数的地址在某一模块的起始地址和结束地址即起始地址+模块大小之间时,认为函数属于此模块,读取并记录模块的名字和函数相对于模块的偏移量即函数实际地址-模块加载地址.这样在解析的时候就能够直接利用相应模块的符号表信息了.

1.6 日志解析

使用DBCG-RTL工具可以画出函数调用关系图.需要将日志文件处理成DBCG-RTL规定的标准格式,即“PID:pid TID:tid CALL_TIME:time1 RETURN_TIME:time2 FROM:function1:file1:line1 TO:function2:file2:line2 TIME:deltatime”.

主要是将函数地址解析成函数名称和将栈顶指针处理为线程标识符即可.仅需要执行程序的符号表信息,而不需要整体的源代码.DBCG-RTL的数据库表中存储有函数的地址、名称、所在文件、起始行号等信息,因此可以利用数据库来进行解析工作.

以自己编写的可加载模块的函数调用为例,验证数据的正确性.内核的可加载模块有着一系列的编写格式和规范,比如必须包括初始化模块的函数init_module和卸载模块的函数cleanup_module,这里不作赘述.按照标准编写一个非常简单的可加载模块,它的功能是递归地计算5的阶乘并输出,源代码如下:

#include 〈linuxmodule.h〉

#include 〈linuxkernel.h〉

int factorial(int num) {

if (num==1‖num==0)

return 1;

else

return num*factorial(num-1);

}

int init_module(void){

printk(KERN_INFO “5!=%d\n”,factorial(5));

return 0;

}

void cleanup_module(void){

printk(KERN_INFO “Goodbye!\n”);

}

编译后生成内核模块文件,然后加载这个模块,会自动执行其中的函数init_moudle.跟踪出的原始数据如图4所示:

Fig.4 Loadable kernel module tracing data
图4 可加载模块跟踪结果

factorial.ko文件的符号表如表1 所示:

Table 1 Symbol Table of File factorial.ko
表1 factorial.ko文件的符号表

SymbolAddressSymbolTypeSymbolName00000000T(in.textsection)factorial00000020T(in.textsection)init_module00000040T(in.textsection)cleanup_module

可以看到,第2条记录表明首先调用了函数init_module进行初始化,然后调用了4次函数factorial,因为factorial(2)在计算factorial(1)时后者的结果已被编译器直接优化为1了,不再发生函数调用了,然后是4次逐级的函数返回.接下来的一条函数调用地址c1620fdc正是内核函数printk的地址.经过对比可以发现跟踪出的数据与源代码的行为相同,即发生了init_modulefactorial,factorial到自身的递归调用和init_moduleprintk的函数调用.而且运行时读取的进程号和栈顶指针的值可以对应起来,函数调用与返回信息能够互相匹配,说明了数据的正确性.

从这个例子也可以看出,我们的工具对于嵌套的函数调用如递归和普通的函数调用的处理是一致的.递归函数的进程标识都是071c5000,线程标识经过计算都是c71a6000,被调用的地址也都一样,唯一不同的是栈顶指针依次递减,通过栈帧的结构可以看出发生了3次函数factorial到自身的递归调用.

当然有的特殊情况可能只有函数调用或返回,而没有配对的信息,比如一些明确声明为noreturn的函数,主要为一些可能出现错误的函数,而经过fork之后父子进程会分别返回,但是只有一次调用.

对于将函数地址解析成函数名称和位置,以及调用与返回匹配的工作,可以选择在运行时进行,也可以在整个系统运行结束后进行.日志的数量很大,边运行边处理会较快地得到结果,选择这种在线的处理方式效果更好.

1.7 扩展支持其他CPU平台

为了将函数调用跟踪分析功能在其他CPU平台上实现,本文已对实现方法中与平台相关的部分进行了特别说明,只需了解目标平台的一些架构信息和指令集信息;其他部分如输出日志的功能都是通用的,不需要重新编写.

以在MIPS上实现基于QEMU的动态函数调用跟踪为例.首先应根据MIPS32或者MIPS64数据位数的不同来修正一些成员变量在结构体中偏移量的值和输出的格式;其次,应了解一些架构信息,如对应x86中CR3寄存器存储页目录基地址的是MIPS中的哪个寄存器,堆栈指针又是由哪个寄存器来存储的,在QEMU中由哪些变量来表示,输出日志时需要获取这些变量的值;最后是查阅MIPS的指令集,如果有专门的函数调用和返回语句,像x86那样,那么只要找到这些指令对应的所有操作码并作标记操作即可.否则,可能需要将所有可能的跳转语句都记录下来,利用一些其他条件如操作数进行筛选和过滤,处理之后形成最终的调用和返回语句.

2 与S2E的比较

2.1 功能特性

S2E本身对QEMU进行了大量的修改,最大的功能是符号执行,其中最主要的是基于虚拟机当前状态的事件处理机制和插件编写模式.S2E有完整的模块化插件架构,插件之间通过发布、订阅的事件处理机制交互.事件可以由S2E软件本身或者其他自己编写的插件产生.如果插件需要订阅某种事件,比如函数调用,需要注册这个事件,并声明回调函数.那么当这个事件触发之后,会自动调用插件中设定的对应的回调函数,执行规定的动作.

然而S2E不能够很好地跨平台,目前仅支持在x86_64系统中模拟x86_32客户机,对ARM的支持还处在实验状态.同时开发者的发布速度较慢[12],相比于原始的QEMU缺失了很多新功能.

我们直接对QEMU进行修改,只实现了跟踪函数调用与返回这一个常用功能.这样的优点是修改的内容较少,也不会对原有的其他功能产生影响,便于开发和调试.同时也能很快地在其他的平台上实现,而不需要重新实现S2E这样复杂的功能平台.修改分为2部分:1)平台无关的,如整体的执行流程框架:翻译—执行—输出日志,这部分各个平台是统一的,不需要重复实现;2)与平台相关,我们提供了设置翻译块类型等帮助函数,在不同平台的翻译过程中的适当位置插入即可,同时将获取寄存器值等跟CPU架构相关的函数抽象成接口,只要实现了特定平台上的这些函数,执行时就会根据平台调用不同的函数,比起S2E更加容易扩展.

2.2 性能比较

本文工具的另一个优点是直接操纵QEMU原生的一些数据结构,而不通过S2E,同时将解析的工作交给另一个进程,处理的速度也会大大地提高.针对动态函数调用跟踪这个功能而言,在相同的实验环境中,跟踪内核为Linux3.5.4和根文件系统为busybox的镜像从开机到内核启动完毕出现shell的所有函数调用关系,S2E消耗的时间为3 520.13 s,大约1 h,而使用本文的工具运行和解析总共的时间为1 810.69 s.整体效率比S2E提升了94.4%.通过以上实验可以看出本文工具的性能要强于S2E.

3 结论与展望

本文基于QEMU实现了针对Linux操作系统的动态函数调用分析,利用模拟器的特性,可跟踪自系统启动开始后的所有函数调用和返回信息.此功能可以方便地支持多种平台,分别在x86_32,x86_64,ARM平台进行了实现.同时将数据的获取和解析2个步骤并行进行,提高了处理效率.在对跟踪得出的原始数据经过初步处理后,可以为分析软件的执行逻辑、瓶颈或逆向工程提供一定的支持.

本文的工作还有一些不足,例如输出信息的性能有很大的提升空间,可以使用一些压缩算法;由于使用模拟器,QEMU的时钟与真实系统有所差距,可能导致操作系统的行为异常[13].可以扩展的地方也有很多,如增加对函数参数和返回值的获取,进一步提高函数调用分析的能力;目前模拟跟踪的是单核CPU,以后会扩展到多核CPU上,有很多文献利用了多核加速QEMU的CPU模拟[14-16],主要的变化是需在输出中增加CPU的标识符,并在解析时对不同CPU上的函数调用和返回信息使用不同的栈来匹配,可以显著地提高程序的运行速度.

参考文献:

[1]Zheng Yuhui, Mu Yongmin, Zhang Zhihua. Research on the static function call path generating automatically[C] Proc of the 2nd IEEE Int Conf on Information Management and Engineering. Piscataway, NJ: IEEE, 2010: 405-409

[2]Terashima Y, Gondow K. Static call graph generator for C++ using debugging information[C] Proc of the 14th Asia-Pacific Software Engineering Conf (APSEC’07). Piscataway, NJ: IEEE, 2007: 127-134

[3]Hall M W, Kennedy K. Efficient call graph analysis[J]. ACM Letters on Programming Languages & Systems, 1997, 1(3): 227-242

[4]Graham S L, Kessler P B, McKusick M K. Gprof: A call graph execution profiler[J]. ACM SIGPLAN Notices, 2004, 39(4): 49-57

[5]Chipounov V, Kuznetsov V, Candea G. S2E: A platform for in-vivo multi-path analysis of software systems[J]. Computer Architecture News, 2012, 39(1): 265-278

[6]Bellard F. QEMU, a fast and portable dynamic translator[C] Proc of USENIX ATC’05. Berkeley, CA: USENIX Association, 2005: 41-46

[7]Singh P, Batra S. A novel technique for call graph reduction for bug localization[J]. International Journal of Computer Applications, 2012, 47(15): 1-5

[8]Bartholomew D. Qemu: A multihost multitarget emulator[J]. Linux Journal, 2006(145): 68-71

[9]Liu Yuchen, Wang Jia, Chen Yunji, et al. Survey on computer system simulator[J]. Journal of Computer Research and Development, 2015, 52(1): 3-15 (in Chinese)(刘雨辰, 王佳, 陈云霁, 等. 计算机系统模拟器研究综述[J]. 计算机研究与发展, 2015, 52(1): 3-15)

[10]Chylek S. Collecting program execution statistics with Qemu processor emulator[C] Proc of 2009 Int Multi Conf on Computer Science and Information Technology. Piscataway, NJ: IEEE, 2009: 555-558

[11]Jia Di, Xiang Yong, Sun Weizhen, et al. Database-based online call graph tool applications[J]. Journal of Chinese Computer Systems, 2016, 37(3): 422-427 (in Chinese)(贾荻, 向勇, 孙卫真, 等. 基于数据库的在线函数调用图工具[J]. 小型微型计算机系统, 2016, 37(3): 422-427)

[12]Chipounov V, Kuznetsov V, Candea G. The S2E platform: Design, implementation, and applications[J]. ACM Trans on Computer Systems, 2012, 30(1): 1-49

[13]Dovgalyuk P. Deterministic replay of system’s execution with multi-target QEMU simulator for dynamic analysis and reverse debugging[C] Proc of the 16th European Conf on Software Maintenance and Reengineering. Piscataway, NJ: IEEE, 2012: 553-556

[14]Ding J H, Chang P C, Hsu W C, et al. PQEMU: A parallel system emulator based on QEMU[C] Proc of the 17th IEEE Int Conf on Parallel & Distributed Systems. Piscataway, NJ: IEEE, 2011: 276-283

[15]Hong D Y, Hsu C C, Yew P C, et al. HQEMU: A multi-threaded and retargetable dynamic binary translator on multicores[C] Proc of the 10th Int Symp on Code Generation and Optimization. New York: ACM, 2012: 104-113

[16]Wang Zhaoguo, Liu Ran, Chen Yufei, et al. COREMU: A scalable and portable parallel full-system emulator[J]. ACM SIGPLAN Notices, 2011, 46(8): 213-222

Xiang Yong, born in 1967. PhD and associate professor. Senior member of CCF. His main research interests include operating system and computer networks.

Cao Ruidong, born in 1992. Master candidate. His main research interests include operating system and computer networks (crdong@csnet4.cs.tsinghua.edu.cn).

Mao Yingming, born in 1988. PhD candidate. His main research interests include operating systems and computer networks (myming@csnet4.cs.tsinghua.edu.cn).

QEMU-Based Dynamic Function Call Tracing

Xiang Yong1, Cao Ruidong1, and Mao Yingming2

1(Department of Computer Science and Technology, Tsinghua University, Beijing 100084)2(School of Computer Science, Beijing Institute of Technology, Beijing 100081)

Abstract:Function call has always been an important research topic in Linux kernel analysis. There are two main approaches to obtain function calls, static analysis and dynamic analysis. Using dynamic tracing approach can provide accurate and real-time function calls. It is great help to analyze and debug software programs. Considering that existing tools need some particular compile options or their tracing data is not very comprehensive, a new dynamic function call tracing tool that supports multiple CPU architectures based on an open source emulator QEMU is designed and implemented. It can provide function call and function return information including those in the Linux kernel booting phase on three architectures, x86_32, x86_64 and ARM. When the system is running, this tool intercepts procedure call and return assembly instructions. Then it logs necessary state information to file. Based on the property that these kinds of instructions must be the last one of a QEMU translation block, the amount of checked instructions is lowered and the efficiency is promoted. Only the symbol table of the program not the source code is needed to parse function call data. Test result shows that the behavior indicated by tracing data concurs with the corresponding source code. This tool has higher performance and supports more CPU architectures than S2E. It is easier to extend to other architectures.

Key words:function call; dynamic tracing; emulator; multiple platform; Linux kernel analysis

收稿日期:2016-02-26;

修回日期:2016-08-29

基金项目:“核高基”国家科技重大专项基金项目(2012ZX01039-004-41,2012ZX01039-003) This work was supported by the National Science and Technology Major Projects of Hegaoji (2012ZX01039-004-41,2012ZX01039-003).

中图法分类号:TP391