Skip to the content.

Crash analysis of Hyper-V virtual machine startup

发表于 2025-07-23

Hyper-V虚拟机启动crash分析

1. 问题现象

测试上报说Hyper-V虚拟机因为crash导致无法正常启动。串口异常栈信息如下

image

其他虚拟机平台和实体机都正常。

2. 初步分析

看到系统crash在 copy_thread+0xd2位置,于是 gdb vmlinux ,查看对应位置。

(gdb) disass copy_thread
Dump of assembler code for function copy_thread:
   0xffffffff8025c530 <+0>:	nopl   0x0(%rax,%rax,1)
   0xffffffff8025c535 <+5>:	push   %rbp
//省略无关
   0xffffffff8025c5d9 <+169>:	callq  *0x1bb8f59(%rip)        # 0xffffffff81e15538 <pv_ops+248>
   0xffffffff8025c5df <+175>:	mov    %gs:0x7fdf32d9(%rip),%r15        # 0x4f8c0 <current_task>
   0xffffffff8025c5e7 <+183>:	mov    %fs,%ax
   0xffffffff8025c5ea <+186>:	mov    %ax,0xb64(%r15)
   0xffffffff8025c5f2 <+194>:	mov    %gs,%ax
   0xffffffff8025c5f5 <+197>:	mov    %ax,0xb66(%r15)
   0xffffffff8025c5fd <+205>:	jmpq   0xffffffff81f60a3e
   0xffffffff8025c602 <+210>:	rdfsbase %rax        //这里  0xd2对应210
   0xffffffff8025c607 <+215>:	mov    %rax,0xb68(%r15)
   0xffffffff8025c60e <+222>:	callq  0xffffffff810405e0 <__rdgsbase_inactive>

看起来像是不支持rdfsbase指令导致的 invalid opcode 异常。

这部分对应函数 save_fsgs, 查看代码:

static __always_inline void save_fsgs(struct task_struct *task)
{
	savesegment(fs, task->thread.fsindex);
	savesegment(gs, task->thread.gsindex);
	if (static_cpu_has(X86_FEATURE_FSGSBASE)) {    //支持FSGSBASE 则使用 rdfsbase指令
		/*
		 * If FSGSBASE is enabled, we can't make any useful guesses
		 * about the base, and user code expects us to save the current
		 * value.  Fortunately, reading the base directly is efficient.
		 */
		task->thread.fsbase = rdfsbase();
		task->thread.gsbase = __rdgsbase_inactive();
	} else {                                      //不支持则使用旧指令
		save_base_legacy(task, task->thread.fsindex, FS);
		save_base_legacy(task, task->thread.gsindex, GS);
	}
}

当CPU 支持FSGSBASE功能时,才会使用rdfsbase指令, 也就是说当前CPU是支持的。 从异常栈CR4寄存器看也能确认。 CR4=0x1506f0 ,第16个bit为1,表示 CPU 支持FSGSBASE。

#define X86_CR4_FSGSBASE_BIT	16 /* enable RDWRFSGS support */
#define X86_CR4_FSGSBASE	_BITUL(X86_CR4_FSGSBASE_BIT)

static void identify_cpu(struct cpuinfo_x86 *c)
{
	int i;

	c->loops_per_jiffy = loops_per_jiffy;
	c->x86_cache_size = 0;
//忽略无关

	/* Enable FSGSBASE instructions if available. */
	if (cpu_has(c, X86_FEATURE_FSGSBASE)) {
		cr4_set_bits(X86_CR4_FSGSBASE);    //set CR4 bit16
		elf_hwcap2 |= HWCAP2_FSGSBASE;
	}
//忽略无关

}

这就有点诡异了, 明明CPU支持,但在运行时又认为时invalid opcode。

3. 继续深入

为了印证猜想,并找出真相,做了以下验证。

3.1 关闭CPU FSGSBASE

在启动参数里加入 nofsgsbase ,关闭CPU对fsgs的支持,

static __init int x86_nofsgsbase_setup(char *arg)
{
	/* Require an exact match without trailing characters. */
	if (strlen(arg))
		return 0;

	/* Do not emit a message if the feature is not present. */
	if (!boot_cpu_has(X86_FEATURE_FSGSBASE))
		return 1;

	setup_clear_cpu_cap(X86_FEATURE_FSGSBASE);
	pr_info("FSGSBASE disabled via kernel command line\n");
	return 1;
}
__setup("nofsgsbase", x86_nofsgsbase_setup);

修改后,系统可以正常启动。 也印证了就是CPU不支持rdfsbase指令导致的。

3.2 在宿主机上创建B产品的Hyper-V虚拟机

公司还有B产品,内核和该产品相同,且同样有Hyper-V虚拟机,但是没听说QA上报Hyper-V虚拟机启动异常问题。 联系B产品QA,提供了一台Hyper-V虚拟机,登陆后确实工作正常。

为了对比差异,在问题宿主机上手动创建了B产品的Hyper-V虚拟机, 结果同样出现了启动后crash问题。 于是怀疑问题宿主机有问题,对fsgsbase的指令支持异常。

为了确认猜想,在B产品的测试宿主机上创建了A产品的Hyper-V虚拟机,结果运行正常。 也就是说同样的A产品image,在问题宿主机上无法启动,在B产品的宿主机上就能正常运行。

3.3 对比两台宿主机CPU差异

  • 先是在问题宿主机上,部署一台升级内核前的Hyper-V虚拟机,因为旧内核没有支持fsgsbase指令,所以正常运行。 查看虚拟机CPU:
/var/log# cat /proc/cpuinfo 
processor	: 0
vendor_id	: AuthenticAMD
cpu family	: 23
model		: 1
model name	: AMD EPYC 7281 16-Core Processor
stepping	: 2
microcode	: 0xffffffff
cpu MHz		: 2099.995
cache size	: 512 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 2
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm rep_good nopl cpuid extd_apicid pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ssbd vmmcall fsgsbase bmi1 avx2 smep bmi2 xsaveopt arat
bugs		: fxsave_leak sysret_ss_attrs null_seg spectre_v1 spectre_v2 spec_store_bypass
bogomips	: 4199.99
TLB size	: 2560 4K pages
clflush size	: 64
cache_alignment	: 64
address sizes	: 42 bits physical, 48 bits virtual
power management:

AMD EPYC 7281 16-Core Processor 且支持 fsgsbase指令集

  • 随后在可以正常运行的新内核Hyper-V虚拟机上查看
/var/log# cat /proc/cpuinfo 
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 63
model name	: Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz
stepping	: 2
microcode	: 0xffffffff
cpu MHz		: 2399.978
cache size	: 15360 KB
physical id	: 0
siblings	: 1
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single ibrs ibpb stibp fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt arch_capabilities
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit mmio_stale_data retbleed
bogomips	: 4799.95
clflush size	: 64
cache_alignment	: 64
address sizes	: 43 bits physical, 48 bits virtual
power management:

Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz 同样也支持 fsgsbase指令集。

4. 如何修正问题宿主机

最终问题出在这个有问题的宿主机,它明明支持fsgsbase指令集,但是运行时却又invalid opcode。 如果解决这种问题?经过研究,我们可以在创建虚拟机时,把处理器Processor的兼容性compatibility选项选中。

image

打开后,经测试,Hyper-V虚拟机可以正常启动。查看发现CPU特性不再支持fsgsbase, 推测AMD这款CPU在对fsgsbase的兼容性上存在问题。

image

5. 后记

FSGSBASE Instructions

Accessing FS/GS base with the FSGSBASE instructions

本文访问次数:...