5.用户APC执行过程

当产生系统调用、中断或者异常,线程在返回用户空间前都会调用, _KiServiceExit函数,在_KiServiceExit会判断是否有要执行的用户APC,如果有则调用KiDeliverApc函数(第一个参数为1)进行处理。

执行用户APC时的堆栈操作

处理用户APC要比内核APC复杂的多,因为,用户APC函数要在用户空间执行的,这里涉及到大量换栈的操作:

当线程从用户层进入内核层时,要保留原来的运行环境,比如各种寄存 囚器,栈的位置等等(_Trap_Frame),然后切换成内核的堆栈,如果正常返回, 您恢复堆栈环境即可。

但如果有用户APC要执行的话,就意味着线程要提前返回到用户空间去,执行,而且返回的位置不是线程进入内核时的位置,而是返回到其他的位置,每处理一个用户APC都会涉及到:
内核->用户空间->再回到内核空间

堆栈的操作比较复杂,如果不了解堆栈的操作细节不可能理解用户APC是如何执行!

KiDeliverApc函数分析
无论是内核APC还是用户APC首先执行的都是这个函数

  1. 判断用户APC链表是否为空
  2. 判断第一个参数是为1
  3. 判断ApcState.UserApcPending是否为1
  4. 将ApcState.UserApcPending设置为0
  5. 链表操作将当前APC从用户队列中拆除
  6. 调用函数(KAPC.KernelRoutine)释放KAPC结构体内存空间
  7. 调用KilnitializeUserApc函数

在这里插入图片描述
取完内核APC后取用户APC,然后判断你用户APC是不是空的,不为空就跳转。

在这里插入图片描述

KilnitializeUserApc函数分析:备份CONTEXT

线程进0环时,原来的运行环境(寄存器栈顶等)保存到Trap_Frame结构体中,如果要提前返回3环去处理用户APC,就必须要修改Trap _Frame结构体:

比如:进0环时的位置存储在EIP中,现在要提前返回,而且返回的并不,是原来的位置,那就意味着必须要修改EIP为新的返回位置。还有堆栈ESP也要修改为处理APC需要的堆栈。那原来的值怎么办呢?处理完APC后该如何返回原来的, 位置呢?

KilnitializeUserApc要做的第一件事就是备份:

将原来Trap Frame的值备份到一个新的结构体中(CONTEXT),这个功能由其子函数KeContextFromKframes来完成。

在这里插入图片描述

在这里插入图片描述

kd> dt _CONTEXT
nt!_CONTEXT
   +0x000 ContextFlags     : Uint4B
   +0x004 Dr0              : Uint4B
   +0x008 Dr1              : Uint4B
   +0x00c Dr2              : Uint4B
   +0x010 Dr3              : Uint4B
   +0x014 Dr6              : Uint4B
   +0x018 Dr7              : Uint4B
   +0x01c FloatSave        : _FLOATING_SAVE_AREA
   +0x08c SegGs            : Uint4B
   +0x090 SegFs            : Uint4B
   +0x094 SegEs            : Uint4B
   +0x098 SegDs            : Uint4B
   +0x09c Edi              : Uint4B
   +0x0a0 Esi              : Uint4B
   +0x0a4 Ebx              : Uint4B
   +0x0a8 Edx              : Uint4B
   +0x0ac Ecx              : Uint4B
   +0x0b0 Eax              : Uint4B
   +0x0b4 Ebp              : Uint4B
   +0x0b8 Eip              : Uint4B
   +0x0bc SegCs            : Uint4B
   +0x0c0 EFlags           : Uint4B
   +0x0c4 Esp              : Uint4B
   +0x0c8 SegSs            : Uint4B
   +0x0cc ExtendedRegisters : [512] UChar

0环备份的CONTEXT复制到3环堆栈
在这里插入图片描述

APC要执行的4个值放到图上面堆栈中
在这里插入图片描述



在这里插入图片描述

1
在这里插入图片描述

2.3.4
在这里插入图片描述



ntdll.KiUserApcDispatcher分析

1、当用户在3环调用QueueUserAPC函数来插入APC时,不需要提供 NormalRoutine,这个参数是在QueueUserAPC内部指定的:
BaseDispatchAPC

2、 ZwContinue函数的意义:

  1. 返回内核,如果还有用户APC,重复上面的执行过程。
  2. 如果没有需要执行的用户APC,会将CONTEXT赋值给Trap
    Frame结构体。就像从来没有修改过一样。ZwContinue后面的代码不会执行,线程从哪里进0环仍然会从哪里回去。

在这里插入图片描述

总结

  1. 内核APC在线程切换时执行,不需要换栈,比较简单,一个循环执行完毕。
  2. 用户APC在系统调用、中断或异常返回3环前会进行判断,如果有要执行的用户APC,再执行。
  3. 用户APC执行前会先执行内核APC
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值