小言_互联网的博客

Windows操作系统管理进程和线程:内核模式和用户模式

319人阅读  评论(0)

根据前面的介绍,NT内核会把操作系统的代码和数据映射到系统中所有进程的内核空间中。这样,每个进程内的应用程序代码便可以很方便地调用内核空间中的系统服务。这里的“很方便”有多层含义,一方面是内核代码和用户代码在一个地址空间中,应用程序调用系统服务时不需要切换地址空间,另一方面是整个系统中内核空间的地址是统一的,编写内核空间的代码时会简单很多。但是,如此设计也带来一个很大的问题,那就是用户空间中的程序指针可以指向内核空间中的数据和代码,因此必须防止用户代码破坏内核空间中的操作系统。怎么做呢?答案是利用权限控制来实现对内核空间的保护。

2.6.1 访问模式

Windows定义了两种访问模式(access mode)——用户模式(user mode,也称为用户态)和内核模式(kernel mode,也称为内核态)。应用程序(代码)运行在用户模式下,操作系统代码运行在内核模式下。内核模式对应于处理器的最高权限级别(不考虑虚拟机情况),在内核模式下执行的代码可以访问所有系统资源并具有使用所有特权指令的权利。相对而言,用户模式对应于较低的处理器优先级,在用户模式下执行的代码只可以访问系统允许其访问的内存空间,并且没有使用特权指令的权利。

本书卷1介绍过,IA-32处理器定义了4种特权级别(privilege level),或者称为环(ring),分别为0、1、2、3,优先级0(环0)的特权级别最高。处理器在硬件一级保证高优先级的数据和代码不会被低优先级的代码破坏。Windows系统使用了IA-32处理器所定义的4种优先级中的两种,优先级3(环3)用于用户模式,优先级0用于内核模式。之所以只使用了其中的两种,主要是因为有些处理器只支持两种优先级,比如Compaq Alpha处理器。值得说明的是,对于x86处理器来说,并没有任何寄存器表明处理器当前处于何种模式(或优先级)下,优先级只是代码或数据所在的内存段或页的一个属性,参见卷1的2.6节和2.7节。

因为内核模式下的数据和代码具有较高的优先级,所以用户模式下的代码不可以直接访问内核空间中的数据,也不可以直接调用内核空间中的任何函数或例程。任何这样的尝试都会导致保护性错误。也就是说,即使用户空间中的代码指针正确指向了要访问的数据或代码,但一旦访问发生,那么处理器会检测到该访问是违法的,会停止该访问并产生保护性异常(#GP)。

虽然不可以直接访问,但是用户程序可以通过调用系统服务来间接访问内核空间中的数据或间接调用、执行内核空间中的代码。当调用系统服务时,主调线程会从用户模式切换到内核模式,调用结束后再返回到用户模式,也就是所谓的模式切换。在线程的KTHREAD结构中,定义了UserTime和KernelTime两个字段,分别用来记录这个线程在用户模式和内核模式的运行时间(以时钟中断次数为单位)。模式切换是通过软中断或专门的快速系统调用(fast system call)指令来实现的。下面通过一个例子来分别介绍这两种切换机制。

2.6.2 使用INT 2E切换到内核模式

图2-4展示了在Windows 2000中通过INT 2E从应用程序调用ReadFile() API的过程。因为ReadFile() API是从Kernel32.dll导出的,所以我们看到该调用首先转到Kernel32.dll中的ReadFile()函数,ReadFile()函数在对参数进行简单检查后便调用NtDll.dll中的NtReadFile()函数。

 

图2-4 通过INT 2E从应用程序调用ReadFile() API的过程

通过反汇编可以看到,NtDll.dll中的NtReadFile ()函数非常简短,首先将ReadFile()对应的系统服务号(0xa1,与版本有关)放入EAX寄存器中,将参数指针放入EDX寄存器中,然后便通过INT n指令发出调用。这里要说明的一点是,虽然每个系统服务都具有唯一的号码,但微软公司没有公开这些服务号,也不保证这些号码在不同的Windows版本中会保持一致。


  
  1. ntdll!NtReadFile: // Windows 2000
  2. 77f8fb5d b 8a 1000000 mov eax, 0xa 1
  3. 77f8fb62 8d 542404 lea edx,[esp+ 0x 4]
  4. 77f8fb66 cd 2e int 2e
  5. 77f8fb68 c 22400 ret 0x 24

在WinDBG下通过!idt 2e命令可以看到2e号向量对应的服务例程是KiSystemService ()。KiSystemService ()是内核态中专门用来分发系统调用的例程。


  
  1. lkd> !idt 2e
  2. Dumping IDT:
  3. 2e: 804db 1ed nt!KiSystemService

Windows将2e号向量专门用于系统调用,在启动早期初始化中断描述符表(Interrupt Descriptor Table,IDT)时(见第11章)便注册好了合适的服务例程。因此当NTDll.DLL中的NtReadFile()发出INT 2E指令后,CPU便会通过IDT找到KiSystemService ()函数。因为KiSystemService ()函数是位于内核空间的,所以CPU在把执行权交给KiSystemService ()函数前,会做好从用户模式切换到内核模式的各种工作,包括:

(1)权限检查,即检查源位置和目标位置所在的代码段权限,核实是否可以转移;

(2)准备内核模式使用的栈,为了保证内核安全,所有线程在内核态执行时都必须使用位于内核空间的内核栈(kernel stack),内核栈的大小一般为8KB或12KB。

KiSystemService ()会根据服务ID从系统服务分发表(System Service Dispatch Table)中查找到要调用的服务函数地址和参数描述,然后将参数从用户态栈复制到该线程的内核栈中,最后KiSystemService ()调用内核中真正的NtReadFile()函数,执行读文件的操作,操作结束后会返回到KiSystemService (),KiSystemService ()会将操作结果复制回该线程用户态栈,最后通过IRET指令将执行权交回给NtDll.dll中的NtReadFile()函数(继续执行INT 2E后面的那条指令)。

通过INT 2E进行系统调用时,CPU必须从内存中分别加载门描述符和段描述符才能得到KiSystemService ()的地址,即使门描述符和段描述符已经在高速缓存中,CPU也需要通过“内存读(memory read)”操作从高速缓存中读出这些数据,然后进行权限检查。

2.6.3 快速系统调用

因为系统调用是非常频繁的操作,所以如果能减少这些开销还是非常有意义的。可以从两个方面来降低开销:一是把系统调用服务例程的地址放到寄存器中以避免读IDT这样的内存操作,因为读寄存器的速度比读内存的速度要快很多;二是避免权限检查,也就是使用特殊的指令让CPU省去那些对系统服务调用来说根本不需要的权限检查。奔腾II处理器引入的SYSENTER/SYSEXIT指令正是按这一思路设计的。AMD K7引入的SYSCALL/SYSRETURN指令也是为这一目的而设计的。相对于INT 2E,使用这些指令可以加快系统调用的速度,因此利用这些指令进行的系统调用称为快速系统调用。

下面我们介绍Windows系统是如何利用IA-32处理器的SYSENTER/SYSEXIT指令(从奔腾II开始)实现快速系统调用的[2]。首先,Windows 2000或之前的Windows系统不支持快速系统调用,它们只能使用前面介绍的INT 2E方式进行系统调用。Windows XP和Windows Server 2003或更新的版本在启动过程中会通过CPUID指令检测CPU是否支持快速系统调用指令(EDX寄存器的SEP标志位)。如果CPU不支持这些指令,那么仍使用INT 2E方式。如果CPU支持这些指令,那么Windows系统便会决定使用新的方式进行系统调用,并做好如下准备工作。

(1)在全局描述符表(GDT)中建立4个段描述符,分别用来描述供SYSENTER指令进入内核模式时使用的代码段(CS)和栈段(SS),以及SYSEXIT指令从内核模式返回用户模式时使用的代码段和栈段。这4个段描述符在GDT中的排列应该严格按照以上顺序,只要指定一个段描述符的位置便能计算出其他的。

(2)设置表2-1中专门用于系统调用的MSR(关于MSR的详细介绍见卷1的2.4.3节),SYSENTER_EIP_MSR用于指定新的程序指针,也就是SYSENTER指令要跳转到的目标例程地址。Windows系统会将其设置为KiFastCallEntry的地址,因为KiFastCallEntry例程是Windows内核中专门用来受理快速系统调用的。SYSENTER_CS_MSR用来指定新的代码段,也就是KiFastCallEntry所在的代码段。SYSENTER_ESP_MSR用于指定新的栈指针(ESP)。新的栈段是由SYSENTER_CS_MSR的值加8得来的。

(3)将一小段名为SystemCallStub的代码复制到SharedUserData内存区,该内存区会被映射到每个Win32进程的进程空间中。这样当应用程序每次进行系统调用时,NTDll.DLL中的残根(stub)函数便调用这段SystemCallStub代码。SystemCallStub的内容因系统硬件的不同而不同,对于IA-32处理器,该代码使用SYSENTER指令,对于AMD处理器,该代码使用SYSCALL指令。

表2-1 供SYSENTER指令使用的MSR(略)

例如在配有Pentium M CPU的Windows XP系统上,以上3个寄存器的值分别为:


  
  1. lkd> rdmsr 174
  2. msr[ 174] = 00000000` 00000008
  3. lkd> rdmsr 175
  4. msr[ 175] = 00000000`bacd 8000
  5. lkd> rdmsr 176
  6. msr[ 176] = 00000000` 8053cad 0

其中SYSENTER_CS_MSR的值为8,这是Windows系统的内核代码段的选择子,即常量KGDT_R0_CODE的值。WinDBG帮助文件中关于dg命令的说明中列出了这个常量。SYSENTER_EIP_MSR的值是8053cad0,检查nt内核中KiFastCallEntry函数的地址。


  
  1. lkd> x nt! KiFastCallEntry
  2. 8053cad0 nt! KiFastCallEntry = <no type information>

可见,Windows把快速系统调用的目标指向内核代码段中的KiFastCallEntry函数。

通过反汇编Windows XP下NTDll.DLL中的NtReadFile ()函数,可以看到SystemCallStub被映射到进程的0x7ffe0300位置。与前面Windows 2000下的版本相比,容易看到该服务的系统服务号码在这两个版本间是不同的。


  
  1. kd> u ntdll...
  2. ntdll!NtReadFile: // Windows XP
  3. 77f5bfa8 b 8b 7000000 mov eax, 0xb 7
  4. 77f5bfad ba 0003fe 7f mov edx, 0x 7ffe 0300
  5. 77f5bfb2 ffd 2 call edx {SharedUserData!SystemCallStub ( 7ffe 0300)}
  6. 77f5bfb4 c 22400 ret 0x 24
  7. 77f5bfb7 90 nop

观察本段下面反汇编SystemCallStub的结果,它只包含3条指令,分别用于将栈指针(ESP寄存器)放入EDX寄存器中、执行sysenter指令和返回。第一条指令有两个用途:一是向内核空间传递参数;二是指定从内核模式返回时的栈地址。因为笔者使用的是英特尔奔腾M处理器,所以此处是sysenter指令,对于AMD处理器,此处应该是syscall指令。


  
  1. kd> u...
  2. SharedUserData!SystemCallStub:
  3. 7ffe0300 8bd 4 mov edx,esp
  4. 7ffe0302 0f 34 sysenter
  5. 7ffe0304 c 3 ret

下面让我们看一下KiFastCallEntry例程,其清单如下所示。


  
  1. kd> u nt!KiFastCallEntry L 20
  2. nt!KiFastCallEntry:
  3. 804db1bb 368b 0d 40f 0dfff mov ecx,ss:[ffdff 040]
  4. 804db1c2 368b 6104 mov esp,ss:[ecx+ 0x 4]
  5. 804db1c6 b 90403fe 7f mov ecx, 0x 7ffe 0304
  6. 804db1cb 3b 2504f 0dfff cmp esp,[ffdff 004]
  7. 804db1d1 0f 84cc 030000 je nt!KiServiceExit 2+ 0x 13f ( 804db 5a 3)
  8. 804db1d7 6a 23 push 0x 23
  9. 804db1d9 52 push edx
  10. 804db1da 83c 208 add edx, 0x 8
  11. 804db1dd 6802020000 push 0x 202
  12. 804db1e2 6a 02 push 0x 2
  13. 804db1e4 9d popfd
  14. 804db1e5 6a 1b push 0x 1b
  15. 804db1e7 51 push ecx // Fall Through,自然进入KiSystemService函数
  16. nt!KiSystemService:
  17. 804db1e8 90 nop
  18. 804db1e9 90 nop
  19. 804db1ea 90 nop
  20. 804db1eb 90 nop
  21. 804db1ec 90 nop
  22. nt!KiSystemService:
  23. 804db1ed 6a 00 push 0x 0
  24. 804db1ef 55 push ebp

显而易见,KiFastCallEntry在做了些简单操作后,便下落(fall through)到KiSystemService函数了,也就是说,快速系统调用和使用INT 2E进行的系统调用在内核中的处理绝大部分是一样的。另外,请注意ecx寄存器,mov ecx,0x7ffe0304将其值设为0x7ffe0304,也就是SharedUserData内存区里SystemCallStub例程中ret指令的地址(参见上文的SystemCallStub代码)。在进入nt!KiSystemService之前,ecx连同其他一些参数被压入栈中。事实上,ecx用来指定SYSEXIT返回用户模式时的目标地址。当使用INT 2E进行系统调用时,由于INT n指令会自动将中断发生时的CS和EIP寄存器压入栈中,当中断处理例程通过执行iretd返回时,iretd指令会使用栈中保存的CS和EIP值返回合适的位置。因为sysenter指令不会向栈中压入要返回的位置,所以sysexit指令必须通过其他机制知道要返回的位置。这便是压入ECX寄存器的原因。通过反汇编KiSystemCallExit2例程,我们可以看到在执行sysexit指令之前,ecx寄存器的值又从栈中恢复出来了。


  
  1. kd> u nt!KiSystemCallExit l 20
  2. nt!KiSystemCallExit:
  3. 804db3b4 cf iretd
  4. nt!KiSystemCallExit 2:
  5. 804db3b5 5a pop edx
  6. 804db3b6 83c 408 add esp, 0x 8
  7. 804db3b9 59 pop ecx
  8. 804db3ba fb sti
  9. 804db3bb 0f 35 sysexit
  10. nt!KiSystemCallExit 3:
  11. 804db3bd 59 pop ecx
  12. 804db3be 83c 408 add esp, 0x 8
  13. 804db3c1 5c pop esp
  14. 804db3c2 0f 07 sysret

以上代码中包含了3个从系统调用返回的例程,即KiSystemCallExit、KiSystemCallExit2和KiSystemCallExit3,它们分别对应于使用INT 2E、sysenter和syscall发起的系统调用,如表2-2所示。

表2-2 系统调用(略)

图2-5展示了使用sysenter/sysexit指令对进行系统调用的完整过程(以调用ReadFile服务为例)。

 

图2-5 快速系统调用(针对IA-32处理器)


 

 

格物

下面通过一个小的实验来加深大家对系统调用的理解。首先启动WinDBG程序,选择File → Open Crash Dump,然后选择本书实验文件中的dumps\w732cf4.dmp文件。在调试会话建立后,先执行.symfix c:\symbols和.reload加载模块与符号,再执行k命令,便得到清单2-4所示的完美栈回溯。

第22章将详细讲解栈回溯的原理,现在大家只要知道栈上记录着函数相互调用时的参数和返回地址等信息。栈回溯是从栈上找到这些信息,然后显示出来的过程,是追溯线程执行轨迹的一种便捷方法。

清单2-4还显示了任务管理器程序(taskmgr)调用NtTerminateProcess系统服务时的执行过程。栈回溯的结果包含4列,第一列是序号,第二列是每个函数的栈帧基地址,第三列是返回地址,第四列是使用“函数名+字节偏移量”形式表达的执行位置。以00栈帧为例,它对应的函数是著名的蓝屏函数KeBugCheckEx,它的栈帧基地址是9796fb9c,它的返回地址是82b1ab51,翻译成符号便是PspCatchCriticalBreak+0x71。

清单2-4 完美栈回溯


  
  1. # ChildEBP RetAddr
  2. 00 9796fb 9c 82b 1ab 51 nt!KeBugCheckEx+ 0x 1e
  3. 01 9796fbc 0 82a 6daa 8 nt!PspCatchCriticalBreak+ 0x 71
  4. 02 9796fbf 0 82a 605b 6 nt!PspTerminateAllThreads+ 0x 2d
  5. 03 9796fc 24 8287c 87a nt!NtTerminateProcess+ 0x 1a 2
  6. 04 9796fc 24 77da 7094 nt!KiFastCallEntry+ 0x 12a
  7. 05 001df 4dc 77da 68d 4 ntdll!KiFastSystemCallRet
  8. 06 001df 4e 0 76193c 82 ntdll!NtTerminateProcess+ 0xc
  9. 07 001df 4f 0 00bf 57b 9 KERNELBASE!TerminateProcess+ 0x 2c
  10. 08 001df 524 00bf 67ec taskmgr!CProcPage::KillProcess+ 0x 116
  11. 09 001df 564 00bebc 96 taskmgr!CProcPage::HandleWMCOMMAND+ 0x 10f
  12. 0a 001df 5d 8 76abc 4e 7 taskmgr!ProcPageProc+ 0x 275
  13. 0b 001df 604 76ad 5b 7c USER 32!InternalCallWinProc+ 0x 23
  14. 0c 001df 680 76ad 59f 3 USER 32!UserCallDlgProcCheckWow+ 0x 132
  15. 0d 001df 6c 8 76ad 5be 3 USER 32!DefDlgProcWorker+ 0xa 8
  16. 0e 001df 6e 4 76abc 4e 7 USER 32!DefDlgProcW+ 0x 22
  17. 0f 001df 710 76abc 5e 7 USER 32!InternalCallWinProc+ 0x 23
  18. 10 001df 788 76ab 5294 USER 32!UserCallWinProcCheckWow+ 0x 14b
  19. 11 001df 7c 8 76ab 5582 USER 32!SendMessageWorker+ 0x 4d 0
  20. 12 001df 7e 8 74e 94601 USER 32!SendMessageW+ 0x 7c
  21. 13 001df 808 74e 94663 COMCTL 32!Button_NotifyParent+ 0x 3d
  22. 14 001df 824 74e 944ed COMCTL 32!Button_ReleaseCapture+ 0x 113
  23. 15 001df 884 76abc 4e 7 COMCTL 32!Button_WndProc+ 0xa 18
  24. 16 001df 8b 0 76abc 5e 7 USER 32!InternalCallWinProc+ 0x 23
  25. 17 001df 928 76abcc 19 USER 32!UserCallWinProcCheckWow+ 0x 14b
  26. 18 001df 988 76abcc 70 USER 32!DispatchMessageWorker+ 0x 35e
  27. 19 001df 998 76ab 41eb USER 32!DispatchMessageW+ 0xf
  28. 1a 001df 9bc 00be 16fc USER 32!IsDialogMessageW+ 0x 588
  29. 1b 001dfdac 00be 5384 taskmgr!wWinMain+ 0x 5d 1
  30. 1c 001dfe 40 76bbed 6c taskmgr!_initterm_e+ 0x 1b 1
  31. 1d 001dfe 4c 77dc 377b kernel 32!BaseThreadInitThunk+ 0xe
  32. 1e 001dfe 8c 77dc 374e ntdll!__RtlUserThreadStart+ 0x 70
  33. 1f 001dfea 4 00000000 ntdll!_RtlUserThreadStart+ 0x 1b

仔细观察清单2-4中的地址部分,很容易看出用户空间和内核空间的分界,也就是在栈帧04和栈帧05之间。栈帧05中的KiFastSystemCallRet函数属于ntdll模块,位于用户空间。栈帧04中的KiFastCallEntry函数属于nt模块,位于内核空间。栈帧04的基地址是9796fc24,属于内核空间;栈帧05的基地址是001df4dc,属于用户空间。它们分别来自这个线程的内核态栈和用户态栈。WinDBG的k命令穿越两个空间,遍历两个栈,显示出线程在用户空间和内核空间执行的完整过程,能产生如此完美的栈回溯显示了WinDBG的强大。


2.6.4 逆向调用

前文介绍了从用户模式进入内核模式的两种方法,通过这两种方法,用户模式的代码可以“调用”位于内核模式的系统服务。那么内核模式的代码是否可以主动调用用户模式的代码呢?答案是肯定的,这种调用通常称为逆向调用(reverse call)。

简单来说,逆向调用的过程是这样的。首先内核代码使用内核函数KiCallUserMode发起调用。接下来的执行过程与从系统调用返回(KiServiceExit)类似,不过进入用户模式时执行的是NTDll.DLL中的KiUserCallbackDispatcher。而后KiUserCallbackDispatcher会调用内核希望调用的用户态函数。当用户模式的工作完成后,执行返回动作的函数会执行INT 2B指令,也就是触发一个0x2B异常。这个异常的处理函数是内核模式的KiCallbackReturn函数。于是,通过INT 2B异常,CPU又跳回内核模式继续执行了。


  
  1. lkd> !idt 2b
  2. Dumping IDT:
  3. 2b: 8053d 070 nt!KiCallbackReturn

以上是使用WinDBG的!idt命令观察到的0x2B异常的处理函数。

2.6.5 实例分析

下面通过一个实际例子来进一步展示系统调用和逆向调用的执行过程。清单2-5显示了使用WinDBG的内核调试会话捕捉到的记事本进程发起系统调用进入内核和内核函数执行逆向调用的全过程(栈回溯)。

清单2-5 记事本进程从发起系统调用进入内核和内核函数逆向调用的全过程


  
  1. kd> kn
  2. # ChildEBP RetAddr
  3. 00 0006fe 94 77fb 4da 6 USER 32!XyCallbackReturn
  4. 01 0006fe 94 8050f 8ae ntdll!KiUserCallbackDispatcher+ 0x 13
  5. 02 f 4fc 19b 4 80595d 2c nt!KiCallUserMode+ 0x 4
  6. 03 f 4fc 1a 10 bf 871e 98 nt!KeUserModeCallback+ 0x 87
  7. 04 f 4fc 1a 90 bf 8748d 4 win 32k!SfnDWORD+ 0xa 0
  8. 05 f 4fc 1ad 8 bf 87148d win 32k!xxxSendMessageToClient+ 0x 174
  9. 06 f 4fc 1b 24 bf 8714d 3 win 32k!xxxSendMessageTimeout+ 0x 1a 6
  10. 07 f 4fc 1b 44 bf 8635f 6 win 32k!xxxSendMessage+ 0x 1a
  11. 08 f 4fc 1b 74 bf 84a 620 win 32k!xxxMouseActivate+ 0x 22d
  12. 09 f 4fc 1c 98 bf 87a 0c 1 win 32k!xxxScanSysQueue+ 0x 828
  13. 0a f 4fc 1cec bf 87a 8ad win 32k!xxxRealInternalGetMessage+ 0x 32c
  14. 0b f 4fc 1d 4c 804da 140 win 32k!NtUserGetMessage+ 0x 27
  15. 0c f 4fc 1d 4c 7ffe 0304 nt!KiSystemService+ 0xc 4
  16. 0d 0006feb 8 77d 43a 21 SharedUserData!SystemCallStub+ 0x 2
  17. 0e 0006febc 77d 43c 95 USER 32!NtUserGetMessage+ 0xc
  18. 0f 0006fed 8 010028e 4 USER 32!GetMessageW+ 0x 31
  19. 10 0006ff 1c 01006c 54 notepad!WinMain+ 0xe 3
  20. 11 0006ffc 0 77e 814c 7 notepad!WinMainCRTStartup+ 0x 174
  21. 12 0006fff 0 00000000 kernel 32!BaseProcessStart+ 0x 23

根据执行的先后顺序,最下面一行(帧#12)对应的是进程的启动函数BaseProcessStart,而后是编译器生成的进程启动函数WinMainCRTStartup,以及记事本程序自己的入口函数WinMain。帧#0f表示记事本程序在调用GetMessage API进入消息循环。接下来GetMessage API调用Windows子系统服务的残根函数NtUserGetMessage。从第2列的栈帧基地址都小于0x800000000可以看出,帧#12~#0d都是在用户模式执行的。帧#0d执行我们前面分析过的SystemCallStub,而后(帧#0c)便进入了内核模式的KiSystemService。KiSystemService根据系统服务号码,将调用分发给Windows子系统内核模块win32k中的NtUserGetMessage函数。

帧#0a~#05表示内核模式的窗口消息函数在工作。帧#07~#05表示要把一个窗口消息发送到用户态。帧#04的SfnDWORD表示在将消息组织好后调用KeUserModeCallback函数,发起逆向调用。帧#02表明在执行KiCallUserMode函数,帧#01表明已经在用户模式下执行,这两行之间的部分过程没有显示出来。同样,帧#01 和帧#00 之间执行用户模式函数的过程没有完全体现出来。XyCallbackReturn函数是用于返回内核模式的,它的代码很简单,只有如下几条指令。


  
  1. USER32! XyCallbackReturn:
  2. 001 b: 77d44168 8b442404 mov eax,dword ptr [esp+ 4] ss: 0023: 0006fe84= 00000000
  3. 001 b: 77d4416c cd2b int 2Bh
  4. 001 b: 77d4416e c20400 ret 4

第1行把用户模式函数的执行结果赋给EAX寄存器,第2行执行INT 2B指令。执行过INT 2B后,CPU便转去执行异常处理程序KiCallbackReturn,回到了内核模式。

本文摘自《软件调试(第2版)卷2:Windows平台调试(上、下册)》

 

本书是国内当前集中介绍软件调试主题的权威著作。本书第2卷分为5篇,共30章,主要围绕Windows系统展开介绍。第一篇(第1~4章)介绍Windows系统简史、进程和线程、架构和系统部件,以及Windows系统的启动过程,既从空间角度讲述Windows的软件世界,也从时间角度描述Windows世界的搭建过程。第二篇(第5~8章)描述特殊的过程调用、垫片、托管世界和Linux子系统。第三篇(第9~19章)深入探讨用户态调试模型、用户态调试过程、中断和异常管理、未处理异常和JIT调试、硬错误和蓝屏、错误报告、日志、事件追踪、WHEA、内核调试引擎和验证机制。第四篇(第20~25章)从编译和编译期检查、运行时库和运行期检查、栈和函数调用、堆和堆检查、异常处理代码的编译、调试符号等方面概括编译器的调试支持。第五篇(第26~30章)首先纵览调试器的发展历史、工作模型和经典架构,然后分别讨论集成在Visual Studio和Visual Studio(VS)Code中的调试器,最后深度解析WinDBG调试器的历史、结构和用法。

本书理论与实践结合,不仅涵盖了相关的技术背景知识,还深入研讨了大量具有代表性的技术细节,是学习软件调试技术的珍贵资料。


转载:https://blog.csdn.net/epubit17/article/details/109895116
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场