wangchao.org
注册 | 登录 | 添加收藏 | 订阅该频道
 
商城汽车珠宝健康家饰女性王朝网络游戏互联网探索下载娱乐学院
 
数码 | 旅游 | 美容 | 母婴 | 家电 | 美食 | 景区 | 养生 | 手机 | 购车 | 首饰 | 美妆 | 装修 | 厨房 | 科普 | 动物 | 植物 |  | 百态 | 编程 | 商品 | 财经 | 信息 | 军事
  
 
当前位置: 王朝网络 >> system >> Windows 95 System Programming SECRENTS学习笔记(四)
 

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

字体: ||
  OpenProcess
  此函数要求一个process ID作为参数,并返回一个process handle。Process handle随后被交给像ReadProcessMemory或VirtualQueryEx之类的函数。而你知道,TOOLHELP32有能力给你任何进程的process ID。因此,如果你能组合这两股力量,你就可以大有作为。奇怪的是Windows 95允许你打开一个process handle却不允许你打开一个thread handle。或许微软认为线程一旦打开会造成很大破坏,无法承担。
  OpenProcess首先把process ID转换为一个PROCESS_DATABASE指针。转换process ID为process database指针的算法和转换thread ID为thread database指针的算法完全相同。接下来参数转换来的指针会被检查。最后,OpenProcess调用一个内部函数,在当前进程的handle table中分配一块空间,并把PROCESS_DATABASE指针存放进去。
  OpenProcess虚拟代码:
  // Parameters:
  // DWORD fdwAccess;
  // BOOL fInherit;
  // DWORD IDProcess;
  // Locals:
  // RPOCESS_DATABASE ppdb;
  // DWORD flags;
  x_LogSomeKernelFunction( function number for OpenProcess );
  // Convert the process ID to a PROCESS_DATABASE
  ppdb = PidToPDB( IDProcess );
  if ( !ppdb )
   return 0;
  if ( ppdb->Type != K32OBJ_PROCESS ) // Make sure thread ID not passed.
  {
   InternalSetLastError( ERROR_INVALID_PARAMETER );
   return 0;
  }
  flags = fdAccess & 0x001FFFBF; // Turn off all non-allowed flags.
   // flags like PROCESS_QUERY_INFORMATION
   // and PROCESS_VM_WRITE are allowed.
  if ( fInherit )
   flags |= 0x80000000;
  flags |= PROCESS_DUP_HANDLE; // Always pass. PROCESS_DUP_HANDLE
  // Allocate a new slot in the handle table of the current process.
  // The slot contains the ppdb pointer.
  return x_OpenHandle( ppCurrentProcess, ppdb, flags );
  SetFileApisToOEM
  这个函数改变与文件名有关的KERNEL32函数对于文件名的解释方式。默认情况下KERNEL32使用ANSI字符串作为文件名。如果调用了SetFileApisToOEM,就可以改用OEM字符串。请参考前面提到的GetModuleFileName和GetModuleHandle两个函数。
  本函数的内部实现并不简单,他截获一个指向当前process database的指针,并把设置fFileApisAreOem标志。
  Environment Database
  Process database的40h成员中是一个指针,指向一个重要的数据结构,内含与进程相关的数据。KERNEL32内部称此指针为pEDB,我把它解释为“pointer to Environment Database”。就像对待PROCESS_DATABASE一样。我在PROCDB.H中描述了ENVIRONMENT DATABASE的结构布局,如下所示:
  typedef struct _ENVIRONMENT_DATABASE
  {
  PSTR pszEnvironment; // 00h Pointer to Environment
  DWORD un1; // 04h
  PSTR pszCmdLine; // 08h Pointer to command line
  PSTR pszCurrDirectory; // 0Ch Pointer to current directory
  LPSTARTUPINFOA pStartupInfo;// 10h Pointer to STARTUPINFOA struct
  HANDLE hStdIn; // 14h Standard Input
  HANDLE hStdOut; // 18h Standard Output
  HANDLE hStdErr; // 1Ch Standard Error
  DWORD un2; // 20h
  DWORD InheritConsole; // 24h
  DWORD
  BreakType; // 28h
  DWORD
  BreakSem; // 2Ch
  DWORD
  BreakEvent; // 30h
  DWORD
  BreakThreadID; // 34h
  DWORD
  BreakHandlers; // 38h
  } ENVIRONMENT_DATABASE, *PENVIRONMENT_DATABASE;
  现在我们来看看这些成员的具体含义:
  00h PSTR pszEnvironment
  这个位置指向进程的环境区。所谓环境区是标准的DOS环境(形式如string = value; string =value)。进程环境块是一块内存,位于每个进程私有的地址空间中,通常就是模块被载入的地址之上。
  04h DWORD un1
  此位置意义未明。通常总是0。
  08h PSTR pszCmdLine
  此成员内含CreateProcess函数中的命令行参数内容。大部分情况下这个命令行是一个完整的EXE文件名。有时候它会指向空字符串(0)。
  0Ch PSTR pszCurrDirectory
  此成员指向当前的磁盘目录
  10h LPSTARTUPINFOA pStartupInfo
  这是一个指针,指向进程的STARUPINFOA结构(定义在WINBASE.H中)。STARTUPINFOA结构是CreateProcess的参数之一,可用来指定窗口的大小、标题、标准的file handles等等。这个成员所指的是该结构的一个副本。
  14h HANDLE hStdIn
  这是一个file handle,进程用它作为标准的输入设备。如果没有找到(例如一个GUI程序),此值为-1。
  18h HANDLE hStdOut
  这是一个file handle,进程用它作为标准的输出设备。如果没有找到(例如一个GUI程序),此值为-1。
  1Ch HANDLE hStdErr
  这是一个file handle,进程用它作为标准的错误输出设备。如果没有找到(例如一个GUI程序),此值为-1。
  20h DWORD un2
  此成员意义未明。通常是1。
  24h DWORD InheritConsole
  从名称可以推测,此成员表示进程是否继承自Console程序。请参考CreateProcess函数的CREATE_NEW_CONSOLE标志。在我的观察中,此成员的值总是0。
  28h DWORD BreakType
  这个成员最可能用来指示console event(例如 Ctrl+C)如何处理。在我所执行过的程序中,它通常为0,偶尔会是0xA。
  2Ch DWORD BreakSem
  通常是0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个KERNEL32 semaphore object(K32OBJ_SEMAPHORE)。
  30h DWORD BreakEvent
  通常为0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个KERNEL32 Event Object(K32OBJ_EVENT)。
  34h DWORD BreakThreadID
  通常为0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个线程对象(K32OBJ_THREAD),而该线程正是安装此处理例程的线程本身。
  38h DWORD BreakHandles
  通常是0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个从KERNEL32 Shared Heap中分配得来的数据结构,存放一系列安装好的主控台控制函数(console control handler)。
  现在让我们看看这些函数的虚拟代码。这次是与EVNIRONMENT_DATABAS有关。
  GetCommandLineA
  其实这个函数没有太多东西可以说。它返回命令行指针,命令行字符串存放在environment database中。
  GetCommandLineA的虚拟代码:
  return ppCurrentProcess->pEDB.pszCmdLine;
  GetEnvironmentStrings
  该函数返回与environment database相关的指针。值得注意的事,这个函数的真正代码和SDK说明文件之间有两个差异。
  SDK文件上说:
  当GetEnvironmentStrings被调用时,它会分配一块内存作为一个环境区。当此环境区不再需要时,它应该调用FreeEnvironmentStrings。
  这对于Windows NT是成立的。但对Windows 95却不正确。
  FreeEnvironmentStringA
  这个函数比较有趣些。由于在Windows 95下GetEnvironemntStingA并不真正分配内存,所以其实也没有什么是FreeEnvironmentStirngA必须作的事情。然而,也许纯粹是为了消遣,这个函数检查其字符串参数,看看其是否吻合environment database中的环境区指针。如果不吻合,FreeEnvironmentStringA会将LastError值设定为ERROR_INVALID_PARAMTER。
  GetStdHandle
  这个函数和你所能想象的一样直接。给它一个DeviceID(stdin、stdou、stderr等)这个函数会返回对应的file handle。如果你给的是一个冒牌的DeviceID,此函数会失败,并设定LastError代码。
  GetStdHandle函数的虚拟代码:
  // Parameters:
  // DWORD fdwDevice
  // Locals:
  // PENVIRONMENT_DATABASE pEDB
  pEDB = ppCurrentProcess->pEDB;
  if ( fdwDevice == STD_INPUT_HANDLE )
   return pEDB->hStdIn;
  else if ( fdwDevice == STD_OUTPUT_HANDLE )
   return pEDB->hStdOut;
  else if ( fdwDevice == STD_ERROR_HANDLE )
   retrun pEDB->hStdErr;
  InternalSetLastError( ERROR_INVALID_FUNCTION );
  Return 0xFFFFFFFF;
  SetStdHandle
  这个函数比GetStdHandle有趣一些。它首先验证handle的确代表一个合法的KERNEL32对象。怎么做呢?请x_ConvertHandleToK32Object代劳。后者会返回一个指针,指向对应的KERNEL32对象—如果handle合法的话。SetStdHandle从不使用K32对象指针,简单的NULL检验是唯一需要做的动作。在检验过hHandle参数的合法性之后,其余函数代码把hHandle塞进environment database结构的适当位置中去。
  
  Process Handle Tables
  PROCESS_DATABASE的44h偏移处是一个指针,指向进程的handle table。我将使用handle一词代表可以从handle table中取得的东西。除了file handle,Windows 95还会产生其他的系统对象的handle,如进程对象、线程、事件、Mutex等等。
  Handle的内容理论上来讲是不透明的,也就是说handle本身没有办法告诉你它究竟代表什么东西。如果它的值是5,你判断不出这是一个file handle还是一个mutex handle。然而,一但你了解Windows 95进程的handle table,你就可以轻易的将一个handle值和其引用到的数据产生关系。
  Windows 95进程的handle table结构十分简单。第一个DWORD放的是这个表格的最大容量(项目个数)。此初始值为0x30(48)。然而这并不意味着进程最多只能有48个打开的handle。当进程需要更多的handles时,KERNEL32会重新分配一块内存,使表格有成长空间。每次增加0x10(16)个handles。似乎并没有明显的上限。我写了一个小程序,不断打开file handles,在超过255个handles之后仍然很好—255是DOS的限制。
  第一个DWORD之后,是由许多结构所组成的数组。每一个结构都由两个DWORD构成:
  DWORD flags
  DWORD pK32Object
  其中第二个DWORD是一个指针,指向17种可能的K32对象。至于第一个DWORD则是此对象的access control flags。这些标志的意义与对象是很种类型有关。对于一个K32OBJ_PROCESS对象,这些标志将是PROCESS_xxx(定义在WINNT.H中),像是PROCESS_TERMNATE、PROCESS_VM_READ等等。
  进行到这里,也许你已经可以感觉到handle是什么东西了。如果你猜测handle是一个索引,指向进程的handle table,你对了!一但这么认为,你就很容易把一个handle值比对其所引用的KERNEL32对象类型。一个没有用的handle,其两个DWORD一定都填满0。当程序分配一个新的handle,KERNEL32就使用handle table中的第一个空白项的索引作为handle。但浏览进程的handle table并不是微软建议的程序动作。
  补充内容:
  以下内容来自《Windows核心编程》第三章
  当一个进程被初始化时,系统会为其分配一个句柄表。该句柄表只用于K32对象(即内核对象),不用于用户对象或GDI对象。句柄表的详细结构和管理方法并没有具体的资料说明。但作为一个合格的Windows程序员,必须懂得如何管理进程的句柄表。由于这些信息没有文档资料,因此不能保证所有的详细信息都是正确无误的。下图显示了进程的句柄表的样子,可以看到,它只是个数据结构的数组,每个结构都包含一个指向K32对象(即内核对象)的指针、一个访问屏蔽和一些标志。

  当进程被初始化时,它的句柄表是空的。然后,当进程中的线程调用创建K32对象的函数时,比如CreateFileMapping,KERNEL32就为该对象分配一块内存,并进行初始化。这些,KERNEL32对该进程的句柄表进行扫描,找出一个空项。将其指针成员初始化为K32对象的内存地址,访问屏蔽设置为全部访问权,同时,各个标志也作了相应设置。
  下面列出一些用于创建K32对象(即内核对象)的函数(并不是完整的列表):
  l CreateThread
  l CreateFile
  l CreateFileMapping
  l CreateSemaphore
  l CreateEvent
  l CreateMutex
  这些创建K32对象的函数返回与调用进程相关的句柄,这些句柄可以被在相同进程中运行的任何线程使用。该句柄值实际上就是放入进程的句柄表中的索引,它用于标识K32对象的存放位置。因此,当条是一个应用程序并观察K32对象句柄的实际值时,会看到一些较小的值,如1、2等。请记住,句柄的含义并没有记入文档资料,并且可能随时变更。实际上在Windows 2000种,返回的值用于标识放入进程的句柄表的该对象的字节数,而不是索引号本身。
  再次强调一下,由于句柄值实际上是K32对象在进程句柄表中的索引,因此这些句柄是与进程相关的,并且不能由其他进程使用。
  如果创建一个K32对象失败了,那么返回的句柄值通常是0(NULL)。发生此种情况是因为系统的内存非常短缺,或者遇到了安全方面的问题。不过有少数函数在运行时失败时返回的句柄值是-1(INVALID_HANDLE_VALUE)。例如,如果CreateFile未能打开指定的文件,那么它将返回INVALID_HANDLE_VALUE,而不是NULL。当察看创建K32对象的函数的返回值时,必须格外小心。特别要注意的是,只有当调用CreateFile函数时,才能将其返回值与INVALID_HANDLE_VALUE进行比较。下面的代码是不正确的:
  HANDLE hMutex = CreateMutex(….);
  if ( hMutex == INVALID_HANDLE_VALUE )
  {
   // We will never execute this code because CreateMutex returns NULL if it fails。
  }
  关闭K32对象时,需要调用CloseHandle函数来进行。该函数首先检查调用进程的句柄表,以确保传递给它的索引(句柄)用于标识一个进程有权访问的对象。如果该所引有效,那么系统就可以获取该K32对象的指针,并可确定该对象的引用计数是否为0,如果是,该K32对象就会被KERNEL32销毁,同时收回其占用的内存。
  如果将一个无效的句柄值传递给CloseHandle,将会出现两种情况之一:如果进程运行正常,CloseHandle返回FALSE,而GetLastError则返回ERROR_INVALID_HANDLE。如果进程处于调试状态,系统将通知调试程序,以便进行除错。
  在CloseHandle返回之前,它会清除进程的句柄表中的项目,该句柄现在对你的进程已经无效,不应该试图再去使用它。无论K32对象是否已经撤销,都会发生清除句柄表项目的操作。当调用CloseHandle汉书之后,将不再拥有对K32对象的访问权。不过,如果该对象的引用计数没有递减为0,那么该K32对象仍将存在
  如果忘记调用CloseHandle函数,那么会出现内存泄漏吗?答案是可能,但不是一定。在进程运行时,进程可能会泄漏资源(如K32对象)。但是,当进程结束时,操作系统能确保该进程使用的任何资源都被释放,这是有保证的。对于K32对象来说,系统将执行下列操作:当进程终止运行时,系统会自动扫描该进程的句柄表。如果该表中拥有任何在终止进程运行前没有关闭的对象,系统将关闭这些对象的句柄。如果这些K32对象的引用计数将为0,那么该K32对象将被系统收回。
  需要记住的是,K32对象的生命周期至少和其代表的对象一样长,有时会远远长于其代表的对象。比如,进程K32对象,当一个进程结束时,其对应的K32对象的引用计数将递减1,如果此时还有别的进程在使用该K32对象,则其并不会被销毁。
  操作句柄的一些函数:
  l GetHandleInformation
  l SetHandleInformation
  l CloseHandle
  l DuplicateHandle
  Thread(线程)
  你也经看过模块和进程,只要再看过线程,就可以完成整个KERNEL32基础结构之旅。进程主要是表达对file handles、地址空间等的拥有权,线程则主要表达对模块中代码执行的事实。你看,有这么多的东西相互关联,我很难把什么东西从另一个东西中完全的抽出来。例如在前面讨论进程时,我必须先提到线程和同步控制对象。
  从抽象层面来说,线程是一种方便的表达方式,让你的某一部分代码执行—当其他部分的代码正在等待某些外部事件发生时。将进程的各项工作进一步分配给线程之后,你似乎可以消除像“pooling loop”这样的动作。Pooling loop浪费了许多CPU时间。
  任何时候,线程可能处于三种状态之一。第一种是:执行中状态(running state)。这个时候CPU寄存器内容就是该线程的寄存器的值。
  第二种是:准备执行(read to run state)。这种状态下的线程没有什么理由不会被执行—只是早晚问题。它终有一刻能够控制CPU。
  第三种是:阻塞状态(blocked state)。线程如果被阻塞,表示其正在等待某件事情发生。在那之前CPU调度器不会安排该线程执行起来。引起执行中的线程阻塞的东西称之为同步控制对象(synchronization objects)。Windows的同步控制对象有:Critical Sections、Event、Semaphores、Mutexes四种。
  关于同步对象的基本功能和运用,参考Jeffrey Richter的《Advanced Windows 3rd》或《Windows核心编程》,还有一本书《Win32多线程程序设计》也非常不错。本书架设你知道同步控制对象的存在,并且知道如何运用它们。
  最初,每个进程都以一个主线程开始。如果需要,进程可以产生更多线程,使CPU可以在同一时间执行进程中不同区段的代码(在多CPU环境下,可实现真正的并发执行,在单CPU环境下,实际上在同一时刻还是只有一个线程在执行)。标准的例子就是文字处理软件。当文字处理软件需要打印时,它把打印工作交给另一个线程,让主线程依然能够对使用者的动作有所回应。
  当然,如果你熟悉CPU的基础结构,你就会知道,对于只有一颗CPU的机器来说不可能同时执行两个线程。“许多线程同时执行”的幻觉是靠VMM(Virtual Memory Manage虚拟内存管理)对线程的调度实现的。它使用一个硬件计时器和一组复杂的规则,在不同的线程之间快速切换(常见的CPU调度方式有:时间片轮转算法、多级反馈队列调度算法,详细内容参考讲解操作系统原理的教材)。
  微软宣告Windows 95的时间片(timeslice)是20毫秒(milliseconts)。也就是说,如果不考虑其他因素(例如线程优先级),每个线程执行20毫秒,然后切换到别的线程执行。我将在[Thread Priority线程优先级]一节中说的更详细些。不过我得先声明,本书不打算深入讨论线程调度和VMM线程调度器。就像同步控制对象一样,这些主题应该留待另一本书讨论。
  和进程一样,线程是一块从KERNEL32共享内存中分配而来的内存块来表现出来的。这块内存保存有所有必要的数据,让KERNEL32用来维护一个线程。虽然我说“所有必要的数据”,实际上这块内存中有一些指针指向其他结构,不过你懂得我的意思就好。这块内存在本书中被称为Thread Database或TDB(注意,在不同的时间,微软分别使用TDB代表Task DataBase和Thread Database两种意义)。就像Process database一样,Thread Database也是一个K32对象,它的第一个DWORD值为6,表示这是一个K32OBJ_THREAD对象。
  如果你是一个高级程序员,能够改写DDK或使用Wdeb386或SoftIce/W,你可能遭遇过另一个与线程有关的数据结构,名为THCB(Thread Control Block)。THCB是线程在ring0中的表现形式。在Windows 95中,线程表现为ring0和ring3两份数据结构。Ring0级的代码如VMM VXD、WDM(Windows 2000 or Later)都通过THCB来处理线程。Ring3级的代码如KERNEL32则通过Thread Database来处理线程。本章描述ring3级线程的行为和机制,并不打算涵盖ring0一级。
  补充:
  如果假设微软的Windows NT/2000是一种微内核结构的操作系统,可否认为,在系统内核(ring0)和用户层面(ring3)分别有两种不同但却相关的控制机制,比如,在OS教材中,提到过PCB(Process Control Block)代表一个进程,而本书则认为Process Database表示一个进程,套用本书中对Thread Database和Thread Control Block的解释,是否可认为PDB是进程在ring3的表示,而PCB是进程在ring0的表示?操作系统如此做的真实意义是什么?这二者之间是否存在某种对应关系?
  对于像SoftIce这样的软件,必定会与ring0级的这些结构打交道,仔细研究这些对我们认识Windows将大有帮助。
  线程本身拥有一些东西。第一样东西是一组寄存器(register set)。正如我前面说过的,线程要么是在执行,要么就是并为执行(这不是废话吗?呵呵)。当线程正在执行,它的寄存器集合将被放到CPU的寄存器中,也就是说线程的EIP值就是CPU寄存器EIP的值。当线程不在执行状态,它的寄存器必须存放在内存的某处。因此,每个线程有一个指针指向一块内存块,线程的寄存器内容就存放在那里。
  与每个线程有关系的另一样东西是进程。进程中的所有线程共享进程的每一样东西。例如,进程拥有memory context和一个私有的地址空间,所以其中的所有线程都在相同的地址空间中运行。进程有一个handle table,用来管理文件、控制台(console)、内存映射文件(memory mapped file)、Events等等,进程中的所有线程也共享这些handles。如果hande 3代表一个内存映射文件,则进程中任何一个线程都可以使用handle 3来使用这个内存映射文件。
  线程还拥有许多其它东西。每个线程都有一个专用的堆栈、一个专用的消息队列,一个专用的Thread Local Storage(TLS)以及一个专用的结构化异常处理链(如果你不知道后两个是什么,别急,稍后我会介绍它们)。此外,线程在执行过程中可能会请求、释放同步控制对象的拥有权。在看过Thread Database之后,我会解释这些东西。
  什么是Thread Handle?什么是Thread ID?
  本章稍早我曾说过process handle和process ID的不同。我的说明可以轻易的套到Thread handle和Thread ID身上—只要把进程改为线程就行了。如果你不确定,请回头去看看什么是Process Handle?什么是Process ID?那一节。
  GetThreadHandle返回一个常数(微软总是说那是一个“虚拟handle”),可以适用于任何真正的Thread Handle可以用的地方:
  
  GetThreadHandle函数的虚拟代码:
  x_LogSomeKernelFunction( function number for GetCurrentThread );
  return 0xFFFFFFFE;
  就像GetCurrentProcessId那样,GetCurrentThreadId返回一个指针,指向当前的thread database(但KERNEL32小组会加上一个令人迷惑的数值):
  GetCurrentThreadId函数的虚拟代码:
  return TDBToTid( ppCurrentThread );
  KERNEL32为何如此迷惑世人呢?让我们看看:
  TDBToTid函数的虚拟代码:
  // Parameters:
  // THREAD_DATABSE * ptdb
  if ( ObsfucatorDWORD == FALSE )
  {
   _DebugOut( “TDBToTid() Called too early! ObsFucator not yet initialized!” );
   return 0;
  }
  if ( ptdb & 1 )
  {
   _DebugOut( “TDBToTid: This TDB looks like a TID ( 0%1xh) Do “
   “statck trace BEFORE reporting as bug.” );
  }
  // Here’s the key! XOR the obsfucator DWORD with the thread database
  // pointer to make the TID value.
  return ptdb^ObsfucatorDWORD;
  如果你认为这一段看起来真像先前提过的PDBToPid函数,那么你是对的。KERNEL32使用同一个ObsfucatorDWORD把process database指针和thread database指针转换为IDs。一旦你了解ObsfucatorDWORD的值(并且记住微软拼错了这个字),你就可以把进程或现程的ID转化为有用的指针了。我要再说一次,这并不是被鼓励的程序行为,但是为了多了解系统的底层动作,我们没有太多选择。J(王朝网络 wangchao.net.cn)
 
标签: 95  Programming  SECRENTS  System  Windows  学习  笔记  
OpenProcess 此函数要求一个process ID作为参数,并返回一个process handle。Process handle随后被交给像ReadProcessMemory或VirtualQueryEx之类的函数。而你知道,TOOLHELP32有能力给你任何进程的process ID。因此,如果你能组合这两股力量,你就可以大有作为。奇怪的是Windows 95允许你打开一个process handle却不允许你打开一个thread handle。或许微软认为线程一旦打开会造成很大破坏,无法承担。 OpenProcess首先把process ID转换为一个PROCESS_DATABASE指针。转换process ID为process database指针的算法和转换thread ID为thread database指针的算法完全相同。接下来参数转换来的指针会被检查。最后,OpenProcess调用一个内部函数,在当前进程的handle table中分配一块空间,并把PROCESS_DATABASE指针存放进去。 OpenProcess虚拟代码: // Parameters: // DWORD fdwAccess; // BOOL fInherit; // DWORD IDProcess; // Locals: // RPOCESS_DATABASE ppdb; // DWORD flags; x_LogSomeKernelFunction( function number for OpenProcess ); // Convert the process ID to a PROCESS_DATABASE ppdb = PidToPDB( IDProcess ); if ( !ppdb ) return 0; if ( ppdb->Type != K32OBJ_PROCESS ) // Make sure thread ID not passed. { InternalSetLastError( ERROR_INVALID_PARAMETER ); return 0; } flags = fdAccess & 0x001FFFBF; // Turn off all non-allowed flags. // flags like PROCESS_QUERY_INFORMATION // and PROCESS_VM_WRITE are allowed. if ( fInherit ) flags |= 0x80000000; flags |= PROCESS_DUP_HANDLE; // Always pass. PROCESS_DUP_HANDLE // Allocate a new slot in the handle table of the current process. // The slot contains the ppdb pointer. return x_OpenHandle( ppCurrentProcess, ppdb, flags ); SetFileApisToOEM 这个函数改变与文件名有关的KERNEL32函数对于文件名的解释方式。默认情况下KERNEL32使用ANSI字符串作为文件名。如果调用了SetFileApisToOEM,就可以改用OEM字符串。请参考前面提到的GetModuleFileName和GetModuleHandle两个函数。 本函数的内部实现并不简单,他截获一个指向当前process database的指针,并把设置fFileApisAreOem标志。 Environment Database Process database的40h成员中是一个指针,指向一个重要的数据结构,内含与进程相关的数据。KERNEL32内部称此指针为pEDB,我把它解释为“pointer to Environment Database”。就像对待PROCESS_DATABASE一样。我在PROCDB.H中描述了ENVIRONMENT DATABASE的结构布局,如下所示: typedef struct _ENVIRONMENT_DATABASE { PSTR pszEnvironment; // 00h Pointer to Environment DWORD un1; // 04h PSTR pszCmdLine; // 08h Pointer to command line PSTR pszCurrDirectory; // 0Ch Pointer to current directory LPSTARTUPINFOA pStartupInfo;// 10h Pointer to STARTUPINFOA struct HANDLE hStdIn; // 14h Standard Input HANDLE hStdOut; // 18h Standard Output HANDLE hStdErr; // 1Ch Standard Error DWORD un2; // 20h DWORD InheritConsole; // 24h DWORD BreakType; // 28h DWORD BreakSem; // 2Ch DWORD BreakEvent; // 30h DWORD BreakThreadID; // 34h DWORD BreakHandlers; // 38h } ENVIRONMENT_DATABASE, *PENVIRONMENT_DATABASE; 现在我们来看看这些成员的具体含义: 00h PSTR pszEnvironment 这个位置指向进程的环境区。所谓环境区是标准的DOS环境(形式如string = value; string =value)。进程环境块是一块内存,位于每个进程私有的地址空间中,通常就是模块被载入的地址之上。 04h DWORD un1 此位置意义未明。通常总是0。 08h PSTR pszCmdLine 此成员内含CreateProcess函数中的命令行参数内容。大部分情况下这个命令行是一个完整的EXE文件名。有时候它会指向空字符串(0)。 0Ch PSTR pszCurrDirectory 此成员指向当前的磁盘目录 10h LPSTARTUPINFOA pStartupInfo 这是一个指针,指向进程的STARUPINFOA结构(定义在WINBASE.H中)。STARTUPINFOA结构是CreateProcess的参数之一,可用来指定窗口的大小、标题、标准的file handles等等。这个成员所指的是该结构的一个副本。 14h HANDLE hStdIn 这是一个file handle,进程用它作为标准的输入设备。如果没有找到(例如一个GUI程序),此值为-1。 18h HANDLE hStdOut 这是一个file handle,进程用它作为标准的输出设备。如果没有找到(例如一个GUI程序),此值为-1。 1Ch HANDLE hStdErr 这是一个file handle,进程用它作为标准的错误输出设备。如果没有找到(例如一个GUI程序),此值为-1。 20h DWORD un2 此成员意义未明。通常是1。 24h DWORD InheritConsole 从名称可以推测,此成员表示进程是否继承自Console程序。请参考CreateProcess函数的CREATE_NEW_CONSOLE标志。在我的观察中,此成员的值总是0。 28h DWORD BreakType 这个成员最可能用来指示console event(例如 Ctrl+C)如何处理。在我所执行过的程序中,它通常为0,偶尔会是0xA。 2Ch DWORD BreakSem 通常是0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个KERNEL32 semaphore object(K32OBJ_SEMAPHORE)。 30h DWORD BreakEvent 通常为0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个KERNEL32 Event Object(K32OBJ_EVENT)。 34h DWORD BreakThreadID 通常为0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个线程对象(K32OBJ_THREAD),而该线程正是安装此处理例程的线程本身。 38h DWORD BreakHandles 通常是0,但如果程序调用SetConsoleCtrlHandle,此成员就会指向一个从KERNEL32 Shared Heap中分配得来的数据结构,存放一系列安装好的主控台控制函数(console control handler)。 现在让我们看看这些函数的虚拟代码。这次是与EVNIRONMENT_DATABAS有关。 GetCommandLineA 其实这个函数没有太多东西可以说。它返回命令行指针,命令行字符串存放在environment database中。 GetCommandLineA的虚拟代码: return ppCurrentProcess->pEDB.pszCmdLine; GetEnvironmentStrings 该函数返回与environment database相关的指针。值得注意的事,这个函数的真正代码和SDK说明文件之间有两个差异。 SDK文件上说: 当GetEnvironmentStrings被调用时,它会分配一块内存作为一个环境区。当此环境区不再需要时,它应该调用FreeEnvironmentStrings。 这对于Windows NT是成立的。但对Windows 95却不正确。 FreeEnvironmentStringA 这个函数比较有趣些。由于在Windows 95下GetEnvironemntStingA并不真正分配内存,所以其实也没有什么是FreeEnvironmentStirngA必须作的事情。然而,也许纯粹是为了消遣,这个函数检查其字符串参数,看看其是否吻合environment database中的环境区指针。如果不吻合,FreeEnvironmentStringA会将LastError值设定为ERROR_INVALID_PARAMTER。 GetStdHandle 这个函数和你所能想象的一样直接。给它一个DeviceID(stdin、stdou、stderr等)这个函数会返回对应的file handle。如果你给的是一个冒牌的DeviceID,此函数会失败,并设定LastError代码。 GetStdHandle函数的虚拟代码: // Parameters: // DWORD fdwDevice // Locals: // PENVIRONMENT_DATABASE pEDB pEDB = ppCurrentProcess->pEDB; if ( fdwDevice == STD_INPUT_HANDLE ) return pEDB->hStdIn; else if ( fdwDevice == STD_OUTPUT_HANDLE ) return pEDB->hStdOut; else if ( fdwDevice == STD_ERROR_HANDLE ) retrun pEDB->hStdErr; InternalSetLastError( ERROR_INVALID_FUNCTION ); Return 0xFFFFFFFF; SetStdHandle 这个函数比GetStdHandle有趣一些。它首先验证handle的确代表一个合法的KERNEL32对象。怎么做呢?请x_ConvertHandleToK32Object代劳。后者会返回一个指针,指向对应的KERNEL32对象—如果handle合法的话。SetStdHandle从不使用K32对象指针,简单的NULL检验是唯一需要做的动作。在检验过hHandle参数的合法性之后,其余函数代码把hHandle塞进environment database结构的适当位置中去。 Process Handle Tables PROCESS_DATABASE的44h偏移处是一个指针,指向进程的handle table。我将使用handle一词代表可以从handle table中取得的东西。除了file handle,Windows 95还会产生其他的系统对象的handle,如进程对象、线程、事件、Mutex等等。 Handle的内容理论上来讲是不透明的,也就是说handle本身没有办法告诉你它究竟代表什么东西。如果它的值是5,你判断不出这是一个file handle还是一个mutex handle。然而,一但你了解Windows 95进程的handle table,你就可以轻易的将一个handle值和其引用到的数据产生关系。 Windows 95进程的handle table结构十分简单。第一个DWORD放的是这个表格的最大容量(项目个数)。此初始值为0x30(48)。然而这并不意味着进程最多只能有48个打开的handle。当进程需要更多的handles时,KERNEL32会重新分配一块内存,使表格有成长空间。每次增加0x10(16)个handles。似乎并没有明显的上限。我写了一个小程序,不断打开file handles,在超过255个handles之后仍然很好—255是DOS的限制。 第一个DWORD之后,是由许多结构所组成的数组。每一个结构都由两个DWORD构成: DWORD flags DWORD pK32Object 其中第二个DWORD是一个指针,指向17种可能的K32对象。至于第一个DWORD则是此对象的access control flags。这些标志的意义与对象是很种类型有关。对于一个K32OBJ_PROCESS对象,这些标志将是PROCESS_xxx(定义在WINNT.H中),像是PROCESS_TERMNATE、PROCESS_VM_READ等等。 进行到这里,也许你已经可以感觉到handle是什么东西了。如果你猜测handle是一个索引,指向进程的handle table,你对了!一但这么认为,你就很容易把一个handle值比对其所引用的KERNEL32对象类型。一个没有用的handle,其两个DWORD一定都填满0。当程序分配一个新的handle,KERNEL32就使用handle table中的第一个空白项的索引作为handle。但浏览进程的handle table并不是微软建议的程序动作。 补充内容: 以下内容来自《Windows核心编程》第三章 当一个进程被初始化时,系统会为其分配一个句柄表。该句柄表只用于K32对象(即内核对象),不用于用户对象或GDI对象。句柄表的详细结构和管理方法并没有具体的资料说明。但作为一个合格的Windows程序员,必须懂得如何管理进程的句柄表。由于这些信息没有文档资料,因此不能保证所有的详细信息都是正确无误的。下图显示了进程的句柄表的样子,可以看到,它只是个数据结构的数组,每个结构都包含一个指向K32对象(即内核对象)的指针、一个访问屏蔽和一些标志。 [url=http://www.wangchao.net.cn/bbsdetail_57545.html][img]http://blog.csdn.net/images/blog_csdn_net/kendiv/80753/r_proc_handle_table.jpg[/img][/url] 当进程被初始化时,它的句柄表是空的。然后,当进程中的线程调用创建K32对象的函数时,比如CreateFileMapping,KERNEL32就为该对象分配一块内存,并进行初始化。这些,KERNEL32对该进程的句柄表进行扫描,找出一个空项。将其指针成员初始化为K32对象的内存地址,访问屏蔽设置为全部访问权,同时,各个标志也作了相应设置。 下面列出一些用于创建K32对象(即内核对象)的函数(并不是完整的列表): l CreateThread l CreateFile l CreateFileMapping l CreateSemaphore l CreateEvent l CreateMutex 这些创建K32对象的函数返回与调用进程相关的句柄,这些句柄可以被在相同进程中运行的任何线程使用。该句柄值实际上就是放入进程的句柄表中的索引,它用于标识K32对象的存放位置。因此,当条是一个应用程序并观察K32对象句柄的实际值时,会看到一些较小的值,如1、2等。请记住,句柄的含义并没有记入文档资料,并且可能随时变更。实际上在Windows 2000种,返回的值用于标识放入进程的句柄表的该对象的字节数,而不是索引号本身。 再次强调一下,由于句柄值实际上是K32对象在进程句柄表中的索引,因此这些句柄是与进程相关的,并且不能由其他进程使用。 如果创建一个K32对象失败了,那么返回的句柄值通常是0(NULL)。发生此种情况是因为系统的内存非常短缺,或者遇到了安全方面的问题。不过有少数函数在运行时失败时返回的句柄值是-1(INVALID_HANDLE_VALUE)。例如,如果CreateFile未能打开指定的文件,那么它将返回INVALID_HANDLE_VALUE,而不是NULL。当察看创建K32对象的函数的返回值时,必须格外小心。特别要注意的是,只有当调用CreateFile函数时,才能将其返回值与INVALID_HANDLE_VALUE进行比较。下面的代码是不正确的: HANDLE hMutex = CreateMutex(….); if ( hMutex == INVALID_HANDLE_VALUE ) { // We will never execute this code because CreateMutex returns NULL if it fails。 } 关闭K32对象时,需要调用CloseHandle函数来进行。该函数首先检查调用进程的句柄表,以确保传递给它的索引(句柄)用于标识一个进程有权访问的对象。如果该所引有效,那么系统就可以获取该K32对象的指针,并可确定该对象的引用计数是否为0,如果是,该K32对象就会被KERNEL32销毁,同时收回其占用的内存。 如果将一个无效的句柄值传递给CloseHandle,将会出现两种情况之一:如果进程运行正常,CloseHandle返回FALSE,而GetLastError则返回ERROR_INVALID_HANDLE。如果进程处于调试状态,系统将通知调试程序,以便进行除错。 在CloseHandle返回之前,它会清除进程的句柄表中的项目,该句柄现在对你的进程已经无效,不应该试图再去使用它。无论K32对象是否已经撤销,都会发生清除句柄表项目的操作。当调用CloseHandle汉书之后,将不再拥有对K32对象的访问权。不过,如果该对象的引用计数没有递减为0,那么该K32对象仍将存在 如果忘记调用CloseHandle函数,那么会出现内存泄漏吗?答案是可能,但不是一定。在进程运行时,进程可能会泄漏资源(如K32对象)。但是,当进程结束时,操作系统能确保该进程使用的任何资源都被释放,这是有保证的。对于K32对象来说,系统将执行下列操作:当进程终止运行时,系统会自动扫描该进程的句柄表。如果该表中拥有任何在终止进程运行前没有关闭的对象,系统将关闭这些对象的句柄。如果这些K32对象的引用计数将为0,那么该K32对象将被系统收回。 需要记住的是,K32对象的生命周期至少和其代表的对象一样长,有时会远远长于其代表的对象。比如,进程K32对象,当一个进程结束时,其对应的K32对象的引用计数将递减1,如果此时还有别的进程在使用该K32对象,则其并不会被销毁。 操作句柄的一些函数: l GetHandleInformation l SetHandleInformation l CloseHandle l DuplicateHandle Thread(线程) 你也经看过模块和进程,只要再看过线程,就可以完成整个KERNEL32基础结构之旅。进程主要是表达对file handles、地址空间等的拥有权,线程则主要表达对模块中代码执行的事实。你看,有这么多的东西相互关联,我很难把什么东西从另一个东西中完全的抽出来。例如在前面讨论进程时,我必须先提到线程和同步控制对象。 从抽象层面来说,线程是一种方便的表达方式,让你的某一部分代码执行—当其他部分的代码正在等待某些外部事件发生时。将进程的各项工作进一步分配给线程之后,你似乎可以消除像“pooling loop”这样的动作。Pooling loop浪费了许多CPU时间。 任何时候,线程可能处于三种状态之一。第一种是:执行中状态(running state)。这个时候CPU寄存器内容就是该线程的寄存器的值。 第二种是:准备执行(read to run state)。这种状态下的线程没有什么理由不会被执行—只是早晚问题。它终有一刻能够控制CPU。 第三种是:阻塞状态(blocked state)。线程如果被阻塞,表示其正在等待某件事情发生。在那之前CPU调度器不会安排该线程执行起来。引起执行中的线程阻塞的东西称之为同步控制对象(synchronization objects)。Windows的同步控制对象有:Critical Sections、Event、Semaphores、Mutexes四种。 关于同步对象的基本功能和运用,参考Jeffrey Richter的《Advanced Windows 3rd》或《Windows核心编程》,还有一本书《Win32多线程程序设计》也非常不错。本书架设你知道同步控制对象的存在,并且知道如何运用它们。 最初,每个进程都以一个主线程开始。如果需要,进程可以产生更多线程,使CPU可以在同一时间执行进程中不同区段的代码(在多CPU环境下,可实现真正的并发执行,在单CPU环境下,实际上在同一时刻还是只有一个线程在执行)。标准的例子就是文字处理软件。当文字处理软件需要打印时,它把打印工作交给另一个线程,让主线程依然能够对使用者的动作有所回应。 当然,如果你熟悉CPU的基础结构,你就会知道,对于只有一颗CPU的机器来说不可能同时执行两个线程。“许多线程同时执行”的幻觉是靠VMM(Virtual Memory Manage虚拟内存管理)对线程的调度实现的。它使用一个硬件计时器和一组复杂的规则,在不同的线程之间快速切换(常见的CPU调度方式有:时间片轮转算法、多级反馈队列调度算法,详细内容参考讲解操作系统原理的教材)。 微软宣告Windows 95的时间片(timeslice)是20毫秒(milliseconts)。也就是说,如果不考虑其他因素(例如线程优先级),每个线程执行20毫秒,然后切换到别的线程执行。我将在[Thread Priority线程优先级]一节中说的更详细些。不过我得先声明,本书不打算深入讨论线程调度和VMM线程调度器。就像同步控制对象一样,这些主题应该留待另一本书讨论。 和进程一样,线程是一块从KERNEL32共享内存中分配而来的内存块来表现出来的。这块内存保存有所有必要的数据,让KERNEL32用来维护一个线程。虽然我说“所有必要的数据”,实际上这块内存中有一些指针指向其他结构,不过你懂得我的意思就好。这块内存在本书中被称为Thread Database或TDB(注意,在不同的时间,微软分别使用TDB代表Task DataBase和Thread Database两种意义)。就像Process database一样,Thread Database也是一个K32对象,它的第一个DWORD值为6,表示这是一个K32OBJ_THREAD对象。 如果你是一个高级程序员,能够改写DDK或使用Wdeb386或SoftIce/W,你可能遭遇过另一个与线程有关的数据结构,名为THCB(Thread Control Block)。THCB是线程在ring0中的表现形式。在Windows 95中,线程表现为ring0和ring3两份数据结构。Ring0级的代码如VMM VXD、WDM(Windows 2000 or Later)都通过THCB来处理线程。Ring3级的代码如KERNEL32则通过Thread Database来处理线程。本章描述ring3级线程的行为和机制,并不打算涵盖ring0一级。 补充: 如果假设微软的Windows NT/2000是一种微内核结构的操作系统,可否认为,在系统内核(ring0)和用户层面(ring3)分别有两种不同但却相关的控制机制,比如,在OS教材中,提到过PCB(Process Control Block)代表一个进程,而本书则认为Process Database表示一个进程,套用本书中对Thread Database和Thread Control Block的解释,是否可认为PDB是进程在ring3的表示,而PCB是进程在ring0的表示?操作系统如此做的真实意义是什么?这二者之间是否存在某种对应关系? 对于像SoftIce这样的软件,必定会与ring0级的这些结构打交道,仔细研究这些对我们认识Windows将大有帮助。 线程本身拥有一些东西。第一样东西是一组寄存器(register set)。正如我前面说过的,线程要么是在执行,要么就是并为执行(这不是废话吗?呵呵)。当线程正在执行,它的寄存器集合将被放到CPU的寄存器中,也就是说线程的EIP值就是CPU寄存器EIP的值。当线程不在执行状态,它的寄存器必须存放在内存的某处。因此,每个线程有一个指针指向一块内存块,线程的寄存器内容就存放在那里。 与每个线程有关系的另一样东西是进程。进程中的所有线程共享进程的每一样东西。例如,进程拥有memory context和一个私有的地址空间,所以其中的所有线程都在相同的地址空间中运行。进程有一个handle table,用来管理文件、控制台(console)、内存映射文件(memory mapped file)、Events等等,进程中的所有线程也共享这些handles。如果hande 3代表一个内存映射文件,则进程中任何一个线程都可以使用handle 3来使用这个内存映射文件。 线程还拥有许多其它东西。每个线程都有一个专用的堆栈、一个专用的消息队列,一个专用的Thread Local Storage(TLS)以及一个专用的结构化异常处理链(如果你不知道后两个是什么,别急,稍后我会介绍它们)。此外,线程在执行过程中可能会请求、释放同步控制对象的拥有权。在看过Thread Database之后,我会解释这些东西。 什么是Thread Handle?什么是Thread ID? 本章稍早我曾说过process handle和process ID的不同。我的说明可以轻易的套到Thread handle和Thread ID身上—只要把进程改为线程就行了。如果你不确定,请回头去看看什么是Process Handle?什么是Process ID?那一节。 GetThreadHandle返回一个常数(微软总是说那是一个“虚拟handle”),可以适用于任何真正的Thread Handle可以用的地方: GetThreadHandle函数的虚拟代码: x_LogSomeKernelFunction( function number for GetCurrentThread ); return 0xFFFFFFFE; 就像GetCurrentProcessId那样,GetCurrentThreadId返回一个指针,指向当前的thread database(但KERNEL32小组会加上一个令人迷惑的数值): GetCurrentThreadId函数的虚拟代码: return TDBToTid( ppCurrentThread ); KERNEL32为何如此迷惑世人呢?让我们看看: TDBToTid函数的虚拟代码: // Parameters: // THREAD_DATABSE * ptdb if ( ObsfucatorDWORD == FALSE ) { _DebugOut( “TDBToTid() Called too early! ObsFucator not yet initialized!” ); return 0; } if ( ptdb & 1 ) { _DebugOut( “TDBToTid: This TDB looks like a TID ( 0%1xh) Do “ “statck trace BEFORE reporting as bug.” ); } // Here’s the key! XOR the obsfucator DWORD with the thread database // pointer to make the TID value. return ptdb^ObsfucatorDWORD; 如果你认为这一段看起来真像先前提过的PDBToPid函数,那么你是对的。KERNEL32使用同一个ObsfucatorDWORD把process database指针和thread database指针转换为IDs。一旦你了解ObsfucatorDWORD的值(并且记住微软拼错了这个字),你就可以把进程或现程的ID转化为有用的指针了。我要再说一次,这并不是被鼓励的程序行为,但是为了多了解系统的底层动作,我们没有太多选择。J
 
声明:王朝网络登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,文章内容仅供参考。
 
网友评论 查看所有评论
 
 
验证码:  
2006-01-09 15:44:19 繁體版 编辑
 
 
转载本文
UBB代码HTML代码
复制到剪贴板...
 
 最新文章
 ·印度暂缓封锁黑莓 紧盯网密服务密钥- ·黑客借钓鱼网站设陷阱-安全资讯 ·黄海波女友周诗雅性感写真-美女明星 ·一起来看看那些天雷滚滚的广告-搞笑
 ·微软推便携式触摸式鼠标Arc Tou ·图说那些世界上最美丽的地方-风景壁纸 ·快递“先签字后验货”被指违法-业内资 ·QQ.CN成为黑龙江联通IDC业务新
 ·拒绝50万美元奖金 跳槽Facebo ·网络管理员工作错误处理常见十宗罪-安 ·李彦宏百度大会 门票遭黄牛热炒-业内 ·李彦宏坦克大战 搜索可在线玩游戏-业
 ·步步追踪 破译远程控制失效之谜-应用 ·2010年度上半年全国病毒传播趋势  ·网络知识:OSPF路由协议基础-应用 ·林志颖娇妻陈若仪照片大曝光-美女明星
 ·十分钟完全体验Maxthon3.0- ·《庄园物语》8月风靡金山游戏世界 - ·表现满意 快车3.7新版下载能力测试 ·学生开学换手机 专家提醒先安全扫描-
 ·卡巴安全部队震撼发布保护网银安全-业 ·360安全:600万网民电脑龟速开机 ·卡巴斯基全力打造交易安全软件-安全资 ·搜狗“五级加速”引领高速浏览时代-网
 
 
© 2005- 王朝网络 版权所有