基于BranchTraceStore机制的CPU执行分支追踪工具 —— CpuWhere

基于BranchTraceStore机制的CPU执行分支追踪工具 —— CpuWhere

[前言]

在张银奎老师的《软件调试》一书中,详细地讲解了使用内存的分支记录机制——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. 代码如下:

 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 }

 

 

二、使用CPUID指令判断当前DS机制是否为DTES64模式。若是,则DS结构中BTS的基地址、索引、边界地址和中断阈值都应为64位,BranchRecord中的来源地址、目标地址以及标志数据也应为64位,PEBS同理。

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 }

 

 

三、根据第二步的结果来设置相应的DS和BTS(仅编写了DTES64模式的代码,非DTES64模式的情况请参照原书)。

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就变成了正数,自然是不对的。

此次事件再次教育了我:凡事不能想当然,要求甚解。

 

 

四、启用BTS机制

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 }

 

 

五、将以上步骤写入DriverEntry例程

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 }

 

六、为了简化代码(仅测试用),将以上禁用BTS机制、获取分支记录以及释放DS和BTS内存的所有代码都放入了DriverUnload例程。

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%08Xn%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 }

 

 

七、由此便完成了在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

 

基于BranchTraceStore机制的CPU执行分支追踪工具 —— CpuWhere

相关文章:

你感兴趣的文章:

标签云: