QEMU TCG Plugins详解

QEMU TCG Plugins

本文部分内容参考了中科院PLCT实验室王俊强师兄的讲解视频,下附链接,如有侵权,立即整改!
QEMU线上讨论会-王俊强-QEMU TCG Plugings

QEMU TCG Plugins 简介

  • TCG Plugins是自QEMU 4.2以来的新功能,它提供了在代码上运行指令测试的功能。它们能够对系统发出的每个指令和内存访问进行被动监视,不会修改系统模拟的状态。

  • TCG Plugins可以向Translation、execution以及callback中插入事件。

  • Building

    • configure --enable-plugins
  • QEMU启动

    $QEMU $OTHER_QEMU_ARGS \
     -plugin contrib/plugin/libhowvec.so,inline=on,count=hint \
     -plugin contrib/plugin/libhotblocks.so 
    
  • Plugin是一种观察者模式,虽然是观察者模式但它是被动的,不会影响到QEMU的整体性

  • 在启动参数中,我们发现qemu的plugin被编译成.so

image-20220615180358256

  • QEMU共提供了7个原始的plugin,这7个plugin的功能都非常简单,甚至可以说没完全实现。它们给出了程序员开发plugin的示范

QEMU TCG Plugins 源码

在这里插入图片描述

  • Plugin涉及到的源码还是比较多的,其中功能源码指的是Plugin和QEMU内核的交互代码,增加在这些内核文件中。
  • 接口API将Plugin的使用和内核代码解耦合,更安全。不会暴露QEMU的内核代码。同时为了保证安全性,Plugin还设计了一系列自己的结构体而避免使用QEMU的结构体,这点会在后面讲解。
  • 原始plugin是QEMU Plugins提供的几个示例Plugin的源码,这里面大量使用了接口API和新定义的结构体
  • 头文件主要位于这两个文件夹下面。定义了结构体,函数方法等

QEMU TCG Plugins 流程

在这里插入图片描述

  • 首先插件加载后,会调用install函数,install函数会注册相关的回调函数cbs,包括插件处理的事件,插件退出atexit函数,这个函数会在QEMU退出时执行,会释放掉插件处理中收集和产生的数据。

  • 每当事件发生时,事件回调函数就会被调用,在函数中写插件的处理逻辑。我们可以选择处理信息的范围,所有的指令信息都可以被获得。

  • 同时事件回调函数也可以是内联函数,注意内联函数的操作不要太复杂,使用内敛函数时一些数据的一致性则无法保证。

  • 当QEMU退出时,atexit_cb回调函数会被调用。

New Structs

QEMU内核源码应该是对插件模糊的,为了隐藏源码细节,Plugin定义了一套自己的结构体和数据类型,基本都以"qemu_plugin_"开头,包括函数命名。

在这里插入图片描述

在这里插入图片描述

  • 为了保护内核代码,Plugin提供了新的结构体供编写插件使用。主要分为三个方面

    • qemu_plugin_tb 就是为了替代翻译代码时用到的TB块
      在这里插入图片描述

    • 指令信息方面
      在这里插入图片描述

    • Mem信息方面
      在这里插入图片描述

      • hwaddr是具体的mem的结构体
      • 而meminfo这个只是一个32位的值,它被用来做与或运算来判断mem信息的具体类型,比如store、load、 大小端
  • 回调函数一共分2大类和2小类,大类就是insn相关和mem相关,小类就是一般的回调函数和内联函数

QEMU TCG Plugins API

代码在/plugins/api.c中

/plugins文件夹下还有其他文件,其中:

  • core.c是如何向qemu中注入plugin的代码,包括plugin注册,plugin生成callback方法等
  • loader.c是专门处理plugin的加载和卸载,在移除plugin时保证plugin的代码及生成的代码能够完全清除

TCG Plugins提供了很多写好的api给开发者,程序员可以使用这些api完成一个Plugin的编写。主要分为3大块:

  • Plugin注册相关:

    这些API可以让程序员决定Plugin什么时候起作用,可以是vCPU上触发,也可以是TB块、Instruction和Memory中。

  • Plugin功能逻辑相关:

    我们的Plugin一般都有处理数据的一系列逻辑,如果想从QEMU模拟内核中获取相关数据,就可以使用这一类API。比如获取TB块指令数,获取指令的各种信息等等。

  • Plugin生命周期相关:

    源码提供了5个生命周期相关的API,分别是install、reset、output、exit、uninstall。其中,install和exit需要每个Plugin都实现自己的,其他的可以使用通用的API

QEMU TCG Plugins 数据收集

从上文得知,我们知道了QEMU Plugins的基本运行逻辑,也似乎清楚了如何写一个新的Plugin。下面介绍Plugin和QEMU内核的交互代码,弄清Plugin是如何驱动QEMU生成Plugin需要的数据,即维护了新定义的qemu_plugin_tb等结构体。这样不受源码的拘束,写更强大的Plugin。

在这里插入图片描述

在Plugin的生命周期中,“Call qemu_plugin_install”时期定义了Plugin的注册代码和事件回调函数,注册代码我们可以选择是在tb翻译时期、insn生成时期以及mem生成时期。但是Plugin的注册事件只分为:

enum qemu_plugin_event {
    QEMU_PLUGIN_EV_VCPU_INIT,
    QEMU_PLUGIN_EV_VCPU_EXIT,
    QEMU_PLUGIN_EV_VCPU_TB_TRANS,
    QEMU_PLUGIN_EV_VCPU_IDLE,
    QEMU_PLUGIN_EV_VCPU_RESUME,
    QEMU_PLUGIN_EV_VCPU_SYSCALL,
    QEMU_PLUGIN_EV_VCPU_SYSCALL_RET,
    QEMU_PLUGIN_EV_FLUSH,
    QEMU_PLUGIN_EV_ATEXIT,
    QEMU_PLUGIN_EV_MAX, /* total number of plugin events we support */
};

并不像前面提到的Plugin API中那么多种。qemu_plugin_register_vcpu_tb_trans_cb对应了QEMU_PLUGIN_EV_VCPU_TB_TRANS事件,只有该事件下才会影响到而且QEMU内核代码生成数据,其他的事件都是plugin的内部逻辑代码,而且有的register方法都没有对应的事件,比如vcpu_mem_cb是纯plugin逻辑方法,感觉这也是plugin 函数名设计迷惑人的地方。

TB & insn数据收集

在${QEMU}/accel/tcg/translator.c中,有一个关键方法translator_loop。这个方法控制着TB的翻译

void translator_loop(const TranslatorOps *ops, DisasContextBase *db,
                     CPUState *cpu, TranslationBlock *tb, int max_insns)
{
	...
    plugin_enabled = plugin_gen_tb_start(cpu, tb, cflags & CF_MEMI_ONLY);
    while(true) {
	...
        if (plugin_enabled) {
            plugin_gen_insn_start(cpu, db);
        }
	...
        if (plugin_enabled) {
            plugin_gen_insn_end();
        }
	...
    }
	...
    if (plugin_enabled) {
        plugin_gen_tb_end(cpu);
    }
	...
}

我们只关注Plugin相关的,看出Plugin作者模仿TB翻译的流程,在每个TB块翻译的开始和结束分别调用了plugin_gen_tb_start和end,每个指令的翻译都调用了plugin_gen_insn_start和end。这些方法实现在${QEMU}/accel/tcg/plugin-gen.c中,这时Plugin非常核心的代码。通过这几个方法,Plugin就生成完整的qemu_plugin_tb和qemu_plugin_insn结构体数据。

在这里插入图片描述

在translator_loop中执行plugin_gen_tb_end之前,tb和insn的信息就已经生成完毕,在plugin_gen_tb_end中会执行tb_trans_cb,它会拿Plugin工具的vcpu_tb_trans来执行,这也就是为什么每个示例Plugin中,作者都实现了vcpu_tb_trans函数,将这个函数作为每个Plugin逻辑的开始。有的工具比较简单,只需要到这个阶段就可以了,而有的工具比较复杂,比如hotpages工具,那么就需要在vcpu_tb_trans中后面其他时期要运行的代码。下面是hotpages的vcpu_tb_trans函数:

static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
{
    size_t n = qemu_plugin_tb_n_insns(tb);
    size_t i;

    for (i = 0; i < n; i++) {
        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
        qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr,
                                         QEMU_PLUGIN_CB_NO_REGS,
                                         rw, NULL);
    }
}

mem数据收集

Plugin的mem数据收集比较特殊,只有当使用到mem数据的时候,Plugin才会收集。比如调用qemu_plugin_get_hwaddr方法,此时会查TLB来确定hwaddr的值。当TB翻译被执行时,默认TLB是被填满的。

io_writex可能会造成TLB flush或者TLB的大小调整,导致信息丢失。在这种情况下需要从iotlbentry中恢复数据。但是从源码来看好像并没写恢复数据的代码。

Run

首先编译,本例以qemu-6.1为例

  • 在根目录下(验证的是arm架构)

    ./configure --target-list=aarch64-softmmu --enable-plugins
    

    运行完成后会生成build目录

  • 编译

    cd build
    make -j
    
  • qemu编译完成后需要手动去编译plugin

    cd build/contrib/plugins
    make
    

    这样就可以得到所有plugin的.so文件,该目录也是plugin所在的目录

hotblocks

  • cmd

    ./build/aarch64-softmmu/qemu-system-aarch64  \
    	-machine virt,virtualization=true,gic-version=3 \
    	-nographic \
    	-m size=1024M \
    	-cpu cortex-a57  \
    	-smp cpus=1 \
    	-kernel /yourKernel  \
    	-hda /your.img \
    	-append "root=/dev/vda rw console=ttyAMA0 rdinit=/linuxrc"  \
    	-plugin build/contrib/plugins/libhotblocks.so -d plugin \
    
  • 运行结果

    collected 118731 entries in the hash table
    pc, tcount, icount, ecount
    0x000000000047e268, 1, 3, 1902517016
    0x0000ffff9b0c2b00, 1, 4, 869322965
    0x000000000063b630, 1, 3, 675017603
    0x000000000063b63c, 1, 3, 675017603
    0x0000000000405330, 1, 1, 163144534
    0x0000000000405334, 1, 4, 163144534
    0x0000000000405328, 1, 2, 163137397
    0x00000000004053a4, 1, 2, 163137397
    0x00000000004417ac, 1, 1, 150975332
    0x00000000004417b4, 1, 2, 150975332
    0x0000000000441860, 1, 6, 150975332
    0x0000000000441890, 1, 12, 150175916
    0x00000000004417bc, 1, 1, 149083686
    0x0000000000441830, 1, 6, 147212517
    0x00000000004418c0, 1, 5, 146872575
    0x00000000004419ec, 1, 2, 146506598
    0x00000000004419f4, 1, 7, 145575716
    0x00000000004418f0, 1, 7, 144941383
    0x00000000005f85d0, 1, 5, 141259864
    0x00000000005f6b5c, 1, 3, 141259864
    

hotpages

  • cmd

    ./build/aarch64-softmmu/qemu-system-aarch64  \
    	-machine virt,virtualization=true,gic-version=3 \
    	-nographic \
    	-m size=1024M \
    	-cpu cortex-a57  \
    	-smp cpus=1 \
    	-kernel /yourKernel  \
    	-hda /your.img \
    	-append "root=/dev/vda rw console=ttyAMA0 rdinit=/linuxrc"  \
    	-plugin build/contrib/plugins/libhotpages.so -d plugin
    
  • 运行结果

    未识别qemu_plugin_hwaddr_phys_addr

howvec

  • cmd

    ./build/aarch64-softmmu/qemu-system-aarch64  \
    	-machine virt,virtualization=true,gic-version=3 \
    	-nographic \
    	-m size=1024M \
    	-cpu cortex-a57  \
    	-kernel /yourKernel  \
    	-hda /your.img \
    	-append "root=/dev/vda rw console=ttyAMA0 rdinit=/linuxrc"  \
    	-smp 1 -plugin build/contrib/plugins/libhowvec.so -d plugin
    

howvec plugin官网文档运行命令为"…libhowvec.so,count=sreg -d plugin"但是这样会报错,我查看了源码也没有对count参数的处理,暂且去掉

  • 运行结果

    Instruction Classes:
    Class:   UDEF                  	not counted
    Class:   PCrel addr            	(2189089904 hits)
    Class:   Add/Sub (imm)         	(12016160717 hits)
    Class:   Logical (imm)         	(290405307 hits)
    Class:   Move Wide (imm)       	(1278700511 hits)
    Class:   Bitfield              	(601491132 hits)
    Class:   Extract               	(73188 hits)
    Class:   Cond Branch (imm)     	(10963119449 hits)
    Class:   Exception Gen         	(1877 hits)
    Class:     NOP                 	not counted
    Class:   Hints                 	(6020 hits)
    Class:   Barriers              	(4949456 hits)
    Class:   PSTATE                	(1660531 hits)
    Class:   System Insn           	(889210271 hits)
    Class:   System Reg            	(19424950 hits)
    Class:   Branch (reg)          	(1451181617 hits)
    Class:   Branch (imm)          	(2226632283 hits)
    Class:   Cmp & Branch          	(2999486060 hits)
    Class:   Tst & Branch          	(299176864 hits)
    Class:   ldst excl             	(7531814 hits)
    Class:   Load Reg (lit)        	(5054690402 hits)
    Class:   ldst noalloc pair     	(9049600 hits)
    Class:   ldst pair             	(17420022029 hits)
    Class:   ldst reg (imm)        	(11669731753 hits)
    Class: Loads & Stores          	(176306 hits)
    Class: Data Proc Reg           	(3015727186 hits)
    Class: Scalar FP               	(11884944 hits)
    

execlog

  • cmd

    ./build/aarch64-softmmu/qemu-system-aarch64  \
    	-machine virt,virtualization=true,gic-version=3 \
    	-nographic \
    	-m size=1024M \
    	-cpu cortex-a57  \
    	-smp cpus=1 \
    	-kernel /yourKernel  \
    	-hda /your.img \
    	-append "root=/dev/vda rw console=ttyAMA0 rdinit=/linuxrc"  \
    	-plugin build/contrib/plugins/libexeclog.so -d plugin
    
  • 运行结果

    和hotpages相同

cache

  • cmd

    ./build/aarch64-softmmu/qemu-system-aarch64  \
    	-machine virt,virtualization=true,gic-version=3 \
    	-nographic \
    	-m size=1024M \
    	-cpu cortex-a57  \
    	-smp cpus=1 \
    	-kernel /yourKernel  \
    	-hda /your.img \
    	-append "root=/dev/vda rw console=ttyAMA0 rdinit=/linuxrc"  \
    	-plugin build/contrib/plugins/libcache.so -d plugin -D cache.log
    
  • 运行结果

    undefined symbol: qemu_plugin_insn_symbol

测试性能结果

  • 实验参数

    • version: qemu-6.1
    • benchmark: spec2006 403.gcc-1
    • cpu: 1
  • 结果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZlUMfSQA-1655289681989)(https://raw.githubusercontent.com/JaCenz/images/master/img/image-20220615181418293.png)]
    l
    -hda /your.img
    -append “root=/dev/vda rw console=ttyAMA0 rdinit=/linuxrc”
    -plugin build/contrib/plugins/libcache.so -d plugin -D cache.log

    
    
  • 运行结果

    undefined symbol: qemu_plugin_insn_symbol

测试性能结果

  • 实验参数

    • version: qemu-6.1
    • benchmark: spec2006 403.gcc-1
    • cpu: 1
  • 结果

在这里插入图片描述
欢迎讨论~

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值