深入Windows APC

Windows11_22H2下的APC机制分析 APC机制分析 阅读详情

Python微信订餐小程序课程视频

https://edu.youkuaiyun.com/course/detail/36074

Python实战量化交易理财系统

https://edu.youkuaiyun.com/course/detail/35475
  本篇原文为 寂静的羽夏(wingsummer) 中文翻译,非机翻,著作权归原作者 Rbmm 和 Dennis A. Babkin 所有。
  由于原文十分冗长,也十分干货,采用机翻辅助,人工阅读比对修改的方式进行,如有翻译不得当的地方,欢迎批评指正。翻译不易,如有闲钱,欢迎支持。注意在转载文章时注意保留原文的作者链接,我(译者)的相关信息。话不多说,正文开始:

本篇文章包含一些没有被原厂家(指微软)进行文档化的功能和特色。在阅读本篇文章的相关内容和建议之后,你应该为自己的所作所为负责。展示在本文的方法依赖于内部实现并且可能以后不再有效。

介绍

在我们的第一篇关于错综复杂的用户APC的博文发布之后,我们决定从细节的深度拓展该主题,将会介绍有关异步过程调用(APC)在Windows操作系统的内部实现。
  那我们就开始吧,这里介绍没有什么特别的先后顺序。

目录

以下所有主题之间的联系并不是特别紧密,所以你或许想要一个可以更容易查阅的目录表:

APC 内部实现概要

为了在后面的文章——《伸入 NT 内核的异步过程调用内幕》,能够更好让大家更深入的理解内核 APC 的内部实现,我们不会重复原来已经讲过的了,而是多多陈述一些其他且鲜为人知的 APC 相关的细节。

为了更简洁的描述,在技术上说 APC 就是一堆在内存里存储的二进制字节,也就是所谓的 KAPC 结构体:

typedef struct \_KAPC {
  UCHAR Type;
  UCHAR SpareByte0;
  UCHAR Size;
  UCHAR SpareByte1;
  ULONG SpareLong0;
  _KTHREAD * Thread;
  _LIST_ENTRY ApcListEntry;
  void (* KernelRoutine)( _KAPC * , void (* * )( void * , void * , void * ), void * * , void * * , void * * );
  void (* RundownRoutine)( _KAPC * );
  void (* NormalRoutine)( void * , void * , void * );
  void * Reserved[0x3];
  void * NormalContext;
  void * SystemArgument1;
  void * SystemArgument2;
  CHAR ApcStateIndex;
  CHAR ApcMode;
  UCHAR Inserted;
}KAPC, *PKAPC;

上述结构体是在KAPC_STATE结构体里面的双向链表一部分:

typedef struct \_KAPC\_STATE {
  _LIST_ENTRY ApcListHead[0x2];
  _KPROCESS * Process;
  UCHAR InProgressFlags;
  UCHAR KernelApcInProgress : 01; // 0x01;
  UCHAR SpecialApcInProgress : 01; // 0x02;
  UCHAR KernelApcPending;
  UCHAR UserApcPendingAll;
  UCHAR SpecialUserApcPending : 01; // 0x01;
  UCHAR UserApcPending : 01; // 0x02;
}KAPC_STATE, *PKAPC_STATE;

并且KAPC_STATE自身也是线程对象的一部分,存储在内核里的KTHREAD结构体中:

🔒 点击查看 KTHREAD 🔒

typedef struct \_KTHREAD {
  _DISPATCHER_HEADER Header;
  void * SListFaultAddress;
  ULONGLONG QuantumTarget;
  void * InitialStack;
  void * volatile StackLimit;
  void * StackBase;
  ULONGLONG ThreadLock;
  ULONGLONG volatile CycleTime;
  ULONG CurrentRunTime;
  ULONG ExpectedRunTime;
  void * KernelStack;
  _XSAVE_FORMAT * StateSaveArea;
  _KSCHEDULING_GROUP * volatile SchedulingGroup;
  _KWAIT_STATUS_REGISTER WaitRegister;
  UCHAR volatile Running;
  UCHAR Alerted[0x2];
  ULONG AutoBoostActive : 01; // 0x00000001;
  ULONG ReadyTransition : 01; // 0x00000002;
  ULONG WaitNext : 01; // 0x00000004;
  ULONG SystemAffinityActive : 01; // 0x00000008;
  ULONG Alertable : 01; // 0x00000010;
  ULONG UserStackWalkActive : 01; // 0x00000020;
  ULONG ApcInterruptRequest : 01; // 0x00000040;
  ULONG QuantumEndMigrate : 01; // 0x00000080;
  ULONG UmsDirectedSwitchEnable : 01; // 0x00000100;
  ULONG TimerActive : 01; // 0x00000200;
  ULONG SystemThread : 01; // 0x00000400;
  ULONG ProcessDetachActive : 01; // 0x00000800;
  ULONG CalloutActive : 01; // 0x00001000;
  ULONG ScbReadyQueue : 01; // 0x00002000;
  ULONG ApcQueueable : 01; // 0x00004000;
  ULONG ReservedStackInUse : 01; // 0x00008000;
  ULONG UmsPerformingSyscall : 01; // 0x00010000;
  ULONG TimerSuspended : 01; // 0x00020000;
  ULONG SuspendedWaitMode : 01; // 0x00040000;
  ULONG SuspendSchedulerApcWait : 01; // 0x00080000;
  ULONG CetUserShadowStack : 01; // 0x00100000;
  ULONG BypassProcessFreeze : 01; // 0x00200000;
  ULONG Reserved : 10; // 0xffc00000;
  LONG MiscFlags;
  ULONG BamQosLevel : 02; // 0x00000003;
  ULONG AutoAlignment : 01; // 0x00000004;
  ULONG DisableBoost : 01; // 0x00000008;
  ULONG AlertedByThreadId : 01; // 0x00000010;
  ULONG QuantumDonation : 01; // 0x00000020;
  ULONG EnableStackSwap : 01; // 0x00000040;
  ULONG GuiThread : 01; // 0x00000080;
  ULONG DisableQuantum : 01; // 0x00000100;
  ULONG ChargeOnlySchedulingGroup : 01; // 0x00000200;
  ULONG DeferPreemption : 01; // 0x00000400;
  ULONG QueueDeferPreemption : 01; // 0x00000800;
  ULONG ForceDeferSchedule : 01; // 0x00001000;
  ULONG SharedReadyQueueAffinity : 01; // 0x00002000;
  ULONG FreezeCount : 01; // 0x00004000;
  ULONG TerminationApcRequest : 01; // 0x00008000;
  ULONG AutoBoostEntriesExhausted : 01; // 0x00010000;
  ULONG KernelStackResident : 01; // 0x00020000;
  ULONG TerminateRequestReason : 02; // 0x000c0000;
  ULONG ProcessStackCountDecremented : 01; // 0x00100000;
  ULONG RestrictedGuiThread : 01; // 0x00200000;
  ULONG VpBackingThread : 01; // 0x00400000;
  ULONG ThreadFlagsSpare : 01; // 0x00800000;
  ULONG EtwStackTraceApcInserted : 08; // 0xff000000;
  LONG volatile ThreadFlags;
  UCHAR volatile Tag;
  UCHAR SystemHeteroCpuPolicy;
  UCHAR UserHeteroCpuPolicy : 07; // 0x7f;
  UCHAR ExplicitSystemHeteroCpuPolicy : 01; // 0x80;
  UCHAR RunningNonRetpolineCode : 01; // 0x01;
  UCHAR SpecCtrlSpare : 07; // 0xfe;
  UCHAR SpecCtrl;
  ULONG SystemCallNumber;
  ULONG ReadyTime;
  void * FirstArgument;
  _KTRAP_FRAME * TrapFrame;
  _KAPC_STATE ApcState;
  UCHAR ApcStateFill[0x2b];
  CHAR Priority;
  ULONG UserIdealProcessor;
  LONGLONG volatile WaitStatus;
  _KWAIT_BLOCK * WaitBlockList;
  _LIST_ENTRY WaitListEntry;
  _SINGLE_LIST_ENTRY SwapListEntry;
  _DISPATCHER_HEADER * volatile Queue;
  void * Teb;
  ULONGLONG RelativeTimerBias;
  _KTIMER Timer;
  _KWAIT_BLOCK WaitBlock[0x4];
  UCHAR WaitBlockFill4[0x14];
  ULONG ContextSwitches;
  UCHAR WaitBlockFill5[0x44];
  UCHAR volatile State;
  CHAR Spare13;
  UCHAR WaitIrql;
  CHAR WaitMode;
  UCHAR WaitBlockFill6[0x74];
  ULONG WaitTime;
  UCHAR WaitBlockFill7[0xa4];
  SHORT KernelApcDisable;
  SHORT SpecialApcDisable;
  ULONG CombinedApcDisable;
  UCHAR WaitBlockFill8[0x28];
  _KTHREAD_COUNTERS * ThreadCounters;
  UCHAR WaitBlockFill9[0x58];
  _XSTATE_SAVE * XStateSave;
  UCHAR WaitBlockFill10[0x88];
  void * volatile Win32Thread;
  UCHAR WaitBlockFill11[0xb0];
  _UMS_CONTROL_BLOCK * Ucb;
  _KUMS_CONTEXT_HEADER * volatile Uch;
  void * Spare21;
  _LIST_ENTRY QueueListEntry;
  ULONG volatile NextProcessor;
  ULONG NextProcessorNumber : 31; // 0x7fffffff;
  ULONG SharedReadyQueue : 01; // 0x80000000;
  LONG QueuePriority;
  _KPROCESS * Process;
  _GROUP_AFFINITY UserAffinity;
  UCHAR UserAffinityFill[0xa];
  CHAR PreviousMode;
  CHAR BasePriority;
  CHAR PriorityDecrement;
  UCHAR ForegroundBoost : 04; // 0x0f;
  UCHAR UnusualBoost : 04; // 0xf0;
  UCHAR Preempted;
  UCHAR AdjustReason;
  CHAR AdjustIncrement;
  ULONGLONG AffinityVersion;
  _GROUP_AFFINITY Affinity;
  UCHAR AffinityFill[0xa];
  UCHAR ApcStateIndex;
  UCHAR WaitBlockCount;
  ULONG IdealProcessor;
  ULONGLONG NpxState;
  _KAPC_STATE SavedApcState;
  UCHAR SavedApcStateFill[0x2b];
  UCHAR WaitReason;
  CHAR SuspendCount;
  CHAR Saturation;
  USHORT SListFaultCount;
  _KAPC SchedulerApc;
  UCHAR SchedulerApcFill0[0x1];
  UCHAR ResourceIndex;
  UCHAR SchedulerApcFill1[0x3];
  UCHAR QuantumReset;
  UCHAR SchedulerApcFill2[0x4];
  ULONG KernelTime;
  UCHAR SchedulerApcFill3[0x40];
  _KPRCB * volatile WaitPrcb;
  UCHAR SchedulerApcFill4[0x48];
  void * LegoData;
  UCHAR SchedulerApcFill5[0x53];
  UCHAR CallbackNestingLevel;
  ULONG UserTime;
  _KEVENT SuspendEvent;
  _LIST_ENTRY ThreadListEntry;
  _LIST_ENTRY MutantListHead;
  UCHAR AbEntrySummary;
  UCHAR AbWaitEntryCount;
  UCHAR AbAllocationRegionCount;
  CHAR SystemPriority;
  ULONG SecureThreadCookie;
  _KLOCK_ENTRY LockEntries[0x6];
  _SINGLE_LIST_ENTRY PropagateBoostsEntry;
  _SINGLE_LIST_ENTRY IoSelfBoostsEntry;
  UCHAR PriorityFloorCounts[0x10];
  ULONG PriorityFloorSummary;
  LONG volatile AbCompletedIoBoostCount;
  LONG volatile AbCompletedIoQoSBoostCount;
  SHORT volatile KeReferenceCount;
  UCHAR AbOrphanedEntrySummary;
  UCHAR AbOwnedEntryCount;
  ULONG ForegroundLossTime;
  _LIST_ENTRY GlobalForegroundListEntry;
  _SINGLE_LIST_ENTRY ForegroundDpcStackListEntry;
  ULONGLONG InGlobalForegroundList;
  LONGLONG ReadOperationCount;
  LONGLONG WriteOperationCount;
  LONGLONG OtherOperationCount;
  LONGLONG ReadTransferCount;
  LONGLONG WriteTransferCount;
  LONGLONG OtherTransferCount;
  _KSCB * QueuedScb;
  ULONG volatile ThreadTimerDelay;
  LONG volatile ThreadFlags2;
  ULONG PpmPolicy : 02; // 0x00000003;
  ULONG ThreadFlags2Reserved : 30; // 0xfffffffc;
  ULONGLONG TracingPrivate[0x1];
  void * SchedulerAssist;
  void * volatile AbWaitObject;
}KTHREAD, *PKTHREAD;

将线程挂靠到另一个进程

值得注意的一点就是任何一个线程都可以通过调用KeStackAttachProcess(该函数会接收KAPC_STATE对象,并查看它的ApcState参数)临时地挂靠到另一个进程上,也可以通过调用KeUnstackDetachProcess函数脱离进程。但是这会有会导致问题一点点的可能性,所以开发者需要把注意力放到上面。
  因此,有一个十分重要的事情去理解,我们需要通过使用一个未被文档化但是导出的KeInitializeApc调用初始化一个APC对象:

VOID KeInitializeApc(
  IN PRKAPC Apc,          //pointer to KAPC
  IN PKTHREAD Thread,
  IN KAPC_ENVIRONMENT Environment,
  IN PKKERNEL_ROUTINE KernelRoutine,
  IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,
  IN PKNORMAL_ROUTINE NormalRoutine OPTIONAL,
  IN KPROCESSOR_MODE ApcMode,
  IN PVOID NormalContext
);

我们使用该函数需要提供KAPC_ENVIRONMENT类型的参数,它的枚举如下:

typedef enum \_KAPC\_ENVIRONMENT {
  OriginalApcEnvironment,
  AttachedApcEnvironment,
  CurrentApcEnvironment
} KAPC_ENVIRONMENT;

这个参数指定了APC环境,换句话说,当我们插入一个APC,我们会告诉系统是应该为当前线程激活它,还是应该在线程挂靠到另一个进程之前为保存的状态(KTHREAD::SavedApcState)激活它。该参数将会在后面保存到KAPC::ApcStateIndex成员当中。
  为了更好的说明这个概念,让我们回顾如下的KiInsertQueueApc代码:

// KiInsertQueueApc() excerpt:

Thread = Apc->Thread;
PKAPC_STATE ApcState;

if (Apc->ApcStateIndex == 0 && Thread->ApcStateIndex != 0)
{
  ApcState = &Thread->SavedApcState;
}
else
{
  Apc->ApcStateIndex = Thread->ApcStateIndex;
  ApcState = &Thread->ApcState;
}

所以本质上KAPC::ApcStateIndex是一个布尔值:

  • 非0:指示APC插入到当前线程中,话句话说,APC应该执行在当前进程的上下文环境中,也就是线程当前运行的环境。
  • 0:指示当前APC应该仅仅在源进程的环境中运行,或者在线程在进程挂靠之前的进程环境中。

在KeStackAttachProcess函数中,有如下逻辑:

// KeStackAttachProcess() excerpt:

if (Thread->ApcStateIndex != 0)
{
  KiAttachProcess(Thread, Process, &LockHandle, ApcState);
}
else
{
  KiAttachProcess(Thread, Process, &LockHandle, &Thread->SavedApcState);
  ApcState->Process = NULL;
}

也就是意味着,当我们第一次挂靠到另一个进程,打个比方:如果它的KAPC::ApcStateIndex值为0,当前的KTHREAD::ApcState存储在KTHREAD::SavedApcState当中,并且以前的ApcState不会被使用,除非设置KAPC_STATE::Process为0表示这个状态存储在KTHREAD::SavedApcState。
  但是如果我们递归式挂靠,或当一个线程已经挂靠到另一个进程时我们已经调用了KeStackAttachProcess,在那种情况下APC的状态被保存在ApcState对象中,被作为参数传递到函数当中。
  这种逻辑处理是为了让系统始终可以访问线程的原始APC状态。这可以用于将APC插入原始线程,或通过调用KeUnstackDetachProcess将线程脱离原进程。

APC 的类型

APC有两个基础类型:内核APC和用户APC。内核APC给予了开发者更多便利来处理APC排列和处理(我们在本篇博文已讨论过用户APC)。内核APC不向用户层开发者们开放能够直接访问的权限。
  KAPC_STATE::ApcListHead里面包含了两个链表用来存放内核APC和用户APC。这两个链表分别有APC排队等待线程处理:

typedef enum \_MODE {
  KernelMode = 0x0,
  UserMode = 0x1,
  MaximumMode = 0x2
}MODE;

内核使用这些列表来维护每种类型的APC的状态。当APC排队或调用KeInsertQueueApc处理时,KAPC::ApcMode用作KAPC_STATE::ApcListHead的索引:

NTSTATUS NtQueueApcThread(
  IN HANDLE Thread,
  IN PKNORMAL_ROUTINE NormalRoutine,
  IN PVOID NormalContext,
  IN PVOID SystemArgument1,
  IN PVOID SystemArgument2
);

内核 APC 的使用内存的易错点

许多内核开发新手犯了一个错误:为内核模式APC指定了错误的内存类型。认识到这一点很重要,以防止各种意外的蓝屏死机(BSOD)。
  这是一定要记住经验,KAPC结构体只能使用从非分页内存分配的内存(或者从类似NonPagedPool*类型分配)。即使在PASSIVE_LEVEL的IRQL初始化并插入APC,这也是没问题的。
  为什么要有这样的内存类型限制呢?其他一些APC也可以插入到运行在更高调度级别IRQL的同一线程中。在插入双链接APC列表期间,系统将尝试访问列表中已经存在的其他KAPC结构。因此,如果其中任何一个使用的是从分页内存分配的内存,你将会从DISPATCH_LEVEL间接访问分页内存,这也是一种会导致蓝屏保护原因。
  比较棘手的是,我在描述如上的情况非常少见,在开发和测试阶段可能不会出现。这将很难在生产代码中进行诊断,正如我在上面解释的,可能过一段时间之后,会在你无法控制的环境中发生蓝屏。

中断和阻塞内核 APC

关于内核模式APC,需要记住的重要一点是,它通过中断实现,这意味着它可以发生在代码中的任意两个CPU指令之间。
  内核开发允许我们阻止APC的执行。只有在代码的某些特殊部分起作用:将IRQL提升到APC_LEVEL或更高级别或将写的代码放在KeEnterCriticalRegion和KeLeaveCriticalRegion的调用之间。(请注意,这些函数不会阻止所谓的特殊内核APC的执行,只有提高IRQL级别才能阻止这些APC的执行)。
  关于我在上面展示的IRQL条件限制,一个有趣的事实是,如果APC到达关键

Windows 进程APC学习笔记 学习Windows进程,APC肯定少不了啦。 PS:由于本人才疏智浅,本文仅为个人学习笔记,如有错误,希望大牛不吝赐教。 下面是我学习APC的一点笔记。 1、什么是APC APC就是异步过程调用的所写。据说PE格式DLL映像的装入和动态连接就是由ntdll.dll中的 阅读详情

相关推荐

ReactOS分析windows APC机制

#define KeEnterGuardedRegion() \ { \ PKTHREAD _Thread = KeGetCurrentThread();

dayenglish的专栏 1800

别再手动算考勤了!教你用Python+企业微信API自动生成月度报表

本文介绍如何利用Python和企业微信API构建自动化考勤分析系统,实现从数据获取到可视化报表生成的全流程自动化。通过企业微信打卡接口获取数据,使用Pandas进行高效清洗和分析,并生成Excel和HTML格式的报表,大幅提升考勤统计效率。系统支持智能判定考勤状态,如迟到、早退等,并可通过定时任务实现月度自动化运行。

weixin_27875131的博客 289

自学PCB思路 | PCB设计零基础入门-小白适用(附详细入门经验分享)

本人大一在实践课上就听说PCB,被虎的一楞一楞的,不过现在回看,发现其实并没有相信的那么难!想着把自己这一路摸索的学习路线分享一下,当时我是一边摸索一边上手,一把鼻涕一把泪的过来的。如果你想搞硬件或者需要搞硬件,下面是我的入门学习过程,希望可以帮到你。目录前言选择一款设计软件好的入门课程掌握抄板元件采购焊板子烙铁头热风枪自己半设计明确设计需求全设计最后我并非什么大佬,也没法自己去教大家如何画板。

自动化小秋葵的博客 1万+

多相机标定与单应变换:用OpenCV实现BEV俯视全景图

多路监控画面分散在不同屏幕,难以快速理解全局空间关系。通过相机标定与畸变校正,利用单应矩阵将各视角图像投影到统一的地面坐标系,即可合成BEV鸟瞰全景图。该技术是车载360°环视系统和自动驾驶BEV感知的雏形,也适用于场地安防与移动机器人导航。基于Python+OpenCV的四路相机实现,完整覆盖标定、投影变换、多路羽化融合与实时优化,为多视角拼接和俯视全景图项目提供可复现的工程参考。

weixin_29056865的博客 244

FPGA实战:手把手教你实现雷达信号数字下变频(DDC)完整流程

本文提供了一份完整的FPGA实现雷达信号数字下变频(DDC)的实战指南。内容涵盖从Matlab行为级仿真到FPGA硬件实现的完整闭环流程,详细讲解了数字混频、滤波、抽取等核心步骤,并分享了基于Vivado的IP核配置、数据位宽量化及资源优化等关键工程实践经验,旨在帮助读者掌握DDC系统的硬件实现与调试。

dog123的博客 341

Unity Tilemap瓦片动态缩放:保持网格尺寸不变的三种实现方案

本文详细探讨了在Unity中实现Tilemap瓦片动态缩放同时保持网格尺寸不变的三种核心方案。针对编辑器静态调整、整体风格化处理及运行时动态控制等不同场景,分别解析了直接修改瓦片属性、调整Tilemap Renderer材质参数以及使用SetTransformMatrix与Matrix4x4矩阵变换的实践方法、优缺点及选型指南,为2D游戏开发提供灵活高效的视觉与交互解决方案。

grass的博客 626

GEE 案例分析:利用Landsat数据进行FAI、NDVI及CHLA分析并统计平均值(巢湖为例)

浮游藻类指数(FAI)、归一化植被指数(NDVI)及叶绿素a(CHLA)是水体环境监测的重要指标。本文利用Google Earth Engine(GEE)平台,基于Landsat 5和Landsat 8数据,进行水体藻类及植被监测。

此星光明博客 1336

WIN10系统下VS2019编译CloudCompare2.12.4

CloudCompare2.12.4

点云侠的博客 3143

【skill】漫剧 AI 视频创作的知识库 + API 调用手册

方案适用场景在对话中使用,自动触发注入到任何 LLM(Cursor/Windsurf/Claude API)Python 模块集成到自动化管线/批量生成GitHub 又限速了,不过前几轮搜到的已经够用了。根据搜索结果,结合已分析过的项目,给你整理完整对比 🐮。

突围 984

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

上一篇: Linux下的5种I/O模型与3组I/O复用
下一篇: Lesson3——Pandas Series结构
pythonxxoo
博客等级 码龄5年 337粉丝 670原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值