QEMU理解与分析系列(1):QEMU简介

news2024/9/24 23:30:16

QEMU简介

  • 一、QEMU基本介绍
    • 1.1操作模式
    • 1.2 虚拟化方式
    • 中间代码实现方式简介
    • 源码结构分布
  • 二、qemu tcg前端解码逻辑
    • 2.1 tcg翻译流程
      • 2.1.1 decode tree语法
      • 2.1.2 trans_xxx函数的逻辑
  • 三、编译相关
    • 3.1 代码拉取(拉取自己想要的版本)
    • 3.2 编译参数
    • 3.3 依赖包
    • 3.4 运行相关
  • 四、KVM和TCG对比

一、QEMU基本介绍

QEMU是一种通用的开源计算机仿真器和虚拟软件。

1.1操作模式

QEMU共有两种操作模式:全系统仿真和用户模式仿真

全系统仿真:能够在任意支持的架构上为任何机器运行一个完整的操作系统,包括跑操作系统以及操作系统上的软件,启动的流程要涉及bootloader。

用户模式仿真:能够在任意支持的架构上为另一个Linux/BSD运行程序。这属于用户级的模拟,程序跑在用户态,相当于跑裸机程序,用户态的程序调用采用调用语义进行模拟。

1.2 虚拟化方式

QEMU提供两种方式的CPU虚拟化:基于KVM实现的和基于中间代码实现的
基于KVM实现:全虚拟化是一种QEMU通过模拟完整计算机硬件的方式来运行客户机操作系统的虚拟化技术。QEMU使用KVM(内核虚拟机)来加速虚拟化。这种方法在没有硬件虚拟化支持的系统上可以实现虚拟化,但是其性能通常比较低。此类虚拟化能够支持不同类型的客户机操作系统,这需要处理器支持VMX功能。基于KVM的方式,直接使用host CPU执行target CPU的指令,性能接近host上的性能,但是需要target CPU和host CPU是相同的构架。

基于中间代码实现(TCG,Tiny Code Generator):当处理器不支持虚拟化技术(VMX)时,QEMU使用全虚拟化技术来实现CPU虚拟化。在这种情况下,QEMU会完全模拟虚拟机的硬件环境,包括模拟CPU、内存、磁盘、网络和其它硬件设备等。QEMU通过软件来模拟CPU指令集,安装并运行客户机操作系统,当指令执行时,QEMU的CPU执行模拟程序可以在主机CPU上进行模拟。因此,在非虚拟化CPU上执行的指令被QEMU模拟并转换为可以在CPU上运行的指令。实现的具体方式时通过纯软的方式将目标架构代码转换为中间代码(相当于前端),然后将中间代码转换为宿主机架构的代码(相当于后端),这种方式虽然会造成仿真效率的降低,但是不需要强制CPU支持VMX。

中间代码实现方式简介

TCG(tiny code generator),这种方式的基本思路是用纯软件的方式把targetCPU的指令先翻译成所谓的中间码,然后再把中间码翻译成host CPU 的指令,把targetCPU指令翻译成中间码的过程叫整个过程的前端,中间码翻译成hostCPU的过程对应的叫做后端。给qemu增加一个新CPU的模型需要既增加前端也增加后端,如果要把整个系统支持起来,还要增加基础设备以及user mode的支持。

为了移植性和通用性方面的考虑,qemu定义了micro-op,qemu代码翻译流程如下:target instruction->micro-op->tcg->host
instruction

源码结构分布

在这里插入图片描述
hw:基础外设和machine代码

tcg:后端代码

target:前端代码
在这里插入图片描述

二、qemu tcg前端解码逻辑

将target cpu指令翻译成host cpu指令有两种方式,一种是使用helper函数,另一种是使用tiny code generator函数的方式。

所谓的target cpu运行,就是根据target CPU指令流去不断的改变target CPU软件描述结构里的数据状态,实际的代码是要运行在host CPU上。target代码要被翻译成host代码,才可以执行。qemu为了解耦先将target CPU翻译成中间码,本质上中间码的语义就是改变target CPU数据状态的一组描述语句,所以target CPU状态参数会被当做入参传入中间码描述语句。

tcg的方式,我们要使用tcg_gen_xxx的函数组织逻辑描述target CPU指令对target CPU状态的改变。一些公共的代码(trans的声明和入参的结构体等)是可以自动生成的,qemu里使用decode tree的方式自动生成这一部代码。

以riscv的代码来具体说明。qemu定义了一组target CPU指令的描述格式,说明文档在:docs/devel/decodetree.rst,riscv的指令描述在target/riscv/insn16.decode、insn32.decode里,qemu编译的时候会解析.decode文件,使用脚本(scripts/decodetree.py)生成对应的定义和函数,生成的文件放在qemu/build/libqemu-riscv64-softmmu.fa.p/decode-insn32.c.inc,decode-insn16.c.inc里。这些文件声明的trans_xxx函数需要自己实现,riscv的这部分实现是放在了在target/riscv/insn_trans/*里。生成的文件里有两个很大的解码函数decode-insn32.c.inc和decode-insn16.c.inc,qemu把target CPU指令翻译成中间码的时候就需要调用上面的解码函数。

2.1 tcg翻译流程

整个tcg前后端的翻译流程按照指令块的粒度来执行,收集一个指令块翻译成中间码,然后再把中间码翻译成host CPU指令,这是一个动态执行的过程,tcg前端解码的时候,先在缓存里找,如果找到就i执行。

2.1.1 decode tree语法

因为cpu指令编码具有唯一性,可以用decode去描述这些固定的结构,然后qemu根据这些指令定义,使用在scripts目录下的decodetree.py脚本在qemu编译的时候生成解码函数的框架。

decode
tree里定义了几个描述:field,argument,format,pattern,group。细节描述文件在decodetree.rst这个文件。

field描述一个指令编码中特定的域段,根据描述可以生成取对应域段的函数。

±--------------------------±--------------------------------------------+ |
Input | Generated code | +=+===================
+
| %disp 0:s16 | sextract(i, 0, 16) |
±--------------------------±--------------------------------------------+
| %imm9 16:6 10:3 | extract(i, 16, 6) << 3 | extract(i, 10, 3) |
±--------------------------±--------------------------------------------+
| %disp12 0:s1 1:1 2:10 | sextract(i, 0, 1) << 11 | |
| | extract(i, 1, 1) << 10 | |
| | extract(i, 2, 10) |
±--------------------------±--------------------------------------------+
| %shimm8 5:s8 13:1 | expand_shimm8(sextract(i, 5, 8) << 1 | |
| !function=expand_shimm8 | extract(i, 13, 1)) |
±--------------------------±--------------------------------------------+

argument用来定义数据结构,比如,riscv insn32.decode里定义的: &b imm rs2 rs1,

编译后的decode-insn32.c.inc里生成的数据结构如下,这个结构可以做trans_xxx函数的入参:

typedef struct 
{     
	int imm;     
	int rs2;     
	int rs1; 
} arg_b;

format定义指令的格式。比如;

@opr    ...... ra:5 rb:5 ... 0 ....... rc:5
 @opi    ...... ra:5 lit:8    1 ....... rc:5

比如上面就是对一个32bit指令编码的描述,.表示一个0或者1的bit位,描述里可以用field、之前定义的filed的引用、argument的引用,field的引用还可以赋值。field可以用来匹配pattern用来定义具体指令。比如riscv32里的lui指令:

lui … …0110111 @u
@u … … … &u imm=%imm_u %rd
&u imm rd
%imm_u 12:s20 !function=ex_shift_12
%rd 7:5

2.1.2 trans_xxx函数的逻辑

tcg的trans_xxx函数在qemu/tcg/README里很好的介绍,这个文档里介绍了中间码的整套指令(本质就是一些函数),这样可以将指令和对应的trans_xxx函数对上,trans_xxx函数的作用是生成这些中间码指令。以下以riscv的add指令为例,看下qemu对这条指令的

static bool trans_add(DisasContext *ctx, arg_add *a) 
{     return gen_arith(ctx, a, &tcg_gen_add_tl); 
} 
static bool gen_arith(DisasContext *ctx, arg_r *a,                       
				void(*func)(TCGv, TCGv, TCGv)) 
{     
TCGv source1, source2;
     source1 = tcg_temp_new();     
     source2 = tcg_temp_new();  
     gen_get_gpr(source1, a->rs1);     
     gen_get_gpr(source2, a->rs2);  
     (*func)(source1, source1, source2);  
     gen_set_gpr(a->rd, source1);     
     tcg_temp_free(source1);     
     tcg_temp_free(source2);     
     return true; 
 } 
 static inline void tcg_gen_add_i32(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) 
 {     
 	tcg_gen_op3_i32(INDEX_op_add_i32, ret, arg1, arg2); 
 }  

tcg_gen_add_i32可以看作是tcg_gen_add_tl函数的入参,riscv的add指令从target从 CPU的rs1和rs2中取两个加数,相加后放到rd寄存器中,add的实现如下:

tcg_gen_add_tl 
  ->tcg_gen_add_i32(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2)     
  ->tcg_gen_op3_i32(INDEX_op_add_i32, ret, arg1, arg2)
         -> tcg_gen_op3(opc, tcgv_i32_arg(a1), tcgv_i32_arg(a2), tcgv_i32_arg(a3))
                  -> TCGOp *op = tcg_emit_op(opc);
                           -> op->args[0] = a1;
                                    -> op->args[1] = a2;
                                            ->op->args[2] = a3;

上面 gen_get_gpr也是中间码:把rs1/2位置上的数据放到source1/2位置上,它的实现如下

tcg_gen_mov_tl(t, cpu_gpr[reg_num]) 
  -> tcg_gen_mov_i64
       -> tcg_gen_op2_i64(INDEX_op_mov_i64, ret, arg)
              -> tcg_gen_op2(opc, tcgv_i64_arg(a1), tcgv_i64_arg(a2))
                       -> TCGOp *op = tcg_emit_op(opc);
                                -> op->args[0] = a1;
                                         -> op->args[1] = a2;

可以看到最后生成的mov指令被挂载到一个链表中,后端在解码后会将这些指令翻译成host指令,生成的指令就是qemu/tcg/README里介绍的mov_i32/i64 t0, t1这个指令。其中有两个重点1、tcg_temp_new()创建的变量使用TCGTemp这个结构体完成的;2、 cpu_gpr[reg_num]是寄存器的描述,需要索引到target寄存器的基本逻辑,需要在前端和后端约定号描述target CPU的软件结构,cpu_gpr[reg_num]描述的就是相关寄存器在这个软件里的位置。这个数组的初始化流程在translate里:

void riscv_translate_init(void) {
     int i;
     cpu_gpr[0] = NULL;  
     for (i = 1; i < 32; i++) {
              cpu_gpr[i] = tcg_global_mem_new(cpu_env,
                           offsetof(CPURISCVState, gpr[i]),
              riscv_int_regnames[i]);
              }  

cpu_env在tcg_context_init(unsigned max_cpus)里初始化,得到的是tcg_ctx里TCGTemp
temps的地址。tcg_global_mem_new一次在tcg_ctx里从TCGTemp
temps上分配空间,返回空间为在tcg_ctx上的相对地址。这样cpu_gpr[reg_name]就可以作为标记在前端和后端之间建立连接。

后端的代码直接把中间码翻译成host指令,中间码中的TCGv直接映射到host
CPU的寄存器上,从逻辑上讲,应该是翻译得到的host代码修改中间码对应TCGv对应的内存才对。

三、编译相关

3.1 代码拉取(拉取自己想要的版本)

git clone https://gitlab.com/qemu-project/qemu.git
cd qemu
git submodule init
git submodule update --recursive

3.2 编译参数

…/qemu/configure --target-list=riscv64-softmmu --extra-cxxflags=-O0
–extra-cflags=-O0 --enable-debug --enable-debug-info --enable-debug-tcg --prefix=/home/s29363/qemu4/install

3.3 依赖包

Ninja glib libffi pcre pixman….

PKG_CONFIG_PATH环境配置:

export PKG_CONFIG_PATH= P K G C O N F I G P A T H : PKG_CONFIG_PATH: PKGCONFIGPATH:HOME/tmp/libffi-3.1/install/lib

export PKG_CONFIG_PATH= P K G C O N F I G P A T H : PKG_CONFIG_PATH: PKGCONFIGPATH:HOME/tmp/pcre-8.45/install/lib/pkgconfig

export PKG_CONFIG_PATH= P K G C O N F I G P A T H : PKG_CONFIG_PATH: PKGCONFIGPATH:HOME/tmp/glib/install/lib/pkgconfig

export PKG_CONFIG_PATH= P K G C O N F I G P A T H : PKG_CONFIG_PATH: PKGCONFIGPATH:HOME/tmp/glib/install/lib/pkgconfig

export PKG_CONFIG_PATH= P K G C O N F I G P A T H : PKG_CONFIG_PATH: PKGCONFIGPATH:HOME/tmp/pixman-0.40.0/install/lib/pkgconfig

3.4 运行相关

-d op,out_asm,in_asm 可以显示qemu的log信息

用户模式下
 -d QEMU_LOG,使能对特定事件的日志记录,其后参数加一些item 
 > Log items (comma separated): 
 out_asm         show generated host assembly code for each compiled TB
 in_asm          show target assembly code for each compiled TB
 op              show micro ops for each compiled TB 
 op_opt          show micro ops after optimization 
 op_ind          show micro ops before indirect lowering 
 int             show interrupts/exceptions in short format 
 exec            show trace before each executed TB (lots of logs) 
 cpu             show CPU registers before entering a TB (lots of logs)
 fpu             include FPU registers in the 'cpu' logging 
 mmu             log MMU-related activities 
 pcall           x86 only: show protected mode far calls/returns/exceptions 
 cpu_reset       show CPU state before CPU resets 
 unimp           log unimplemented functionality 
 guest_errors    log when the guest OS does something invalid (eg accessing a non-existent register) 
 page            dump pages at beginning of user mode emulation 
 nochain         do not chain compiled TBs so that "exec" and "cpu" show complete traces  
 -D QEMU_LOG_FILENAME,指定日志输出文件,默认为stderr
  -g QEMU_GDB,后加端口号,可以连接GDB或者IDA进行远程调试 
  -strace QEMU_STRACE,将二进制文件的系统调用输出到终端。这样做的好处是,可以帮助了解二进制代码正在从事哪些活动。即查看目标二进制文件调用了哪些系统调用函数。 
  -trace QEMU_TRACE,在源文件目录/docs/devel/tracing.txt文件中有部分说明  

四、KVM和TCG对比

在QEMU中,地址空间的处理方式受到不同CPU类型的影响,TCG和KVM的处理方式会有所不同。
对于TCG类型的CPU,因为它是在QEMU用户空间中运行的,所以可以允许多个虚拟CPU同时运行,每个虚拟CPU拥有各自的AddressSpace。在内存访问时,每个虚拟CPU会使用自己的AddressSpace映射虚拟地址到物理地址,不同虚拟CPU之间彼此独立、互不干扰。因此,在TCG模式下,一个CPU可以拥有多个地址空间。
相反,对于KVM类型的CPU,因为它是在Linux内核中运行的,和其它Linux进程一样,只能拥有一个AddressSpace。在内存访问时,KVM虚拟CPU使用的AddressSpace来自于它所运行的进程,即QEMU进程,所有虚拟CPU共享同一份AddressSpace映射表,每个虚拟CPU使用的是同一份映射关系,从而实现对模拟器内的地址空间的统一管理和访问。
所以,无论采用哪种CPU类型,AddressSpace在QEMU中都是非常重要的,因为它是QEMU模拟器内存管理的关键组成部分。在使用QEMU进行模拟时,需要根据具体需求和CPU类型等因素来选择使用相应的地址空间处理方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1989344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot - 在Spring Boot中实现灵活的API版本控制(上)

文章目录 为什么需要多版本管理&#xff1f;在Spring Boot中实现多版本API的常用方法1. URL路径中包含版本号2. 请求头中包含版本号3. 自定义注解和拦截器 注意事项 为什么需要多版本管理&#xff1f; API接口的多版本管理在我们日常的开发中很重要&#xff0c;特别是当API需要…

关于Zoho mail邮箱续费、退款、升级的说明

在使用企业邮箱服务的过程中&#xff0c;可能会遇到续费&#xff0c;退款及升级服务的情况。遇到这些情况时应该如何处理&#xff1f;本文将为您提供遇到邮箱情况时的详细操作步骤&#xff1a;邮箱续费方式及续费流程、邮箱申请退款方式、邮箱升级服务的流程。 一、Zoho邮箱如…

动态面板门槛模型及 Stata 具体操作步骤

目录 一、文献综述 二、理论原理 三、实证模型 四、稳健性检验 五、程序代码及解释 六、代码运行结果 一、文献综述 动态面板门槛模型作为一种先进的计量经济学方法&#xff0c;在众多领域的研究中发挥着关键作用。在经济增长领域&#xff0c;[学者 A]通过构建动态面板门槛…

你的会议记录还用手写吗?3款免费语音转文字工具

现在&#xff0c;随着大家越来越习惯用电脑和手机工作&#xff0c;很多专业人士都在找更快捷的方法来记录会议内容。以前那种手写笔记&#xff0c;不仅写起来慢&#xff0c;而且整理和分享起来也很麻烦。幸好&#xff0c;随着科技的发展&#xff0c;出现了一些能将说话的声音直…

【计算机网络】什么是socket编程?以及相关接口详解

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

letcode 分类练习 x个数之和问题 15. 三数之和 18. 四数之和 454. 四数相加 II 383. 赎金信

letcode 分类练习 x个数之和问题 15. 三数之和 18. 四数之和 454. 四数相加 II 383. 赎金信 三数之和四数之和454. 四数相加 II383. 赎金信 三数之和 三数之和&#xff0c;双指针模版代码&#xff0c;注意去重逻辑&#xff0c;还有只需要去重第一和第二重循环&#xff0c;第三…

从Python翻译Go代码谈起:AI辅助编程的现状与展望

最近&#xff0c;一位同学使用GPT-4o将一个约300行的Python程序转换成Golang&#xff0c;正确率达到了90%。这引发了一个有趣的讨论&#xff1a;如果是整个项目规模的代码转换&#xff0c;准确率会如何&#xff1f;作为被的对象&#xff0c;我决定深入探讨这个话题&#xff0c;…

高等数学精解【6】

文章目录 直线与二元一次方程直线方程斜率两点式方程截距式方程将不同形式的直线方程转换为截距方程直线的一般方程直线一般方程的系数有一个或两个为零的直线 参考文献 直线与二元一次方程 直线方程 斜率 直线对于 x 轴的倾角&#xff0c;平行于 x 轴&#xff0c;倾角为 0 &…

从一个服务预热不生效问题谈微服务无损上线

作者&#xff1a;凡问、启淮 前言 本文基于阿里云技术服务团队和产研团队&#xff0c;在解决易易互联使用 MSE&#xff08;微服务引擎&#xff09;产品无损上线功能所遇到问题的过程总结而成。本文将从问题和解决方法谈起&#xff0c;再介绍相关原理&#xff0c;后进一步拓展…

jupyter下载

https://blog.csdn.net/qq_48372575/article/details/125630622 我下面是CPU运行的&#xff0c;GPU链接在上面 Anaconda下载 https://docs.anaconda.com/miniconda/miniconda-other-installer-links/ 参考链接&#xff1a; https://blog.csdn.net/qq_48372575/article/detai…

计算机编码 - 笔记

1 ASCII码 - 0- 127 2 ASCII码扩展字符集 - 128- 255

反转字符串(LeetCode)

题目 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 的额外空间解决这一问题。 解题 def reverse_string(s):left 0right len(s) - 1while left …

SDF Marching Cubes Ray-marching Teahouse

SDF & Marching Cubes & Ray-marching SDF SDF(Signed Distance Field)有向距离场。SDF是由到&#xff08;多边形模型&#xff09;物体表面最近距离的采样网格。作为惯例&#xff0c;使用负值来表示物体内部&#xff0c;使用正值表示物体外部。 Marching Cubes marc…

CUDA编程05 - GPU内存架构和数据局部性

一&#xff1a;概述 到目前为止&#xff0c;我们已经学会了如何编写 CUDA 核函数&#xff0c;以及如何设置和分配大量线程来执行核函数。我们还了解了当前 GPU 硬件的计算架构&#xff0c;以及线程在硬件上调度执行过程。在本章中&#xff0c;我们将重点关注 GPU 的片上(on-chi…

golang实现Digest认证鉴权接口

什么是Digest认证鉴权接口? Digest认证鉴权接口是一种基于摘要算法的身份验证方法,用于确保API请求的安全性。在实际应用中,常常使用HTTP协议的Digest认证鉴权接口来验证请求的合法性。下面是一种常见的Digest认证鉴权流程: 1. 客户端发送HTTP请求到服务器,请求接口资源…

【MCAL】TC397+EB-tresos之ADC配置实战 - (模数转换)

本篇文章首先从理论讲起&#xff0c;基于《MC-ISAR_TC3xx_UM_Adc.pdf》介绍了ADC模块的理论知识&#xff0c;然后详细介绍了在TC397平台使用EB-tresos对ADC驱动模块进行配置与调试的实战过程&#xff0c;帮助第一次接触这个模块的读者能够更快的上手来实现符合自己项目要求的开…

OSPF TE

OSPF TE&#xff08;OSPF Traffic Engineering&#xff0c;即OSPF流量工程&#xff09;是为了支持MPLS流量工程&#xff08;MPLS TE&#xff09;&#xff0c;支持建立和维护TE的标签交换路径LSP&#xff08;Label Switch Path&#xff09;而在OSPF协议基础上扩展的新特性。在MP…

Qt WebEngine基于WebEngineScript注入js脚本

在之前的文章中&#xff0c;我们介绍了Qt WebEngine注入js的用法&#xff0c;及runJavaScript()的用法&#xff0c;该方法主要是用在页面加载完成后&#xff0c;为了和网页做一些交互时使用。有时候需要监听网页加载完成的一些状态或信息&#xff0c;则需要网页加载前注入js来实…

【CodinGame】趣味算法(教学用) CLASH OF CODE -20240802

[toc] 正文 最大最小值 import math import sys# Auto-generated code below aims at helping you parse # the standard input according to the problem statement.a int(input()) b int(input()) c int(input()) d int(input())mylist [] mylist.append(a) mylist.app…

如何写好提示词?《Midjourney常用关键词大全》-附关键词文件

​ Midjourney如何写好提示词从而生成高质量图片&#xff1f; 并且随心所欲生成各种风格的图片&#xff1f; 这是一份关于Midjourney常用关键词的文件&#xff1a; 风格形式/摄影构图/灯光材质/渲染方式/常见设备/常见元素等不同类别&#xff0c; 关键词分门别类&#xff0…