READMSR和CPUID指令在Guest中的代码执行路径学习
READMSR和CPUID指令在Guest中的代码执行路径学习
内核版本:5.3.0
qemu版本:4.2.0
READMSR指令
作用
读MSR,MSR由ECX(RCX)的内容指定,读出的内容保存在EDX(RDX):EAX(RAX)中.
VMX相关
如果guest中执行rdmsr指令,并且以下情况之一成立,就会触发vmexit.
- "use MSR bitmaps" control为0
- RCX既不在0x00000000H-0x00001FFFH中,也不在0xC0000000H-0xC0001FFFH中
- RCX在0x00000000H-0x00001FFFH中,但是给Low MSRs的read bitmap的第RCX个bit为1.
- RCX在0xC0000000H-0xC0001FFFH中,但是给HIGH MSRs的read bitmap的第n个bit为为1,n=RCX & 0x00001FFFH
MSR bitmap address指向MSR bitmaps(4K),每1K对应low/high MSRs(read/write).且MSR bitmap address是VMCS的一部分,访问该address只需要正常的memory access即可.
代码分析(MSR bitmap)
kvm代码
- VMCS中MSR bitmap的初始化
qemu=> kvm_vm_ioctl(KVM_CREATE_VCPU) => kvm_vm_ioctl_create_vcpu() => kvm_arch_vcpu_create() => vmx_create_vcpu() => vmx_vcpu_setup()
在qemu请求创建VCPU时,就会将MSR bitmap的地址写入VMCS中.
- MSR bitmap的空间分配
qemu=> kvm_vm_ioctl(KVM_CREATE_VCPU) => kvm_vm_ioctl_create_vcpu() => kvm_arch_vcpu_create() => vmx_create_vcpu() => alloc_loaded_vmcs()
在qemu请求创建VCPU时,为MSR bitmap分配4K空间,初始化为全1
- 对MSR bitmap中的特定bit(对应特定MSR)进行初始化操作
qemu=> kvm_vm_ioctl(KVM_CREATE_VCPU) => kvm_vm_ioctl_create_vcpu() => kvm_arch_vcpu_create() => vmx_create_vcpu() => vmx_vcpu_setup()
之后在运行过程中,还会更新一些APIC相关的中断MSR设置,其余MSR如没有特别设置,访问MSR均需要vmexit.
qemu代码
qemu提供以下代码获得MSR bitmap信息, 也可以对该信息进行修改,但qemu实际运行过程中没有修改MSR bitmap.
代码分析(read_msr)
guest中,在读MSR bitmap中对应bit为1的MSR时, 会导致vmexit.
guest中的read_msr会出现以下执行函数链:
guest读MSR => handle_rdmsr() => vmx_get_msr() => kvm_get_msr_common()
其中,vmx_get_msr()中处理一部分读特殊MSR请求,kvm_get_msr_common()中处理普通读MSR请求.
以MSR_IA32_ARCH_CAPABILITIES为例:
由于MSR_IA32_ARCH_CAPABILITIES是一个普通的MSR,所以交给kvm_get_msr_common()函数处理.
代码中的msr_info->host_initiated用于区分此次读MSR内容的动作是由qemu发起的,还是由guest自己发起的.如果是qemu发起的,msr_info->host_initiated就为true,如果是guest自己发起的,msr_info->host_initiated就为false.很明显,guest读MSR_IA32_ARCH_CAPABILITIES时,msr_info->host_initiated应该为false.
guest_cpuid_has()用于检验guest是否有CPUID feature: X86_FEATURE_ARCH_CAPABILITIES, 关于CPUID在后面的一节分析,这里只需要知道,guest_cpuid_has(vcpu, X86_FEATURE_ARCH_CAPABILITIES)检查guest是否有该CPUID feature, 有则为true,无为false.
如果guest有该feature, 则将vcpu->arch.arch_capabilities中的内容填充到msr_info->data中去,完成读MSR工作.
如果guest没有该feature,则返回1,表明读取MSR失败.(一般guest在读msr之前,会现将读取结果初始化为0,如果读取失败,那么读取结果仍旧为0,这种设计能够防止读msr失败后程序无法继续执行)
假设guest有该feature(如果没有的话,代码分析也就到此结束了), 读取到的内容为arch_capabilities的内容.
这个vcpu->arch.arch_capabilities在内核中的2个地方有被赋值操作:
- kvm_arch_vcpu_setup()中,即在初始化vcpu时被赋值:
qemu=> kvm_vm_ioctl(KVM_CREATE_VCPU) => kvm_vm_ioctl_create_vcpu() => kvm_vm_ioctl_create_vcpu() => kvm_arch_vcpu_setup()
再次假设boot cpu有feature X86_FEATURE_ARCH_CAPABILITIES(事实是现在很多CPU都有该feature),也就是一个物理CPU,有这个X86_FEATURE_ARCH_CAPABILITIES标志,那么还是通过rdmsr读取MSR_IA32_ARCH_CAPABILITIES的数据到data.不过这次rdmsr不会vmexit,而是在host内核空间中获取PCPU的X86_FEATURE_ARCH_CAPABILITIES.
也就是说,在qemu发起创建VCPU请求时,会将 vcpu->arch.arch_capabilities设置为PCPU(物理CPU)的对应MSR读到的内容.
- 在kvm_set_msr_common()中对arch_capabilities做了赋值,这是qemu在通过vcpu_ioctl时设置了arch_capabilities的值.
[ kvm_arch_put_registers(cpu, KVM_PUT_RESET_STATE),
kvm_arch_put_registers(cpu, KVM_PUT_FULL_STATE),
kvm_arch_put_registers(cpu, KVM_PUT_RUNTIME_STATE)] => kvm_arch_put_registers() => kvm_put_msrs() =>
在vcpu运行期间,vcpu复位时,初始化vcpu时,都会调用kvm_put_msrs()设置vcpu支持的MSR和对应的内容.最终通过kvm_vcpu_ioctl(KVM_SET_MSRS)写入guest中.
has_msr_arch_capabs flag 在qemu通过thread创建vcpu时,就通过kvm_ioctl(s, KVM_GET_MSR_INDEX_LIST, &msr_list)获得guest的msrlist,然后检查guest中是否存在arch_capability feature, 来设置的. kvm在收到KVM_GET_MSR_INDEX_LIST请求后,返回guest支持的MSR和kvm可以模拟的MSR列表.
kvm收到KVM_SET_MSRS的ioctl请求后,调用do_set_msr
do_set_msr() => kvm_set_msr => kvm_x86_ops->set_msr => vmx_set_msr => kvm_set_msr_common
最终由kvm_set_msr_common()完成对arch_capabilities的赋值.这里的data,首先由qemu从kvm中获取,然后又由qemu向kvm写入,所以归根结底,还是来自于kvm,即host.
CPUID指令
向EAX,ECX写入需要查询的内容,执行CPUID,查询结果会出现在EAX,EBX,ECX,EDX中.
代码分析
guest执行CPUID肯定会导致VMEXIT.然后由kvm处理CPUID.
handle_cpuid() => kvm_emulate_cpuid()
比较重要的函数为kvm_find_cpuid_entry
,该函数寻找Qemu写入到kvm中的CPUID_entry,如果存在,就返回CPUID的结果,如果不存在,并且check_limit为1,就确定EAX传入的数据是否超过了该vcpu的最大可接受参数,如果超过了,就返回vcpu所支持的最大EAX的值的CPUID值.
所以比较重要的是这个"entry",该entry由Qemu写入.
大致过程为:
- qemu通过ioctl(KVM_GET_SUPPORTED_CPUID)读取到host支持的CPUID列表
- qemu通过与运算剔除掉qemu不支持的CPUID
- 最后通过ioctl(KVM_SET_CPUID2)将CPUID数据写入到KVM中供guest使用
__EOF__

本文链接:https://www.cnblogs.com/haiyonghao/p/14440954.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律