第二章 The Windows 2000 Native API
翻译: Kendiv
更新: Friday, February 04, 2005
Windows 2000 运行时库
Nt*() 和 Zw*() 函数构成了 Native API 的基本部分,但并不是主要部分,还有一部分代码位于 ntdll.dll 中。该 DLL 至少导出了 1179 个符号。其中的 249 和 248 个分别属于 Nt*() 和 Zw*() 函数集,剩余的 682 个函数并不通过 INT 2eh 中断进行调用。显然,这一大组函数并不依赖 Windows 2000 内核。那提供它们的目的何在呢?让我们继续往下看。
C 运行时库
如果你研究过位于 ntdll.dll 导出节( export section )的符号,你会发现很多在 C 程序员看来很熟悉的小写的函数名称。这些都是众所周知的名子,如 memcpy() 、 sprintf() 和 qsort() ,这些 C 运行时库中的函数都合并到了 ntdll.dll 中。对于 ntoskrnl.exe 也是如此,它同样提供了一组与 C 运行时函数十分相像的函数,虽然这两组函数并不相同。 附录 B 的 表 B-3 列出了这两组函数,并指出了每个函数分别属于哪个模块。
你可以简单的将 ntdll.lib (来自 Windows 2000 DDK )添加到导入库列表(链接器在解析符号期间将扫描该列表)中,就可以链接到这些函数。如果你更喜欢对话框,你可以选择 Visual C/C++ 的工程菜单中的 Settings 子菜单,然后单击 Linke 页,选择 Category General ,然后将 ntdll.dll 添加到 Object/Library 模块列表中。还有一种方法:在源文件中,添加如下的内容:
#pragma comment(linker,”/defaultlib:ntdll.lib”)
这同样有效,好处是,其他开发人员可以使用 Visual C/C++ 的默认设置来 rebuild 你的工程。
反编译这些与 C 运行时函数类似的函数(来自 ntdll.dll 和 ntoskrnl.exe ),会发现 ntdll.dll 并不依赖于 ntoskrnl.exe ,这和 ndll.dll 中的 Native API 不一样。事实上,这两个模块分别实现了这些函数。本节出现的其他函数也是如此。注意,表 B-3 中的一些函数并不使用其导出的名称。例如,如果在内核模式的驱动程序中针对一个 64 位的 LARGE_INTEGER 使用移位操作符 << 和 >> ,编译器和链接器会自动导入 ntoskrnl.exe 的 _allshr() 和 _allshl() 。
扩展的运行时函数
随同标准的 C 运行时函数, Windows 2000 还提供了一组扩展的运行时函数。在次强调, ntdll.dll 和 ntoskrnl.exe 分别实现了它们。并且其中有些函数是重叠的。这些扩展函数的名字都有一个共同的前缀 Rtl ( for Runtime Library )。 附录 B 的 表 B-4 列出了所有这些扩展函数。 Windows 2000 提供的这些运行时函数还包含用于普通任务的助手函数( helper function ),这些任务都超过了 C 运行时函数的能力范围。例如,其中的某些用于管理安全性,另一些用于操作 Windows 2000 特有的数据结构,还有一些对内存管理提供支持。很难理解为什么微软仅在 Windows 2000 DDK 中提供了其中 115 个函数的文档,而扔掉了其余 406 个非常有用的函数。
浮点模拟器( The Floating-Point Emulator )
让我用 ntdll.dll 提供的另一组函数集合来结束这次 API 函数汇展。 表 2-1 列出了这些函数的名称,这些名称可能对于汇编程序员有些眼熟。去了名称前的 __e 前缀,你就会得到 i386 系列 CPU 中的 FPU ( Floating-Point Unit )汇编助记符。事实上,从 表 2-1 中列出的函数来看, ntdll.dll 包含了一个完整的浮点模拟器。这再次证明了这个 DLL 是一个庞大的代码仓库,这吸引了众多的 System Spelunker 去反编译它。
表 2-1. ntdll.dll 的浮点模拟器接口
函数名称
_eCommonExceptions
_eFIST32
_eFLD64
_eFSTP32
_eEnulatorInit
_eFISTP16
_eFLD80
_eFSTp64
_eF2XM1
_eFISTP32
_eFLDCW
_eFSTP80
_eFABS
_eFISTP64
_eFLDENV
_eFSTSW
_eFADD32
_eFISUB16
_eFLDL2E
_eFSUB32
_eFADD64
_eFISUB32
_eFLDLN2
_eFSUB64
_eFADDPreg
_eFISUBR16
_eFLDPI
_eFSUBPreg
_eFADDreg
_eFISUBR32
_eFLDZ
_eFSUBR32
_eFADDtop
_eFLDI
_eFMUL32
_eFSUBR64
_eFCHS
_eFIDIVR16
_eFMUL64
_eFSUBreg
_eFCOM
_eFIDIVR32
_eFMULPreg
_eFSUBRPreg
_eFCOM32
_eFILD16
_eFMULreg
_eFSUBRreg
_eCOM64
_eFILD32
_eFMULtop
_eFSUBRtop
_eFCOMP
_eFILD64
_eFPATAN
_eFSUBtop
_eFCOMP32
_eFIMUL16
_eFPREm
_eFTST
_eFCOMP64
_eFIMUL32
_eFPREM1
_eFUCOM
_eFCOMPP
_eFINCSTP
_eFPTAN
_eFUCOMP
_eFCOS
_eFINIT
_eFRNDINT
_eFUCOMPP
_eFDECSTP
_eFIST16
_eFRSTOR
_eFXAM
_eFIDIVR16
_eFIST32
_eFSAVE
_eFXCH
_eFIDIVR32
_eFISTP16
_eFSCALE
_eFXTRACT
_eFILD16
_eFISTP32
_eFSIN
_eFYL2X
_eFILD32
_eFISTP64
_eFSQRT
_eFYL2XP1
_eFILD64
_eFISUB16
_eFST
_eGetStatusWord
_eFIMUL16
_eFISUB32
_eFST32
NPXEMULATORTABLE
_eFIMUL32
_eFISUBR16
_eFST64
RestoreEm87Context
_eFINCSTP
_eFISUBR32
_eFSTCW
SaveEm87Context
_eFINIT
_eFLD16
_eFSTENV
_eFIST16
_eFLD32
_eFSTP
有关浮点指令集的更多信息,请参考 Intel 80386 CPU 的原始文档。可以从 Intel 官方网站: http://developer.intel.com/design/pentium/manuals/ 来下载 PDF 格式的 Pentium 手册。讲解这些机器码指令集的手册是: Intel Architecture SoftWare Developer's Manual . Volume 2 : Instruction Set Reference ( Intel 1999b )。
其它的 API 函数
除 附录 B 和 表 2-1 列出的函数外, ntdll.dll 和 ntoskrnl.exe 还为多个内核组件导出了为数众多的函数。为了避免更长的表格,我这里仅列出可用函数的名称前缀及其所属类别( 表 2-2 )。
表 2-2 函数名前缀及其所属分类
前缀
ntdll.dll
ntoskrnl.exe
分类
_e
N/A
浮点模拟器
Cc
N/A
Cache 管理器
Csr
Client-Server 运行时库
Dbg
N/A
调试支持
Ex
N/A
执行支持( Executive Support )
FsRtl
N/A
文件系统运行时库
Hal
N/A
硬件抽象层调度器
Inbv
N/A
系统初始化 /VGA 启动驱动( bootvid.dll )
Init
N/A
系统初始化
Interlocked
N/A
处理线程安全的变量
Io
N/A
I/O 管理器
Kd
N/A
内核调试支持
Ke
N/A
内核例程
Ki
内核中断例程
Ldr
映像加载器
Lpc
N/A
本地过程调用( LPC )设备
Lsa
N/A
本地安全授权
Mm
N/A
内存管理器
Nls
National Language Support (NLS)
Nt
NT Native API
Ob
N/A
对象管理器
Pfx
前缀处理
Po
N/A
电源管理器
Ps
N/A
进程支持
READ_REGISTER_
N/A
从寄存器地址中读取
Rtl
Windows 2000 运行时库
Se
N/A
安全处理
WRITE_REGISTER_
N/A
向寄存器地址中写入
Zw
另一组 Native API
<other>
帮助函数和 C 运行时库
很多内核函数都使用统一的命名规则 ----PrefixOperationObject() 。例如, NtQueryInformationFile() 函数属于 Native API ,这是因为其 Nt 前缀,而且该函数显然针对一个文件对象执行了 QueryInformation 操作。但并不是所有函数都遵循这一规则,不过绝大多数都是如此。因此,可以很容易的通过函数的名称猜测其功能。
经常使用的数据类型
当编写与 Windows 2000 内核有关的软件时 --- 不管是和用户模式的 ntdll.dll 还是和内核模式的 ntoskrnl.exe ,你都必须处理几个基本的数据类型,而这些数据类型在 Win32 世界里非常少见。它们中的多数都会在本书中反复出现。下面的章节将介绍使用频率最高的数据类型。
整型
一般说来,整数类型有多个不同的变体。 Win32 SDK 的头文件和 SDK 文档使用了其专有的术语,这些术语很容易和 C/C++ 的基本类型以及一些派生类型相混淆。 表 2-3 列出了这些整数类型,以及它们之间的等价关系。在“ MASM ”列中,给出了微软宏汇编语言( MASM )使用的类型名称。 Win32 SDK 为 C/C++ 的基本数据类型定义了对应的 BYTE 、 WORD 、 DWORD 别名。“别名 1 ”和“别名 2 ”两列包含其经常使用的别名。例如, WCHAR 代表基础的 Unicode 字符类型。最后一列“有符号的”,列出了对应的有符号类型的常见别名。一定要记住 ANSI 字符类型 CHAR 是有符号的,而 Unicode 类型 WCHAR 是无符号的。当编译器将表达式或计算中的这些类型转换为整数类型时,这种不一致性将导致意外的错误。
表 2-3 最后一行的 MASM 的 TBYTE 类型(读做“ 10-byte ”)是一个 80 位的浮点数,用于高精度的浮点运算操作。 Microsoft Visual C/C++ 没有为 Win32 程序员提供对应的数据类型。需要注意的是, MASM 的 TBYTE 和 Win32 的 TBYTE (读做“ text byte ”)没有任何关系,后者只是一个用于转换的宏,根据源文件中是否有 #define UNICODE 而分别对应 CHAR 或 WCHAR 。
表 2-3. 等价的整数类型
位数
MASM
基本类型
别名 1
别名 2
有符号的
8
BTYE
unsigned char
UCHAR
CHAR
16
WORD
unsigned short
USHORT
WCHAR
SHORT
32
DWORD
unsigned long
ULONG