简易调试器的实现(二)

先说一下上次对于软件断点CC还原的位置,int 3断点,属于陷阱类异常,恢复的地方应该是发生异常指令的下一条指令,但是我们在收到信息的时候FirstChance的时候是下一条,在第二次的时候确是断点发生的地方。

最近看了下<软件调试>得到了解释

首先写个小程序

int _tmain(int argc,_TCHAR* argv[])
{
  __asm int 3;
  printf("Hello INT 3");
  return 0l
}

当调试的时候,进入反汇编窗口我们看到发生异常的地址为0x013813CE

在查看寄存器窗口,发现EIP为也是0x013813CE

在软件调试中得到的答案是

硬件断点的实现

·DRx调试寄存器总共有8个,从DRx0到DRx7。每个寄存器的特性如下:
·DR0~DR3:调试地址寄存器,保存需要监视的地址,如设置硬件断点;
·DR4~DR5:保留,未公开具体作用;
·DR6:调试寄存器组状态寄存器;
·DR7:调试寄存器组控制寄存器。

DR6控制哪个寄存器被命中

DR7控制着哪个DRx设置的断点,局部或者全局,读写/执行/写断点类型,断点长度1/2/4/8,的信息

typedef struct _DBG_REG6
{
    /*
    //     断点命中标志位,如果位于DR0~3的某个断点被命中,则进行异常处理前,对应
    // 的B0~3就会被置为1。
    */
    unsigned B0 : 1;  // Dr0断点触发置位
    unsigned B1 : 1;  // Dr1断点触发置位
    unsigned B2 : 1;  // Dr2断点触发置位
    unsigned B3 : 1;  // Dr3断点触发置位
    /*
    // 保留字段
    */
    unsigned Reserve1 : 9;
    /*
    // 其它状态字段
    */
    unsigned BD : 1;  // 调制寄存器本身触发断点后,此位被置为1
    unsigned BS : 1;  // 单步异常被触发,需要与寄存器EFLAGS的TF联合使用
    unsigned BT : 1;  // 此位与TSS的T标志联合使用,用于接收CPU任务切换异常
    /*
    // 保留字段
    */
    unsigned Reserve2 : 16;
}DBG_REG6,*PDBG_REG6;

typedef struct _DBG_REG7
{
    /*
    // 局部断点(L0~3)与全局断点(G0~3)的标记位
    */
    unsigned L0 : 1;  // 对Dr0保存的地址启用 局部断点
    unsigned G0 : 1;  // 对Dr0保存的地址启用 全局断点
    unsigned L1 : 1;  // 对Dr1保存的地址启用 局部断点
    unsigned G1 : 1;  // 对Dr1保存的地址启用 全局断点
    unsigned L2 : 1;  // 对Dr2保存的地址启用 局部断点
    unsigned G2 : 1;  // 对Dr2保存的地址启用 全局断点
    unsigned L3 : 1;  // 对Dr3保存的地址启用 局部断点
    unsigned G3 : 1;  // 对Dr3保存的地址启用 全局断点
    /*
    // 【以弃用】用于降低CPU频率,以方便准确检测断点异常
    */
    unsigned LE : 1;
    unsigned GE : 1;
    /*
    // 保留字段
    */
    unsigned Reserve1 : 3;
    /*
    // 保护调试寄存器标志位,如果此位为1,则有指令修改条是寄存器时会触发异常
    */
    unsigned GD : 1;
    /*
    // 保留字段
    */
    unsigned Reserve2 : 2;
    /*
    // 保存Dr0~Dr3地址所指向位置的断点类型(RW0~3)与断点长度(LEN0~3),状态描述如下:
    */
    unsigned RW0 : 2;  // 设定Dr0指向地址的断点类型
    unsigned LEN0 : 2;  // 设定Dr0指向地址的断点长度
    unsigned RW1 : 2;  // 设定Dr1指向地址的断点类型
    unsigned LEN1 : 2;  // 设定Dr1指向地址的断点长度
    unsigned RW2 : 2;  // 设定Dr2指向地址的断点类型
    unsigned LEN2 : 2;  // 设定Dr2指向地址的断点长度
    unsigned RW3 : 2;  // 设定Dr3指向地址的断点类型
    unsigned LEN3 : 2;  // 设定Dr3指向地址的断点长度
}DBG_REG7,*PDBG_REG7;

知道了Dr7的各个位的意义,我们就能设置硬件断点了。

首先通过GetThreadContext获得Dr7的值,在通过SetThreadContext来设置Dr7的值。

代码如下

HANDLE SetHardwareBreakpoint(HANDLE hThread,HWBRK_TYPE Type,HWBRK_SIZE Size,void* s)
{    

    if(m_vecHard.size( ) > 3)
        return FALSE;

    PointInfo bkpt;
    bkpt.lpPointAddr = (DWORD)s; //记录断点地址
    bkpt.ptType = DR_POINT; //记录断点类型

    int j = 0;
    int y = 0;
    CONTEXT ct = {0};
    int iReg = 0;

    j =SuspendThread(g_hThread);   //这里我遇到一个坎一直过不去
     //这里总是无效句柄,
    y = GetLastError();

    ct.ContextFlags = CONTEXT_DEBUG_REGISTERS|CONTEXT_FULL;
    if(!GetThreadContext(hThread,&ct))
    {
        y = GetLastError();
        MessageBox(NULL,L"Fail",L"1",1);
    }
    int FlagBit = 0;

    bool Dr0Busy = false;
    bool Dr1Busy = false;
    bool Dr2Busy = false;
    bool Dr3Busy = false;
    if (ct.Dr7 & 1) //0位  0 local
        Dr0Busy = true;
    if (ct.Dr7 & 4) //2位  1 local
        Dr1Busy = true;
    if (ct.Dr7 & 16)//4位  2 local
        Dr2Busy = true;
    if (ct.Dr7 & 64)//6位  3 local
        Dr3Busy = true;

        if (!Dr0Busy)
        {  bkpt.Number = 0;
            iReg = 0;
            ct.Dr0 = (DWORD_PTR)s;  //地址
            Dr0Busy = true;
        }
        else
            if (!Dr1Busy)
            {
                 bkpt.Number = 1;
                iReg = 1;
                ct.Dr1 = (DWORD_PTR)s;
                Dr1Busy = true;
            }
            else
                if (!Dr2Busy)
                { bkpt.Number= 2;
                    iReg = 2;
                    ct.Dr2 = (DWORD_PTR)s;
                    Dr2Busy = true;
                }
                else
                    if (!Dr3Busy)
                    {
                        bkpt.Number = 3;
                        iReg = 3;
                        ct.Dr3 = (DWORD_PTR)s;
                        Dr3Busy = true;
                    }
                    else
                    {
                        //h->SUCC = false;
                        j = ResumeThread(hThread);
                        y = GetLastError();
                        return 0;
                    }
    ct.Dr6 = 0;
    int st = 0;
    if (Type == HWBRK_TYPE_CODE)
               st = 0;
    if (Type == HWBRK_TYPE_READWRITE)
        st = 3;
    if (Type == HWBRK_TYPE_WRITE)
        st = 1;
    int le = 0;
    if (Size == HWBRK_SIZE_1)
        le = 0;
    if (Size == HWBRK_SIZE_2)
        le = 1;
    if (Size == HWBRK_SIZE_4)
        le = 3;
    if (Size == HWBRK_SIZE_8)
        le = 2;

    SetBits(ct.Dr7, 16 + iReg*4, 2, st);
    SetBits(ct.Dr7, 18 + iReg*4, 2, le);
    SetBits(ct.Dr7, iReg*2,1,1);

    ct.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    if(!SetThreadContext(hThread,&ct))
    {
        y = GetLastError();
        MessageBox(NULL,L"Fail",L"1",1);
    }

    ct.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    if(!GetThreadContext(hThread,&ct))
    {
        y = GetLastError();
        MessageBox(NULL,L"Fail",L"1",1);
    }

    j = ResumeThread(hThread);

    y = GetLastError();

    m_vecHard.push_back(bkpt);
    return 0;
}

void SetBits(DWORD_PTR& dw, int lowBit, int bits, int newValue)
{
    DWORD_PTR mask = (1 << bits) - 1;
    dw = (dw & ~(mask << lowBit)) | (newValue << lowBit);
}

在GetThreadContext之前一定要挂起线程,SuspendThread,不然可能context在获得的时候线程的堆栈正发生变化,那么结构也不能准确,但是我在SuspendThread的时候一直getlasterror返回6,无效的句柄,不知道为什么,调试的时候硬件断点都断的下来,不调试直接运行,就断不下来,感觉就是SuspendThread的问题,记录一下,下次一起解决了。

时间: 2024-11-14 12:38:15

简易调试器的实现(二)的相关文章

简易调试器的实现(一)

看过SEH结构化异常处理,看了<软件调试>这本书,觉得调试真是一件特别棒的事情,于是在网上搜索调试器怎么做,跟着大牛的脚步慢慢的往前走,于是借用大牛提供的代码,自己也开始慢慢做一个调试器 前期基本按照这个大牛的思路 http://www.cnblogs.com/zplutor/archive/2011/03/04/1971279.html 前期的分析也照着来点0.0 这一部分实现的功能是显示寄存器状态.显示字节码(感觉有一些问题,以后再修正),处理异常,断点处理(本来想在OEP断下来的,结果不

简单调试器的实现(二)使用反汇编引擎&amp;建立第一个程序

让程序停下来: 动态调试器的一个重要特点就是:让程序停下来,这样我们才可以观测到程序的即时情况. 不过现在我们并不需要研究怎么下断点,系统已经帮我们激活了第一个断点.在创建调试进程时,系统会帮我们在ntdll.dll中设置一个INT3断点,我们就让程序在这里断下来. switch (DebugEvent->u.Exception.ExceptionRecord.ExceptionCode)//Int3断点的事件 { case EXCEPTION_BREAKPOINT: // ((DWORD )0

[Win32]一个调试器的实现(二)调试事件的处理

[Win32]一个调试器的实现(二)调试事件的处理 作者:Zplutor 出处:http://www.cnblogs.com/zplutor/ 本文版权归作者和博客园共有,欢迎转载.但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 上一篇文章说到了调试循环的写法,这回讲一下调试器应该如何处理各种调试事件. RIP_EVENT 关于这种调试事件的文档资料非常少,即使提到也只是用“系统错误”或者“内部错误”一笔带过.既然如此,我们也不需要对其进行什么处理

C语言基于GTK+Libvlc实现的简易视频播放器(二)

简易视频播放器-全屏播放 一.课程说明上一次我们使用gtk+libvlc实现了一个最简单的视频播放器,可以实现点击按钮暂定和停止播放视频,以及同步显 示视频播放进度,但即使作为一个视频播放器,只有这些功能也还是不够的,至少我们还应该有全屏播放的功能吧,所以这一次我们就来为上一次的视频播放器添加 上全屏播放功能.这个功能实现起来思路很简单,只是具体实现过程中有很多坑罢了,需要我们注意很多细节问题,还要解决一些bug等等.这次我们的代码出了 增加功能之外,也还会对上一次的基础代码做一些修改. 二.功

[连载]《C#通讯(串口和网络)框架的设计与实现》- 11.调试器的设计

目       录 第十一章     调试器设计... 2 11.1         调试接口... 2 11.2         界面方式调试... 3 11.3         命令行方式调试... 5 11.4         小结... 6 第十一章      调试器设计 SuperIO 框架平台设计.开发完毕后,想把代码编译成程序集(DLL),二次开发都通过引用DLL实现接口.继承类库来实现驱动和插件的开发,SuperIO框架的代码不会轻易去改变.这是框架设计最终要达到的效果,但是在二

《python灰帽子》学习笔记:调试器设置

一.构造 C  数据类型 C Type | Python Type | ctypes Type _______________________________________________________________________________________ char | 1-character | string c_char wchar_t | 1-character Unicode | string c_wchar char | int/long | c_byte char |

学习笔记之--认识Xcode中的重要成员:lldb调试器

之前对lldb调试器了解比较少,平时主要用来打印日志和暂定时用鼠标查看属性数据以及使用p po一些简单的命令语句. 今天看了一些关于lldb的文章,顿时觉得之前对它了解太少了,原来它还有那么多的功能. 好记性不如烂笔头,我把方便易用的命令记录下来,方便以后查看. 一.ldb的语法结构 lldb的语法结构如下:<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]

《python灰帽子》学习笔记:写一个windos 调试器(一)

一.开发内容介绍 为了对一个进程进行调试,你首先必须用一些方法把调试器和进程连接起来.所以, 我们的调试器要不然就是装载一个可执行程序然后运行它, 要不然就是动态的附加到一个运行的进程.Windows 的调试接口(Windows debugging API)提供了一个非常简单的方法完成这两点. 运行一个程序和附加到一个程序有细微的差别. 打开一个程序的优点在于他能在程序运行任何代码之前完全的控制程序. 这在分析病毒或者恶意代码的时候非常有用. 附加到一个进程,仅仅是强行的进入一个已经运行了的进程

TSPLIB简介与简易解析器实现

背景知识 TSP即Travelling SalesmanProblem(旅行商人问题)的简称.是数学领域中的著名问题之一.有n个城市,一个旅行商人要从其中某一个城市出发,唯一走遍所有的城市,再回到他出发的城市,求最短的路线.这个问题对快递业等行业也非常具有现实意义,当然现实中的TSP一般是动态的,要更为复杂.TSP可以分为两类,一类是对称TSP(Symmetric TSP),另一类是非对称TSP(Asymmetric TSP).区别就在于都市a到b和都市b到a的cost是否相等,相等的就是对称T