此项目为本人在武汉科锐学习的第三阶段项目。因本人水平有限,程序难免存在BUG。现发布调试程序供测试使用。如发现BUG或有好的建议,请告之! ------------  万分感谢!

程序流程:

                                   第一章  程序处理框架
一.CREATE_PROCESS_DEBUG_EVENT处理流程
   当创建调试进程时,进入此流程。在程序的入口点下一个INT3断点,作为单独的软中断处理,即这个断点不加入INT3断点列表。

作用是调试程序时,使程序暂停执行于程序入口点。其断点原理参照软中断详细设计。


二.LOAD_DLL_DEBUG_EVENT处理流程
   当CREATE_PROCESS_DEBUG_EVENT创建后,进入DLL装载过程,利用此过程可以进行函数名称解析,显示出该装载的DLL文件所有的

导出表函数。

     
三.EXCEPTION_DEBUG_EVENT处理流程
   1.EXCEPTION_ACCESS_VIOLATION响应
        此部分用于内存断点的处理,原理是程序运行时触发内存访问异常。
   2.EXCEPTION_BREAKPOINT响应
        此部分用于程序入口处理,INT3处理, G命令处理
   3.EXCEPTION_SINGLE_STEP响应
        此部分用于单步进入<T>,单步步过<P>,硬件断点处理,内存断点属性重置,INT3断点
     重置,硬件断点重置




附件为:   调试器使用手册 + debug版的程序 
程序效果图:
1. 显示各个断点列表

2. 将下断点命令导出文件

3.断点效果图

4.显示DLL中的所有导出函数

    
                                 注:源代码后期开放!

上传的附件 MyDebug.rar
MyDebug-Release版.rar
MyDebug-sl.rar

  • 标 题:调试器的实现<控制台版> 第二章 软中断
  • 作 者:Tariq
  • 时 间:2010-04-29 23:35:35

第二章  软中断(CC)的实现

1.相关函数与结构
  //软中断处理部分
public:
  //设置INT3断点
  void Int3BreakPoint(DWORD BreakAddress);
  //设置INT3断点并保存首字节
  BYTE SetBreakPoint(HANDLE hProcess,LPVOID pAdd, BYTE code, BOOL bFlag = TRUE);
  //查找列表中是否已存在该地址断点
  BOOL FindInt3BPoint(DWORD BreakAddress, char *Code = NULL);
        //由硬件断点引起的实效INT3断点重设
  BOOL SetInt3BPointAgain(DWORD BreakAddress);
  //删除找到的INT3断点
  BOOL DelFindInt3BPoint(DWORD BreakAddress);
//INT3结构
struct BreakPoint 
{
  BOOL  DelFlag;
  BYTE  b_code;
  DWORD BreakAddress;  
};

//保存链表
CList<BreakPoint,BreakPoint> m_myBreakList;

2.设计思路
将下断点地址首字节改CC,并保存原字节机器码,并写入链表。触发断点后,先将首字节CC改为原字节,EIP减1。 当程序运行到断

点代码时,进入断点访问处理函数DisposeBreakException(); 在断点函数中,显示并恢复原来的第一个首字节,并置单步,其作用

是在单步异常函数中把当前地址重新设置为此断点,即此断点永久生效,并非一次性断点。


3.删除INT3断点的处理
   直接操作INT3结构体中的DelFlag  作删除标记。 代码如下
  
//多个断点下同一地址,去除INT3断点,执行硬件或内存
BOOL CMyDebugInfor::DelFindInt3BPoint(DWORD BreakAddress)
{
  POSITION pos = m_myBreakList.GetHeadPosition();
  BreakPoint BpNode;
  
  for (int i=0;i<m_myBreakList.GetCount();i++)
  {
    BpNode = m_myBreakList.GetNext(pos);
    if (BpNode.BreakAddress == BreakAddress)
    {
      //清除INT3断点
      SetBreakPoint(m_hProcess,(LPVOID)BpNode.BreakAddress,BpNode.b_code);
      //删除标记
      BpNode.DelFlag = FALSE;
      pos = m_myBreakList.FindIndex(i);
      m_myBreakList.SetAt(pos, BpNode);
      m_EpnFlag.bSetInt3Flag = FALSE;
      //m_myBreakList.RemoveAt(pos);
      return TRUE;      
    }
  }
  return FALSE;
}


4. 多种断点的特殊处理
对INT3断点和硬件执行断点在同一地址的特殊处理:

   1.当下硬件断点时,检测是否下硬件的地址是否存在于INT3断点列表,如果存在,则迫使INT3断点暂时失效,当且仅当用户删除

硬件执行断点时,INT3断点才自动生效,具体示例请见测试分析报告。

  对INT3断点和硬件访问,写入。内存访问,写入断点在同一地址的特殊处理:

2. 当下硬件访问,写入,内存访问,写入断点时,检测是否存在于INT3断点列表,存在则迫使其永久失效。<无太大作用,因为下硬

件访问,写入,内存访问,写入断点时地址一般情况下是数据段,而INT3在代码段>


   3. 对于INT3断点和内存访问,写入,硬件访问,写入断点同时存在于当前EIP时,程序执行时会断2次或多次,在此情况下和OD有

区别<OD还是只断一次>,不过会提示当前的被断下的是哪种断点。

对硬件访问,写入和内存访问,写入断点在同一地址的特殊处理:
 
4.  在此种情况下和第3种的处理一样,区别于OD的处理,程序执行时会断
2次或多次,但是程序会提示当前被断下的是那种断点。

5.  当INT3,硬件,内存所有断点下与同一个地址EIP,INT3失效,其他断点存在。


                                                              科锐五期学员    易新

  • 标 题:答复
  • 作 者:Tariq
  • 时 间:2010-05-01 21:27:41

第三章 硬件断点实现
1.相关函数,相关结构,与主要成员
 
//硬件断点处理部分
public:
    //硬件断点设置
   void SetHardWarePoint(DWORD SetAddress, int DR, int PointType, int nLen = 1);
    //硬件DR0
    void SetDR0HardWarePoint(int PointType, int nLen);
    //硬件DR1
    void SetDR1HardWarePoint(int PointType, int nLen);
    //硬件DR2
    void SetDR2HardWarePoint(int PointType, int nLen);
    //硬件DR3
    void SetDR3HardWarePoint(int PointType, int nLen);
    //硬件写
    void HardWareWrite(DWORD WriteAddress, int nLen);
    //硬件读或写
    void HardWareReadWrite(DWORD ReadAddress,int nLen);
    //硬件执行
    void HardWareExecute(DWORD ExecuteAddress);
    //硬件断点处理事件
    void DebughardWareProc();
    //释放设置的硬件标志
    void DelHWBPointFlag(int nChoise);
 
//硬件断点处理标记
struct HareWareFlag
{    
      BOOL DR0; //当前寄存器使用状态
    BOOL DR1;
      BOOL DR2;
      BOOL DR3;    
      HareWareFlag()
     {
         DR0 = FALSE;
         DR1 = FALSE;
         DR2 = FALSE;
         DR3 = FALSE;    
     }
};
 
2.设计思路
   硬件调试寄存器DR0-DR3, 将用户下断地址赋给空闲的调试寄存器。并设置好DR7中位的数值。当命中硬件执行断点,在单步中,先取消掉这个硬件执行断点,设置单步,然后执行程序,进入单步后再重新设置这个硬件执行断点。 
当设置硬件断点时,通过对DR7进行相应取值即可设置成功。
 
3.删除硬件断点
  在删除硬件断点时,同样通过对DR7进行赋值可达到删除断点的效果。
   DR7赋值:
   DR0 &= 0xfffffffe; DR1 &= 0xfffffffb 
    DR2 &= 0xffffffef; DR3 &= 0xffffffbf
  
4.图片资料部分详解: <DR6和DR7寄存器>

LE和GE:
   P6 family和之后的IA32处理器都不支持这两位。当设置时,使得处理器会检测触发数据断点的精确的指令。当其中一个被设置的时候,处理器会放慢执行速度,这样当命令执行的时候可以通知这些数据断点。建议在设置数据断点是需要设置其中一个。切换任务时LE会被清除而GE不会被清除。为了兼容性,Intel建议使用精确断点时把LE和GE都设置为1。
 
LEN0到LEN3
    指定在调试地址寄存器DR0到DR3中指定的地址位置的大小。如果R/Wx位为0,则LENx位也必须为0,否则会产生不确定的行为。
 
可能取值:
    00 1字节
    01 2字节
    10 保留
    11 4字节
R/W0到R/W3
    指定各个断点的触发条件。它们对应于DR0到DR3中的地址以及DR6中的4个断点条件标志。 
    00 只执行
    01 写入数据断点
    10 I/O端口断点(只用于pentium+,需设置CR4的DE位,DE是CR4的第3位 )
    11 读或写数据断点
调试状态寄存器Dr6
    该寄存器用于表示进入陷阱1的原因,各个位的含义如下:
   B0~B3,如果其中任何一个位置位,则表示是相应的Dr0~3断点引发的调试陷阱
 
注: 硬件断点为永久断点!
 
                                                                     科锐五期学员 Tariq

上传的附件 hd.JPG

  • 标 题:答复
  • 作 者:Tariq
  • 时 间:2010-05-03 21:35:01

第四章 内存断点

1. 函数和结构
//内存断点处理部分
   public:
      //设置内存读写断点
   void SetMemReadWriteBPoint(DWORD StartAddress,DWORD EndAddress);
    //查找指定的当前内存页
   BOOL FindMemPage(DWORD PageAddress, DWORD EndAddress);
    //删除指定内存断点数据               <仅仅用于显示内存链表信息个数>
    void DelMemBreakPoint(DWORD StartAddress, DWORD EndAddress, int MemFlag);
    //删除已下的内存断点
   BOOL DelMemBPoint(DWORD StartAddress, DWORD EndAddress, int MemFlag);

//内存断点处理标记
struct MemBPointFlag
{
   BOOL bMemRead;
   BOOL bMemWrite;
   MemBPointFlag()
    {
        bMemRead = FALSE;
        bMemWrite = FALSE;
   }

};

#define READ  0
#define WRITE 1

struct MemBPointInfor 
{
     BOOL bDelFlag;        // 删除标记  
    BOOL bRWFlag;      // READ   or  WRITE
     DWORD beginAddress;   //断点开始地址
    DWORD endAddress;     //断点终止地址
};

//添加用户断点表 <当前页>
struct MemPageBreakPoint
{  
     DWORD  BeginPageAddress;      //分页开始地址
   DWORD  EndPageAddress;              //设置内存断点当前分页的最后终止地址
   DWORD  OldPageProtect;        //原来页的保护属性  
   CList<MemBPointInfor,MemBPointInfor> *RWMemBPList;  //保存当前分页中所有读地址断点 <段+偏移 (保存偏移)>   
};

3. 设计思路
   内存断点和内存分页之间存在多对多的关系。用户下内存断点时,先决定出断点所跨分页,再每个分页中再组建一个读写断点链表,这个链表用来保存多个内存读断点和多个内存写断点。

设置内存断点部分:
   SetMemReadWriteBPoint()中进行读或写内存断点的设置,在工程代码中分为三种情况写入到链表保存。

第一种:当下断点的地址的首地址和结束地址相同
 
   可根据下断点的属性<访问或写>设置对应的值 PAGE_NOACCESS, PAGE_EXECUTE_READ,并保存原来的属性,保存在结构体MemBPointInfor中,在写入内存链表中。

第二种:当下断点地址跨分页时。
 
   跨分页情况下,结束地址减去首地址得出跨的分页数,通过一个循环设置内存属性。断点开始地址,结束地址的分页分别保存开始地址断点,结束地址断点,跨分页的过程中保存分页的首地址。
例如:bpmr 405520-408520 跨4个分页,链表中对应产生4个分页结点405000分页中保存断点405520,
      406000分页保存406000,
     407000分页保存407000,
   408000分页保存408520
    
得出当前分页: (下断的地址/0x1000) * 0x1000

第三种,下断地址已存在但断点的位置不同,即通过查找到对应分页加入断点列表,同时设置相对应属性。
 
  因为当前分页的旧属性已存在,所以在此分页中多次下断点时不在保存分页属性,dwProtect的值即可舍弃。

触发访问异常部分: 当程序执行到访问异常时,触发访问异常处理
在DisposeAccessException(EXCEPTION_RECORD RecordTmp)函数中
RecordTmp.ExceptionInformation[0]是否为空可以判断触发的当前动作时访问数据还是写入数据。RecordTmp.ExceptionInformation[1]的具体地址值与内存链表中的地址进行匹配来判断是否处理访问异常,如果匹配则保存下断地址的《访问或写》属性,然后设置单步进入单步异常重新设置内存页的属性。   
单步处理中恢复内存页属性的代码段:
 
 if(m_MemStepFlag == TRUE)
  {
       DWORD dwProtect = 0;
       if (m_RWMemFlag == READ)
      ::VirtualProtectEx(m_hProcess,(void *)m_StartMemAddr,1,PAGE_NOACCESS,&dwProtect);
    
       if (m_RWMemFlag == WRITE)
      ::VirtualProtectEx(m_hProcess,(void *)m_StartMemAddr,1,PAGE_EXECUTE_READ,&dwProtect);
    
        //代码跟踪,保存当前程序运行时的EIP值
     m_CurCode.pCurCodeEntryAddress = m_Context.Eip;
       m_MemStepFlag = FALSE;
}


4.查询内存断点设计
//保存内存链表用于显示   <仅仅用于显示内存链表信息个数>
CList<MemBPointInfor, MemBPointInfor> m_ShowMemList;
  考虑到内存断点跨分页的情况,所以依靠保存下断地址的分页查找很麻烦,所以在设计上多增加了一个链表m_ShowMemList保存所有内存断点的链表,主要方便与在查找内存断点和删除内存断点。
   

5.删除内存断点设计         <删除标记位>
先在m_ShowMemList链表中查到内存断点,将删除标记位清0,通过DelMemBreakPoint在m_MemBPList内存链表将对应的内存断点的删除标记位清0。
  
注意: 当删除分页中最后一个断点时,必须恢复原来分页的属性。
        内容中涉及到的具体代码请见后期发布源代码和相关详细文档!

  • 标 题:答复
  • 作 者:Tariq
  • 时 间:2010-05-05 20:32:03

第五章  函数名称解析
1.相关函数  相关结构,与主要成员
      
//导出表结构
struct ExportFunInfor                        
{
    char  szFunName[64];                    //函数名称
    int   nExportNumber;                    //导出编号
    DWORD dwFunAddress;                     //函数所在地址
};

struct ExportTableInfor                                 //导出函数表
{
    char szDllTableName[32];                             //表名<DLL>
    CList<ExportFunInfor,ExportFunInfor> *pEptTableFun;  //数据    
};

//保存解析DLL的所有函数    
CList<ExportTableInfor,ExportTableInfor> m_ExportTableList;
   
 
2.设计思路
   进入StartDebugDll时,传来一个参数,此参数保存了当前加载DLL的信息,可以通过此信息解析出导出表在文件中的位置,然后可以定位到导出函数的位置,把函数用类成员m_ExportTableList保存。

3.关键代码部分
   ExportFunInfor pEptFunInfor;
    ZeroMemory(&pEptFunInfor,sizeof(ExportFunInfor));

   for ( i = 0; i <= (int)m_DllExportTable.NumberOfFunctions; i++ )
   {
       ReadProcessMemory(hProcess,(LPCVOID)nStartFunAddr,
                            &nAllFunOffset,sizeof(int),&dwOldProto);
       if ( 0 == nAllFunOffset )
       {
  //循环读取链表
  nStartFunAddr += sizeof(DWORD);
  continue;
       }

       for( j = 0; j <= (int)m_DllExportTable.NumberOfNames; j++)
      {        

  ReadProcessMemory(hProcess,(LPVOID)(nStartFunNameOrd + j*2),
                     &nFunNameAddr,sizeof(WORD),&dwOldProto);

  if(i == (int)nFunNameAddr)
  {           
                    memset(szFunName,0,MAX_PATH);

      DWORD nAddress = j*sizeof(DWORD) + nStartFunName;  
          
      ReadProcessMemory(hProcess,(LPVOID)(nAddress),
                            CurAddress,sizeof(DWORD),&dwOldProto);

      nCurAddress += (DWORD)StartAddress;
                   ReadProcessMemory(hProcess,(LPVOID)(nCurAddress),               szFunName,MAX_PATH,&dwOldProto);
     break;
  }
   }
  

4.显示解析函数部分
  主要对CALL, MOV, JMP, 6个寄存器<eax,ebx,ecx,edx,esi,edi>的函数名称显示进行解析。
处理函数:void CMyDebugInfor::ShowExportFun(char *szBuf)
注意:当CALL/JMP一个指令地址时,又JMP/CALL另一个地址或更深层次的嵌套解析情况下,采用递归来处理这种特殊情况。

  • 标 题:答复
  • 作 者:Tariq
  • 时 间:2010-05-10 23:09:47

最后一次上传调试器源代码,相关文档,  请各位大侠多多指教!
                               QQ:88123414
                                                         

上传的附件 调试器源代码.rar