标签:
在张银奎老师的《软件调试》一书中,详细地讲解了使用内存的分支记录机制——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机制进行虚拟化,所以请尽量不要在虚拟机中测试和使用此类工具。
1. EAX=1时,EDX中表示DS和RDMSR/WRMSR支持情况的标志位分别为:
2. IA32_MISC_ENABLE寄存器表示的BTS机制支持情况:
3. 代码如下:
1 BOOLEAN IsSupported() 2 { 3 DWORD _edx = 0; 4 DWORD _eax = 0; 5 6 _asm 7 { 8 mov eax,1 9 cpuid 10 mov _edx,edx 11 } 12 13 if ((_edx & (1 << BIT_DS_SUPPORTED)) == 0) 14 { 15 DBGOUT(("Debug store is not supported.")); 16 return FALSE; 17 } 18 19 if ((_edx & (1 << BIT_RWMSR_SUPPORTED)) == 0) 20 { 21 DBGOUT(("RDMSR/WRMSR is not supported.")); 22 return FALSE; 23 } 24 25 ReadMSR(IA32_MISC_ENABLE, &_edx, &_eax); 26 if ((_eax & (1 << BIT_BTS_UNAVAILABLE)) != 0) 27 { 28 DBGOUT(("Branch trace store is not supported.")); 29 return FALSE; 30 } 31 32 return TRUE; 33 }
1. EAX=1时,ECX中表示是否为DTES64模式的标志位是:
2. 代码如下:
1 BOOLEAN IsDTES64() 2 { 3 DWORD _ecx = 0; 4 5 _asm 6 { 7 mov eax,1 8 cpuid 9 mov _ecx,ecx 10 } 11 12 return ((_ecx & (1 << BIT_DTES64)) != 0) ? TRUE : FALSE; 13 }
1. DTES64模式下,DS和BranchRecord的结构:
2. DTES64模式下,DS和BranchRecord的结构声明如下:
1 typedef struct _DEBUG_STORE 2 { 3 ULONG64 btsBase; 4 ULONG64 btsIndex; 5 ULONG64 btsAbsolute; 6 ULONG64 btsInterruptThreshold; 7 ULONG64 pebsBase; 8 ULONG64 pebsIndex; 9 ULONG64 pebsAbsolute; 10 ULONG64 pebsInterruptThreshold; 11 ULONG64 pebsCounterReset; 12 ULONG64 reserved; 13 } DEBUG_STORE, *PDEBUG_STORE;
1 typedef struct _BRANCH_RECORD 2 { 3 ULONG64 from; 4 ULONG64 to; 5 ULONG64 flags; 6 } BRANCH_RECORD, *PBRANCH_RECORD;
3. 必须为DS和BTS申请非分页内存:
4. 代码如下:
1 BOOLEAN InitDebugStore() 2 { 3 g_pDebugStore = ExAllocatePoolWithTag(NonPagedPool, sizeof(DEBUG_STORE), (ULONG)"SD__"); 4 if (g_pDebugStore == NULL) 5 { 6 DBGOUT(("Failed to allocate memory for debug store.")); 7 return FALSE; 8 } 9 memset(g_pDebugStore, 0, sizeof(DEBUG_STORE)); 10 11 return TRUE; 12 }
1 BOOLEAN InitBranchTraceStore() 2 { 3 g_pBranchTraceStore = ExAllocatePoolWithTag(NonPagedPool, sizeof(BRANCH_RECORD) * MAX_RECORD, (ULONG)"STB_"); 4 if (g_pBranchTraceStore == NULL) 5 { 6 DBGOUT(("Failed to allocate memory for branch trace store.")); 7 return FALSE; 8 } 9 memset(g_pBranchTraceStore, 0, sizeof(BRANCH_RECORD) * MAX_RECORD); 10 11 return TRUE; 12 }
5. 设置DS的代码如下:
1 VOID SetDebugStore() 2 { 3 g_pDebugStore->btsBase = (ULONG64)g_pBranchTraceStore; 4 g_pDebugStore->btsIndex = (ULONG64)g_pBranchTraceStore; 5 g_pDebugStore->btsAbsolute = (ULONG64)g_pBranchTraceStore + sizeof(BRANCH_RECORD) * MAX_RECORD; 6 g_pDebugStore->btsInterruptThreshold = (ULONG64)g_pBranchTraceStore + sizeof(BRANCH_RECORD) * (MAX_RECORD + 1); 7 WriteMSR(IA32_DS_AREA, HIDWORD(g_pDebugStore), LODWORD(g_pDebugStore)); 8 }
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就变成了正数,自然是不对的。
此次事件再次教育了我:凡事不能想当然,要求甚解。
1. IA32_DEBUGCTL寄存器中表示分支启用、分支记录方式和是否缓冲区满时触发中断的标志位分别为:
2. 设置TR和BTS位为1来启用BTS机制,设置BTINT位为0来表示一个环形缓冲区,代码如下:
1 VOID EnableBranchTraceStore() 2 { 3 DWORD _edx = 0; 4 DWORD _eax = 0; 5 6 ReadMSR(IA32_DEBUGCTL, &_edx, &_eax); 7 _eax |= 1 << BIT_TR; 8 _eax |= 1 << BIT_BTS; 9 _eax &= ~(1 << BIT_BTINT); 10 WriteMSR(IA32_DEBUGCTL, _edx, _eax); 11 }
1. 根据顺序依次调用即可在DTES64模式下顺利启用BTS机制:
1 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegisterPath) 2 { 3 UNREFERENCED_PARAMETER(pRegisterPath); 4 5 DBGOUT(("DriverEntry()")); 6 7 pDriverObject->DriverUnload = MyCpuWhereUnload; 8 9 if (!IsSupported()) 10 { 11 return STATUS_FAILED_DRIVER_ENTRY; 12 } 13 14 if (IsDTES64()) 15 { 16 DBGOUT(("Running on DTES64 mode.")); 17 } 18 else 19 { 20 DBGOUT(("Not running on DTES64 mode.")); 21 } 22 23 if (!InitDebugStore()) 24 { 25 return STATUS_FAILED_DRIVER_ENTRY; 26 } 27 28 if (!InitBranchTraceStore()) 29 { 30 return STATUS_FAILED_DRIVER_ENTRY; 31 } 32 33 SetDebugStore(); 34 35 EnableBranchTraceStore(); 36 37 return STATUS_SUCCESS; 38 }
1. 在读取BTS缓冲区之前,要先禁用BTS机制(与开启过程一致但标志位值取反):
1 VOID DisableBranchTraceStore() 2 { 3 DWORD _edx = 0; 4 DWORD _eax = 0; 5 6 ReadMSR(IA32_DEBUGCTL, &_edx, &_eax); 7 _eax &= ~(1 << BIT_TR); 8 _eax &= ~(1 << BIT_BTS); 9 WriteMSR(IA32_DEBUGCTL, _edx, _eax); 10 }
2. 根据BTS的结构来循环读取BranchRecord:
见下
3. 释放之前为DS和BTS申请的非分页内存:
见下
4. 代码如下
1 VOID MyCpuWhereUnload(PDRIVER_OBJECT pDriverObject) 2 { 3 PBRANCH_RECORD pRecord = NULL; 4 DWORD count = 0; 5 6 UNREFERENCED_PARAMETER(pDriverObject); 7 8 DBGOUT(("DriverUnload()")); 9 10 DisableBranchTraceStore(); 11 12 pRecord = (PBRANCH_RECORD)LODWORD(g_pDebugStore->btsBase); 13 for (; pRecord < (PBRANCH_RECORD)LODWORD(g_pDebugStore->btsAbsolute) && count < MAX_RECORD; ++pRecord, ++count) 14 { 15 if (pRecord->from == 0) 16 { 17 break; 18 } 19 DBGOUT(("%d: From: 0x%08X\n%d: To: 0x%08X", count + 1, (DWORD)pRecord->from, count + 1, (DWORD)pRecord->to)); 20 } 21 22 ExFreePoolWithTag(g_pBranchTraceStore, (ULONG)"STB_"); 23 ExFreePoolWithTag(g_pDebugStore, (ULONG)"SD__"); 24 }
运行效果:
仅作学习而用,并未编写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
基于BranchTraceStore机制的CPU执行分支追踪工具 —— CpuWhere [修正版 仅驱动]
标签:
原文地址:http://www.cnblogs.com/Chameleon/p/5135860.html