zoukankan      html  css  js  c++  java
  • 浅议APC

    0x01  APC中断请求级别

         在Intel x86体系结构中,外部硬件中断是通过处理器上的"中断管脚"或者一个称为"本地APIC(local APIC)"的内置模块来发生的。本地APIC可以接收的中断源包括:  

      

        1处理器管脚(LINT0和LINT1)

        2本地APIC定时器(timer)

        3性能监视计数器中断

        4热传感器中断

        5 APIC内部错误中断

       

      APIC这个硬件中断设备是有优先级的,它使用了一个可编程阵列硬件来实现,并且在系统初始化的时候就完成了,Intel x86定义了256个中断向量号(Interrupt Vector Number),也称为中断向量,从0~255。这就对应了IDT中断描述符表IDT的表项数目就是256,也就是说,APIC定义的是IDT中的中断例程的优先级

       对于一个处理器,它一旦被中断(可能来自内部,可能来自外部,可能是硬中断,可能是软中断),则某个预设的"中断服务例程"便被执行。而系统软件(操作系统)要做的事情就是,提供这些例程,并将它们设定到处理器的硬件中断向量表(即IDT)中。

      然而尽管APIC中断控制器已经提供了中断优先级支持,windows还是自己定义了一套优先级方案,称为"中断请求级别(IRPL Interrupt Request Level)"。在Intel x86系统中,windows使用了0~31来表示优先级,数值越大,优先级越高内核为软件中断定义了一组标准的IRQL,而HAL则将硬件中断号映射为IRQL。

      IRQL的宏定义:

    #define PASSIVE_LEVEL 0        //Passive release level(被动级别)
    #define LOW_LEVEL 0        //Lowest interrupt level
    #define APC_LEVEL 1        //APC interrupt level
    #define DISPATCH_LEVEL 2    //Dispatcher level
    #define PROFILE_LEVEL 27    //Timer used for profiling
    #define CLOCK1_LEVEL 28        //Interval clock 1 level
    #define CLOCK2_LEVEL 28        //Interval clock 2 level
    #define IPI_LEVEL 29        //Interprocessor interrupt level
    #define POWER_LEVEL 30        //Power failure level
    #define HIGH_LEVEL 31        //Highest interrupt level
    

      对于IRQL,PASSIVE_LEVEL(被动级别)代表了最低的IRQL,那既然你最低,运行在PASSIVE_LEVEL的线程可以被任何更高的IRQL(从LOW_LEVEL开始都可以)的事情打断,所有的的用户模式代码都运行在PASSIVE_LEVEL(被动模式)上。而APC_LEVEL(APC级别)比PASSIVE_LEVEL高,这也正是在一个线程中插入一个APC可以打断该线程(如果被插入的这个线程正在PASSIVE_LEVEL上运行)的原因(APC注入的原理)。

      

      

      APC_LEVEL位于PASSIVE_LEVEL和DISPATCH_LEVEL之间,这是"专门"为另一种称为APC(异步过程调用 Asynchronous Procedure Call)的"软件中断"而保留的IRQL。每个APC都是在特定的线程环境中执行的,从而也一定在特定的进程环境中执行。APC是针对线程的,每个线程都有自己特有的APC链表。同一个线程的APC也是被排队执行的(思考线程的IRP对清队列,我们的IO请求要被排队执行,APC作为IRP请求的一种也不例外)。

      由于APC的IRQL高于PASSIVE_LEVEL,所以,它优先于普通的"线程代码"。当一个线程获得控制时,它的APC进程会立刻被执行(即执行APC队列中的IRP请求)。这一特性使得APC非常适合于实现各种异步通知事件。例如,I/O完成通知可以用APC来实现。

      (详细内容见《windows内核原理与实现》第5章2.6节)

    0x02  APC结构

      APC是与线程相关的,在描述线程的结构KTHREAD中,可以看到很多带APC字眼的成员。

    win7   32:
    
    kd> dt _kthread
    dtx is unsupported for this scenario.  It only recognizes dtx [<type>] [<address>] with -a, -h, and -r.  Reverting to dt.
    ntdll!_KTHREAD
       +0x000 Header           : _DISPATCHER_HEADER
       +0x010 CycleTime        : Uint8B
       +0x018 HighCycleTime    : Uint4B
       +0x020 QuantumTarget    : Uint8B
       +0x028 InitialStack     : Ptr32 Void
       +0x02c StackLimit       : Ptr32 Void
       +0x030 KernelStack      : Ptr32 Void
       +0x034 ThreadLock       : Uint4B
       +0x038 WaitRegister     : _KWAIT_STATUS_REGISTER
       +0x039 Running          : UChar
       +0x03a Alerted          : [2] UChar
       +0x03c KernelStackResident : Pos 0, 1 Bit
       +0x03c ReadyTransition  : Pos 1, 1 Bit
       +0x03c ProcessReadyQueue : Pos 2, 1 Bit
       +0x03c WaitNext         : Pos 3, 1 Bit
       +0x03c SystemAffinityActive : Pos 4, 1 Bit
       +0x03c Alertable        : Pos 5, 1 Bit
     ……
       +0x040 ApcState         : _KAPC_STATE
    ……
       +0x168 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
       +0x170 SavedApcState    : _KAPC_STATE
        ……
    

      可以看到在win7的32位下,与kthread偏移为0x40和0x170处,有两个成员:ApcState和SavedApcState,这两个成员指向两个不同的_KAPC_STATE结构:

    typedef struct _KAPC_STATE {
            LIST_ENTRY ApcListHead[MaximumMode];       //线程的apc链表 只有两个 内核态和用户态
            struct _KPROCESS *Process;                 //当前线程的进程体   PsGetCurrentProcess()
            BOOLEAN KernelApcInProgress;              //内核APC正在执行
            BOOLEAN KernelApcPending;                 //内核APC正在等待执行
            BOOLEAN UserApcPending;                  //用户APC正在等待执行
    } KAPC_STATE, *PKAPC_STATE, *PRKAPC_STATE;
    

      在_KAPC_STATE结构中,第一个成员为ApcListHead[2]第一个成员指向一个用户模式APC链表,而第二个成员指向内核模式APC链表。如果注意观察,可以看出特殊内核模式APC都排在普通内核模式APC之前。

      (图片源自危险漫步:http://www.weixianmanbu.com/article/33.html)

      

      APC结构:

    //win7 32
    kd> dt _kapc
    dtx is unsupported for this scenario.  It only recognizes dtx [<type>] [<address>] with -a, -h, and -r.  Reverting to dt.
    ntdll!_KAPC
       +0x000 Type             : UChar
       +0x001 SpareByte0       : UChar
       +0x002 Size             : UChar
       +0x003 SpareByte1       : UChar
       +0x004 SpareLong0       : Uint4B
       +0x008 Thread           : Ptr64 _KTHREAD
       +0x010 ApcListEntry     : _LIST_ENTRY
       +0x020 KernelRoutine    : Ptr64     void 
       +0x028 RundownRoutine   : Ptr64     void 
       +0x030 NormalRoutine    : Ptr64     void 
       +0x038 NormalContext    : Ptr64 Void
       +0x040 SystemArgument1  : Ptr64 Void
       +0x048 SystemArgument2  : Ptr64 Void
       +0x050 ApcStateIndex    : Char
       +0x051 ApcMode          : Char
       +0x052 Inserted         : UChar
    

      

      在 KAPC 结 构 中 , Type 域 应 为 KOBJECTS 枚 举 类 型 的 ApcObJect; Size 域 等 于 KAPC结 构 的 大 小 ; Thread 域 指 向 此 APC 对 象 所 在 的 线 程 KTHREAD 对 象 ; ApcListEntry 域 是APC 对 象 被 加 人 到 线 程 APC 链 表 中 的 节 点 对 象 ; KernelRoutine 域 是 一 个函 数 指 针 , 该 函数 将 在 内 核 模 式 的 APC-LEVEL 上 被 执 行 ; RundownRoutine 域 也 是 一 个 函 数 指 针 , 当 一个 线 程 终 止 时 如 果 它 的 APC 链 表 中 还 有 APC 对 象 , 那 么 , 若 成 员 非 空 ,则 调 用 它 所 指 的 函 数 。 № rmalRoutine 域 指 向 一 个 在 PASSIVE-LEVEL 上 执 行 的 函 数 。 在

    三 个 函 数 指 针 成 员 中 , 只 有 KernelRoutine 是 必 需 的 , RundownRoutine 和 NormalRoutine都 是 可 选 的 。 而 且 , 如 果 NormalRoutine 为 空 的 话 , 则 其 后 的 Norma ℃ ontext 和 ApcMode域 也 将 被 忽 略 , 本 节 稍 后 会 解 释 这 三 个 域 的 关 系 。 SystemArgumentl 和 SystemArgument2是 两 个 提 供 给 KernelRoutine 或 NormalRoutine 函 数 的 参 数 。 ApcStateIndex 域 说 明 了 APC对 象 的 环 境 状 态 , 它 是 KAPC ENVIRONMENT 枚 举 类 型 的 成 员 , 一 旦 APC 对 象 被 插 人到 线 程 的 APC 链 表 中 , 则 ApcStatelndex 域 指 示 了 它 位 于 线 程 KTHREAD 对 象 的 哪 个 APC链 表 中 。 最 后 , 布 尔 类 型 Inserted 域 指 示 该 APC 对 象 是 否 已 被 插 人 到 线 程 的 APC 链 表 中。

      提炼几点关键:

      1.如果mode为UserMode,但是NormalRoutine为NULL,那么实际的模式变为Kernelmode,因为没有用户空间的APC例程可以使用;

      2.每种APC都有内核模式的例程;

      3.特殊的内核模式APC与普通的内核模式APC区别在于Normalroutine是否为空;

      4.当普通的内核模式APC例程被执行时,先执行KernelRoutine例程,再执行NormalRoutine例程(除非执行过KernelRoutine例程后,NormalRoutine例程被清除为NULL)。

      APC可以分成三种:用户模式APC、普通的内核模式APC,特殊的内核模式APC

      (图片源自危险漫步:http://www.weixianmanbu.com/article/33.html)

      

  • 相关阅读:
    将aspx页面编译成dll
    Jquery 验证数字
    c#反编译生成DLL过程
    c#进制转换
    Spring Mvc 实例
    wamp phpMyAdmin error #1045
    Tomcat相关知识点总结(jsp)
    Java ---学习笔记(泛型)
    Java IO ---学习笔记(文件操作与随机访问文件)
    Java IO ---学习笔记(字符流)
  • 原文地址:https://www.cnblogs.com/lsh123/p/7461021.html
Copyright © 2011-2022 走看看