QEMU
- 入门知识
- 基本理解
- qemu的理解
- QEMU里运行的系统可以被视为一个虚拟机,QEMU本身也是一个纯软件的虚拟机,也是运行在hosts上的一个APP(还是一个应用层的APP);
- QEMU是工作在操作系统的用户态程序,它有一个如一般应用程序的入口点函数——main函数(vl.c源码文件)
- 支持两种操作模式:用户模式仿真和系统模式仿真
- KVM
- 是什么
- KVM(Kernel Virtual Machine)是Linux的一个内核驱动模块,它能够让Linux主机成为一个Hypervisor(虚拟机监控器)
- 做什么
- KVM通过调用Linux本身内核功能,实现对CPU的底层虚拟化和内存的虚拟化,使Linux内核成为虚拟化层。
- KVM模块的职责就是打开并初始化VMX功能,提供相应的接口以支持虚拟机的运行。
- 本质上,KVM是linux内核中管理虚拟硬件设备的驱动,该驱动使用字符设备/dev/kvm(由KVM本身创建)作为管理接口,主要负责vCPU的创建、虚拟内存的分配、vCPU寄存器的读写以及vCPU的运行
- 虚拟机运行在客户模式中,客户模式是linux在用户模式和内核模式之后新增的模式
- qemu-KVM
- QEMU使用了KVM模块的虚拟化功能,为自己的虚拟机提供硬件虚拟化加速以提高虚拟机的性能。
- QEMU+KVM成了一个完整的虚拟化平台
- KVM运行在内核空间,只是内核模块
- QEMU运行在用户空间,实际模拟创建,管理各种虚拟硬件(磁盘,网卡,显卡等)
- 两个人是相互利用的关系,KVM利用QEMU管理用户空间,实现用户和内核的交换;QEMU利用KVM实现硬件虚拟化的加速,使得运用在其上的虚拟机有匹敌真实硬件的速度;
- VMX(Virtual Machine Extension)
- 虚拟机扩展,是处理器级别的功能,用于支持虚拟化,比如:KVM
- 注:
- 拿做饭举例,VMX就是提供基础的硬件设施,比如灶台,锅碗瓢盆,KVM是个配菜的,把准备工序准备好,qemu上来直接做菜吧,虽然qemu配菜的活也是会干的,但是干的太慢;
- qemu其实就是个虚拟机软件,和VMware基本一回事,就是VMware是图形化的傻瓜式的,这个所有的操作都需要我们自己操作罢了;
- 几个概念
- 基础名称
- Backend
- Host中模拟Guest中某种行为的那些代码
- qemu中为了模拟guest的某些功能,运行在qemu下的代码;
- Qemu是Host上的一个进程,它模拟了一个VM(guest),一个VM是一组被模拟对象(Object)的组合体;
- Qemu的对象主要有两种
- cpu
- qemu为每个Guest的CPU对象创建一个线程,这些线 程就可以利用Host CPU的算力,模拟VM CPU在遇到每条指令时的行为,更新VM CPU对象的 状态,如果遇到VM CPU做了IO一类的影响其他对象的行为,这个线程就暂时离开VM CPU的 模拟,跳出来,寻找对应的设备(或者其他CPU),去处理这个变化;
- 注:
- 这里的CPU是VM的CPU,线程确实host的线程;
- device
- Device当然也可以创建自己的线程,但更多时候,是它被动地被CPU的行为所控制;
- QOM
- Qemu的代码主要是基于C的,不支持面向对象特性,但偏偏设备极为适合使用面向对象管理 。所以Qemu写了一套用C模拟的面向对象接口,QOM,Qemu Object Model。
- Qemu几乎所有被模拟的对象,都通过这种对象管理。
- QOM是一个粗封装的面向对象模型
- virtio
- 它的目标是定义一个标准的虚拟设备和Guest的接口。也就是说在设备上实现“半虚拟化”,让 guest感知host的存在,让guest上的模拟变成一种guest和host通讯的行为。
- 在QEMU里就是实现一些IO接口的虚拟化;
- CPU模拟
- TCG
- TCG不是通过一条条指令解释执行的,而是先把Guest的指令翻译成Qemu中间代码,进行中间代码优化,然后再把中间代码翻译成Host代码,才执行的;
- TB
- TCG target组织成多个Translate Buffer
- TB作为 Cache存在
- qemu架构和组成
-
-
- Hypervisor管理程序
- QEMU中的Hypervisor(虚拟机管理程序)从磁盘映像加载二进制机器代码,使用TCG将其转换为本机机器代码,连接到虚拟或实际设备,并启动软件MMU,然后开始在磁盘映像中模拟操作系统。
- KVM基本上是Linux内核中的Hypervisor(虚拟机管理程序)
- 微代码生成器(TCG)
- Tiny Code Generator(TCG)将源处理器机器代码转换为虚拟机运行所需的机器代码块(如x86机器代码块)
- Tiny Code Generator(TCG)中,这些已经翻译的代码块放在转换缓存中,并通过跳转指令将源处理器的指令集(ISA)和目标处理器的指令集(ISA)链接在一起
- TCG在运行的过程中存在一个小缺点
- 它无法正确运行自修改代码,因为它没有将修改后的代码页进行标记,再次运行时需要重新翻译
- 注:
- 总的来说就是实现,看起来程序是在qemu上模拟的这个硬件结构上运行,实际上程序还是在宿主机上运行(也就是X86);
- 硬件设备
- 虚拟机的硬件设备要求可以通过直接连接主机中的实际物理设备或通过QEMU中的硬件设备仿真来实现。
- QEMU可以选择连接到Linux内核中的“virtio”半虚拟化驱动程序,这意味着Linux内核处理虚拟机和硬件设备之间的输入/输出,而不采用QEMU的模拟设备进行中转和传输(仅用作中介)。(由于QEMU模拟IO设备效率不高的原因,现在常常采用半虚拟化的virtio方式来虚拟IO设备。)
- 磁盘映像
- 软件MMU
- 内存管理单元(MMU)处理对计算机内存位置的访问
- 当处理器想要访问某个存储器地址时,MMU获取该地址的内容。此内容可以来自处理器芯片上的本地快速缓存,来自随机存取存储器(RAM)或来自光盘。它甚至可以做出一些关于缓存某些内存位置的控制决定。
- QEMU的MMU是基于软件的,工作方式与硬件MMU类似
- 它使用地址转换缓存,其中包含访客地址、主机地址和偏移值,以提高转换速度。
- 它还允许智能链接代码块,以便在没有内存故障的情况下实现更快的执行,其中必须重新加载和重新转换内存块。
- 在寻找在QEMU中运行的虚拟机的漏洞时,软件MMU是否正在进行翻译和正确放置块会是其测试和Fuzz的重点。
- 内外设、内存与寄存器
- QEMU设计的核心
- 完全模拟了总线控制器的行为
- 当有内存访问的时候,总线控制器会根据内存地址将读写请求“分发”给不同的器件
- 唯一的区别
- 真实硬件上的分发行为由硬件译码电路完成,而QEMU用软件来完成分发
- QEMU对内外设的核心框架逻辑
- 各种内设、外设都被注册成一个个独立的设备并配备相应的read/write、物理内存区间。当发生内存、寄存器访问时,QEMU框架会根据物理内存所处的区间去调用相应的read/write。
- 源码初步
- Qemu 软件虚拟化实现的思路是采用二进制指令翻译技术
-
-
-
- 注:
- TCG是qemu的核心
- guest binary instructions -> TCG IR -> host binary instructions
- 这就是一个Guest OS程序向Host OS程序转换的内置翻译器,主要目的是为了在Host的硬件上面可以运行Guest的代码
- configs和组件化编译
- 要编译的组件开关定义在这
- default-configs/arm-softmmu.mak
- default-configs/aarch64-softmmu.mak
- 注:
- aarch64-softmmu,mak include了arm-softmmu.mak,所以qemu-system-aarch64能兼容qemu-system-arm的所有特性。
- 如果要自己加一个ARM单板,需要先修改hw/arm/Kconfig,增加相应的宏,再去修改arm-softmmu.mak。
- 每个目录下都有一个Makefile.obj,和kernel一样,使用了Kconfig的写法
- 外设仿真
- 所有设备相关的仿真都位于hw目录下
- audio
- block
- 块设备,包括Nand/Nor Flash、CDROM
- bt
- char
- display
- dma
- input
- ipmi
- Intelligent Platform Management Interface
- mem
- misc
- net
- nvram
- pcmcia
- rdma
- scsi
- sd
- watchdog
- 其他
- 对dtb的特殊处理
- QEMU提供灵活的dtb处理方式,dtb可以是以下任意一种。
- 用户指定的dtb
- dtb注入:用户无需指定dtb,QEMU自动生成一个dtb
- Virt这个machine却不需要指定dtb,因为在virt这个machine的实现中,QEMU动态生成了一个dtb,并把它传递给Linux kernel
- dtb劫持:在用户指定的dtb的基础上,静默修改某些内容
- 理论上,任何一个Linux kernel都需要dtb文件配合,dtb描述的是单板差异,dtb的存在也让同一个编译好的kernel可以运行在不同的单板上,即使这些单板间有小差异
- event trace(事件跟踪)的实现技巧
- QEMU用了一个比较有意思的技巧
- Event trace相关的代码都是自动生成的,开发者只需要维护一个名为trace-events的文件,并在自己的代码里调用约定好的函数,即可完成event trace内容的输出。这样做的好处是可以得到格式统一的日志,并且手工书写的代码量最少。
- TCG Plugin(插件)
- Plugin不能改变TCG的行为,只能做记录、统计。
- 目前见到有这些plugins
- bb.c:统计TCG的branches和instructions
- insn.c:简单统计Gues上所运行过的汇编指令
- mem.c:内存访问的统计
- empty.c:一个空的plugin
- howvec.c:对所有运行过的汇编指令做归类,并为每一种分类做统计
- 加入plugin启动qemu
- 统计bb.c的内容和insn.c的内容
- ./qemu-system-aarch64 \ -nographic -M virt -cpu cortex-a72 -smp 2 -m 1G \ -kernel ../Image/prebuilt_image/Image.virt64le \ -append "root=/dev/ram0 rdinit=/init console=ttyAMA0 ignore_loglevel printk.time=y" \ -initrd ../Image/prebuilt_image/initrd_64le.ext4 \ -plugin tests/plugin/libbb.so,arg=inline \ -d plugin
- 当使用CTRL-a x退出QEMU时,就能看到统计信息
- 其他特别的Plugin
- howvec
- 这个plugin拦截了TCG,对每一条汇编指令做归类,同时对每一类汇编指令做统计,在退出的时候输出统计内容。
- ./qemu-system-aarch64 \ -nographic -M virt -cpu cortex-a72 -smp 2 -m 1G \ -kernel ../Image/prebuilt_image/Image.virt64le \ -append "root=/dev/ram0 rdinit=/init console=ttyAMA0 ignore_loglevel printk.time=y" \ -initrd ../Image/prebuilt_image/initrd_64le.ext4 \ -plugin tests/plugin/libhowvec.so \ -d plugin
- 以上的就是改了一下-plugin的参数
- libinsn.so
- 这个plugin统计Guest所运行过的指令,可以用它来算MIPS(Million Instructions Per Second),MIPS一个衡量仿真速度的指标。
- QEMU中的时间
- 默认地,QEMU中Guest的时间就是真实世界(Wall clock)的时间。
- 问题
- QEMU上运行的程序比单板慢,其时间流逝却又和真实世界一样,而某些对时间敏感的用户态程序就会发生流程紊乱
- 预计在10ms内必须运行完的代码段,在QEMU上不能运行完,导致后期运行到了出错分支。
- 解决办法
- 让QEMU中流逝的时间变慢
- icount参数
- 使用了icount参数后,不管有多少个vCPU,TCG都只用Host的一个线程来仿真
- qemu运行
- 系统态
- AArch64的virt machine
- ./qemu-system-aarch64 \ -nographic \ -M virt \ -cpu cortex-a72 \ -smp 4 \ -m 4096M \ -kernel ../Image/prebuilt_image/Image.virt64le \ -append "root=/dev/ram0 console=ttyAMA0 init=/linuxrc ignore_loglevel" \ -initrd ../Image/prebuilt_image/initrd_64le.ext4
- qemu启动参数
- ./qemu-system-aarch64 -M help
- -kernel
- -initrd
- 指定initrd文件
- initrd就是一个文件系统
- 英文:boot loader initialized RAM disk
- 由bootloader初始化的内存盘
- 内存盘:指的处于内存中的文件系统
- 是将内存仿成磁盘的形式,而非真正的磁盘
- -m
- -smp
- -cpu
- -M:
- 指定machine,help可以列出所有所支持的machine
- qemu命令行参数
- -d
- 后面加help可以看到所有选项
- in_asm
- out_asm
- cpu
- cpu_reset
- int
- exec
- trace
- guest_errors
- 对应qemu_log_mask(LOG_GUEST_ERROR, …)输出的内容,一般被用来查看设备中缺漏的寄存器
- -D logfile
- 用户态
- qemu-aarch64 <qemu_paras> elf
- HMP&QMP
- HMP(Human Monitor Protoco)if
- 手动交互
- 子命令
- info cpus
- info roms
- 显示从bootrom开始的各种镜像及所加载的物理地址
- info qtree
- info mtree
- 进入QEMU的HMP
- ctrl-a c
- 启动之后从#进入(qemu),就是进入HMP
- QMP(QEMU Machine Protocol)
- 自动交互
- 完全基于JSON语法的交互途径
- 进入QMP
- 方法一
- 启动QEMU的时候,命令行参数增加如下内容
- -chardev socket,id=qmp,port=4444,host=localhost,server -mon chardev=qmp,mode=control,pretty=on
- 然后在另一个终端中(哪个目录都可以)
- 连接成功后,输入命令(在这之后就进入QMP了)
- { "execute" : "qmp_capabilities" }
- 注:
- { "execute": "query-commands" }
- 所有的命令都是JSON格式
- 一些小操作
- ctrl-a x
- ctrl-a c
- QEMU重启后文件丢失
- 错误操作
- 拷贝完后直接退出QEMU,写入的数据还在geust内存中,来不及保存就被丢弃了。
- 解决方案
- 退出QEMU前,在guest命令行中输入reboot命令,保证guest操作系统把内存中的数据写入QEMU。
- 错误操作
- 使用kill命令杀死QEMU,由于写入QEMU的数据会被加载到一个队列上,直到IO线程被调度时才会真正写入文件,如果在写入之前QEMU就异常退出,这些数据就丢失了。
- 使用CTRL+A,X命令退出QEMU。由于X命令会直接调用exit函数退出QEMU,挂载IO线程队列上的数据一样会被丢掉。
- 解决方案
- CTRL+A,C进入QEMU命令行,使用quit命令退出QEMU。quit命令会做必要的cleanup,然后通过return 0优雅的退出main函数。
- TCG
- 是什么
- Tiny Code Generator,别翻译成微代码生成器,应该是动态代码生成器。
- TCG实际的作用和编译器的后端一样,主要就是负责分析,优化Target(guest)代码,生成host代码。这里的代码指的是指令,实际完成的工作就是把自动的guest的指令动态的翻译成host的指令。
- 组成框架
- 一句话流程:
- TCG就是将目标CPU架构的二进制代码反汇编成等价的TCG中间码,然后再将TCG中间码转化为等价的host CPU架构的二进制代码。
- 流程
- 解释
- 前端解码器
- 中间码
- 终端分析优化器
- 对前端产生的中间码进行优化分析,消除代码中目标体系架构的一些特性,让后续中间码到host指令的性能更好。
- 手段
- 参数活性分析
- 分析操作的输入参数在本操作完成之后,是否还会被基本块中的后续操作使用,是就是有活性,否就是没有活性。
- 没有活性的参数,使用之后应该立即释放其占用的资源。
- 操作简化
- 后端翻译器
- 类似编译器的后端,就是把中间码转化为host的二进制代码,或者说是编译成。
- 翻译过程
- 两个阶段
- 以基本块为单位
- 一个入口一个出口的基本代码块,也就是从当前PC开始,一直到遇到控制流转移指令为止(call,jump,return等)。
- 将中间码序列翻译成本地可执行的host指令序列
- TCG维护了一个中间码映射表,映射表里存着中间码与host架构指令集的映射关系。
- TCG是一个基本块,一个基本块的解码,也就一个基本块,一个基本块的分析优化....
- 翻译结束之后,TCG会将翻译好的代码放到主机代码缓存区。
- 翻译过程设计的数据结构
- TB(TranslationBlock)
- 定义
- 保存了guest二进制的PC,tc 指向code cache。
- TCGContext
- 定义
- 代码生成上下文相关,存放IR(intermediate representation,中间码)
- 全局变量tcg_ctx
- 所有的微操作都把微操作码和微操作数放到全局变量tcg_ctx中,也就是说把指令存在了tcg_ctx中。也就是说IR的实体就是这个tcg_ctx的全局变量。
- tcg_ctx的原始数据结构就是TCGContext
- DisasContext
- 定义
- 前端解码器反汇编时的主要数据结构,主要包含反汇编target架构时需要的相关信息。
- CPUState
- 定义
- include/qom/cpu.h
- 包含抽象出来的各种CPU架构共同具有的一些CPU状态,CPUARCHState(包含寄存器等)由它派生。
- TranslatorOps
- 定义
- include/exec/translator.h
- 主要包含前端解码器将指令翻译为中间码的7个函数,在具体的架构中挂载各自架构相应的函数实现,完成target指令的解码。
- 工作原理
- host侧开始执行是,会先在哈希表(维护一个哈希表,存放最近执行的翻译块)中找对应翻译块(就是中间码编译出来的host指令块),如果哈希表里找不到,就去翻译换缓存里找,如果翻译缓存里也找不到代表没翻译,就启动TCG翻译过程。
- 翻译时的代码执行
- gen_intermediate_code()
- tcg_gen_code()
- TCG中间码
- 可以理解为,中间码是一种简单,间接的类似汇编的特定代码。
- helper函数机制
- target和host的架构不一样,可能会发生target指令翻译成中间码,中间码再翻译成host侧指令的时候,找不到对应的指令,就需要helper函数机制。
- helper函数机制就是,现有的翻译模块翻译不了某个指令,TCG支持你自己写个函数模拟这个指令功能,也就是你自己写个函数翻译。
- 中间码的使用
- TCG把种类繁多的中间码都封装车成了操作码,操作码定义在tcg/tcg.h。
- qemu源码分析
- user模式
- main()
- ./linux-user/main.c
- 准备工作 + cpu_loop()
- cpu_loop()
- 每个架构都有自己的,以rv为例:./linux-user/riscv/cpu_loop.c
- 循环运行cpu_exec()
- 因为cpu_exe()的执行是一个tb,一个tb执行的,所以这里要循环的执行cpu_exec(),直到程序运行结束。
- cpu_exec()
- ./accel/tcg/cpu-exec.c
- 功能和名字一样,这个才是真正的main函数。
- TCG
- 前端框架
- tcg_cpus_exec()
- accel/tcg/tcg-accel-ops.c
- 执行TCG代码,记录profile
- ?
- 为什么cpu_loop()和这个函数,都在执行cpu_exec(),一个是执行一次,一个是循环执行,实现的功能有啥区别?这个函数在什么时候被调用?
- tcg_accel_ops_init()
- accel/tcg/tcg-accel-ops.c
- 挂接TCG的各种钩子,不同的情况下,给ops->create_vcpu_thread,ops->kick_vcpu_thread, ops->handle_interrupt这三个变量不同的值。
- tb_gen_code()
- accel/translate-all.c
- 负责生成IR,包含guest --> IR --> Host的全过程
- 架构相关的前端
- gen_intermediate_code()
- tcg_gen_code()
- IR->host
- TranslatorOps结构体
- TranslatorOps { .init_disas_context // 每个TB都会调用一次 .tb_start // 每个TB都会调用到,给TCG plugin用 .insn_start // 开始翻译一条汇编之前调用它 .translate_insn // 执行guest->op的翻译,disas_a64_insn、disas_arm_insn是AArch64、AArch32的翻译函数 .tb_stop // .disas_log // };
- target
- guest的代码,如arm的,rv的,linx的。
- linx
- LinxISA的代码
- xx.decode
- 指令解码,其实就是指令的编码格式
- blockxx.decode
- headxx.decode
- Decode_Tree语法
- 注:
- 整个decode_tree的实现其实就是一层一层的定义指令结构相关的变量,然后迭代表示每一条指令的详细编解码。
- https://wiki.huawei.com/domains/4821/wiki/12455/WIKI2022031000950
- Fields
- 定义如何取出一条指令中,各个字段的值。
- 解释
- Field 由 % 开头,一个 identifier,零个或多个 unnamed_field,零个或一个 !function。
- %imm_b 31:s1 7:1 25:6 8:4 !function=ex_shift_1
- identifier
- 指令字段的标识符,自定义,例如这里的imm_b,也可以是srcL
- unnamed_field
- 描述该字段的有效位,第一个数字是最低有效位位置,第二个数字是有效位的长度,如果加上s,就代表该字段需要有符号扩展。
- 如果存在多个unnamed_field,则这些字段将合并在一起,组成一个字段。通过这种方式,可以定义不相交的字段。例子中的 31:s1、7:1 等。
- 问题
不加s就是代表该字段需要进行无符号扩展?
是的
-
-
-
-
-
-
-
- !function
- 将组合后的字段,所会在调用的 function。例子中的 !function=ex_shift_1。
- 两种情况
指令编码中存在实际中取的是一个数的M:N位,不是从0开始,需要用ex_shift_1移位来表示实际用的,1是左移1位。
指令定义本身对参数有移位的需要
-
-
-
-
-
- Argument Sets
- 保存从指令取出的各字段的值,实际是标识每类指令需要保存的字段,字段在上一个部分定义过。
- 解释
- Argument Set 由 & 开头,一个 identifier,一个或多个 args_elt,零个或一个 !extern。
- &r rd rs1 rs2 !extern
- identifier
- args_elt
- 描述字段,每个 args_elt 在 Argument Set 中定义一个参数。
- args_elf有冒号
第一个标识符是参数名称
第二个标识符是参数类型
默认参数类型是int
-
-
-
-
-
-
-
- !extern
- 表示是否在其他地方已经由其他的 decoder 定义过。如果加上表示以及在别处定义,不会再次生成对应的结构体。
- 结构体
typedef struct { int64_t rd; int rs1; int rs2; } arg_r;
-
-
-
-
-
- Formats
- 定义每个指令的详细格式,利用上面已经定义好的变量
- 解释
- Format 由 @ 开头,一个 identifier,一个或多个 fmt_elt。
- @atom_ld ..... aq:1 rl:1 ..... ........ ..... ....... &atomic rs2=0 %rs1 %rd
- fmt_def
- fmt_elt
由一个或多个 0、1、.、- 组成,每一个字符代表指令编码中的一个 bit。
0、1:代表该 bit 为0或者是1。
.:代表该 bit 可以用0或是1表示。
-:代表该 bit 将被忽略。
-
-
-
- insn_trans
- 指令翻译,就是用一个一个的函数描述一条一条的指令,函数的最底层就是中间码,这样每一条指令都会通过函数对应到一个或者一组中间码。
- 实例
- c.add.16
- static bool trans_blk_c_add_16(DisasContext *ctx, arg_blk_c_add_16 *a) { return gen_comp_arith(ctx, a, tcg_gen_add_tl); }
- 解释
- PLUGIN
- 是什么
- 插件
- 在不改变主程序的代码的情况下,为了实现某个功能,依赖主程序写的程序,只有依赖主程序才能运行。
- qemu的插件
- qemu的插件类似观察者,插件在qemu的翻译和执行期间注册一系列事件,在注册条件触发之后,插件就会收到通知。插件无法更改qemu的运行,只能被动的监测。
- 插件基本结构
- 包含qemu-plugin.h
- include/qemu/qemu-plugin.h
- qemu_plugin_version
- 插件的版本标识,跨版本的插件需要重新编译才可以使用
- qemu_plugin_install()
- 插件的install函数,qemu通过该函数加载plugin,一般实现插件的初始化,以及基本的事件注册。qemu-plugin.h中声明,插件里是实现
- QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, cosnt qemu_info_t *info, int argc, char **argv)
- vpuc_tb_trans()
- 自定义函数,tb块翻译时调用的函数,实现插件的功能。
- plugin_exit()
- 自定义函数,插件退出时被调用的函数,实现插件退出时的动作,比如输出。
- qemu暴露给插件的事件(接口)
- 接口定义
- 插件级别的事件(接口)
- 每个事件,同一个插件只能注册一次,(以下不是全部)
- QEMU_PLUGIN_EV_VCPU_TB_TRANS
- QEMU_PLUGIN_EV_VCPU_SYSCALL
- QEMU_PLUGIN_EV_VCPU_SYSCALL_RET
- qemu系统调用返回后的回调,每次系统调用都会被调用
- 插件内事件(接口)
- 每个接口,同一个插件可以注册多次
- qemu_plugin_register_vcpu_tb_exec_cb()
- qemu_plugin_register_vcpu_insn_exec_cb()
- qemu_plugin_register_vcpu_mem_cb()
- qemu_plugin_register_vcpu_tb_trans_cb()
- tb翻译时,调用vpuc_tb_trans()函数
- qemu_plugin_register_atexit_cb()
- qemu自带的插件
- tests/plugin/empty.c
- tests/plugin/bb.c
- tests/plugin/insn.c
- tests/plugin/mem.c
- tests/plugin/syscall.c
- 实操