[前言]
在张银奎老师的《软件调试》一书中,详细地讲解了使用内存的分支记录机制——BTS机制(5.3),并且给出了示例工具CpuWhere及其源代码。但实际运行(VMware XP_SP3 单核)并没有体现应有的效果,无法读取到分支记录。查看了源代码并没有发现任何问题,与书中所讲一致。既然软件本身没有问题,那会不会是在虚拟机中运行的问题呢?
翻出了闲置多年的老机器,奔腾Dual+XP_SP3,在启动配置中增加/numproc=1,设置单核启动,测试结果依然没有什么改变。网上搜索几遍也是无果,毕竟是很小众的东西,只找到了一个论坛上有针对多核的修改版本,由于我没有该论坛的账号,也没能下载测试。
最后翻看了Intel手册,发现了问题的原因:如果DS机制使用DTES64模式,CPU会将分支记录的大小扩充为64位。
经测试,在奔腾Dual+XP_SP3的配置下,DTES64模式是启用的,所以问题应该就在这里。VMware为何无效暂时不清楚,只是发现操作DS和BTS相关的MSR寄存器时没有效果(无法读写),也许是没有配置好虚拟机,也可能是VMware未对DS和BTS机制进行虚拟化,所以请尽量不要在虚拟机中测试和使用此类工具。
[正确的使用BTS机制的详细步骤]
一、使用CPUID指令判断是否支持DS机制和RDMSR/WRMSR指令;读IA32_MISC_ENABLE寄存器判断是否支持BTS机制。
1. EAX=1时,EDX中表示DS和RDMSR/WRMSR支持情况的标志位分别为:
2. IA32_MISC_ENABLE寄存器表示的BTS机制支持情况:
3. 代码如下:
BOOLEAN IsSupported()
{
DWORD _edx = ;
DWORD _eax = ; _asm
{
mov eax,
cpuid
mov _edx,edx
} if ((_edx & ( << BIT_DS_SUPPORTED)) == )
{
DBGOUT(("Debug store is not supported."));
return FALSE;
} if ((_edx & ( << BIT_RWMSR_SUPPORTED)) == )
{
DBGOUT(("RDMSR/WRMSR is not supported."));
return FALSE;
} ReadMSR(IA32_MISC_ENABLE, &_edx, &_eax);
if ((_eax & ( << BIT_BTS_UNAVAILABLE)) != )
{
DBGOUT(("Branch trace store is not supported."));
return FALSE;
} return TRUE;
}
二、使用CPUID指令判断当前DS机制是否为DTES64模式。若是,则DS结构中BTS的基地址、索引、边界地址和中断阈值都应为64位,BranchRecord中的来源地址、目标地址以及标志数据也应为64位,PEBS同理。
1. EAX=1时,ECX中表示是否为DTES64模式的标志位是:
2. 代码如下:
BOOLEAN IsDTES64()
{
DWORD _ecx = ; _asm
{
mov eax,
cpuid
mov _ecx,ecx
} return ((_ecx & ( << BIT_DTES64)) != ) ? TRUE : FALSE;
}
三、根据第二步的结果来设置相应的DS和BTS(仅编写了DTES64模式的代码,非DTES64模式的情况请参照原书)。
1. DTES64模式下,DS和BranchRecord的结构:
2. DTES64模式下,DS和BranchRecord的结构声明如下:
typedef struct _DEBUG_STORE
{
ULONG64 btsBase;
ULONG64 btsIndex;
ULONG64 btsAbsolute;
ULONG64 btsInterruptThreshold;
ULONG64 pebsBase;
ULONG64 pebsIndex;
ULONG64 pebsAbsolute;
ULONG64 pebsInterruptThreshold;
ULONG64 pebsCounterReset;
ULONG64 reserved;
} DEBUG_STORE, *PDEBUG_STORE;
typedef struct _BRANCH_RECORD
{
ULONG64 from;
ULONG64 to;
ULONG64 flags;
} BRANCH_RECORD, *PBRANCH_RECORD;
3. 必须为DS和BTS申请非分页内存:
4. 代码如下:
BOOLEAN InitDebugStore()
{
g_pDebugStore = ExAllocatePoolWithTag(NonPagedPool, sizeof(DEBUG_STORE), (ULONG)"SD__");
if (g_pDebugStore == NULL)
{
DBGOUT(("Failed to allocate memory for debug store."));
return FALSE;
}
memset(g_pDebugStore, , sizeof(DEBUG_STORE)); return TRUE;
}
BOOLEAN InitBranchTraceStore()
{
g_pBranchTraceStore = ExAllocatePoolWithTag(NonPagedPool, sizeof(BRANCH_RECORD) * MAX_RECORD, (ULONG)"STB_");
if (g_pBranchTraceStore == NULL)
{
DBGOUT(("Failed to allocate memory for branch trace store."));
return FALSE;
}
memset(g_pBranchTraceStore, , sizeof(BRANCH_RECORD) * MAX_RECORD); return TRUE;
}
5. 设置DS的代码如下:
VOID SetDebugStore()
{
g_pDebugStore->btsBase = (ULONG64)g_pBranchTraceStore;
g_pDebugStore->btsIndex = (ULONG64)g_pBranchTraceStore;
g_pDebugStore->btsAbsolute = (ULONG64)g_pBranchTraceStore + sizeof(BRANCH_RECORD) * MAX_RECORD;
g_pDebugStore->btsInterruptThreshold = (ULONG64)g_pBranchTraceStore + sizeof(BRANCH_RECORD) * (MAX_RECORD + );
WriteMSR(IA32_DS_AREA, HIDWORD(g_pDebugStore), LODWORD(g_pDebugStore));
}
PS:此处让我了解了C语言强制类型转换的原理(小类型转大类型)。
双机调试时,查看g_pDebugStore强制转为ULONG64后的内存,其高32位为0xFFFFFFFF。我一直以为小类型转大类型是在其高位补0,出现这种情况令我十分不解,于是查看反汇编发现了原因:
由于32位程序可使用的寄存器最大宽度为32位,所以当要表示一个64位数时,其形式为[Reg:Reg],如EDX:EAX。当要把一个32位数据扩充为64位时,CPU使用CDQ指令将该数值的符号位复制到EDX中的每一位,这样EDX:EAX即表示一个64位的数据。
回到代码中,因为这是一个驱动程序,运行在Ring0,所以系统分配的虚拟地址一定大于0X7FFFFFFF,这样一来,对于32位宽度的数据来说,这表是一个负数。负数的符号位是1,用1填满EDX即为0xFFFFFFFF,这样可以保证0xFFFFFFFF~XXXXXXXX和原值相等,如果补0就变成了正数,自然是不对的。
此次事件再次教育了我:凡事不能想当然,要求甚解。
四、启用BTS机制
1. IA32_DEBUGCTL寄存器中表示分支启用、分支记录方式和是否缓冲区满时触发中断的标志位分别为:
2. 设置TR和BTS位为1来启用BTS机制,设置BTINT位为0来表示一个环形缓冲区,代码如下:
VOID EnableBranchTraceStore()
{
DWORD _edx = ;
DWORD _eax = ; ReadMSR(IA32_DEBUGCTL, &_edx, &_eax);
_eax |= << BIT_TR;
_eax |= << BIT_BTS;
_eax &= ~( << BIT_BTINT);
WriteMSR(IA32_DEBUGCTL, _edx, _eax);
}
五、将以上步骤写入DriverEntry例程
1. 根据顺序依次调用即可在DTES64模式下顺利启用BTS机制:
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegisterPath)
{
UNREFERENCED_PARAMETER(pRegisterPath); DBGOUT(("DriverEntry()")); pDriverObject->DriverUnload = MyCpuWhereUnload; if (!IsSupported())
{
return STATUS_FAILED_DRIVER_ENTRY;
} if (IsDTES64())
{
DBGOUT(("Running on DTES64 mode."));
}
else
{
DBGOUT(("Not running on DTES64 mode."));
} if (!InitDebugStore())
{
return STATUS_FAILED_DRIVER_ENTRY;
} if (!InitBranchTraceStore())
{
return STATUS_FAILED_DRIVER_ENTRY;
} SetDebugStore(); EnableBranchTraceStore(); return STATUS_SUCCESS;
}
六、为了简化代码(仅测试用),将以上禁用BTS机制、获取分支记录以及释放DS和BTS内存的所有代码都放入了DriverUnload例程。
1. 在读取BTS缓冲区之前,要先禁用BTS机制(与开启过程一致但标志位值取反):
VOID DisableBranchTraceStore()
{
DWORD _edx = ;
DWORD _eax = ; ReadMSR(IA32_DEBUGCTL, &_edx, &_eax);
_eax &= ~( << BIT_TR);
_eax &= ~( << BIT_BTS);
WriteMSR(IA32_DEBUGCTL, _edx, _eax);
}
2. 根据BTS的结构来循环读取BranchRecord:
见下
3. 释放之前为DS和BTS申请的非分页内存:
见下
4. 代码如下
VOID MyCpuWhereUnload(PDRIVER_OBJECT pDriverObject)
{
PBRANCH_RECORD pRecord = NULL;
DWORD count = ; UNREFERENCED_PARAMETER(pDriverObject); DBGOUT(("DriverUnload()")); DisableBranchTraceStore(); pRecord = (PBRANCH_RECORD)LODWORD(g_pDebugStore->btsBase);
for (; pRecord < (PBRANCH_RECORD)LODWORD(g_pDebugStore->btsAbsolute) && count < MAX_RECORD; ++pRecord, ++count)
{
if (pRecord->from == )
{
break;
}
DBGOUT(("%d: From: 0x%08X\n%d: To: 0x%08X", count + , (DWORD)pRecord->from, count + , (DWORD)pRecord->to));
} ExFreePoolWithTag(g_pBranchTraceStore, (ULONG)"STB_");
ExFreePoolWithTag(g_pDebugStore, (ULONG)"SD__");
}
七、由此便完成了在DTES64下启用BTS机制的全部过程,因未支持多核,所以可能会出现不可预料的状况,请谨慎使用。
运行效果:
[总结]
仅作学习而用,并未编写GUI界面和R3&R0的通讯例程,也未实现兼容非DTES64模式的代码,但这几点都可在张银奎老师编写的原版CpuWhere的源码中找到相关代码。
张银奎老师的原版CpuWhere(Bin&Src)
下载并使用这个工具的许可条件是使用者本人购买了《软件调试》一书
下载地址:http://advdbg.org/books/swdbg/t_cpuwhere.aspx
针对DTES64模式的修正版CpuWhere(Src VS2013 + WDK8.1)
下载地址:http://files.cnblogs.com/files/Chameleon/MyCpuWhere.zip