[调试器实现]第五章 单步异常的处理

因为在调试器的设计与实现中,很多关键性的操作都是在单步异常处理中完成的,故本章重点论述在单步异常中的处理。首先我们来看看会有哪些情况导致调试器进入单步异常。
 
进入单步异常的原因:

1. 用户输入了单步进入的命令,调试器需要设置单步,让被调试程序单步执行。

2. 用户所下的INT3断点被断下后,调试器会暂时恢复INT3断点处的字节为原有的字节,并让被调试线程的EIP减一,为了重新设置这个INT3断点,调试器自己设置了单步。

3. 用户所下的硬件断点被断下时,会触发单步异常。

4. 用户所下的硬件执行断点被断下后,调试器会暂时取消掉该硬件执行断点,以便被调试进程顺利跑下去,为了重新设置该硬件执行断点,调试器自己设置了单步。

5. 用户所下的内存断点被断下后,调试器会暂时恢复产生访问异常的内存分页为原来的属性,以便被调试进程顺利跑下去,为了重新设置该内存分页的属性(以便内存断点继续起作用),调试器自己设置了单步。
 
单步异常的处理:

    从以上所述各点来看,进入单步的原因有三种,一是用户需要单步步入运行程序;二是调试器需要重新设置之前被临时取消的断点而设置了单步;三是硬件断点被断下时触发的单步。当然也有可能几种原因同时存在。所以我们需要几个BOOL变量来表明是否有需要重设的断点。INT3断点对应一个BOOL变量,硬件执行断点对应一个BOOL变量,是否是用户输入的单步步入命令对应一个BOOL变量。另外,进入单步后还需要检查线程环境中的Dr6的低4位是否有值为1的位,如果有那么进入单步的原因之一是因为触发了硬件断点,此时需要进一步判断该硬件断点是否是硬件执行断点,如果是硬件执行断点需要做相应的处理(具体处理方法见《调试器实现(第三章)硬件断点》)。
 
多断点重合的情况:

    当用户对代码段的同一个地址(指令首字节)即设置了硬件执行断点,又设置了INT3断点,同时还设置了内存访问断点,此时会先触发硬件执行断点,然后会触发内存访问断点,最后会触发INT3断点。如果用户不想在同一个地址被多个断点断下多次,可以在相应的异常中做判断,先临时取消掉同一地址处的其他类型的断点,然后设置一个单步,进入单步后再把前面取消的断点再重新设置上。

处理单步异常的模块代码:

代码:
//处理单步异常
BOOL CDoException::DoStepException()
{
   BOOL                    bRet;
   DWORD                  dwDr6 = 0;                //硬件调试寄存器Dr6的值
   DWORD                 dwDr6Low = 0;    //硬件调试寄存器Dr6低4位的值
   stuPointInfo               tempPointInfo;
   stuPointInfo*              pResultPointInfo = NULL;
   char                     CodeBuf[24] = {0};
 
   UpdateContextFromThread();
 
   //是否需要重设INT3断点
   if (m_isNeedResetPoint == TRUE)
   {
       m_isNeedResetPoint = FALSE;
       char chCC = (char)0xcc;
       bRet = WriteProcessMemory(m_hProcess, m_pFindPoint->lpPointAddr, 
                                 &chCC, 1, NULL);
       if (bRet == FALSE)
       {
           printf("WriteProcessMemory error!\r\n");
           return FALSE;
       }
   }
 
   //是否需要重设硬件断点
   if (m_isNeedResetHardPoint == TRUE)
   {
       m_Context.Dr7 |= (int)pow(4, m_nNeedResetHardPoint);
       UpdateContextToThread();
       m_isNeedResetHardPoint = FALSE;
   }
 
   dwDr6 = m_Context.Dr6;
   dwDr6Low = dwDr6 & 0xf; //取低4位
 
   //如果是由硬件断点触发的单步,需要用户输入才能继续
   //另外,如果是硬件执行断点,则需要先暂时取消断点,设置单步,下次再恢复断点
   if (dwDr6Low != 0)
   {
       ShowHardwareBreakpoint(dwDr6Low);
       m_nNeedResetHardPoint = log(dwDr6Low)/log(2)+0.5;//加0.5是为了四舍五入
       //判断由 dwDr6Low 指定的DRX寄存器,是否是执行断点
       if((m_Context.Dr7 << (14 - (m_nNeedResetHardPoint * 2))) >> 30 == 0)
       {
           switch (m_nNeedResetHardPoint)
           {
           case 0:
               m_Context.Dr7 &= 0xfffffffe;
               break;
           case 1:
               m_Context.Dr7 &= 0xfffffffb;
               break;
           case 2:
               m_Context.Dr7 &= 0xffffffef;
               break;
           case 3:
               m_Context.Dr7 &= 0xffffffbf;
               break;
           default:
               printf("Error!\r\n");
           }
           m_Context.EFlags |= TF;
           UpdateContextToThread();
           m_isNeedResetHardPoint = TRUE;
       }
 
       m_isUserInputStep = TRUE; //这个设置只是为了能够等待用户输入
   }
 
   if (m_isUserInputStep == FALSE)
   {
       //重设内存断点
       ResetMemBp();
       return TRUE;
   }
 
   //以下代码在用户输入为 "T" 命令、或硬件断点触发时执行
   //如果此处有INT3断点,则需要先暂时删除INT3断点
   //这样做是为了在用户输入“T”命令、或硬件断点触发时忽略掉INT3断点
   //以免在一个地方停下两次
   memset(&tempPointInfo, 0, sizeof(stuPointInfo));
   tempPointInfo.lpPointAddr = m_DbgInfo.ExceptionRecord.ExceptionAddress;
   tempPointInfo.ptType = ORD_POINT;
 
   if (FindPointInList(tempPointInfo, &pResultPointInfo, TRUE))
   {
       //非一次性断点,才需要重设断点
       if (pResultPointInfo->isOnlyOne == FALSE)
       {
           m_Context.EFlags |= TF;
           UpdateContextToThread();
           m_isNeedResetPoint = TRUE;
       }
       else//一次性断点,从链表里面删除
       {
           delete[] m_pFindPoint;
           g_ptList.erase(m_itFind);
       }
       WriteProcessMemory(m_hProcess, m_pFindPoint->lpPointAddr, 
           &(m_pFindPoint->u.chOldByte), 1, NULL);
       if (bRet == FALSE)
       {
           printf("WriteProcessMemory error!\r\n");
           return FALSE;
       }
}
 
   m_lpDisAsmAddr = m_DbgInfo.ExceptionRecord.ExceptionAddress;
   m_isUserInputStep = FALSE;
 
   //更新m_Context为现在的环境值
   UpdateContextFromThread();
 
   //显示汇编代码和寄存器信息
   ShowAsmCode();
   ShowRegValue(NULL);
 
   //重设内存断点
   ResetMemBp();
 
//等待用户输入
   bRet = FALSE;
   while (bRet == FALSE)
   {
       bRet = WaitForUserInput();
   }
   return TRUE;
}

 

本系列文章参考书目、资料如下:
1.《加密与解密3》 编著:段钢
2.《调试寄存器(DRx)理论与实践》 作者:Hume/冷雨飘心
3.《数据结构》 作者:严蔚敏

posted @ 2015-05-10 10:41  银河彼岸  阅读(1208)  评论(0编辑  收藏  举报