内核与系统中的 Windows 进程
我有一些与内核和用户模式下的 Windows 进程相关的问题。
如果我有一个 hello world 应用程序和一个公开新系统调用 foo() 的 hello world 驱动程序,我很好奇一旦处于内核模式,我能做什么和不能做什么。
对于初学者来说,当我编写新的 hello world 应用程序时,我会得到一个新进程,这意味着我有自己的用户模式 VM 空间(让我们保持简单,32 位窗口)。所以我有 2GB 的空间,我“拥有”,我可以戳看,直到我满意为止。然而,我受到我的流程的约束。我不能(暂时不要将共享内存带入其中)触及任何人的记忆。
如果我编写这个 hello world 驱动程序,并从我的用户应用程序调用它,我(驱动程序代码)现在处于内核模式。
首先澄清/问题: 我仍然处于与用户模式应用程序相同的过程中,对吗?仍然有相同的 PID 吗?
记忆问题: 内存以虚拟机的形式呈现给我的进程,也就是说,即使我有 1GB 的 RAM,我仍然可以访问 4GB 的内存(2GB 用户/2GB 内核 - 不介意服务器上开关的细节或细节,这里只是一个一般假设)。 作为用户进程,我无法查看任何内核模式内存地址,但我可以对用户空间做任何我想做的事,对吗?
如果我从驱动程序代码中调用我的 hello world 驱动程序,我是否仍然具有相同的用户模式内存视图?但现在我也可以在内核模式下访问任何内存?
这个内核模式内存是共享的(与用户模式不同,用户模式是我自己的进程副本)?也就是说,编写驱动程序更像是为操作系统(除了调度?)的单个进程编写线程应用程序。
下一个问题。作为一名司机,我可以改变我正在运行的进程吗?比如说,我知道另一个应用程序(例如,用户模式网络服务器),并为该进程加载虚拟机,更改其指令指针、堆栈,甚至将不同的代码加载到进程中,然后切换回我自己的应用程序? (我并不想在这里做任何邪恶的事情,我只是好奇处于内核模式真正意味着什么)?
另外,一旦进入内核模式,我可以防止操作系统抢占我吗?我认为(在 Windows 中)你可以设置 IRQL 级别来执行此操作,但即使在阅读了 Solomons 的书(Inside Windows...)之后,我也不完全理解这一点。我会问另一个与 IRQL/DPC 直接相关的问题,但现在,我很想知道内核驱动程序是否有权将 IRQL 设置为高并接管系统。
更多内容即将推出,但这些问题的答案将会有所帮助。
I have a few questions related to Windows processes in kernel and usermode.
If I have a hello world application, and a hello world driver that exposes a new system call, foo(), I am curious about what I can and can't do once I am in kernel mode.
For starters, when I write my new hello world app, I am given a new process, which means I have my own user mode VM space (lets keep it simple, 32 bit windows). So I have 2GB of space that I "own", I can poke and peek until my hearts content. However, I am bound by my process. I can't (lets not bring shared memory into this yet) touch anyone elses memory.
If, I write this hello world driver, and call it from my user app, I (the driver code) is now in kernel mode.
First clarification/questions:
I am STILL in the same process as the user mode app, correct? Still have the same PID?
Memory Questions:
Memory is presented to my process as VM, that is even if I have 1GB of RAM, I can still access 4GB of memory (2GB user / 2GB of kernel - not minding details of switches on servers, or specifics, just a general assumption here).
As a user process, I cannot peek at any kernel mode memory address, but I can do whatever I want to the user space, correct?
If I call into my hello world driver, from the driver code, do I still have the same view of the usermode memory? But now I also have access to any memory in kernel mode?
Is this kernel mode memory SHARED (unlike User mode, which is my own processes copy)? That is, writing a driver is more like writing a threaded application for a single process that is the OS (scheduling aside?)
Next question. As a driver, could I change the process that I am running. Say, I knew another app (say, a usermode webserver), and load the VM for that process, change it's instruction pointer, stack, or even load different code into the process, and then switch back to my own app? (I am not trying to do anything nefarious here, I am just curious what it really means to be in kernel mode)?
Also, once in kernel mode, can I prevent the OS from preempting me? I think (in Windows) you can set your IRQL level to do this, but I don't fully understand this, even after reading Solomons book (Inside Windows...). I will ask another question, directly related to IRQL/DPCs but, for now, I would love to know if a kernel driver has the power to set an IRQL to High and take over the system.
More to come, but answers to these questions would help.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
每个进程都有一个“上下文”,其中包含特定于该进程的 VM 映射(在 32 位模式下通常小于 2 GB)。当在用户模式下执行的线程进入内核模式(例如,通过系统调用或IO请求)时,同一线程仍在进程中以相同的上下文执行。 PsGetCurrentProcessId 此时将返回与 GetCurrentProcessID 就在用户模式下(与线程 ID 相同)。
进入内核模式后,上下文附带的用户内存映射仍然存在:您可以直接从内核模式访问用户内存。为了安全起见,需要做一些特殊的事情: 不使用任何缓冲也不是直接 I/O。特别是,用户空间范围内的无效地址访问尝试将引发需要捕获的 SEH 异常,并且用户内存的内容可能会由于该进程中另一个线程的操作而随时更改。访问内核地址范围内的无效地址会导致错误检查。在用户模式下执行的线程无法访问任何内核内存。
内核地址空间不是进程上下文的一部分,因此它们之间的映射相同。然而,任意数量的线程可以在任何时候在内核模式下处于活动状态,因此它不像单线程应用程序。一般来说,线程在进入内核模式时为其自己的系统调用提供服务(而不是使用专用的内核工作线程来处理所有请求)。
保存线程和进程状态的底层结构在内核模式下都是可用的。映射另一个进程的 VM 最好提前从另一个进程创建 MDL,并将其映射到系统地址空间。如果您只想更改另一个线程的上下文,则完全可以通过 用户模式。请注意,必须挂起线程才能更改其上下文,而不会出现竞争条件。从内核模式将模块加载到进程中是不明智的;所有加载器 API 均设计为仅在用户模式下使用。
每个 CPU 都有一个当前运行的 IRQL。它决定哪些事情可以中断 CPU 当前正在执行的操作。只有来自较高 IRQL 的事件才能抢占 CPU 的当前活动。
PASSIVE_LEVEL
是所有用户代码和大多数内核代码执行的位置。许多内核 API 要求 IRQL 为PASSIVE_LEVEL
APC_LEVEL
用于内核 APCDISPATCH_LEVEL
用于调度程序事件(在 NT 术语中称为调度程序) 。在此级别运行将防止您被调度程序抢占。请注意,在此级别出现任何类型的页面错误都是不安全的;内存管理器尝试检索页面时可能会出现死锁。如果内核在DISPATCH_LEVEL
或更高级别出现页面错误,则会立即进行错误检查。这意味着您无法安全地访问分页池、分页代码段或任何尚未锁定(即通过 MDL)的用户内存。HIGH_LEVEL
。没有什么可以抢占这个级别。内核在错误检查期间使用它来停止系统。我建议阅读调度、线程上下文和 IRQL
Each process has a "context" that, among other things, contains the VM mappings specific to that process (<2 GB normally in 32bit mode). When thread executing in user mode enteres kernel mode (e.g. from a system call or IO request), the same thread is still executing, in the process, with the same context. PsGetCurrentProcessId will return the same thing at this point as GetCurrentProcessID would have just before in user mode (same with thread IDs).
The user memory mappings that came with the context are still in place upon entering kernel mode: you can access user memory from kernel mode directly. There are special things that need to be done for this to be safe though: Using Neither Buffered Nor Direct I/O. In particular, an invalid address access attempt in the user space range will raise a SEH exception that needs to be caught, and the contents of user memory can change at any time due to the action of another thread in that process. Accessing an invalid address in the kernel address range causes a bugcheck. A thread executing in user mode cannot access any kernel memory.
Kernel address space is not part of a process's context, so is mapped the same between all of them. However, any number of threads may be active in kernel mode at any one time, so it is not like a single threaded application. In general, threads service their own system calls upon entering kernel mode (as opposed to having dedicated kernel worker threads to handle all requests).
The underlying structures that save thread and process state is all available in kernel mode. Mapping the VM of another process is best done ahead of time from the other process by creating an MDL from that process and mapping it into system address space. If you just want to alter the context of another thread, this can be done entirely from user mode. Note that a thread must be suspended to change its context without having a race condition. Loading a module into a process from kernel mode is ill advised; all of the loader APIs are designed for use from user mode only.
Each CPU has a current IRQL that it is running at. It determines what things can interrupt what the CPU is currently doing. Only an event from a higher IRQL can preempt the CPU's current activity.
PASSIVE_LEVEL
is where all user code and most kernel code executes. Many kernel APIs require the IRQL to bePASSIVE_LEVEL
APC_LEVEL
is used for kernel APCsDISPATCH_LEVEL
is for scheduler events (known as the dispatcher in NT terminology). Running at this level will prevent you from being preempted by the scheduler. Note that it is not safe to have any kind of page fault at this level; there would be a deadlock possibility with the memory manager trying to retrieve pages. The kernel will bugcheck immediately if it has a page fault atDISPATCH_LEVEL
or higher. This means that you can't safely access paged pool, paged code segments or any user memory that hasn't been locked (i.e. by an MDL).DIRQL
.HIGH_LEVEL
. Nothing can preempt this level. It's used by the kernel during a bugcheck to halt the system.I recommend reading Scheduling, Thread Context, and IRQL
有关此主题的一个很好的入门知识可以在以下位置找到: http://www.codinghorror.com /blog/archives/001029.html
正如 Jeff 指出的用户模式内存空间:
“在用户模式下,执行代码无法直接访问硬件或引用内存。在用户模式下运行的代码必须委托给系统 API 来访问硬件或内存。由于这种隔离提供的保护,用户模式下的崩溃总是可以恢复的。”
因此您的应用程序将无法访问内核模式内存,事实上您与驱动程序的通信可能是通过 IOCTL(即 IRP)。
然而,内核可以访问所有内容,包括用户模式进程的映射。这是一条单行道,出于安全和稳定性原因,用户模式无法映射到内核模式。即使通过内核模式驱动程序可以映射到用户模式内存,我也不建议这样做。
至少在 WDF 之前是这样的。我不确定用户模式驱动程序的内存映射功能。
另请参阅:http://www.google.com/url?sa=t&source=web&ct=res&cd=1&url=http%3A%2F%2Fdownload.microsoft.com%2Fdownload %2Fe%2Fb%2Fa%2Feba1050f-a31d-436b-9281-92cdfeae4b45%2FKM-UMGuide.doc&ei=eAygSvfuAt7gnQe01P3gDQ&rct=j&q=用户+模式+映射+进入+内核+模式&usg=AFQjCNG1QYQM cIpcokMoQSWJlGSEodaBHQ
A good primer for this topic would be found at: http://www.codinghorror.com/blog/archives/001029.html
As Jeff points out for the user mode memory space:
"In User mode, the executing code has no ability to directly access hardware or reference memory. Code running in user mode must delegate to system APIs to access hardware or memory. Due to the protection afforded by this sort of isolation, crashes in user mode are always recoverable. Most of the code running on your computer will execute in user mode."
So your app will have no access to the Kernel Mode memory, infact your communication with the driver is probably through IOCTLs (i.e. IRPs).
The kernel however has access to everything, including to mappings for your user mode processes. This is a one way street, user mode cannot map into kernel mode for security and stability reasons. Even through kernel mode drivers can map into user mode memory I would advise against it.
At least that's the way it was back before WDF. I am not sure of the capabilities of memory mapping with user mode drivers.
See also: http://www.google.com/url?sa=t&source=web&ct=res&cd=1&url=http%3A%2F%2Fdownload.microsoft.com%2Fdownload%2Fe%2Fb%2Fa%2Feba1050f-a31d-436b-9281-92cdfeae4b45%2FKM-UMGuide.doc&ei=eAygSvfuAt7gnQe01P3gDQ&rct=j&q=user+mode+mapping+into+kernel+mode&usg=AFQjCNG1QYQMcIpcokMoQSWJlGSEodaBHQ