Windows 95 System Programming SECRENTS学习笔记(三)

类别:编程语言 点击:0 评论:0 推荐:

进程(Process)

首先让我们看看,Windows中关于进程的定义,以下内容引自《Windows核心编程》:

进程通常被定义为一个正在运行的程序的实例,它由两部分组成:

l         一个是操作系统用来管理进程的内核对象(K32对象)。内核对象也是系统用来存放关于进程统计信息的地方。

l         另一个是地址空间,它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如,线程堆栈和堆分配空间。

进程是不活波的。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,该线程负责执行包含在进程地址空间中的代码。实际上,单个进程可能包含多个线程,所有这些线程都“同时”执行进程地址空间中的代码。此外,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,系统会自动创建它的第一个线程,称为主线程。然后,该线程可以创建其他的线程,而这些线程又能创建更多的线程。

 

需要提及的一点时,一个进程中的线程肯定比进程要先结束,不会存在一个不包含任何线程的进程。

 

进程其实就是一大堆对象的拥有权的集合。也就是说,进程拥有对象。进程可以拥有内存(更确切地说是拥有memory context),可以拥有file handle,可以拥有线程,可以拥有一串的DLL模块(被加载至该进程的地址空间中)

 

需要注意一点,进程并不代表执行(线程才是)。进程也不是EXE文件。在被加载器载入之前,一个EXE文件只不过是一个程序。只有被载入之后,Windows才为其产生一个进程和一个主线程。

 

一旦Windows产生一个进程,它也会产生一个memory context,以容纳进程的线程在其中执行。此外,Windows也产生第一个线程,即主线程,用来执行进程本身。如有必要,进程可以再产生线程。系统还会产生一个file handle table,进程可以在其中持有一些打开的文件。最后,也是最重要的。Windows产生一个process database(即进程内核对象,也叫做K32对象),用以表现该进程。

 

Process database是一种K32对象,内含与进程有关的大量信息。稍后我们将详细讨论Process Database(PDB)的详细结构。Process database所使用的内存来自KERNEL32 Heap,因此所有的Process Database都可以被其他进程看到。

 

Process database含有一系列的线程、一系列的被载入模块,预设于Process Heap中的handle、指向process handle table的指针、以及指向memory context的指针。此外还有更多的东西。

 

什么是Process Handle?什么是Process ID?

在继续前进之前,我需要先澄清常感困惑的process handle和process Ids。两个看起来类似的Win32函数:GetCurrentProcess和GetCurrentProcessId迷惑了不少程序设计人员。事实上其差异很容易分辨。

 

Process handle基本上和file handle一样。它是一个不被明了的数值,你不能够说它是任何东西的指针。系统内部事实上是把K32对象的handle当作process handle table的索引。利用该索引从process handle table中获得的,才是一个K32对象指针。然后,由于应用程序不需要直接处理handle table,所以,process handle是没有用的。

 

记住,因为每一个应用程序有它自己的handle table,所以不同的进程在各自的地址空间中拥有相同的process handle是绝对可能的。例如,正常而言每个进程都有一个process handle代表自己,其值是1。也就是说,process handle并不是可以赖以判别进程的数据。另一个例子是:如果程序为自己这个进程打开另一个process handle,那么就有两个handle,对应同一个进程。

 

在GetCurrentProcess函数的伪代码中你可以确定,process handle并不是可以用来判别进程的数据。

GetCurrentProcess函数的伪代码:

// Normally this function does nothing. It appers to be there

// for the benefit of the KERNEL32 developers

x_LogSomeKernelFunction( function number fro GetCurrentProcess );

return 0x7FFFFFFF;

是的,忽略掉“它调用其他函数”的事实,GetCurrentProcess只不过是固定返回0x7FFFFFFF罢了。不管谁调用该函数,他都获得0x7FFFFFFF。0x7FFFFFFF是一个魔术数字,KERNEL32把它解释为“是用当前的进程”。面对0x7FFFFFFF,那些需要process handle的KERNEL32函数会将其替换为当前的process handle。这需要更多证据以证明process handle只在自己的context中才有作用吗?我想不需要了!

 

现在让我们看看process IDs,早期的Windows 95把process database的基地址当作一个process ID。由于process database位于共享的内存中,所以process database的基地址保证决不会相同。

补充:

    在Windows 98/NT/2000/XP 等后续版本中,Process IDs的具体实现是否还如此,不得而知。

 

在Windows 95的新版中,KERNEL32小组改变了GetCurrentProcessId的实现方式:

GetCurrentProcessId函数的虚拟代码:

x_LogSomeKernelFunction( function number for GetCurrentProcessId );

return PDBToPid( ppCurrentProcess );

 

    再一次,如果我们忽略掉它调用其他函数的话,GetCurrentProcessId只不过是把一个全局变量ppCurrentProcess传给PDBToPid函数。让我们停下来对这里多了解一些,因为它对于后续章节十分重要。ppCurrentProcess是一个指针的指针,指向当前的process database。以C语言来说,就是**ppCurrentProcess指向当前的process database。

 

之所以需要再次间接指向,基于一个让人困惑的原因,在第六章我会提到。现在,你只要记住ppCurrentProcess是KERNEL32.DLL中的全局变量,允许KERNEL32搜寻当前进程的process database即可。为了尽量简化事情,我将在虚拟代码中使用ppCurrentProcess,我假设它是一个指向process database的指针,而不是一个指针的指针。

 

好,如果KERNEL32由一个指针,指向当前的process database,为什么GetCurrentProcessId不直接把它传回来呢?我们看看PDBToPid做了些什么:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PDBToPid函数的虚拟代码:

// Parameters:

// PROCESS_DATABASE* ppDB

if ( ObsfucatorDWORD == FALSE )

{

   _DebugOut( “PDBToPid() Called too early! Obsfucator not yet initialized!” );

   return 0;

}

 

if ( ppDB & 1 )

{

   _DebugOut( “PDBToPid: This PDB looks like a PID (0%1xh) Do a stack trace BEFORE reporting as bug.” );

}

 

// Here’s the key! XOR the obsfucator DWORD with the process database

// pointer to make the PID value

return ppDB^ObsfucatorDWORD;

 

哦,这是真的吗?是的!“Obsfucator”一词竟然赤裸裸的出现在微软的二进制代码中。除了检查传进来的是一个合法的process database指针之外,PDBToPid的基本行为就是将当前的process database指针和ObsfucatorDWORD两者做XOR。这个企图非常明显,微软希望阻绝刺探系统内部数据结构的行为。

 

如果你惊讶ObsfucatorDWORD来自何处,你会狼狈的发现它在每次系统启动时动态产生出来。这又为系统作了一层更好的保护。事实上不只是process database被保护,thread database也被保护了。稍后我会在GetCurrentThreadId函数中展示给你看,GetCurrentThreadId和GetCurrentProcessId有近乎神秘的相似程度。

 

让我做个总结,一个process handle就像一个file handle。在其所属进程之外别无意义。至于一个process ID,则是在各进程之间独一无二不会冲突的数值。它是一个指针,指向process database结构。

 

如果你看过TOOLHELP32的Process32First和Process32Next两个函数,可能你会注意PROCESSENTRY32结构中的th32ProcessID成员。这和GetCurrentProcessID传回的东西有关系吗?幸运的是,答案是Yes。

 

Windows 95 Process Database(PDB)

Windows 95的每一个process dabase 都是一块从KERNEL32 Heap中分配而来的内存。KERNEL32通常以PDB缩写代表Process Database。每一个PDB就是一个第一个字节为5(K32OBJ_PROCESS)的K32对象。让我们仔细来查看一下各个成员:

 

 

 

00h DWORD Type

此值必须为5(K32OBJ_PROCESS)

 

04h DWODD cReference

引用计数(reference count),也就是此PDB被使用的次数

补充:

    使用CloseHandle可以将一个K32对象的引用计数减一

 

08h DWORD un1

此成员真实意义未知。似乎总是0

 

0ch DWORD someEvent

这是一个指向K32OBJ_EVENT对象的指针。Event对象用于WaitForSingleObject这样的函数。

补充:

   当用WaitForSingleObject等待一个进程结束时,就用到了此Event。

 

10h DWORD TerminationStatus

当你是用GetExitCodeProcess,返回的就是这个值。所谓退出代码(exit code)就是main或WinMain的返回值。它也可以被ExitProcess或TerminateProcess指定。当一个进程还在执行时,此成员值为0x103(STILL_ACTIVE)。

 

14h DWORD un2

此成员真实意义未知,似乎总是0。

 

18h DWORD DefaultHeap

默认进程私有堆的地址。GetProcessHeap返回的就是这个值。

 

1Ch DWORD MemoryContext

一个指针,指向进程的memory context。所谓memory context,内含page directory mapping,用以提供进程在4GB地址空间中的私人区域。在第5章对memory context有更多地描述。

 

20h DWORD flags

 

24h DWORD pPSP

这个值是此进程之DOS PSP的线性地址。Win16和Win32程序都会设定此成员。此线性地址总是在1MB(实模式DOS代码所能访问的最高地址)之下。请参考28h结构成员。

 

28h WORD PSPSelector

这是一个selector,指向此进程的DOS PSP。Win16和Win32程序都有DOS PSP。请参考24h结构成员。

 

2Ah WORD MTEIndex

这里内含一个全局模块表(pModuleTableArray)的索引值。可通过该索引值取出此进程对应的IMTE。

 

2Ch WORD cThreads

此位置记录此进程拥有的线程个数

 

2Eh WORD cNotTermThreads

此位置记录属于进程所拥有而尚未结束的线程个数。

 

30h WORD un3

此位置真实意义未知,似乎总是0。

 

32h WORD cRing0Threads

此位置记录由VMM32.VXD管理的ring0线程个数。对于一般程序而言,其值应该与cThreads相同。然而在KERNEL32.DLL之中,此值比cThreads多1。

 

34h HANDLE HeapHandle

此位置是一个Heap Handle,此Heap内含属于该进程的表格(或其他什么东西)。这里记录的总是KERNEL32的shared heap handle。

 

38h HTASK W16TDB

这是一个selector,指向进程相关的Win16 Task Database(TDB)。Win16和Win32程序都有TDB selector,并且维护一个合法的TDB。

 

3Ch DWORD MemMapFiles

一个指针,指向“该进程所使用的内存映射文件所组成的链表”中的第一个节点。每个内存映射文件都是该链表中的一个节点。该节点的格式是:

DWORD 内存映射文件的基地址

DWORD 指向下一个节点,或者为0

 

40h PENVIRONMENT_DATABASE pEDB

一个指针,指向environment database。Environment database内含当前的子目录、环境变量、进程命令行参数、标准Handle(例如,stdin)、以及其他项目。我将在[Environment Database]一节中详细描述其格式。

 

44h PHANDLE_TABLE pHandleTable

一个指针,指向process handle table。所有的handles都在这里面,包括file handles、eventhandles、process handles等等。在DOS/Win16环境中的对等物是DOS的System File Table(SFT)。

 

48h struct _PROCESS_DATABASE* ParentPDB

一个指针,指向父进程的PROCESS_DATABASE。对一般程序而言父进程是Windows Explorer(资源管理器)。MSGSRV32则又是Explorer和initial service processes的父进程。

 

4Ch PMODREF MODREFList

此位置指向进程的模块链表的表头。这也就是前面提到的MODREFs链表。

 

50h DWORD ThreadList

一个指针,指向该进程所拥有的线程的链表。目前,我还不知道该链表的真正格式。

 

54h DWORD DebuggeeCB

这是一个被调试程序的context。当一个进程处于调试状态时,此位置则指向2GB以上的一个区域,该区域包含一个指针,指向被调式者的process database。

 

58h DWORD LocalHeapFreeHead

这个指针指向该进程默认Heap的自由区块链表的表头。第5章将详细描述其格式。

 

5Ch DWORD InitialRing0ID

此位置意义未知,似乎总是0。

 

60h CRITICAL_SECTION crst

这个位置是一个CRICICAL_SECTION,被各种API用来同步控制同一进程中的各个线程。稍后会在许多虚拟代码中你会看到这个cricital section的作用。

 

78h DWORD un4[3]

这三个DWORD真实意义未知,似乎总是0。

 

84h DWORD pConsole

如果这个进程是用console(也就是这是个控制台模式的程序),此位置即指向一个用于输出的console对象(K32OBJ_CONSOLE)。

 

88h DWORD tIsInUseBits1

这个32位单元表示低位的32个TLS(Thread Local Storage)的索引。如果某位被设立,表示对应的TLS索引被用掉了。每个TLS索引都不断累加其值,例如:

TLS index: 0 = 0x00000001

TLS index: 1 = 0x00000002

TLS index: 2 = 0x00000004

稍后将专有一节讨论TLS

 

8Ch DWORD tIsInUseBits2

这个DWORD表示TLS之中第32-63个索引的状态。

 

90h DWORD ProcessDWORD

此位置意义未知。有一个未公开函数(GetProcessDword)可以获取该位的值。

 

94h struct _PROCESS_DATABASE* ProcessGroup

此位置要么为0,要么就指向一个“进程群”中的首要进程。所谓“进程群”是一群进程,彼此呼应。当一个群组被销毁时,其中的所有进程也一并被销毁。注意,每一个进程都认为自己在自己的“进程群”之中,所以实际上此位置指向进程自己的PDB。如果进程处于调试状态,它就属于调试器“进程群”。

 

98h DWORD pExeMODREF

此位置指向EXE的MODREF。一般而言,EXE的MODREF是模块链表中的表头,所以这个位置通常和位置4Ch吻合,除非进程又通过LoadLibrary或LoadModule载入了其他DLL。

补充:

LoadModule函数时为了兼容Win16下的代码而保留下来的,在Windows NT/2000/XP及其后续版本中,不应再使用,建议采用CreateProcess替换之。

 

9Ch DWORD TopExcFilter

这个DWORD内存放进程的“Top Exeception Filter”。如果进程没有安装任何异常处理例程,那么就使用此位所指向的那个。这个例程可以通过SetUnhandledExceptionFilter函数来指定。结构化异常将在稍后讨论。

 

A0h DWORD BasePriority

这个DWORD存放的是进程的基本优先级。Windows 95支持32个优先级,分为四个等级:

  Idle       4

  Normal    8

  High      13

  Realtime   18

 

A4h DWORD HeapOwnList

这个位置指向“进程所用的heaps形成的链表”。默认情况下,每个进程只有一个Heap,可通过GetProcessHeap获取。然而进程也可以调用HeapCreate产生另一个Heap。这些Heaps都放在该链表中。第5章将对此主题有较多的叙述。

 

A8h DWORD HeapHandleBlockList

Heap中的可移动块是通过moveable handle table来管理。每个heap对应一个table。许多个这样的table则形成了一个链表。本位置指向该链表的表头。第5章对于moveable handle table有较多的叙述。

 

ACh DWORD pSomeHeapPtr

本位置的真正意义不十分明确。通常它是0,如果不是0那么就是一个指针,指向该进程的默认堆的moveable handle table。前参看A8h位。

 

B0h DWORD pConsoleProvider

此位置要么为0,要么就是一个指针,指向KERNEL32的控制台对象(K32OBJ_CONSOLE)。对于Win32 Console程序而言该位似乎总是为0。

 

B4h WORD EnvironSelector

这是一个selector,指向进程的环境块。这个selector的基地址和Environment Database的pszEnvironment相同。

 

B6h WORD ErrorMode

这个位置内含由SetErrorMode设定的数值。KERNEL32的SetErrorMode会下移(thunk down)至KRNL386的同名函数,所以这个位置反映了Win16错误模式代码。它们可能是:

0

SEM_FAILCRITICALERRORS

SEM_NOALIGNMENTFAULTEXCEPT

SEM_NOGPFAULTERRORBOX

SEM_NOOPENFILEERRORBOX

 

B8h DWORD pevtLoadFinished

这个位置指向KERNEL32的Event Object(K32OBJ_EVENT)。当进程创建完成后,此Event即被激活。

 

BCh WORD UTState

此位置意义不明。通常是0。

 

 

GetExitCodeProcess和IgetExitCodeProcess

GetExitCodeProcess取得进程的结束状态。它需要一个hProcess参数。函数的主要功能其实只是确认第二个参数是否为合法的指针。真正的动作则交给IgetExitCodeProcess去做。后者利用hProcess寻找对应的指针(指向PROCESS_DATABASE)。由于hProcess是一个handle,所以上述动作意味着先利用索引进入进程的handle table,再取出进程指针。x_CounvertHandleToK32Object负责“增加process database的引用计数”等。

 

有了PROCESS_DATABASE指针,函数就可以取出TerminationStatus成员的值,并将其存放在调用者指定的一个缓冲区中。IGetExitCodeProcess会减少process database的引用计数,并留下“must complete”状态。

 

GetExitCodeProcess虚拟代码:

// Parameters

//   HANDLE   hProcess;

//   LPDWORD  lpdwExitCode;

 

Set up structured exception handling frame

 

if ( lpdwExitCode )          // If a non-null pointer was passed, verify

  EAX = *lpdwExitCode;    // that the DWORD it points to can be written.

 

Remove structured exception handling frame

Goto IgetExitCodeProcess;

 

 

 

 

 

IGetExitCodeProcess虚拟代码:

// Parameters

//   HANDLE  hProcess;

//   LPDWORD  lpdwExitCode;

// Local:

//   PROCESS_DATABASE ppdb;

//   BOOL               retValue;

retValue = TRUE;   // Assume successful return

 

x_EnterMustComplete();  // Prevent us from being interrupted.

// Increments ptdbx->MustCompleteCount.

x_LogSomeKernelFunction( function number for GetExitCodeProcess );

 

// Get a pointer to the PROCESS_DATABASE struct

ppdb = x_ConvertHandleToK32Object( hProcess, 0x80000010, 0 );

 

if ( ppdb )

{

     // Save away exit status

     *lpdwExitCode = ppdb->TerminationStatus;

     x_UnuseObjectWrapper( ppdb );  // Decrement usage count

}

else

{

     retValue = FALSE;

}

 

// Call the API logging function again (???)

x_LogSomeKernelFunction( function number for GetExitCodeProcess );

 

LeaveMustComplete();  // Decrements ptdbx->MustCompleteCount

return retValue;

}

 

 

 

 

 

 

 

 

 

 

 

SetUnhandledExceptionFilter

此函数设定KERNEL32的UnhandledExceptionFilter函数地址—后者将在没有其他异常情况过滤器(exception filter)被用来处理异常情况时使用。这个函数把TopExcFilter的当前值保存在process database中,然后以参数中的值取代,并返回原先的值。

SetUnhandledExceptionFilter虚拟代码:

// Parameters:

//   LPTOP_LEVEL_EXCEPTION_FILTER   lpTopLevelExceptionFilter

// Locals:

//   LPTOP_LEVEL_EXCEPTION_FILTER  prevValue

 

// Save old value

prevValue = ppCurrentProcess->TopExcFilter;

 

// Stuff in new value

ppCurrentProcess->TopExcFilter = lpTopLevelExceptionFilter;

 

return prevValue;  // Return old value

 

本文地址:http://com.8s8s.com/it/it24037.htm