探索NTFS探寻Windows NT/2000 Copy On Write机制

探寻Windows NT/2000 Copy On Write机制
WebCrazy(http://wecrazy.yeah.net)

Copy On Write机制是典型的Lazy evaluation实现,现代操作系统如Windows NT/2000,UNIX/Linux的内存管理部分大量使用这种机制。本文通过对Windows NT/2000中Copy On Write机制作一深入分析,旨在探寻Windows NT/2000内核态内存管理器的几个重要的数据结构,在继续以下的讨论之前,您务必要明白PDE/PTE、VAD等一些术语(参见我先前的《小议Windows NT/2000分页机制》与《分析Windows NT/2000堆内存与虚拟内存组织 》),另外我将述及另几个与内存子系统相关的术语,如Control Area,Subsection,Working Set List,Page Frame Database。下面列出我在本文中用于分析Copy On Write机制的代码,文中所有的叙述均基于这段代码。

// cow.c
// Writed by ChenChengQin(tsu00@263.net )

#include <stdio.h>
#include <string.h>

#define BUFSIZE 10

#pragma data_seg(".seg_cow")
char data[BUFSIZE]={'A','A','A','A','A','A','A','A','A',''};
#pragma data_seg()
#pragma comment(linker, "/SECTION:.seg_cow,RWC")

void main(int argc,char *argv[])
{
int i;
if (argc>1){
//memset(data,'B',BUFSIZE);
__asm int 3;
for(i=0;i<BUFSIZE;i++)
data[i]='B';
data[BUFSIZE-1]='';
}
printf("%s",data);

getchar();
}


非常简单的一段代码,使用如下指令编译:

cl /Zi /Fa cow.c

运行一个实例(别退出):
c:>cow
AAAAAAAAAA

使用SoftICE看看cow.exe映像在内存中的分布:

:addr cow
:map32 cow
Owner Obj Name Obj# Address Size Type
cow .text 0001 001B:00401000 00006BB8 CODE RO
cow .rdata 0002 0023:00408000 000005C8 IDATA RO
cow .data 0003 0023:00409000 00002EC4 IDATA RW
cow .idata 0004 0023:0040C000 00000633 IDATA RW
cow .seg_cow 0005 0023:0040D000 0000010C IDATA RW
cow .reloc 0006 0023:0040E000 000006B2 IDATA RO

//查看.seg_cow段(虚拟地址0x40d000)的首地址的PTE,详见《小议Windows NT/2000分页机制 》
:dd c0000000+1*1000+d*4 l 10
0023:C0001034 003F9225 00000000 00000000 00C3C067 %.?.........g...
--------
|
|_cow.exe进程.seg_cow段首地址的PTE

SoftICE的page命令可以dump出这个PTE的属性,如下所示:

:page 40d000
Linear Physical Attributes
0040D000 003F9000 P A U R

不过这里列出的Attributes并没有指出.seg_cow的Copy On Write属性,因为X86的PTE的低12位(0-11),即属性位并没有指出这个属性,但Microsoft(当然不仅仅Microsoft)使用了这12位中的保留的系统(OS)位,下面是这一DWORD值在Windows 2000的具体格式:

struct _HARDWARE_PTE_X86 (sizeof=4)
+0 bits0-0 Valid
+0 bits1-1 Write
+0 bits2-2 Owner
+0 bits3-3 WriteThrough
+0 bits4-4 CacheDisable
+0 bits5-5 Accessed
+0 bits6-6 Dirty
+0 bits7-7 LargePage
+0 bits8-8 Global
+0 bits9-9 CopyOnWrite
+0 bits10-10 Prototype
+0 bits11-11 reserved
+0 bits12-31 PageFrameNumber

从上的PTE为003F9225的值可以看出0x40d000的只读、CopyOnWrite属性。只读属性是实现CopyOnWrite的保证,这样X86才能在另一个cow实例试图写.seg_cow段时raise一个0eh(页故障)中断(陷阱),让Windows处理这个Copy On Write操作。基于这个原理,我即运行cow的另一个实例,跟踪Copy On Write机制:

c:>cow 1(设置一个参数,让cow试图更新.seg_cow段)

我在代码中设置了int 3指令让softice在i3here设置为on状态时让softice激活。然后使用bpint e指令让softice捕获0eh中断。i386在raise 0e中断时将发生页故障的虚拟地址保存于CR2寄存器中。Windows 2000页故障处理入口KiTrap0E就是根据这个地址制作cow进程.seg_cow的副本,并替代第二个cow实例工作集(Working List,这儿只指进程工作集,未牵涉系统工作集)的原始页面,实现CopyOnWrite的目的。我并不准备将KiTrap0E的汇编代码列于此,只是讲一讲Windows寻找CR2指定的地址的属性的步骤,Windows 2000中由MiQueryAddressState过程实现。KiTrap0E当然也会调用MiQueryAddressState。

1、首先检查0x40d000(由CR2指定)的PDE。由MiDoesPdeExistAndMakeValid实现。
2、检查PTE。
3、查页框数据库(PFN,Page Frame Database,由内核结构数组变量MmPfnDatabase指定)
4、根据PFN中工作集索引寻找Working Set List Entry(Working Set基址由内核变量_MmWsle指出),这一步骤由内核例程MiLocateWsle完成。
5、然后根据Wsle的属性(也是前12位)查找MmProtectToValue数组,以获得用户态可以理解的格式。即在winnt.h中定义的PAGE_WRITECOPY、PAGE_READWRITE等等。

步骤3至5其实也实现了物理地址至线性地址的转换,当然这个是在这个地址Present的前提下。这也是为什么Windows 2000使用如此复杂且繁琐的结构来管理内存子系统。确切的说我并没有谈到PFN的PteAddress成员(下面i386kd输出可以看到),这些都是使用分页文件(pagefile.sys,通过ProtoPTE,即原型PTE实现的),共享内存块等的基础。David Solomon的书《Inside Windows NT,2nd Edition》有四种PFN形态的具体结构等等其他很多详细的说明。

下面是i386kd的分析,其实根据上面我给定的一些内核变量SoftICE也可以看出些东西。不过远没有i386kd来的容易。

// cow.exe 进程ID(ClientID)=4ac
kd> !process 4ac
!process 4ac
Searching for Process with Cid == 4ac
PROCESS ff605d60 SessionId: 0 Cid: 04ac Peb: 7ffdf000 ParentCid: 04a0
DirBase: 017bc000 ObjectTable: ff610f88 TableSize: 12.
Image: cow.exe
VadRoot ff624168 Clone 0 Private 37. Modified 0. Locked 0.
.
.
.
//转换40d000至物理地址,取得PFN。
//SoftICE中使用Page命令
kd> !vtop 017bc000 40d000
--------
|_DirBase(见!process输出)
!vtop 017bc000 40d000
Pdi 1 Pti d //输出PDE与PTE
0040d000 00a6e000 pfn(00a6e) //输出PFN

//查PfnDatabase
//Softice中:
// dd @MmPfnDatabase+a6e*18 (每一PFN项占用0x18字节)
kd> !pfn a6e
!pfn a6e
PFN 00000A6E at address FFB8EA50
flink 00000097 blink / share count 00000001 pteaddress E151E1B4
reference count 0001 color 0
restore pte 056B04B0 containing page 002AF3 Active P
Shared


/*
查MmWsle
SoftICE中:
:dd @MmWsle+97*4 l 10
0023:C05028FC 0040DF29 000006A0 00510C09 000009B0 ).@.......Q.....
--------
|_0040D000是不是PFN为A6E的Virtual Address
*/
kd> !wsle 4ac
!wsle 4ac

// KPEB中的VmWorkingSetList成员(Windows 2000 Server Build 2195中偏移0f0处,值为C0502000)指出Working Set List
// 见《Windows 2000内核KPEB/KTEB详细结构 》

Working Set @ c0502000
Quota: 2f FirstFree: 22 FirstDynamic: 4
LastEntry ad NextSlot: 16 LastInitialized 257
NonDirect 1e HashTable: c06f3000 HashTableSize: 200
.
.
.

这些i386kd指令已经较清楚的指出以上5个步骤。但要理解Copy On Write还必须对Section(用户态的FileMapping)对象有一基本的理解。在使用NtCreateSection/NtOpenSection(CreateFileMapping/OpenFileMapping间接使用这些例程)等时,Windows 2000一般会在VAD这个自平衡的二叉树中插入一个节点。(我在《分析Windows NT/2000堆内存与虚拟内存组织 》中详细介绍过VAD)。SoftICE在使用query指令dump VAD树时有一成员MMCI(内存管理结构),她指向的是Control Area。如下所示:

// SoftICE输出
:query cow
Address Range Flags MMCI PTE Name
00010000-00010000 C4000001
.
.
.
00400000-0040E000 07100005 FF62FD48 E11EBF80 cow.exe
--------
|_Control Area
.
.
.

其实我们使用i386kd的!memusage可以dump出系统中所有的Control Area:

kd> !memusage
loading PFN database
loading (99% complete)
Zeroed: 15 ( 60 kb)
Free: 0 ( 0 kb)
Standby: 1274 ( 5096 kb)
Modified: 686 ( 2744 kb)
ModifiedNoWrite: 1 ( 4 kb)
Active/Valid: 14380 ( 57520 kb)
Transition: 11 ( 44 kb)
Unknown: 0 ( 0 kb)
TOTAL: 16367 ( 65468 kb)
Building kernel map
Finished building kernel map

Control Valid Standby Dirty Shared Locked PageTables name
.
.
.
ff62fd48 32 0 0 0 0 0 mapped_file( cow.exe )
--------
|_Control Area与上面SoftICE的MMCI一致
.
.
.

i386kd的ca命令可以看出一个Control Area结构:

kd> !ca ff62fd48

ControlArea @ff62fd48
Segment: e11ebf48 Flink 0 Blink: 0
Section Ref 1 Pfn Ref 8 Mapped Views: 1
User Ref 2 Subsections 7 Flush Count: 0
File Object ff684288 ModWriteCount 0 System Views: 0
WaitForDel 0 Paged Usage a0 NonPaged Usage 120
Flags (10000a0) Image File HadUserReference

File: Desktophackcow.exe

Segment @ e11ebf48:
Base address 0 Total Ptes f NonExtendPtes: f
Image commit 5 ControlArea ff62fd48 SizeOfSegment: f000
Image Base 0 Committed 0 PTE Template: 54f1c30
Based Addr 400000 ProtoPtes e11ebf80 Image Info: e11ebfc0

Subsection 1. @ ff62fd80
ControlArea: ff62fd48 Starting Sector 0 Number Of Sectors 8
Base Pte e11ebf80 Ptes In subsect 1 Unused Ptes 0
Flags 15 Sector Offset 0 Protection 1
ReadOnly CopyOnWrite
.
.
.

由ca命令的输出结果可以看出这个Control Area有7个子区域(Subsection),限于篇幅我删掉部分输出结果,你可以将所有结果与SoftICE的map32指令输出比较比较。Control Area中实际上所有Subsection结构均是使用线性结构组织,每个Subsection在Windows 2000 Server Build 2195中占用0x20字节。所以SoftICE可以很容易的分析所有的这些。

需要指出的是不仅仅Section对象使用Control Area,她在Windows 2000中也由SECTION_OBJECT_POINTERS结构使用:

typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject; //Control Area
PVOID SharedCacheMap;
PVOID ImageSectionObject; //Control Area
} SECTION_OBJECT_POINTERS;

而每一个FILE_OBJECT都有SECTION_OBJECT_POINTERS成员(见ntddk.h)。这个机制是Windows 2000装载可执行文件、文件IO操作(看到DataSectionObject与ImageSectionObject了吗?)的关键所在。只有熟悉这些结构,才可能真正初步明白Copy On Write机制。剩下的只有你自己多研究研究了。

关于cow.exe,我还有两点要说明的是:
1、cow.c使用#pragma comment(linker, "/SECTION:.seg_cow,RWC")显式的指出.cow_seg段的Copy On Write属性,实际上在Windows NT/2000中这种共享属性是默认存在的。可执行文件的映射、可读可写的数据都被默认设为Copy On Write属性,您可以使用Jeffrey Richter的VMMAP验证这种说法。
2、Windows NT/2000使用可执行文件名(ImageName)用于识别这种程序多个实例间使用Copy On Write共享内存,对于有不同文件名的可执行文件,即便其内容完全一致,这种机制也不起作用,而用分别映射Section对象替之。

所有以上讨论的都没有找到Microsoft的Full Documented,所有以上讨论的也只是在我初步分析Windows 2000后得到的,有什么技术问题需要交流的,欢迎赐教(tsu00@263.net)!

参考资料:
1.David Solomom《Inside Windows NT,2nd Edition》
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Table of Contents Chapter 1: Windows NT: An Inside Look EVALUATING WINDOWS NT DELVING INTO THE WINDOWS NT ARCHITECTURE SUMMARY Chapter 2: Writing Windows NT Device Drivers PREREQUISITES TO WRITING NT DEVICE DRIVERS DRIVER BUILD PROCEDURE STRUCTURE OF A DEVICE DRIVER SUMMARY Chapter 3: Win32 Implementations: A Comparative Look WIN32 API IMPLEMENTATION ON WINDOWS 95 WIN32 API IMPLEMENTATION ON WINDOWS NT WIN32 IMPLEMENTATION DIFFERENCES SUMMARY Chapter 4: Memory Management MEMORY MODELS IN MICROSOFT OPERATING SYSTEMS WINDOWS NT MEMORY MANAGEMENT OVERVIEW BELOW THE OPERATING SYSTEM THE INSIDE LOOK VIRTUAL MEMORY MANAGEMENT VIRTUAL ADDRESS DESCRIPTORS IMPACT ON HOOKING SWITCHING CONTEXT DIFFERENCES BETWEEN WINDOWS NT AND WINDOWS 95/98 SUMMARY Chapter 5: Reverse Engineering Techniques HOW TO PREPARE FOR REVERSE ENGINEERING HOW TO REVERSE ENGINEER UNDERSTANDING CODE GENERATION PATTERNS HOW WINDOWS NT PROVIDES DEBUGGING INFORMATION HOW TO DECIPHER THE PARAMETERS PASSED TO AN UNDOCUMENTED FUNCTION TYPICAL ASSEMBLY LANGUAGE PATTERNS AND THEIR MEANINGS THE PRACTICAL APPLICATION OF REVERSE ENGINEERING SUMMARY Chapter 6: Hooking Windows NT System Services SYSTEM SERVICES: THE LONG VIEW NEED FOR HOOKING SYSTEM SERVICES TYPES OF HOOKS IMPLEMENTATIONS OF HOOKS WINDOWS NT SYSTEM SERVICES HOOKING NT SYSTEM SERVICES SUMMARY Chapter 7: Adding New System Services to the Windows NT Kernal DETAILED IMPLEMENTATION OF A SYSTEM SERVICE IN WINDOWS NT ADDING NEW SYSTEM SERVICES EXAMPLE OF ADDING A NEW SYSTEM SERVICE SUMMARY Chapter 8: Local Procedure Call THE ORIGIN OF THE SUBSYSTEMS LOCAL PROCEDURE CALL PORT-RELATED FUNCTIONS LPC SAMPLE PROGRAMS QUICK LPC SUMMARY Chapter 9: Hooking Software Interrupts WHAT ARE INTERRUPTS? HOW OPERATING SYSTEMS USE SOFTWARE INTERRUPTS WHY SOFTWARE INTERRUPTS NEED TO BE HOOKED HOW TO HOOK SOFTWARE INTERRUPTS SUMMARY Chapter 10: Adding New Software Interrupts WHAT HAPPENS WHEN A 32-BIT APPLICATION EXECUTES AN INT NN INSTRUCTION? ADDING NEW SOFTWARE INTERRUPTS TO THE WINDOWS NT KERNEL USING CALLGATES TO EXECUTE PRIVILEGED CODE HOW TO USE THE CALLGATE TECHNIQUE PAGING ISSUES SUMMARY Chapter 11: Portable Executable File Format OVERVIEW OF A PE FILE STRUCTURE OF A PE FILE RELATIVE VIRTUAL ADDRESS DETAILS OF THE PE FORMAT INDICES IN THE DATA DIRECTORY LOADING PROCEDURE SUMMARY
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值