分享
 
 
 

浅析Windows NT/2000环境切换

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

浅析Windows NT/2000环境切换

WebCrazy(tsu00@263.net)

注:本文最初见于www.nsfocus.com

本文假设您已经了解Windows NT/2000系统体系,对Windows NT/2000内部KPEB/KTEB等数据结构与内核工作方式已有一定的概念,对80x86保护模式,Intel/AT&T格式汇编语言有过学习,能熟练使用SoftICE for Windows NT,且曾经接触过Microsoft Visual Studio及其附带工具,翻阅过Linux内核代码,如果您对这些方面不甚了解,请自行参阅相关书籍。

环境切换(Context Switch)牵涉到很多方面的内容,本文仅对与其有关的几个数据进行详细的讨论,并给出取得这些数据的部分程序段,还列出Windows 2000的少量环境切换代码。另外文中讨论的系统内部数据均未来自Microsoft官方文档,在Windows NT/2000的下个版本甚至目前各版本间均会有差别,所以我尽量详细的将文中所涉及的软硬件列于下面,所有因硬件体系、软件版本不同等因素引起的差异,请自行根据您的情况予以调整。

⊙ x86平台单处理机Windows 2000 Server Build 2195

⊙ Numega SoftICE 4.05 for Windows NT/2000 Build 334

⊙ Linux 2.0.30内核

⊙ Datarescue IDA 4.0.4.362

⊙ Microsoft Visual Studio 6.0 SP3

⊙ Windows 2000 DDK

80x86产生的环境切换有以下几种可能:

1.当前任务执行一个FAR CALL或JMP指令,而选择器指向一个TSS描述符或一个任务门。

2.当前任务执行IRET指令返回先前任务,IRET只在EFLAGS寄存器中的NT位置1时产生切换。

3.发生一个中断或异常情况,并且IDT项是个任务门。

Linux内核中有如下代码:

/*

/usr/src/linux/include/asm-i386/system.h

仅列出单处理器实现代码

*/

#define switch_to(prev,next) do { \

__asm__("movl %2,"SYMBOL_NAME_STR(current_set)"\n\t" \

"ljmp %0\n\t" \

"cmpl %1,"SYMBOL_NAME_STR(last_task_used_math)"\n\t" \

"jne 1f\n\t" \

"clts\n" \

"1:" \

: /* no outputs */ \

:"m" (*(((char *)&next->tss.tr)-4)), \

"r" (prev), "r" (next)); \

/* Now maybe reload the debug registers */ \

.

.

.

} \

} while (0)

这段代码使用了上面讨论的第一种情况。

众所周知,Linux是个开放源代码的操作系统,而Microsoft则没如此“大方”,但我们仍能对其进行些逆向工程,可喜的是网上目前已经有很多人对此有过研究,现摘录Mark Russinovich部分成果(http://www.sysinternals.com/tips.htm):

//

// NT's main

// NTOSKRNL main

//

int main( boot parameters )

{

//

// Fire up NT!

//

KiSystemStartup();

return 0;

}

从中可看出ntoskrnl.exe(PE格式,可方便的使用反汇编工具进行分析)是NT OSLOADER真正调用内核的开始,其对文件对象(File)、作业对象(Job)、进程对象(Process)、线程对象(Thread)、纤程对象(Fiber)、文件映射对象(FileMapping)、事件对象(Event)、互斥对象(Mutex)、信号对象(Semaphore)等许多内核对象进行管理,其也负责线程调度,内存管理,进程间通信等所有操作系统功能,让它们协调工作,我们要讨论的线程切换代码也在此模块中。

用IDA等对ntoskrnl.exe进行反汇编所得的结果,其分析的工作量恐怕大家都是可想而知的。在我们讨论Windows 2000环境切换详细代码前,还是先让我们看看以下几个重要的与环境切换有关的系统数据:

1 进程Context

进程Context是指80x86在保护模式下内存分页机制中当前进程的页目录所在的物理地址,其存放在系统CR3寄存器中,在Windows 2000中所处的位置为KPEB偏移后18h处,看看SoftICE的输出结果吧(限于篇幅,我对输出结果进行了删减,但仍对重要数据进行注解,应注意的是与您当前运行的程序等系统环境密切相关,随机性很强,下同):

:cpu //显示当前cpu的寄存器值

Processor 00 Registers

----------------------

CS:EIP=0008:80069582 SS:ESP=0010:8046FD98

EAX=8046BDF0 EBX=FFDFF000 ECX=FFDFF878 EDX=0000BA5A

ESI=8046BDF0 EDI=8046BB60 EBP=FFDFF800 EFL=00000213

DS=0023 ES=0023 FS=0030 GS=0000

CR0=8000003B PE MP TS ET NE PG

CR2=76EE18EC

CR3=00030000

|

|_当前进程的CR3

CR4=000002D1 VME PSE MCE PGE

.

.

.

:proc idle

Process KPEB PID Threads Pri User Time Krnl Time Status

*Idle 8046BB60 0 1 0 00000000 0000BA5A Running

| |

| |_Idle进程的KPEB

|_系统中当前进程(SoftICE中用不同颜色突出,且前面有个*)

:dd 8046bb60+18 l 4 //dd Idle's KPEB+18h

0010:8046BB78 00030000 00000000 00000000 00000000 ................

|

|_Idle进程Context

:addr

CR3 LDT Base:Limit KPEB Addr PID Name

00030000 FE4E1C60 0008 System

02D59000 FF8E6540 0090 smss

01D41000 FF8E17E0 00AC csrss

00686000 FE51BAE0 00C0 winlogon

0095D000 FF8A7AE0 00DC services

0276E000 FF8A5D60 00E8 lsass

00394000 FF881020 0180 svchost

02CAE000 FF884020 01A4 SPOOLSV

00882000 FF85B560 01D0 msdtc

02993000 FF83F020 0238 svchost

00D2F000 FF83D760 024C llssrv

0063A000 FF837860 0274 regsvc

02EFA000 FF6ECD60 0318 dfssvc

00A5E000 FF823A20 0328 inetinfo

03612000 FF6AF860 0384 explorer

003A2000 FF68E460 03B4 internat

003A7000 FF68CD60 0130 OSA

008C1000 FF6769A0 03E8 svchost

01BAA000 FF65A020 01C0 cmd

00822000 FF86C960 038C conime

03362000 FF6B3540 0388 notepad

*00030000 8046BB60 0000 Idle

|

|__当前进程Context,是不是与上一命令输出结果一致。

可以用同样的方法进行再次进行验证。

2.Context Switches Times 线程已被操作系统调度次数

当每次操作系统调用线程时,都会将这个值加一的。Visual Studio所附的工具Spy++,在Thread窗口中Thread Properties中Context Switches指出系统中该线程已调度的次数(Spy++只有在Windows NT/2000中运行时才会显示出这个值,9x中则没有)。Switch Times在系统中所处的位置在KTEB的偏移4ch处。

:thread idle

TID Krnl TEB StackBtm StkTop StackPtr User TEB Process(Id)

0000 8046BDF0 8046D040 80470040 8046FD90 00000000 Idle(00)

:dd 8046bdf0+4c l 4

0010:8046BE3C 0000E778 00000000 00000002 00000000 x...............

|

|_指出当前Idle线程已经被系统调用0E778h(十进制59256)次了。用e命令改改再看看Spy++的输出结果!

3.线程所属进程的KPEB与进程名 //分别位于KTEB+44h与KPEB+1fch处

具体见我在《再谈Windows NT/2000内部数据结构》(Nsfocus Magazine 11)一文所述。

应该指出的是,以上讨论只是针对Windows 2000 Server Build 2195的,如果您的系统不是的话或想知道如何得到这些值的具体位置,请参阅我以下的叙述的方法:

通过找突破口,正像上面我所描述的Context Switches Times在Spy++中显示的一样,然后可以用逆向工程法,我也是用这个方法来取得这个具体位置的。

举个例子吧!我曾经对SoftICE for NT中的addr命令输出结果(包含进程Context)来自何处感到困惹,也曾经在国外的一些著名的新闻组中提问过,不过至今仍没人应答(可能是我的英文水平太差,人家看不懂什么意思吧!)

addr命令输出结果见上。

后来无奈之下我还是想到CR3(存放进程页目录物理地址的寄存器)应与特定的进程有关,其应该存放在KPEB结构中(实际上的确是这样的)。而如果真是这样的话,不是只要枚举(Enum)出系统中所有的KPEB,则能得到所有的CR3值(当然前提是找出其相对KPEB的偏移值),相应的使用我在《再谈Windows NT/2000内部数据结构》(Nsfocus Magazine 11)的方法就可以取出所有进程的进程名了吗?(PID也是一样的)。

我在通过分析PSAPI.DLL中枚举系统进程的函数后(EnumProcesses等),发现系统启动后的第一个进程system的KPEB是存放在ntoskrnl.exe导出的PsInitialSystemProcess指出的地址处的,而系统中各个KPEB由一链表联结着,至于链表的定义在Windows 2000 DDK中的ntdef.h中如下定义的:

typedef struct _LIST_ENTRY {

struct _LIST_ENTRY *Flink;

struct _LIST_ENTRY *Blink;

} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

有了KPEB,跟踪相应的代码(这段反汇编代码我将在下面列出),就能找出偏移地址18h处的CR3值。

以上操作,在Windows 9x中可由Kernel32.dll中序号为1的Undocumented函数(NONAME,Softice Export列表中显示为ORD_0001)实现,但因为Windows 9x与NT/2000的内核的不同,Softice for 9x与NT的addr命令输出的格式也完全不同。至于在Windows NT/2000中不知道是否有现成的函数可以得到结果,至少现在我也没找到,这可是题外话。

因为上面的叙述还是比较抽象,我还是将SoftICE的输出结果列于此,更利于理解:

:dd PsInitialSystemProcess l 4

0008:8046A844 FE4E1C60 E1000968 00000000 00000000 `.N.h...........

|

|_System进程的KPEB

:dd @PsInitialSystemProcess+18 l 4

0008:FE4E1C78 00030000 00000000 00000000 00000000 ................

|

|_System进程的Context

:dd @PsInitialSystemProcess+9c l 4 //9ch是PID相对KPEB的位置

0008:FE4E1CFC 00000008 FF8E65E0 8046A180 00000000 .....e....F.....

|

|_System进程PID

:dd @PsInitialSystemProcess+1fc l 10 //1fch是Process Name相对KPEB的位置

0008:FE4E1E5C 74737953 00006D65 00000000 00000000 System..........

|

System进程Process Name_|

:? @(@PsInitialSystemProcess+a0)-a0

//计算System指向的下一个进程的KPEB,0a0h是链状结构相对KPEB的偏移

FF8E6540 4287522112 (-7445184) "?巈@"

|

|_System进程KPEB指向的下一个KPEB,从Process Name可知为smss.exe(见下)

:dd @(@PsInitialSystemProcess+a0)-a0+18 l 4

0008:FF8E6558 02D59000 02D5A000 00000000 00000000 ................

|

|_smss.exe的进程Context

:dd @(@PsInitialSystemProcess+a0)-a0+9c l 4

0008:FF8E65DC 00000090 FF8E1880 FE4E1D00 00000518 ..........N.....

|

|_smss.exe的PID

:dd @(@PsInitialSystemProcess+a0)-a0+1fc l 10

0008:FF8E673C 73736D73 6578652E 00000000 00000000 smss.exe........

|

smss.exe进程Process Name_|

:dd @(@(@PsInitialSystemProcess+a0))-a0+18 l 4

.

. (可以用Softice用同样的方法一直跟踪到链表结束)

.

实现代码段如下:

/*

由于以下代码段均要求获取系统数据结构,即要求在Ring 0状态下运行,所

以必须位于NT/2000设备驱动程序中。因设备驱动程序的架构,决定的代码的长

度较长,此处仅列出关键代码段,您可以找本WDM的书,将此程序段置入您的代码中,

或是直接联系我(tsu00@263.net)。

*/

.

.

.

PLIST_ENTRY KPEBListHead, KPEBListPtr; //PLIST_ENTRY定义见上

ULONG KPEBListOffset=0xa0; //定义链表相对KPEB的偏移值

ULONG ProcessNameOffset=0x1fc; //定义ProcessName相对KPEB的偏移值

ULONG ProcessContextOffset=0x18; //定义Process Context相对KPEB的偏移值

ULONG PIDOffset=0x9c; //定义PID相对KPEB的偏移值

if(((USHORT)NtBuildNumber)!=2195){

DbgPrint("Only test on Windows 2000 Server Build 2195!\n");

return;

}

DbgPrint("\n CR3\t\tKPEB Addr\tPID\t Name");

DbgPrint("\n ---\t\t-------- \t---\t ----\n");

KPEBListHead=KPEBListPtr=(PLIST_ENTRY)(((char *)PsInitialSystemProcess)+KPEBListOffset);

while (KPEBListPtr->Flink!=KPEBListHead) {

void *kpeb;

char ProcessName[16];

ULONG ProcessContext;

ULONG PID;

//取KPEB

kpeb=(void *)(((char *)KPEBListPtr)-KPEBListOffset);

//取ProcessName

memset(ProcessName, 0, sizeof(ProcessName));

memcpy(ProcessName, ((char *)kpeb)+ProcessNameOffset, 16);

//取Process Context

ProcessContext=*(ULONG *)(((char *)kpeb)+ProcessContextOffset);

//取PID

PID=*(ULONG *)(((char *)kpeb)+PIDOffset);

//向Debugger输出结果

DbgPrint(" %08X\t%08X\t%04X\t %s\n",ProcessContext, kpeb,PID,ProcessName);

//指向下一链表

KPEBListPtr=KPEBListPtr->Flink;

}

.

.

.

使用Checked方式编译运行后调试器输出结果如下(俨然就是一个最底层的EnumProcesses实现方法):

CR3 KPEB Addr PID Name

--- -------- --- ----

00030000 FE4E1C60 0008 System

02D59000 FF8E7920 0090 smss.exe

003C1000 FE520520 00AC csrss.exe

026C6000 FE51A020 00A8 winlogon.exe

03209000 FF8A8D60 00DC services.exe

.

. 略

.

我之所以花如此大的篇幅去讲述进程Context的获得,似乎与环境切换的主题不相一致,主要是由于Windows NT/2000的封闭性,我觉得真正要明白环境切换,Linux平台就可以比较容易理解,在NT中重要是知道如何取得些与此有关的重要数据结构,然后再与x86平台体系结构联系在一起,就能更好的帮助自己理解。使用上面所述的类似方法,还可以找出很多KPEB/KTEB重要信息,如进程优先级(KPEB+62h)、进程在内核态与用户态所使用的时间(KPEB+38h与KPEB+3ch)、线程ID(KTEB+1e4h)等等,可与linux的task_struct等结构比较比较。

好了谈了这么多,我还是简单说说Windows 2000中的环境切换代码吧。

那么Windows NT/2000什么情况下发生环境切换呢?曾见过一DDK FAQ中是这样描述的:

Q:What are the causes of a context switch in Windows NT?

A:There are only two ways that a thread context is switched.

1.The thread yields it's quantum by blocking on something(event,semaphore,etc.).

2.The time period is up.This is caused by a timer interrupt.

KiDispatchInterrupt是NT/2000中定时进行环境切换例程,以下列出其部分代码:

;Linux中实现类似功能的代码位于/usr/src/linux/kernel/sched.c

00403A58 KiDispatchInterrupt proc near

00403A58

00403A58 var_C = dword ptr -0Ch

00403A58 var_8 = dword ptr -8

00403A58 var_4 = dword ptr -4

00403A58

00403A58 mov ebx, ds:0FFDFF01Ch

00403A5E lea eax, [ebx+800h]

00403A64 cli

00403A65 cmp eax, [eax]

00403A67 jz short loc_403A86

00403A69 push ebp

00403A6A push dword ptr [ebx]

00403A6C mov dword ptr [ebx], 0FFFFFFFFh

00403A72 mov edx, esp

00403A74 mov esp, [ebx+81Ch]

00403A7A push edx

00403A7B mov ebp, eax

00403A7D call sub_460BA4

00403A82 pop esp

00403A83 pop dword ptr [ebx]

00403A85 pop ebp

.

.(限于篇幅,此处略去部分,感兴趣的自己步步跟踪)

.

;另CR3切换代码:

;Linux中实现此功能的代码位于/usr/src/linux/include/asm-i386/pgtable.h

;由宏定义SET_PAGE_DIR实现,请参考之。

;此时EDI存储KPEB(自己用SoftICE跟跟),执行后EAX则为进程Context

;这句结合mov cr3,eax是不是可以跟踪到CR3在KPEB的具体位置的呢,我就是从这儿跟踪到的。

00403B87 mov eax, [edi+18h]

00403B8A mov ebp, [ebx+40h]

00403B8D mov ecx, [edi+30h]

00403B90 mov [ebp+1Ch], eax

00403B93 mov cr3, eax ;EAX->CR3

00403B96 mov [ebp+66h], cx

00403B9A xor eax, eax

00403B9C cmp [edi+20h], ax

;转去错误处理,必要时还会调用KeBugCheck,出现可怕的蓝屏死机.

00403BA0 jnz short loc_403BCE

;LDTR置空选择器

00403BA2 lldt ax

00403BA5 lea ecx, [ecx]

00403BA7

00403BA7 loc_403BA7: ; CODE XREF: .text:00403B7Dj

00403BA7 ; .text:00403BFAj

;将Context Switches Times加一

00403BA7 inc dword ptr [esi+4Ch]

00403BAA inc dword ptr [ebx+5C0h]

00403BB0 pop ecx

00403BB1 mov [ebx], ecx

00403BB3 cmp byte ptr [esi+49h], 0

00403BB7 jnz short loc_403BBD

00403BB9 popf

00403BBA xor eax, eax

00403BBC retn

00403BBD loc_403BBD: ; CODE XREF: .text:00403BB7j

00403BBD popf

00403BBE jnz short loc_403BC3

00403BC0 mov al, 1

00403BC2 retn

00403BC3 loc_403BC3: ; CODE XREF: .text:00403BBEj

00403BC3 mov cl, 1

00403BC5 call ds:HalRequestSoftwareInterrupt

00403BCB xor eax, eax

00403BCD retn

.

.(略)

.

部分代码我尚未进行注解,主要是一些代码与代码运行环境有关,如处理NT执行体的错误检查(包括有效性、安全性等),而且我这儿也未列出代码,如果你有兴趣的话用SoftICE步步跟踪可以发现很多NT内部机制。

Windows 2000是抢占式多线程操作系统,文中并未涉及到线程调度的具体方法。真正线程调度切换,还要考虑很多因素,如线程状态(是否可调度等)、线程优先级(Priority)、线程的亲缘性(Affinity)等,这些具体的重要信息,也都由ntoskrnl.exe模块处理,我也不可能都详细的在此列出。本文只讨论如何获得这信息,不过若想知道得更多,仍可以根据文中的讨论对其进行进一步的分析。

我曾经接触过单片机,也曾经对其似乎从头开始设计一个OS(可能只是几条指令,单片机高手千万别见笑)感到不解,但当我接触过NT Kernel后才觉得自己是多么的可笑。不过在接触NT内核时,可大量参照Linux代码,毕竟她们原理应该是一样的,虽然Linux不是一个微内核OS,而NT/2000是。个人认为linux的task_struct与NT的KPEB,linux中的Bottom half机制与NT的DPC(延时过程调用)等有其相似的地方(虽然在机制上实现方法上仍有很大的不同)。由于Microsoft未提供任何官方文档且NT内核的复杂性(曾有人批评NT的微内核比Linux还要大呢),本文所讨论的,我也不能保证其绝对的正确性,如果您发现任何错误之处或是有什么建议,请予以告之,谢谢!

参考资料:

1.Jeffrey Richter

<<Programming Applications for Microsoft Windows,Fourth Edition>>

2.Linux相关文档

3.Mark Russinovich相关文档

4.Intel Corp<<Intel Architecture Software Developer's Manual,Volume 3>>

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有