[前言]在張銀奎老師的《軟體調試》一書中,詳細地講解了使用記憶體的分支記錄機制——BTS機制(5.3),並且給出了示例工具CpuWhere及其源代碼。但實際運行(VMware XP_SP3 單核)並沒有體現應有的效果,無法讀取到分支記錄。查看了源代碼並沒有發現任何問題,與書中所講一致。既然軟體本身沒有...
[前言]
在張銀奎老師的《軟體調試》一書中,詳細地講解了使用記憶體的分支記錄機制——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%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 }
七、由此便完成了在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