内存是存储数据、代码的地方,通过内存查看命令可以分析很多问题。相关命令可以分为:内存查看命令和内存统计命令。内存统计命令用来分析内存的使用状况。
一、查看内存
有非常丰富的内存查看命令,它们被容易为d*格式,如下所示:
- d[类型] [地址范围]
d代表Display,类型包括:字符、字符串、双字等。具体来说,d*命令共有这几种:d、da、db、dc、dd、dD、df、dp、dq、du、dw、dW、dyb、dyd、ds、dS。
1、内存类型
基本类型:
- dw = 双字节WORD格式;
- dd = 4字节DWORD格式 ;
- dq = 8字节格式;
- df = 4字节单精度浮点数格式;
- dD =8字节双精度浮点数格式;
- dp = 指针大小格式,32位系统下4字节,64位系统下为8字节;
基本字符串:
- da = ASCII字符串格式;
- du = UNICODE字符串格式;
- db =字节 + ASCII字符串;
- dW = 双字节WORD + ASCII字符串;
- dc = 4字节DWORD + ASCII字符串;
高级字符串:
- ds = ANSI_STRING类型字符串格式;
- dS = UNICODE_STRING类型字符串格式;
二进制 + 基本类型:
- byb = 二进制 + 字节;
- byd = 二进制 + DWORD值;
/c 列数:指定列数。默认情况下,列数 等于16除以列长,如dd命令的默认列数即为4列(=16/4)。例:
- dd /c 8
此命令每列显示8个DWORD数,即32字节内容。
/p:此选项用来显示物理内存信息,只能用于内核模式中。不使用此命令时,都将显示虚拟内存信息。如:
- d /p [地址范围]
L 长度: 默认情况下,d命令只显示固定长度的内存,一般为128或64字节。L可指定长度,如下面的命令将显示地址0×80000000开始处的0×100个字节内容:
- db 0×80000000 L100
2、数组形式内存
难能可贵的是,d*命令还能够以数组形式显示一段内存信息,包括:dda, ddp、 ddu、dds、dpa、dpp、dpu、dps、dqa、dqp、dqu、dqs。
何谓“以数组形式显示”呢?这一组命令能够将指定地址处的内容,作为一系列指针,进而显示指针所指处内容。听上去有点拗口吧,读者这样想会清楚些:前一组命令显示address值,本节这一组命令显示*address值。
程序代码中如有类似“char *array[10]”的数组变量,可使用这些命令显示数组内容。下面会有例子。这一系列命令实则由第一组命令演化而来,可分为三组:
- 4字节DWORD为单位的dd*系列数组指令;
- 指针长度为单位的dp*系列数组指令;
- 8字节为单位的dq*系列数组指令。
3、查看链表内存
最后,d*命令的另一个变体是以链表形式显示内存内容。命令如下:
- dl 开始地址
默认情况下,以正向从头到尾遍历链表;也可反向(由尾向头)遍历,指定b开关选项:
- dl b 尾地址
应注意的是,命令dl是Display List的缩写,这里的链表不能是用户自定义的链表,而专指符合LIST_ENTRY或SINGLE_LIST_ENTRY格式的链表。
二、内存信息
系统的内核空间很大的,想知道这么广大的内存空间里面都有些什么东西吗?想要知道一个内存地址,到底是被一个内核栈使用着,亦或被堆管理器使用着吗?我们这一节就领大家看看内存的地理概况。首先看Address命令:
- !address [地址]
显示进程或系统的内存状态、信息,!address是最好的工具。不加任何参数,在用户模式下此命令将以内存块为单位,列出从地址0开始到0×80000000(略小于)的全部地址空间信息;内核模式下,将列出从地址0×80000000开始到0xFFFFFFFF(略小于)的全部地址空间信息;如指定地址值,则将显示此地址所在内存块的内存信息(此命令在Vista以后系统中,不能在内核模式下正常使用,此Bug应会在Windbg的以后版本中被修正)。下面分别截取了用户与内核空间中的内存信息片段:
0:009> !address BaseAddress EndAddress+1 RegionSize Type State Protect Usage ------------------------------------------------------------------------------------------------------------------------ + 0`00000000 0`00010000 0`00010000 MEM_FREE PAGE_NOACCESS Free + 0`00010000 0`00020000 0`00010000 MEM_MAPPED MEM_COMMIT PAGE_READWRITE Heap64 [ID: 1; Handle: 0000000000010000; Type: Segment] + 0`00020000 0`00021000 0`00001000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE + 0`00021000 0`00030000 0`0000f000 MEM_FREE PAGE_NOACCESS Free + 0`00030000 0`00031000 0`00001000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE // 省略很多...
上图截取了两段用户区内存,第一段从0开始,长度0×10000,状态为释放(FREE),表明这一段地址空间不可用;第二段从0×10000开始,长度0×1000,属于私有内存,状态为已提交,保护模式为可读写,此内存块被用于环境块。
下面给大家解释一下内存块中的几个值:
内存类型:即Type值,共有四种:第一种是什么都不是,即尚未被使用的;第二种是MEM_IMAGE,即地址映射于一个可执行镜像文件片段,如DLL文件;第三种是MEM_ MAPPED,即地址映射于不可执行的镜像文件片段,如页文件;第四种是MEM_PRIVATE,即私有有内存,这里的私有是针对进程而言的,私有内存无法在多个进程间共享;
保护模式:即Protect值,上例中见识了两种保护模式,NOACCESS和READWRITE。从字面即很容易理解其意思,前者是不能做任何访问的,因为空闲内存是无效内存;后者则可读可写,但不能执行,说明是保存数据的地方。所有可用的保护包括:PAGE_NOACCESS(不可访问),PAGE_READONLY(只读),PAGE_READWRITE(读写),PAGE_EXECUTE(可执行), PAGE_EXECUTE_READ(执行并可读),PAGE_EXECUTE_READWRITE(执行并可读写),PAGE_WRITECOPY(写时拷贝),PAGE_EXECUTE_WRITECOPY(执行,并写时拷贝), PAGE_GUARD(保护)。
内存状态:即State值,共三种:MEM_FREE,即空闲内存;MEM_RESERVED,即保留内存,保留内存尚不能被实际使用,但其地址空间已被预留,尚需一个提交动作。最后是MEM_COMMIT,即内存已被提交,正在被使用。
内存用途:即Usage值,有这样一些值和用途。RegionUsageIsVAD:表示此地址区域已被分配;RegionUsageFree:代表此地址区域已被释放,既没有保留也没有被提交,将来可以申请使用;
- RegionUsageImage:代表此地址区域被映射到二进制文件的镜像;Region UsageStack:代表此地址区域用于线程栈;RegionUsageTeb:代表此地址区域用于保存目标进程的所有线程的TEB结构;
- RegionUsageHeap:代表此地址区域用于堆内存;RegionUsage Pdb:代表此地址区域用于保存目标进程的PEB结构;RegionUsageProcessParameters:代表此内存块用于保存目标进程的启动参数;
- RegionUsageEnviromentBlock:代表此地址区域用于保存目标进程的环境块。
用户环境下可使用下面的命令显示内存统计信息,包括内存用途、内存类型、内存状态。
- !address -summary
0:009> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal Free 101 0`7a5ba000 ( 1.912 Gb) 95.60% Image 294 0`022b8000 ( 34.719 Mb) 38.49% 1.70% 7 0`0113a000 ( 17.227 Mb) 19.10% 0.84% Stack32 51 0`01100000 ( 17.000 Mb) 18.84% 0.83% Heap32 26 0`006e0000 ( 6.875 Mb) 7.62% 0.34% MappedFile 12 0`0069e000 ( 6.617 Mb) 7.34% 0.32% Stack64 51 0`00440000 ( 4.250 Mb) 4.71% 0.21% Other 8 0`001c1000 ( 1.754 Mb) 1.94% 0.09% Heap64 9 0`00190000 ( 1.563 Mb) 1.73% 0.08% TEB64 17 0`00022000 ( 136.000 kb) 0.15% 0.01% TEB32 17 0`00011000 ( 68.000 kb) 0.07% 0.00% PEB64 1 0`00001000 ( 4.000 kb) 0.00% 0.00% PEB32 1 0`00001000 ( 4.000 kb) 0.00% 0.00% --- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal MEM_PRIVATE 181 0`02f11000 ( 47.066 Mb) 52.17% 2.30% MEM_IMAGE 295 0`022b9000 ( 34.723 Mb) 38.49% 1.70% MEM_MAPPED 18 0`0086c000 ( 8.422 Mb) 9.34% 0.41% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal MEM_FREE 101 0`7a5ba000 ( 1.912 Gb) 95.60% MEM_RESERVE 94 0`02f5d000 ( 47.363 Mb) 52.50% 2.31% MEM_COMMIT 400 0`02ad9000 ( 42.848 Mb) 47.50% 2.09% --- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal PAGE_EXECUTE_READ 56 0`01414000 ( 20.078 Mb) 22.26% 0.98% PAGE_READONLY 129 0`0117a000 ( 17.477 Mb) 19.37% 0.85% PAGE_READWRITE 153 0`004b5000 ( 4.707 Mb) 5.22% 0.23% PAGE_WRITECOPY 26 0`0004c000 ( 304.000 kb) 0.33% 0.01% PAGE_READWRITE|PAGE_GUARD 34 0`00048000 ( 288.000 kb) 0.31% 0.01% PAGE_EXECUTE_READWRITE 2 0`00002000 ( 8.000 kb) 0.01% 0.00% --- Largest Region by Usage ----------- Base Address -------- Region Size ---------- Free 0`030b0000 0`6cf40000 ( 1.702 Gb) Image 0`75d71000 0`00879000 ( 8.473 Mb) 0`7f0e0000 0`00f00000 ( 15.000 Mb) Stack32 0`00cd0000 0`000fd000 (1012.000 kb) Heap32 0`02f13000 0`0019d000 ( 1.613 Mb) MappedFile 0`01a90000 0`002cf000 ( 2.809 Mb) Stack64 0`00160000 0`00039000 ( 228.000 kb) Other 0`006b0000 0`00181000 ( 1.504 Mb) Heap64 0`02b90000 0`000bf000 ( 764.000 kb) TEB64 0`7ef76000 0`00002000 ( 8.000 kb) TEB32 0`7ef78000 0`00001000 ( 4.000 kb) PEB64 0`7efdf000 0`00001000 ( 4.000 kb) PEB32 0`7efde000 0`00001000 ( 4.000 kb)
上图分别以内存使用、内存类型、内存状态显示用户空间内存统计信息。
和!address命令类似的,用户模式下还有下面两个命令可用:
- !vprot [地址]
- !vadump [-v]
命令!vprot显示指定内存块的信息,侧重于内存保护信息;命令!vadump显示整个内存空间信息,dump者倾泻也,开启-v选项将显示详细(Verbose)信息。
上面讲过,用户环境下使用“!address –summary”可显示用户空间的内存统计信息;现在再看两个内核命令,在内核环境下显示内存的统计信息:
- !memusage
此命令从物理内存角度显示内存统计信息。无数个页表信息将被打印出来,可以说是“最内存”的信息。此命令会查看所有的页帧,所以运行时会非常地耗时。
- !vm
此命令从虚拟内存的角度显示内存统计信息,不仅能从全局角度显示虚拟内存的使用情况,还能以进程为单位显示内存使用情况。
三、其他命令
内核模式下,查看文件缓存信息,命令格式如下:
- !filecache
此命令在用户内核模式下,显示文件缓存和页表状态。每一行信息表示一个虚拟地址控制块 (VACB)。虚拟地址控制块可能对应着一个命名文件,也可能对应着一个元数据块。如果对应着一个命名文件,则此文件名称将被显示,否则显示元数据名称。
实验:查看文件缓存
很多软件都使用文件缓存的方式保存数据,比如Office Word。直接查看WORD文档,由于其
内部格式不透明,故而不便分析。但如果使用WORD打开一个txt文本文档,它就会以文本文档
的方式来处理之,并且依旧使用文件缓存的方式。
读者用WORD打开一个TXT文档(比如:测试.txt)。
运行内核调试器并执行!filecache命令,在打印信息中查找“测试.txt”。
用户模式下查看堆信息,命令格式如下:
- !heap
下面的清单显示了某个进程中共有4个堆:
0:004> !heap -a Index Address Name Debugging options enabled 1: 00150000 Segment at 00150000 to 00250000 (00031000 bytes committed) 2: 00250000 Segment at 00250000 to 00260000 (00006000 bytes committed) 3: 00260000 Segment at 00260000 to 00270000 (00003000 bytes committed) 4: 00390000 Segment at 00390000 to 003a0000 (00008000 bytes committed) Segment at 01370000 to 01470000 (0007b000 bytes committed)
堆资源是属于进程的,每个进程都会创建若干个堆,如C运行时堆、进程默认堆等。以第一个堆为例,地址范围是[0x150000,0x250000],已经有0×31000个字节被申请提交。