調試寄存器 原理與使用:DR0-DR7

調試寄存器 原理與使用:DR0-DR7

下面介紹的知識性信息來自intel IA-32手冊(可以在intel的開發手冊或者官方網站查到),提示和補充來自學習調試實現時的總結。

希望能給你帶去有用的信息。

(DRx對應任意的一個調試寄存。LENn對應任意一個長度。Ln對應任意一個局部置位)

DR0-DR7可以直接被讀寫操作(MOV 指令之類的,DRx可以是源操作數也可以是目的操作數)

   但是,DRx的訪問是需要一定權限的。比如你用MOV操作的話,你需要在實地址模式,系統管理模式(smm)或者在保護模式(CPL設0).如果權限不夠,將會在訪問DRx的時候嘗產生#GP(general-protection)異常

現在來看看DRx可以幹些什麼? 

1.設置發生斷點的地址(線性地址)
2.設置斷點的長度(1,2,4個字節,但是執行斷點只能是1)
3.設置在調試異常產生的地址執行的操作
4.設置斷點是否可用
5.在調試異常產生時,調試條件是否是可用

(以上直接翻譯自"Intel 64 and IA-32 Architectures Software Developer’s Manual" volume 3。
以下來自個人的總結。當然,也是參考intel官方資料得來的)

我們來看看調試寄存的一些細節信息。
下圖很重要,後面的介紹都是針對這個圖說的。
(當然不是我畫的,是來自intel  ia-32系統結構開發手冊18章2節)。

點擊圖片以查看大圖圖片名稱: DR0_DR7.jpg查看次數: 2105文件大小: 63.6 KB文件 ID : 39226

調試寄存 DR0-DR3
   這四個寄存是用來設置 斷點地址的。斷點的比對在物理地址轉換前(異常產生時,還沒有將線性地址轉換成物理地址)。由於只有0-3四個保存地址的寄存,所以,硬件斷點,在物理上最多隻能有4個。
調試寄存DR4-DR5 
   這兩個調試寄存有CR4的DE標記控制。如果DE置位,那麼對這兩個寄存的訪問會導致#UD異常。如果DE置0,那麼他們就被化名爲DR6-DR7(你一定會問原來的DR6-DR7怎麼辦?這個…… 我也不知道。如果你搞明白了,一定記得告訴我)
調試寄存DR7(控制寄存)
   (先介紹DR7對DR6的理解有好處。)

DR7是調試控制寄存。控制方式嘛!繼續看:
1.  L0-L3(由第0,2,4,6位控制):對應DR0-DR3,設置斷點作用範圍,如果被置位,那麼將只對當前任務有效。每次異常後,Lx都被清零。
2.  G0-G3(由第1,3,5,7位控制):對應DR0-DR3,如果置位,那麼所有的任務都有效。每次異常後不會被清零。以確保對所有任務有效。但是,不知道爲什麼,我在測試時:
設置Gn後,不能返回調試異常給調試(如果你知道爲什麼,記得告訴我)
3.  LE,GE(由第8,9位控制):這個在P6以下系列CPU上不被支持,在升級版的系列裏面:如果被置位,那麼cpu將會追蹤精確的數據斷點。LE是局部的,GE是全局的。(到底什麼算精確的,我也不清楚,但是,我知道如果設置了這兩個,cpu的速度會降低。我在測試中,都沒有置位。)
4.  GD(由第13位控制):如果置位,追蹤下一條指令是否會訪問調試寄存。如果是,產生異常。在下面的DR6裏面,你會知道他還和另外一個標誌位有點關係。
5.  R/W0-R/W3:(由第16,17,20,21,24,25,28,29位控制):這個東西的處理有兩種情況。
如果CR4的DE被置位,那麼,他們按照下面的規則處理問題:
00:執行斷點
01:數據寫入斷點
10:I/0讀寫斷點
11:讀寫斷點,讀取指令不算
如果DE置0,那麼問題會這樣處理:
00:執行斷點
01:數據寫入斷點
10:未定義
11:數據讀寫斷點,讀取指令不算
6.  LEN0-LEN3:(由第18.19.22.23.26.27.30位控制):指定內存操作的大小。
00:1字節(執行斷點只能是1字節長)
01:2字節
10:未定義或者是8字節(和cpu的系列有關係)
11:4字節
調試寄存DR6(調試狀態寄存
   這個寄存主要是在調試異常產生後,報告產生調試異常的相關信息
1.  B0-B3(DR0-DR3):DRx指定的斷點在滿足DR7指定的條件下,產生異常。那麼Bx就置位。但是,有時,即使Ln和Gn置0,也可能產生Bx被置位。這種現象可能這樣出現(提示:在p6系列處理,REP MOVS在不斷循環中產生的調試異常需要執行完了才能準確返回給調試進程):DR0的L0,G0都置0(DR0就是一個不能產生異常的斷點了),然後在DR0指定的地址是一個REP指令的循環,這樣,DR0就可能在這個循環之後的REP指令產生的調試異常中將B0置位
2.  BD:BD需要DR7的GD置位,纔有效。BD是在下一條指令要訪問到某一個調試寄存的時候,被置位的。
3.  BS:單步執行模式時,被置位。單步執行是最高權限的調試異常。
4.  BT:在任務切換的時候,被置位。但是必須在被切換去的任務的TSS段裏面的T標記被置位的情況下才有效。在控制權被切換過去後,在執行指令前,返回調試異常。但是,需要注意,如果調試程序是一個任務,那麼T標記的設置肯定就衝突了。然後,導致了死循環(BT的這些信息都是按照官方資料翻譯而來,由於沒有實際的操作,肯定會有理解上的出入。如果要深入的話,建議看官方資料)
  
有些調試異常會將B0-B3清零。但是其他的DR6的位是不能被產生異常的進程清零的。每次調試異常返回後,調試進程都會先將DR6清零,再按照情況設置。以免產生不必要的錯誤。

對齊問題和64位處理
對齊問題:
這個問題是來源於LENn的設置,如果設置4字節,那麼必須4字節對齊。例如:我們下4字節的斷點,那麼DRx需要是A0000/A0004/A0008這樣的地址上。I/O地址是零擴展的(這個……也許意味着必須完全對齊)。因爲,intel在比對地址時:用LENn的值去覆蓋DRx裏面保存的地址的低位。你可以想到,不對齊會有什麼後果了吧。注意:執行斷點只能是1字節。

再用圖片解釋下(當然,圖片來自intel官方資料):

點擊圖片以查看大圖圖片名稱: Align1.jpg查看次數: 2083文件大小: 68.6 KB文件 ID : 39224
點擊圖片以查看大圖圖片名稱: Align2.jpg查看次數: 2073文件大小: 33.1 KB文件 ID : 39225

在64位處理下:
調試寄存當然也是64位的。在操作過程中,寫入,前面32位被置零。讀取:只返回後32位。MOV DRx操作,前32位被忽略。
DR6-DR7的高32位被保留。置零。如果置位,會產生#GP異常。8字節的讀寫斷點完全被支持。

最後,還是給個圖片(64位處理的佈局):

點擊圖片以查看大圖圖片名稱: 64.jpg查看次數: 2074文件大小: 41.9 KB文件 ID : 39223

最後需要提醒一個小問題:數據寫入斷點設置後。是在原數據被修改後,才產生調試異常。所以,返回異常時,原有數據已經被修改。如果想保留原有數據,需要自己提前保存對應地址的數據。


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

VC利用調試寄存器實現硬件斷點,處理斷點異常

[cpp] view plain copy
  1. /************************************************************************ 
  2. SetHardWareBP: 
  3. 設置線程硬件斷點 
  4. hThread:  線程句柄 
  5. dwAddr:    斷點地址 
  6. dwDrIndex:  硬件寄存器(0~3) 
  7. nType:    斷點類型(0:執行,1:讀取,2:寫入) 
  8. nLen:    讀寫斷點數據長度(1,2,4) 
  9. /************************************************************************/  
  10.   
  11. BOOL SetHardWareBP(HANDLE hThread,DWORD dwAddr,DWORD dwDrIndex=0,UINT nType=0,UINT nLen=1)  
  12. {  
  13.   BOOL bResult=FALSE;  
  14.     
  15.   CONTEXT context = {0};  
  16.   context.ContextFlags=CONTEXT_DEBUG_REGISTERS;  
  17.   if(::GetThreadContext(hThread,&context))  
  18.   {  
  19.     DWORD dwDrFlags=context.Dr7;  
  20.   
  21.   
  22.     //將斷點地址複製進入對應Dr寄存器(參考CONTEXT結構)  
  23.     memcpy(((BYTE *)&context)+4+dwDrIndex*4,&dwAddr,4);    
  24.       
  25.     //決定使用哪個寄存器  
  26.     dwDrFlags|=(DWORD)0x1<<(2*dwDrIndex);  
  27.   
  28.     //見OD讀寫斷點時 這個置位了,執行沒有(置位也正常-_-)  
  29.     dwDrFlags|=0x100;  
  30.       
  31.   
  32.     //先將對應寄存器對應4個控制位清零(先或,再異或,還有其它好方法嗎) =.= 悲催的小學生  
  33.     dwDrFlags|=(DWORD)0xF<<(16+4*dwDrIndex);  
  34.     dwDrFlags^=(DWORD)0xF<<(16+4*dwDrIndex);  
  35.       
  36.   
  37.     //設置斷點類型,執行:00 讀取:11 寫入:01  
  38.     //(不知何故,測試時發現不論是11還是01,讀寫數據時均會斷下來)  
  39.     if (nType==1)      
  40.       dwDrFlags|=(DWORD)0x3<<(16+4*dwDrIndex);  //讀取  
  41.     else if(nType==2)    
  42.       dwDrFlags|=(DWORD)0x1<<(16+4*dwDrIndex);  //寫入  
  43.     //else if(nType==0)   
  44.       //dwDrFlags=dwDrFlags            //執行  
  45.       
  46.   
  47.     //設置讀寫斷點時數據長度  
  48.     if (nType!=0)  
  49.     {  
  50.       if(nLen==2 && dwAddr%2==0)        
  51.         dwDrFlags|=(DWORD)0x1<<(18+4*dwDrIndex);  //2字節  
  52.       else if(nLen==4  && dwAddr%4==0)    
  53.         dwDrFlags|=(DWORD)0x3<<(18+4*dwDrIndex);  //4字節  
  54.     }  
  55.       
  56.     context.Dr7=dwDrFlags;  
  57.     if (::SetThreadContext(hThread,&context)) bResult=TRUE;  
  58.   }  
  59.   return bResult;  
  60. }  

[cpp] view plain copy
  1. //異常處理  
  2. //直接從工程中拷出來的  
  3. typedef ULONG (WINAPI *pfnRtlDispatchException)(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext);  
  4. static pfnRtlDispatchException m_fnRtlDispatchException=NULL;  
  5.   
  6. BOOL RtlDispatchException(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext);  
  7.   
  8. ULONG WINAPI CSysHook::_RtlDispatchException( PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext )  
  9. {  
  10.   if(RtlDispatchException(pExcptRec,pContext)) return 1;  
  11.   return m_fnRtlDispatchException(pExcptRec,pContext);  
  12. }  
  13.   
  14. //Hook程序異常處理,當程序發生異常時,由ring0轉回ring3時調用的第一個函數:KiUserExceptionDispatcher  
  15. BOOL CSysHook::HookSystemSEH()  
  16. {  
  17.   BOOL bResult=FALSE;  
  18.   BYTE *pAddr=(BYTE *)::GetProcAddress(::GetModuleHandleA("ntdll"),"KiUserExceptionDispatcher");  
  19.   if (pAddr)  
  20.   {  
  21.     while (*pAddr!=0xE8)pAddr++;  //XP~Win7正常,Win8尚無緣得見  
  22.     m_fnRtlDispatchException=(pfnRtlDispatchException)((*(DWORD *)(pAddr+1))+5+(DWORD)pAddr);  //得到原函數地址  
  23.     DWORD dwNewAddr=(DWORD)_RtlDispatchException-(DWORD)pAddr-5;                //計算新地址  
  24.     CMemory::WriteMemory((DWORD)pAddr+1,(BYTE *)&dwNewAddr,4);  //這個寫內存的自己改造吧  
  25.     bResult=TRUE;  
  26.   }  
  27.   return bResult;  
  28. }  
  29.   
  30. //異常處理函數  
  31. BOOL RtlDispatchException(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext)  
  32. {  
  33.    返回TRUE,這個異常我已經處理好了,繼續運行程序  
  34.    返回FALSE,這個異常不是我的,找別人處理去  
  35. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章