如何正确获取段寄存器的不可见部分(AMD虚拟化VMCB场景)
正确获取AMD-V中段寄存器不可见部分的方法
你的代码在解析段选择子、计算段描述符地址、组合base/limit/属性位时存在多个问题,导致VMCB客户机状态无效。以下是具体问题点和修正方案:
原代码的核心问题
- 段选择子解析错误:
((PSEGMENT_SELECTOR) & (pRegister->selector))逻辑错误——pRegister->selector是16位数值,取地址强转指针会指向错误内存区域,应直接将数值强制转换为SEGMENT_SELECTOR类型。 - 段描述符地址计算错误:每个段描述符固定为8字节,你用
sizeof(PVOID)属于逻辑侥幸,应显式使用sizeof(CODE_SEGMENT_DESCRIPTOR);LDT处理逻辑错误,需先从GDT读取LDT对应的描述符获取基址,而非直接用Ldtr * sizeof(PVOID)计算偏移。 - Base和Limit计算不完整:64位模式下段描述符Base是40位,原代码仅组合低32位会导致值错误;Limit未处理
G(粒度位),若G=1需将Limit乘以4096并补0xFFF。 - 属性位映射错误:VMCB段属性有固定位定义,原代码中保留位
Reserved_0/Reserved_1不应设置,且部分位偏移与AMD-V规范不匹配。
修正后的实现步骤与代码
1. 定义符合AMD规范的结构体
typedef struct _SEGMENT_SELECTOR { USHORT RPL : 2; USHORT TI : 1; USHORT Index : 13; } SEGMENT_SELECTOR, *PSEGMENT_SELECTOR; typedef struct _CODE_SEGMENT_DESCRIPTOR { USHORT SegmentLimit_LowPart; USHORT BaseAddress_LowPart; UCHAR BaseAddress_MiddlePart; UCHAR Type : 4; UCHAR S : 1; UCHAR DPL : 2; UCHAR P : 1; UCHAR SegmentLimit_HighPart : 4; UCHAR AVL : 1; UCHAR L : 1; UCHAR D : 1; UCHAR G : 1; UCHAR BaseAddress_HighPart; USHORT ExtendedBaseAddress; // 64位模式下扩展的Base高16位 } CODE_SEGMENT_DESCRIPTOR, *PCODE_SEGMENT_DESCRIPTOR; typedef struct _VM_SEGMENT_REGISTER { USHORT selector; struct { ULONGLONG base; ULONGLONG limit; USHORT attrib; } HiddenPart; } VM_SEGMENT_REGISTER, *PVM_SEGMENT_REGISTER;
2. 读取GDTR基址与大小
GDTR STRUCT Limit WORD ? Base QWORD ? GDTR ENDS VOID Asm_ReadGdtr(GDTR* pGdtr) { __asm { sgdt [pGdtr] } }
3. 修正段描述符获取与填充逻辑
VOID FillCsHiddenPart(PVM_SEGMENT_REGISTER pRegister, const GDTR* pGdtr, USHORT LdtrSelector) { pRegister->selector = Asm_ReadCs(); PSEGMENT_SELECTOR pSel = (PSEGMENT_SELECTOR)&pRegister->selector; PCODE_SEGMENT_DESCRIPTOR pCodeDesc = NULL; if (pSel->TI == 0) { // GDT中段描述符:基址 + 索引*8 pCodeDesc = (PCODE_SEGMENT_DESCRIPTOR)(pGdtr->Base + (pSel->Index * sizeof(CODE_SEGMENT_DESCRIPTOR))); } else { // 先从GDT获取LDT描述符 PCODE_SEGMENT_DESCRIPTOR pLdtDesc = (PCODE_SEGMENT_DESCRIPTOR)(pGdtr->Base + (LdtrSelector >> 3) * sizeof(CODE_SEGMENT_DESCRIPTOR)); // 计算LDT完整基址(40位) ULONGLONG LdtBase = (ULONGLONG)pLdtDesc->BaseAddress_LowPart | ((ULONGLONG)pLdtDesc->BaseAddress_MiddlePart << 16) | ((ULONGLONG)pLdtDesc->BaseAddress_HighPart << 24) | ((ULONGLONG)pLdtDesc->ExtendedBaseAddress << 32); // 获取LDT中的代码段描述符 pCodeDesc = (PCODE_SEGMENT_DESCRIPTOR)(LdtBase + (pSel->Index * sizeof(CODE_SEGMENT_DESCRIPTOR))); } // 计算完整Base(40位) pRegister->HiddenPart.base = (ULONGLONG)pCodeDesc->BaseAddress_LowPart | ((ULONGLONG)pCodeDesc->BaseAddress_MiddlePart << 16) | ((ULONGLONG)pCodeDesc->BaseAddress_HighPart << 24) | ((ULONGLONG)pCodeDesc->ExtendedBaseAddress << 32); // 计算Limit,处理粒度位G ULONG limit = pCodeDesc->SegmentLimit_LowPart | ((pCodeDesc->SegmentLimit_HighPart & 0xF) << 16); if (pCodeDesc->G) { limit = (limit << 12) | 0xFFF; } pRegister->HiddenPart.limit = limit; // 严格按照AMD-V规范映射属性位 if (!pCodeDesc->P) { pRegister->HiddenPart.attrib = 0; } else { pRegister->HiddenPart.attrib = 0; pRegister->HiddenPart.attrib |= (pCodeDesc->Type & 0xF); // 类型位(含A、R、C) pRegister->HiddenPart.attrib |= ((pCodeDesc->DPL & 0x3) << 5); // DPL pRegister->HiddenPart.attrib |= (pCodeDesc->P << 7); // 段存在位 pRegister->HiddenPart.attrib |= (pCodeDesc->AVL << 12); // AVL pRegister->HiddenPart.attrib |= (pCodeDesc->L << 13); // 64位模式标志 pRegister->HiddenPart.attrib |= (pCodeDesc->D << 14); // 默认操作大小 pRegister->HiddenPart.attrib |= (pCodeDesc->G << 15); // 粒度位 } }
额外注意事项
- 必须在Ring 0权限下执行代码,用户态无法访问GDT/LDT内存。
- 64位模式下,代码段的
L位必须设为1,D位必须设为0,否则VMCB会判定状态无效。 - 需验证段描述符的
P位是否为1,若为0则段不可用,禁止填充到VMCB。
内容的提问来源于stack exchange,提问作者fahuifai
相关产品推荐
相关产品推荐

