调试事件的发送流程(2)

调试事件的发送流程
   浏览目录

    • 调试子系统服务器将消息发给调试器的过程
      • 调试子系统在内核函数用于描述和传递调试消息的结构
      • 调试子系统控制被调试进程详细过程
    • 调试子系统和调试器之间用于描述和传递调试消息的结构
    • 调试事件的产生和传递
      • 创建调试对象
      • 设置调试对象
      • 传递调试对象
      • 清除调试对象

-1 调试子系统服务器将消息发给调试器的过程
    - 1.1 调试子系统接收到异常事件消息(在采集的时候)
    - 1.2 调试子系统控制被调试进程(冻结除被调试进程的
          当前线程之外的全部线程), 阻塞被调试进程的调
          用线程进入等待状态
    - 1.3 通知调试器来读取调试信息.并等待调试器回复
    - 1.4 等到回复后,唤醒被调试进程的当前线程,恢复之前挂起
          的线程.
      
      
- 1.1.1 调试子系统在内核函数用于描述和传递调试消息的结构:
{

 1 typedef struct _DBGKM_APIMSG
 2 {
 3         PORT_MESSAGE h;                            // LPC端口消息结构,XP之前使用
 4         DBGKM_APINUMBER ApiNumber;                // 消息类型
 5         ULONG ReturnedStatus;                    // 调试器的回复状态
 6         union                                    // 具体描述消息详情的联合结构
 7         {
 8             DBGKM_EXCEPTION Exception;            // 异常
 9             DBGKM_CREATE_THREAD CreateThread;   // 创建线程
10             DBGKM_CREATE_PROCESS CreateProcess;    // 创建进程
11             DBGKM_EXIT_THREAD ExitThread;        // 线程退出
12             DBGKM_EXIT_PROCESS ExitProcess;        // 进程退出
13             DBGKM_LOAD_DLL LoadDll;                // 映射DLl
14             DBGKM_UNLOAD_DLL UnloadDll;            // 反映射Dll
15         };
16 } DBGKM_MSG, *PDBGKM_MSG;

PS:  DBGKM_APINUMBER ApiNumber;// 消息类型
        就是"调试子系统采集调试事件的方法和过程"提到的调试器能够
        采集到的调试事件(消息)

-    调试信息采集函数确认需要向调试子系统报告消息后(确认DebugPort),
         会填写DBGMK_APIMSG结构,然后将其作为参数传给 DbgkpSendApiMessage
         函数.
        
    -    DbgkpSendApiMessage 函数是用来将一个调试消息发送到调试子系统的.
        函数原型:

NTSTATUS DbgkpSendApiMessage(PDBGKM_APIMSG ApiMsg,
                                     PVOID Port,
                                     BOOLEAN SuspendProcess
                                    );

形参1 : 用来描述消息的详细信息
        形参2 : 用来指定要发往的端口,大多数时候,就是EPROCESS结构中的
                debugPort字段的值,偶尔是进程中的异常端口.即Exception
                字段
        形参3 : 如果该形参为真,那么该函数会先调用 DbgkpSuspendProcess
                挂起当前进程. 然后发送消息.等收到消息回复后再调用
                DbgkpResumeProcess 函数唤醒当前进程.
                发消息时, 如果系统的版本是NT或win 2000,由于这两个系统的
                调试子系统服务器位于用户态,因此在这些系统上可以使用
                DbgkpSendApiMessage 函数. DbgkpSendApiMessage函数会通过
                LPC机制来发送调试信息,这时,Port 参数指定的是一个LPC端口.
                这个端口的监听者通常是windows环境子系统的服务进程. 即:
                CSRSS, CSRSS 收到消息后会再转发给位于会话管理进程中的调试
                子系统服务器(CSRSS 相当于转发). 调试子系统再通知等候调试事件的
                调试器.
                流程如下:
                    
                DbgkpSendApiMessage
                        ||
                        \/
                DbgkpSuspendProcess() 挂起当前进程
                        ||
                        \/                 
                DbgkpSendApiMessage()
                 |--调用 LpcRequestWaitReplyPort 函数完成
                 |    具体的LPC收发任务,该函数是阻塞的,只有
                 |    收到回复,该函数才会返回.
                 |
                 |                               LPC机制
                 |--> LpcRequestWaitReplyPort ---------------> CRSS
                                               Port指定端口
        -----------------------------------------------
        
                CSRSS  -->> 调试子系统 -->> 调试器
        PS :
            DbgkpSendApiMessage 函数只能在NT和win 2000 版本系统中会被调
            用
        
    -    DbgkpQueueMessage 函数    
        如果系统的版本是XP或者是以上版本的系统, 那么将不会再继续使用函数
        DbgkpSendApiMessage, 而是改为 DbgkpQueueMessage 函数.
        函数原型:

  NTSTATUS DbgkpQueueMessage(IN PEPROCESS Process,
                                   IN PETHREAD Thread,
                                   IN_OUT PDBGKM_APIMSG ApiMsg,
                                   IN ULONG Flags,
                                   IN PDEBUG_OBJECT TargetDebugObject
                                  );

}
        
- {1.2,1.3,1.4} 调试子系统控制被调试进程详细过程
{
    在调试子系统向调试器发送调试事件之前, 通常会先调用
    DbgkpSuspendProcess()函数, 这个函数内部会调用 KeFreezeAllThreads()
    冻结被调试进程中 除 调用线程之外 的所有线程. 接下来才执行实际的消息
    发送函数, 也就是 DbgkpQueueMessage().
    流程如下:
        DbgkpSuspendProcess()
         |
         |     冻结被调试进程中所有线程(除被调试进程的当前线程)
         |--> KeFreezeAllThreads()
         |
         |        发送消息到调试器
         |---> DbgkpQueueMessage()
         |             ||
         |             \/
         |-- 阻塞等待调试器回复
         |             ||
         |             \/
         |-- 唤醒被调试进程的等待线程,
         |             ||
         |             \/
         |    恢复之前挂起的线程.
         |-- DbgkpResumeProcess()
                |
                |     恢复被调试进程中的所有线程
                |--> KeThawAllThreads()
        
}

-2 调试子系统和调试器之间用于描述和传递调试消息的结构:

typedef struct _DEBUG_OBJECT
    {
        KEVENT        EventsPresent;    // 用于指示有调试事件发生的事件对象
        FAST_MUTEX    Mutex;            // 用于同步的互斥对象
        LIST_ENTRY    StateEventListEntry; // 保存调试事件的链表
        ULONG        Flags;            // 标志
    }DEBUG_OBJECT,*P_DEBUG_OBJECT;
      

-2.1 StateEventListEntry:
            - 用来存储调试事件的链表
    -2.2 EventPresent
            - 用来同步调试器进程和被调试进程,调试子系统服务器通过设置此事件来
              通知调试器读取消息队列中的调试信息.
              调试器进程通过 WaitFOrDebugEvent()函数来等待调试事件,这个函数对
              应的 NtWaitFOrDebugEvent内核服务内部实际上等待的就是这个事件对象
    -2.3 Mutex
            - 用来锁定对这个数据结构的访问, 以防止多个线程同时读写造成数据错误
    -2.4 Flags
            - 该字段包含多个标志位, 比如 1 代表结束调试会话时是否终止被调试进
              程,DebugSetProcessKillOnExit() 设置的就是这个标志位
    
-3 调试事件的产生和传递
    -3.1 创建调试对象
            - 当调试器与调试子系统建立连接时,调试子系统会调用内核API
              NtCreateDebugObject()创建一个调试对象.
            - 并将这个内核对象保存在调试器当前线程的线程环境块的
              DbgSsReserved[1]字段.
            - 一个线程的线程环境块的DbgSsReserved[1]字段保存的调试对象是这个调
              试线程区别于其他普通线程的重要标志.
              
    -3.2 设置调试对象
        - 当调试器建立应用程序调试会话时, 会有两种情况:
            - 被调试进程是在调试器中打开的
                - 系统在创建被调试的进程时, 会把调试器线程TEB结构的
                  DbgSsReserved[1]字段中保存的调试对象句柄传递给创建进程的内核
                  服务.内核中的进程创建函数会将这个句柄所对应的调试对象指针赋
                  给新创建进程的 EPROCESS结构中的DebugPort字段.
            - 调试器进程附加到被调试进程
                - 系统会调用内核中的 DbgkpSetProcessDebugObject() 函数来将一个
                  创建好的调试对象附加到其参数所指定的进程中(被调试进程)
                  DbgkpSetProcessDebugObject() 函数内部除了将调试对象赋给
                  EPROCESS结构的DebugPort字段外, 还会调用
                  DbgkpMarkProcessPeb() 函数设置进程环境块的 BeingDebugged字段
                  
    -3.3 传递调试对象
            - DbgkpQueueMessage() 函数用于向一个调试对象的消息队列追加调试事件.
            
            - 指定 DbgkpQueueMessage()函数的调试对象的方法有两个:
                -    直接在参数中指定调试对象
                -    指定 EPROCESS 结构, DbgkpQueueMessage 函数会使用这个结
                    构中的 DebugPort 字段代替调试对象
            - 调试对象的消息队列的每一个节点的结构: DEBUG_EVENT, 这个结构与调
              试API的 DEBUG_EVENT 同名,但是内容不相同,为了避免混淆, 这里将内核
              中的 DEBUG_EVENT 结构称为 DBGKM_DEBUG_EVENT,其结构定义如下:

 typedef struct _DBGKM_DEBUG_EVENT
                {
                    LIST_ENTRY EventList;      // 与兄弟节点相互链接的节点结构
                    KEVENT        ContinueEvent;// 用于等待调试器回复的事件对象
                    CLIENT_ID  ClientId;      // 调试事件所在线程的线程ID和进程ID
                    PEPROCESS  Process;        // 被调试进程的EPROCESS结构地址
                    PETHREAD   Thread;         // 被调试进程中触发调试事件的线程
                                            // ETHREAD地址
                    NTSTATUS   Statuc;        // 对调试事件的处理结果
                    ULONG       Flags;        // 标志
                    PETHREAD   BackoutThread;//产生假信息的线程
                    DBGKM_MSG  ApiMsg;        // 调试事件的详细信息
                }DBGKM_DEBUG_EVENT,*P_DBGKM_DEBUG_EVENT

CLIENT_ID是一个包含两个DWORD字段的结构体,这两个DWORD字段分别表
                示:
                    -    进程ID
                    -    线程ID
                    
              在把 DBGKM_DEBUG_EVENT 结构赋值之后, DbgkpQueueMessage() 函数会
              把它插入到调试子系统的调试对象(DEBUG_OBJECT)中的消息链表中
              (StateEventListEntry).
              之后 DbgkpQueueMessage() 函数会根据参数Flag是否有NOWAIT标记,来选
              择是否通知调试器来读取调试消息.
              当Flag设置了NOWAIT标记,函数会返回.
              如果没有设置,  函数会设置形参TargetDebugObject(调试对象)的
              EventPresent字段(KEVENT),通知调试器来读取调试信息().
              然后调试器会将 ContinueEvent(插入到调试对象链表的调试对象结构体
              中的)传入 KeWaitForSingleObject()函数,等待调试器的回复.
              
              调试器方面:
              调试器中的一个线程使用了函数WaitforDebugEvent()函数,这个函数最终
              会转到内核API: NtWaitForDebugEvent()。
              当调试子系统设置了EventPresent字段(KEVENT), NtWaitForDebugEvent()
              函数就会被唤醒, 然后就去读取一个调试事件(使用CLIENT_ID遍历匹配调
              试事件链表的调试对象),读取到调试事件之后,先是在这个事件
              DBGKM_DEBUG_EVENT结构的Flags字段中设置一个已读标志, 再调用函数
              DbgkpConvertKernelToUserStateChange()将DBGKM_DEBUG_EVENT结构转换
              成用户态使用的DBGUI_WAIT_STATE_CHANGE结构.
              最后会通过 ContinueDebugEvent() 函数间接或直接调用
              nt!NtDebugContinue 内核API. 而 NtDebugContinue()会根据参数中指定
              的 CLIENT_ID结构找到要恢复的调试事件结构(可能是遍历调试事件链表),
              找到之后, 设置它的 ContinueEvent事件对象, 使处于等待的被调试器的
              等待线程唤醒而继续执行.
              
                    
    -3.4 清除调试对象
        - 系统会调用 DbgkCLearProcessDebugObject()将被调试进程的DebugPort字段
          恢复为NULL
        - 遍历调试对象的消息队列(??),将关于这个进程的调试事件清除,但不破坏调试
          对象.

时间: 2024-10-21 17:19:49

调试事件的发送流程(2)的相关文章

[Win32]一个调试器的实现(一)调试事件与调试循环

[Win32]一个调试器的实现(一)调试事件与调试循环 作者:Zplutor 出处:http://www.cnblogs.com/zplutor/ 本文版权归作者和博客园共有,欢迎转载.但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 前言 程序员离不开调试器,它可以动态显示程序的执行过程,对于解决程序问题有极大的帮助.如果你和我一样对调试器的工作原理很感兴趣,那么这一系列文章很适合你,这些文章记录了我开发一个调试器雏形的过程,希望对你有帮助.或许我

调试事件的处理结束

Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); 在使用 WaitForDebugEvent 之后,会接收到调试事件,经过处理之后,我们调用ContinueDebugEvent 来表示该调试事件处理完毕,最后一个参数来表示处理结果. 当处理成功时(以DBG_CONTINUE作为最后一个

twemproxy发送流程探索——剖析twemproxy代码正编

本文想要完成对twemproxy发送流程--msg_send的探索,对于twemproxy发送流程的数据结构已经在<twemproxy接收流程探索--剖析twemproxy代码正编>介绍过了,msg_send和msg_recv的流程大致类似.请在阅读代码时,查看注释,英文注释是作者对它的代码的注解,中文注释是我自己的感悟. 函数msg_send 1 rstatus_t 2 msg_send(struct context *ctx, struct conn *conn) 3 { 4 rstatu

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

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

源码分析 Kafka 消息发送流程(文末附流程图)

温馨提示:本文基于 Kafka 2.2.1 版本.本文主要是以源码的手段一步一步探究消息发送流程,如果对源码不感兴趣,可以直接跳到文末查看消息发送流程图与消息发送本地缓存存储结构. 从上文 初识 Kafka Producer 生产者,可以通过 KafkaProducer 的 send 方法发送消息,send 方法的声明如下: Future<RecordMetadata> send(ProducerRecord<K, V> record) Future<RecordMetada

Nginx 事件基本处理流程分析

说明:本文章重点关注事件处理模型,顺便介绍相关的结构体,其他的结构体不做介绍.有兴趣的同学可以去http://tengine.taobao.org/book/查找更多资料.Tengine应该是淘宝基于Nginx自己做的修改.这个地址的文档还在不断的完善更新中. 程序流程图: 说明: 一.进程生成顺序 1.main(src/core/nginx.c)函数启动ngx_master_process_cycle,启动主服务进程. 2.ngx_master_process_cycle(src/os/uni

真机调试及上线简略流程

真机调试: 参考完整图文教程 -->生成证书公钥文件 -->系统应用程序-使用工具-钥匙串访问-->证书助理-->从证书颁发机构请求证书-->默认选项-->存到磁盘-->继续-->生成CertificateSigningRequest.certSigningRequest公钥文件 -->生成开发证书 -->开发者管理平台-->Certificates,Identifiers&Profiles选项-->iOS Apps选项--&

Android touch事件的派发流程

http://blog.csdn.net/xyz_lmn/article/details/12517911 通过流程图了解touch事件派发过程. http://blog.csdn.net/stonecao/article/details/6759189 从代码的层面分析,尽管目前代码已经变化了,但是作者的分析对Android touch事件派发流程的理解还是很有帮助的. http://www.2cto.com/kf/201504/388625.html 通过实例了解Button的touch事件

Android安全问题 抢先接收广播 - 内因篇之广播发送流程

导读:本文说明系统发送广播的部分流程,如何利用Intent查找到对应接收器.我们依然只关注接收器的排序问题 这篇文章主要是针对我前两篇文章 android安全问题(四) 抢先开机启动 - 结果篇 android安全问题(五) 抢先拦截短信 - 结果篇 现在给出第二步分的分析 下面就来看看发送广播的流程 Context中的sendBroadCast函数的实现是在ContextImpl中,和发送广播相关的有如下六个函数 void android.app.ContextImpl.sendBroadca