找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 1494|回复: 2

【虚拟化】在AMD-V上实现拦截中断窗口

[复制链接]
发表于 2023-2-16 20:38:54 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 唐凌 于 2023-2-16 20:40 编辑

简介

所谓中断窗口(Interrupt Window),就是指处理器能处理中断的时机。比如rflags.if复位时,处理器就无法接收中断。

Intel上的中断窗口

在Intel VT-x上,VMCS中的Primary Processor-based VM-Execution Control字段直接提供了拦截中断窗口(Interrupt-Window Exiting)的位(见下图)。但在AMD-V中没有直接的相关设置。
Capture.PNG
所谓"no other blocking of interrupts",有两种情况:

  • 处理器不活动:它可能处于关机(Shutdown)状态或者时等待SIPI(Startup Inter-Processor Interrupt)状态。注意这种情况下处理器会屏蔽外部中断,不位于中断窗口。当处理器因hlt指令停止运行时,若rflags.if置位,则可以接收中断,处于中断窗口中。
  • 中断阴影之外:当处理器遇到mov ss,xx, pop ss或者sti指令时,会进入中断阴影(Interrupt Shadow)之中。中断阴影会屏蔽外部中断(External Interrupt),不可屏蔽中断(Non-Maskable Interrupt),以及调试陷阱(Debug Trap)。

AMD-V上拦截中断窗口

AMD-V没有直接提供拦截中断窗口的选项,但是它提供了更高级的虚拟local APIC的功能。详见VMCB的+0x060的偏移处(如下图所示)。
Capture.PNG

  • 第0-7位表示处理器的TPR,也就是cr8寄存器的作用。
  • 第8位表示处理器是否有未接收的虚拟中断请求(Virtual Interrupt Request,简称V_IRQ)。
  • 第9位表示处理器的虚拟GIF位。此位用于加速嵌套虚拟化。
  • 第11位表示处理器是否有未接收的虚拟不可屏蔽中断。
  • 第12位表示处理器是否处于屏蔽虚拟NMI的状态。
  • 第16-19位表示V_IRQ的优先级。当优先级不大于TPR时,中断会被屏蔽。
  • 第20位表示是否无视TPR。
  • 第24位用于控制物理外部中断的屏蔽行为。
    • 若此位置位,物理外部中断不受虚拟机的rflags.if位控制。
    • 若此位复位,物理外部中断受虚拟机的rflags.if位控制。这种情况下,若虚拟机的rflags.if复位,且陷入死循环中时,会使得物理处理器一并卡住,除非有物理NMI使处理器脱离死循环。这种情况往往是OS设置的基于NMI的看门狗启动了。
  • 第25位表示是否启用虚拟GIF机制。此位用于加速嵌套虚拟化,此位若复位则第9位的设置无效。
  • 第26位表示是否启用虚拟NMI机制。注意虚拟NMI是新机制,在Zen 4起才有。老AMD处理器需要自行模拟NMI的屏蔽性。此位若复位则第11和12位的设置无效。
  • 第30位表示是否启用x2AVIC机制。x2AVIC相较于AVIC意义在于将处理器的APIC号从8位扩展至32位(GPU看了都表示够多了)。
  • 第31位表示是否启用AVIC机制。AVIC即Advanced Virtual Interrupt Controller,可用于加速对APIC的虚拟化。即物理设备发送的虚拟中断以及虚拟IPI。
  • 第32-39位表示V_IRQ的中断矢量号(Interrupt Vector),也就是具体投送到IDT/IVT的哪个中断矢量上。

此外,在VMCB的+0x00C的偏移处,第4位可以拦截V_IRQ
Capture.PNG

这就好办了,我们可以将V_IRQ和虚拟中断的拦截同时置位。如此一来,当VMM接到虚拟中断的拦截时,则表明此时虚拟处理器可以接收中断。
注意TPR也会屏蔽优先级小于等于TPR的中断。因此如果需要像Intel那样拦截中断窗口(无视TPR),则需要在虚拟local APIC字段中启用无视TPR的选项。

测试

测试内容基于NoirVisor提供的NoirCvmApi接口(暂时只支持64位Host下的AMD-V)。
咱就说Guest这一侧,其入口代码如下:

void guest_entry()
{
    char a[]="Hello!\n";
    init_idt();
    __outbytestring(stdout_port,a,sizeof(a));
    _enable();
    __nop();    // The sti instruction causes an interrupt-shadow.
    _disable();
    __halt();
}

首先初始化IDT,其中设置两个外部中断的中断矢量。然后用rep outsb指令对外输出一个"Hello"字符串。
然后用_enable这个MSVC的内置宏让MSVC生成sti指令来启用中断。注意sti会产生一个中断阴影,因此后面加了个nop指令来确保度过中断阴影,迎来中断窗口。
在现有的NoirVisor的设计中,中断窗口的拦截仅发生在注入一个外部中断之后,目的在于让VMM维持中断队列。因此guest_entry里生成的中断窗口不会被拦截。但是VMM会在Guest开始之前便注入一个中断,故中断会发生在该中断窗口上。
中断处理函数内容如下:

void handle_true_interrupt()
{
    char msg1[]="True interrupt...\n";
    char msg2[]="Over...\n";
    __outbytestring(stdout_port,msg1,sizeof(msg1));
    _enable();
    __nop();        // At this point, we should receive an interrupt.
    _disable();
    __outbytestring(stdout_port,msg2,sizeof(msg2));
}

可以看到它也会触发一次中断窗口。
在VMM一侧,在遇到第一次中断窗口的拦截时就会注入一次中断。该中断的处理函数如下:

void handle_false_interrupt()
{
    char msg[]="False interrupt...\n";
    __outbytestring(stdout_port,msg,sizeof(msg));
}

那么,理论而言,字符串的输出顺序就是:

True interrupt...
False interrupt...
Over...

运行结果如图所示:
Capture.PNG
可以看到两次中断窗口的拦截都发生在rip=0x604D,可以用IDA看看这里有啥。
Capture.PNG
可以看出这个位置在第一个中断处理函数中,正好位于nop之后。这是由于sti会生成中断阴影,因此在nop指令发生之前不会进入中断窗口。
而当第二个中断返回时,也会返回到这个位置,再次进入中断窗口,故再次触发中断。

总结

虽然AMD-V没有直接提供拦截中断窗口的方式,但是提供了更为高级的虚拟local APIC功能。
通过同时设置拦截虚拟中断并注入虚拟中断,可以实现拦截中断窗口的效果。
而且相比Intel VT-x,还可以实现相对于TPR的中断窗口。在Intel VT-x上,则需要自行拦截对cr8的读写来模拟TPR。
本文的完整代码详见GitHub

回复

使用道具 举报

发表于 2023-2-17 21:06:06 | 显示全部楼层
干货,顶
回复

使用道具 举报

发表于 2023-3-16 17:39:02 | 显示全部楼层
我看成了“用AMD-V上实现拦截窗口创建”。
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-26 22:50 , Processed in 0.040052 second(s), 27 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表