假如C++中定义一个浮点数是4.000,后三个零算是有效数位吗?

用户成功安装微软Windows调试工具集后,能够在安装目录下发现四个调试器程序,分别是:/download/symbols

第一个命令没有任何参数,显示当前设置。后面两个,第二个命令含有“+”代表添加一个选项,第三个命令含有“-”代表去除一个选项。

可用的符号选项请见下表:

关闭C++转换,C++中的::符号将以__显示

如果由于编译器优化导致找不到对应的符号,就以最近的一个符号代替之

使得符号匹配的时候,匹配原则较松散,不那么严格。

忽略镜像文件头中的CV记录

只在已加载模块中搜索符号,如果搜索符号失败,不会自动加载新模块。

不显示文件访问错误对话框。

进行最严格的符号文件检查,只要有微小的差异,符号文件都不会被加载。

允许从内存的一个绝对地址处读取符号信息。

忽视在环境变量中设置的符号路径,也忽视被调试进程的执行路径。也就是说,当搜索符号文件的时候,不会从这些路径中搜索。

让运行在安腾系统上的调试器,也枚举32位模块。

仅搜索符号文件的公共(PUBLIC)符号表,忽略私有符号表。

不搜索符号文件的公共(PUBLIC)符号表

先搜索pdb文件的私有符号表,如果在其中找到对应的符号,就不再搜索公共(PUBLIC)符号表,这可以加快搜索速度。

安全模式,让调试器尽量不影响到主机。

不显示符号代理服务器的认证对话框,将导致某些时候无法访问符号服务器

显示符号搜索的详细过程和信息

本节分下面几个子题目分别讲解。

加载指定模块的符号。调试器默认采用延迟模式加载符号,也就是直到符号被使用的时候,才将符号文件加载到调试器中并进行解析。ld使得延迟模式被打破,让指定模块的符号文件立刻加载到调试器中。此指令可为模块的符号文件设置自定义的匹配名称,比如:

这样一来,abc.pdb将成为123.exe的符号文件。正常情况下,这是不可能的,只能是abc.pdb对应abc.exe。

如果对自己正在使用的符号文件感到疑惑,比如源代码和行号明显不匹配,最好的做法就是重新加载一下符号文件。此命令语法如下:

.reload命令的作用是删除指定或所有已加载的符号文件,默认情况下,调试器不会立刻根据符号路径重新搜索并加载新的符号文件,而是推迟到调试器下一次使用到此文件时。

使用/f参数(force),将迫使调试器立刻搜索并重新加载新的符号文件。

  • /v:将搜索过程中的详细信息都显示出来。
  • /i:不检查pdb文件的版本信息;
  • /l:只显示模块信息,内核模式下,和“lm n t”命令类似,但显示内容比后者更多,因为包含了用户模块信息;
  • /n:仅重载内核符号,不重载用户符号;
  • /o:强制覆盖符号库中的符号文件,即使版本相同;
  • /d:用户层模式下使用Windbg时的默认选项,重载调试器模块列表中的所有模块;
  • /s:内核模式下使用Windbg时的默认选项,重载系统模块列表中的所有模块,另外,如果调试器在用户模式下运行,要加载内核模块,也必须使用/s选项,否则调试器将只会在调试器模块列表中搜索而导致找不到内核模块;
  • /u:卸载指定模块。如发现当前符号版本不对,使用/u开关先卸载之再重新加载。

上面讲到.reload的时候,我们说过,符号文件会出现不匹配的情况。这是很有可能的,程序员在后期测试的时候可能会将工程多次编译,为了维护多个版本而使得自己也被搞混。可以使用下面的命令验证一个模块的符号文件:

有两类符号加载选项。第一类是Noisy/Quiet,Noisy选项将打印符号加载的详细信息,Quiet选项则忽略这些信息。第二类是Prompts/Prompts off,即是否允许执行提示(Prompts)对话框。

一般都是在调用.reload 命令之前,执行加载选项命令,以见立竿见影之效。

所谓Noisy是吵闹的意思,调试器在搜索、加载符号的时候,会显示更多与搜索有关的信息。而安静模式下,则不会显示这些信息。不管吵闹与否,都不会影响到最终的搜索、加载结果。

当从网络上下载符号文件的时候,可能会碰到网络服务器要求客户进行安全认证的情况,如果开启Prompts选项,则弹出认证对话框,让用户输入认证信息;否则,不弹出对话框,并且不会下载符号文件。

不加任何参数的情况下,显示当前加载选项设置,下面的清单表明当前的设置为Quite及Prompts模式:

符号搜索包括全局搜索和就近搜索两种。

命令“x”被用来进行符号的全局搜索,你可以把它直接就理解为search。格式如下:

如果什么参数都没有的话,它将列出当前调试环境下的所有局部变量,前提是要在有局部变量存在的情况下,显示局部变量的另一个命令是dv,后文也会讲到。

上面命令搜索并打印出kernel32模块中所有a开头的符号。x命令支持DML,使用/D选项即以DML格式显示。

如果你不知道ntcreatefile这个函数是在哪个模块中定义的,可以试着使用下面的命令:

同名函数在多个系统模块中并定义,这可能出乎你的意料,但却给你带来真正的知识。

此外,x命令有多个可选参数。建议总是带上/t和/v,可显示更多符号、类型信息。

  • /f:将只显示函数符号;并且会显示函数的详细定义。
  • /d:显示更多的变量类型相关信息。 

如果知道了符号的大概地址,但不能确定确切的符号名称,该怎么处理呢?就近查找命令ln能发挥作用,ln是List Nearest的缩写。它的作用是:(根据给定的地址)列出附近一定范围内的所有符号。下表中,指定地址0x7c8179f0的前后各有一个符号被找到:

如果含有源码信息,可使得调试过程能够以源码模式逐行进行。和源码相关的命令包括下面几个: 

和符号路径类似,要设置源码路径,使用如下语法格式:

不含任何参数的情况下,显示当前设置的源码路径。

下面命令将覆盖原设置,设置新的源码搜索路径:

使用“+”可以将新的路径添加到原设置中,而不会把原设置覆盖掉:

这里列出的源码选项有三个,下面分别来讲。第一个是源码的Noisy选项,语法如下:

此命令乃source noisy缩写,可以理解成“嘈杂的源码”,类似于符号设置中也存在的noisy选项。他的三种运用如下所示:

开始“吵闹的源码”选项后,在源码加载、卸载,甚至单步的时候,都会显示丰富的源码信息。下图显示了一个含有Noisy信息的单步命令:

第二个命令是行号选项,即在符号文件加载过程中,是否将行号也一并加载进来。因为Windbg支持源码级调试,所以它在Windbg中是默认开启(Enable)的,我们一般也不应该去禁止他。语法如下:

参数d是disable的意思;e是enable的意思;t表示切换的意思,即自动在disable和enable两者之间切换。

最后看第三个命令,是代码行选项,包括行号和内容。语法如下:

命令l是line的缩写,和上面的.lines命令不同的是,.lines是加载时选项,l是调试时选项。我建议读者总是调用“l+*”指令,打开所有的行选项,效果会很不错。这样在单步调试的时候,每一步的代码和行号都会显示出来。显得很醒目!

运行这两个命令和在Windbg的Debug菜单下点击source mode选项其效果是一样的。

既可以显示进程和线程列表,又可以显示指定进程或线程的详细信息。调试命令可以提供比taskmgr更详尽的进程资料,在调试过程中不可或缺。

进程命令包括这些内容:显示进程列表、进程环境块、设置进程环境。

多个命令可显示进程列表,但一般只能在特定情况下使用,它们是:|、.tlist、!process和!dml_proc。

竖线命令显示当前被调试进程列表的状态信息,这个命令在本章开头已作过介绍,命令格式如下:

请注意这里的定语:被调试进程列表。大多数情况下调试器中只有一个被调试进程,但可以通过.attach或者.create命令同时挂载或创建多个调试对象。当同时对多个进程调试时,进程号是从0开始的整数。下图中显示了两个被调试的进程。

如何在多个进程间进行切换呢?使用s参数即可,这一点前文已然讲过。

            .tlist命令显示当前系统中的进程列表,他是目前唯一可在用户模式下显示系统当前进程列表的命令。它有两个可选项:-v显示进程详细信息,-c只显示当前进程信息。

  • !process: 显示调试器当前运行进程信息

此外,还有一个DML版本的进程列表命令,如下:

此命令可以看成“|”和“!process”命令的DML合并版本,可在用户与内核模式下使用。显示的进程信息偏重于线程和调用栈。用户模式下此命令和“|”一样,只能显示被调试进程的信息。右图是内核模式下使用此命令的效果:

进程环境块(Process Enviroment Block)是内核结构体,使用!peb命令参看其信息,但也可以用dt命令查看完整的结构体定义。格式如下:

如果未设置PEB地址,则默认为当前进程。内核模式下可通过!process命令获取PEB结构体地址;用户模式下只能显示当前进程的PEB信息,故而一般不带参数。

此命令显示系统nt模块中所定义的内核结构体PEB详细内容。使用之前必须先熟悉结构体定义。

进程环境的切换,将伴随着与进程相关的寄存器、堆栈的切换。在不同进程环境中进行的调试结果有天壤之别。上文在讲“|”命令的时候,讲过用户环境下多进程间如何互相切换,使用命令:

那么内核模式下,情况又不同了。内核模式下的进程切换,不同于用户模式下的被调试进程间切换,而是系统存在的多进程间切换。内核环境下,以进程地址作为参数,调用如下命令以进行进程环境切换:

如果不使用任何参数,.process命令将显示当前进程地址。所谓进程地址,即ERPCESS结构体地址。

或以页目录地址为参数,调用下面命令切换用户地址空间:

如果不使用任何参数,.context命令将显示当前页目录地址。页目录地址就是!process命令中显示的DirBase值。

进程切换后,为了检测是否正确切换,可再用!peb命令检查当前进程的环境信息。

命令“~”能够进行线程相关的操作。不带任何参数的情况下,它列出当前调试进程的线程。下图是计算器进程某时刻的线程列表:

使用此命令可进行的线程操作包括:线程切换、线程环境、线程时间等。

参数f与u分别代表freeze和unfress,前者是指冻住指定线程,后者将被冰冻线程解冻。

表示把2号线程冻住,在解冻之前,不再分发CPU时间给它。

若要让指定线程重新运行,需使用参数u:

针对这两个命令,下面有一个小实验:

运行Windbg,并选择调试记事本程序(Notepad.exe)。程序起来后,CTL+BREAK中断程序运行,输入命令: 
再输入g命令让记事本继续运行。 
此时尝试用鼠标定位到notepad软件,发现软件界面无法被定位、移动、最大小化,甚至“清空桌面”操作也无济于事。这是因为0号线程为notepad的主线程,被冻住后整个软件都失去响应。 
更严重的是,“清空桌面”操作(Win + D)也会失效,应是Notepad拒绝响应的缘故。
参数n和m分别代表increase和resume,前者增加一个线程挂起计数,后者减少一个线程挂起计数。如果两次增加线程挂起计数(即达到2),则必须两次resume才能让线程恢复到运行状态。

把上面实验中的~0f命令改变成~0n,也能达到相似的效果。

查看指定线程的信息,用下面的命令:

线程号是由调试器软件内部维护的线程ID值,是一个从0开始的整数,和线程ID不是一回事。

线程信息中包括有线程环境块地址,可通过!teb命令查看环境块信息:

如要在多线程间作切换,需使用~命令的s参数:

由于线程号在外部是没有太大意义的,所以另一个线程切换命令是以线程ID来标识一个线程的。这个命令比较奇怪,以双波浪线打头,格式如下:

注意这个命令中的[]并非可选符,而是命令的一部分。例如命令:~~[11a0] s,它将当前线程切换到线程ID为0x11a0的线程。线程ID是系统维护的系统唯一的ID值。

下图是关于线程切换的:

第一个命令“~”运行时,当前线程是14号线程,请注意3号线程前面有一小点;运行第二个命令,将当前线程切换为9号线程;为了检验结果,再次运行“~”命令,此时注意到小点移到9号线程前,表明9号线程为当前线程。 

仍然是~命令。它除了能够作为线程列表命令外,还可用来对线程进行遍历,并执行指定命令。只需借助通配符“*”即可。如:

显示所有线程栈信息(此命令意指:对所有线程执行k指令)。下图中,当前进程共包含两个线程,显示了这两个线程各自的栈信息:

其他有用的遍历指令包括:

上面的e是execute(执行)的缩写,后可跟一个或多个Windbg命令。它遍历线程并对每个线程执行指定命令,如:

此命令意为:在所用线程环境中(~*),分别执行(e)栈指令(k)和寄存器指令(r)。

在软件调试的时候,若发现某线程占用执行时间过多,就需要当心是否有问题。线程执行时间的多少,其实就是占用CPU执行工作的时间多少。某线程占用越多,此长彼消,则系统中其它线程占用CPU的时间就越少。

线程的时间信息包括三个方面:自创建之初到现在的总消耗时间、用户模式执行时间、内核模式执行时间。需注意的是,消耗时间一定会远远大于用户时间+内核时间,多出来的是大量空闲时间(为Idle进程占用)。使用下面的命令查看线程时间:

在!runaway命令中加入标志值7,将显示线程的全部三种时间值。

这两个命令的区别之处是,.ttime只能显示当前线程的时间信息,!runaway能显示当前进程的所有线程时间。下图是这两个命令的使用情况:

在调试器语境中,事件是一个基本概念,Windbg是事件驱动的。Windows操作系统的调试子系统,是“事件”的发生源。调试器的所有操作,都是因事件而动,因事件被处理而中继。Windows定义了9类调试事件,异常是其中一类(ID为1)。所以异常和事件,这二者是前者包含于后者的关系。

系统对各种异常和调试事件进行了分类,执行sx命令可以列出针对当前调试目标的异常或非异常事件的处理。下面是一个片段:

我们分析一下前两个事件。使用调试器调试记事本进程时,不管是用.attach挂载方式还是.create创建方式,在调试器正式侵入记事本进程前,都会有一个中断(Initial breakpoint异常);调试开始后运行一段时间,在外面将记事本关闭,又会发生一个中断(Exit Process异常)。

可以通过Debug|Event Filters…打开事件设置对话框。这个对话框中列出了全部调试事件,用户可分别对它们进行设置。

这个对话框列出了对于当前调试会话可用的全部调试事件。针对每个调试事件,可设置其属性。右列Execution和Continue两组单选键,分别表示事件的中断属性中继属性。右列Argument按钮可设置调试事件执行参数(上图中Load Module事件有一个Kernel32.dll参数,即当Kernel32.dll模块被加载时,调试器将被中断),Commands按钮可设置事件两轮机会发生时的执行命令。

更细致的内容,本章无力铺陈,请读者参阅《Windows 高级调试》(Mario & Daniel 2009 机械工业出版社)第三章,及《软件调试》(张银奎 2008 电子工业出版社)第9、30章相关内容。 

      此命令将当前所有对调试事件的设置,恢复到调试器的默认设置。最后一个字母r表示Reset。

这4个命令分别代表了图8-38中Execution组(中断属性)中的四个按钮,即Enable、Disable、Output、Ignore。Enable是开启中断,Disable是禁止事件中断(但对于异常,只禁止第一轮机会,第二轮机会到来时仍会中断到调试器),Output是禁止中断但会输出相关信息,Ignore表示完全忽略这个事件(对于异常,Output和Ignore两选项使得两轮机会都不会中断到调试器)。

显示最近发生的一个调试事件,往往是导致中断发生的那个。下图显示的是一个很典型的初始化断点引发的中断事件。

如果仅仅为了显示最近的一条异常记录,可以用-1代替异常记录地址:

由于异常是事件的一种,所以使用.exr -1命令得到的异常,可能和使用.lastevent命令获取的事件(其实是异常),是同一个。但二者显示的信息各有侧重点。请对照图8-39看下面,同样的初始化断点异常,使用.exr命令时所显示的信息:

还有一个类似的命令:!cppexr,他分析并显示一个C++异常信息。

此命令不带参数。在内核环境下,显示当前bug check的详细信息;可用于活动调试或者crash dump调试环境中。用户环境不可用。见下图:

此命令分析当前最近的异常事件(如果在进行dump分析,则是bug check),并显示分析结果。这个异常事件,就是上面.lastevent命令对应的事件。

  • -v:显示异常的详细信息,这个选项在调试错误的时候,最有用。
  • -f:f是force的缩写。强制将任何事件都当作异常来分析,即使仅仅是普通的断点事件。将因此多输出一些内容。
  • -hang:这个选项很有用,对于遇到死锁的情况,它会分析原因。在内核环境中,它分析内核锁和DPC栈;在用户环境中,它分析线程的调用栈。用户环境中,调试器只会对当前线程进行分析,所以一定要将线程环境切换到最可能引起问题的那个线程中去,才有帮助。这个参数非常有用,当真的遇到死锁时,它可以救命(另一个分析死锁的有效命令是!locks)。

此命令和VC里面内置的errlook工具类似(请有兴趣的读者使用作者编写的免费软件e-look,它比errlook功能更好且易于使用)。用来根据错误码,查看对应的可读错误信息。微软系统中常用的全局错误码有两套,一套是Win32错误码,通过函数GetLastError()获得的值;另一套是NTSTATUS值。!error命令对这二者都能支持。区别的方法,若错误码后面无参数1,则为win32错误码;否则就是NTSTATUS错误码。

此命令是Get Last Error的缩写。它调用Win32接口函数GetLastError()取得线程的错误值,并打印分析结果。如果带有-all选项,则针对当前进程的所有线程(内核环境下为所有用户线程)执行GetLastError()操作;否则仅针对当前线程。

gh/gn:这两个命令是g命令的扩展。

            gh是go with Exception handled的缩写,意思是:把异常标识为已处理而并继续执行程序;注意这里面的措辞,仅仅把异常“标识为”已处理,而并非真的被处理了。gh的作用在于,当遇到某个可以忽略的非致命异常时,将它先放过一边,而继续执行程序。

有两个命令可以打印当前的局部变量列表:x 和dv。x命令前文已经讲过。dv是Display local Variable的缩写。下面是对一段简单的Win32控制台代码获取其局部变量的情况:

软件断点的本质是代码改写,即:将INT 3(代码为0xCC)指令替换到断点所在指令处(第一个字节),并保存被替换掉的代码(即一个字节内容)。等执行到断点处时,调试器将因断点而中断,并将被替换的一字节内容恢复到原内存中。其原理和代码补丁是一样的。

源码或汇编模式下,最简单的断点设置方式,是定位到正确的代码处,并按下F9键。此外还有三种设置软件断点的指令,分别讲解如下:

命令bp是BreakPoint的缩写。其指令格式如下:

参数Address表示需设置断点的地址,缺省情况下使用当前指令指针(EIP)的地址。ID是断点号,一般不手动设置,由调试器内部编号。Passes是一个整数值,即第几次经过断点所在代码时,调试器才需要中断执行,默认为1,即每次都中断。CommandString用来设置一组命令,当断点发生的时候,就执行这一组命令,比如可以把它设置为“k”,这样断点处就会输出当前的调用栈。

Options是一组可选开关项,有下面几种:

/1:即阿拉伯数字1。这个选项表明这个被设置的断点只会在第一次有效,此后断点信息即被删除。

/p:这个开关项后跟一个EPCOESS结构体指针,只能用在内核调试环境下。内核调试环境下,如果要把断点设置到用户程序地址(即用户空间地址),需要使用这个开关,因为用户地址是进程相关的。

/t:这个开关项后跟一个ETHREAD结构体指针,只能用在内核调试环境下。此开关项与/p起到类似的作用,只不过前者定位到进程,后者更进一步定位到线程。

/c与/C:c或者C代表CallStack(调用栈)。这两个开关项和调用栈深度有关,都后跟一个整数值。前者表示调用栈深度如果小于这个整数值,断点才会引发中断,后者表示调用栈深度如果大于这个整数值,断点才会引发中断。

此命令格式与bp类似,u代表了Unresolved。使用此命令设置的断点虽登记到调试器,但它具体对应到哪处代码或指令,尚未确定。

比如某EXE程序使用动态加载的方式加载DLL(使用函数LoadLibrary()),那么当DLL尚未加载时,就可用bu指令设置DLL中的代码断点,等到DLL加载时,调试器再正式落实此断点。

此命令用来批量设置代码断点,它带有一个通配符字符串,凡是符合通配符格式的地址都将被设置断点,如:

硬件断点的原理和软件断点完全不同,硬件断点是通过CPU的断点寄存器来实现的,亦即依靠硬件方式实现。由于CPU的调试寄存器数量是有限的,所以能设置的硬件断点数量也是有限的。设置硬件断点的命令是ba,a代表了Address。指令格式如下:

参数Address是内存地址,有别于前文的指令地址,内存地址既可以是指令地址,也可以是数据地址。缺省为当前指令寄存器地址(EIP)。参数Size表示地址长度,x86系统可选值为1、2、4,X64系统可选值为1、2、4、8。需要注意的是,Address地址必须对齐到Size,即Address值必须是Size的整数倍。参数Access是内存访问类型,有下面几种:

地址@ebp-8一定是一个局部变量地址,所以当CPU对这个局部变量执行读写操作时,将引发硬件中断。

  • bd:禁止断点,d代表Disable。如bd 1,禁止断点1。断点被禁止后将不起作用,但亦未删除。
  • be:恢复断点,e代表Enable。恢复被禁止的断点。如be 1恢复1号断点。
  • bc:清除断点,如:bc 1,清除断点1;bc *,清除全部断点。
  • br:序号管理,r代表ReNumber,即重新排序。如:br 2 0,将2号断点重设为0号断点。

这一节里面,我们学习如何查看内存信息。内存是存储数据、代码的地方,通过内存查看命令可以分析很多问题。相关命令可以分为:内存查看命令和内存统计命令。内存统计命令用来分析内存的使用状况。

  • df = 4字节单精度浮点数格式;
  • dD =8字节双精度浮点数格式;
  • dp = 指针大小格式,32位系统下4字节,64位系统下为8字节。

二进制 + 基本类型

如果读者对此感觉不明白,特别是组合模式究竟是何种情形?看下例应能清楚。下例将同一个ASCII字符串,分别以ASCII字符串、Unicode字符串以及组合模式等共五种方式显示:

此系列命令还有一些可加利用的开关选项,介绍如下:

/c 列数:指定列数。默认情况下,列数 等于16除以列长,如dd命令的默认列数即为4列(=16/4)。例:

此命令每列显示8个DWORD数,即32字节内容。

/p:此选项用来显示物理内存信息,只能用于内核模式中。不使用此命令时,都将显示虚拟内存信息。如:

L 长度: 默认情况下,d命令只显示固定长度的内存,一般为128或64字节。L可指定长度,如下面的命令将显示地址0×开始处的0×100个字节内容:

何谓“以数组形式显示”呢?这一组命令能够将指定地址处的内容,作为一系列指针,进而显示指针所指处内容。听上去有点拗口吧,读者这样想会清楚些:前一组命令显示address值,本节这一组命令显示*address值。

程序代码中如有类似“char *array[10]”的数组变量,可使用这些命令显示数组内容。下面会有例子。这一系列命令实则由第一组命令演化而来,可分为三组:

  • 4字节DWORD为单位的dd*系列数组指令;
  • 指针长度为单位的dp*系列数组指令;
  • 8字节为单位的dq*系列数组指令。

默认情况下,以正向从头到尾遍历链表;也可反向(由尾向头)遍历,指定b开关选项:

应注意的是,命令dl是Display List的缩写,这里的链表不能是用户自定义的链表,而专指符合LIST_ENTRY或SINGLE_LIST_ENTRY格式的链表。

系统的内核空间很大的,想知道这么广大的内存空间里面都有些什么东西吗?想要知道一个内存地址,到底是被一个内核栈使用着,亦或被堆管理器使用着吗?我们这一节就领大家看看内存的地理概况。首先看Address命令:

显示进程或系统的内存状态、信息,!address是最好的工具。不加任何参数,在用户模式下此命令将以内存块为单位,列出从地址0开始到0×(略小于)的全部地址空间信息;内核模式下,将列出从地址0×开始到0xFFFFFFFF(略小于)的全部地址空间信息;如指定地址值,则将显示此地址所在内存块的内存信息(此命令在Vista以后系统中,不能在内核模式下正常使用,此Bug应会在Windbg的以后版本中被修正)。下面分别截取了用户与内核空间中的内存信息片段:

下面给大家解释一下内存块中的几个值:

内存类型:即Type值,共有四种:第一种是什么都不是,即尚未被使用的;第二种是MEM_IMAGE,即地址映射于一个可执行镜像文件片段,如DLL文件;第三种是MEM_ MAPPED,即地址映射于不可执行的镜像文件片段,如页文件;第四种是MEM_PRIVATE,即私有有内存,这里的私有是针对进程而言的,私有内存无法在多个进程间共享;

保护模式:即Protect值,上例中见识了两种保护模式,NOACCESS和READWRITE。从字面即很容易理解其意思,前者是不能做任何访问的,因为空闲内存是无效内存;后者则可读可写,但不能执行,说明是保存数据的地方。所有可用的保护包括:PAGE_NOACCESS(不可访问),PAGE_READONLY(只读),PAGE_READWRITE(读写),PAGE_EXECUTE(可执行),

内存状态:即State值,共三种:MEM_FREE,即空闲内存;MEM_RESERVED,即保留内存,保留内存尚不能被实际使用,但其地址空间已被预留,尚需一个提交动作。最后是MEM_COMMIT,即内存已被提交,正在被使用。

内存用途:即Usage值,有这样一些值和用途。RegionUsageIsVAD:表示此地址区域已被分配;RegionUsageFree:代表此地址区域已被释放,既没有保留也没有被提交,将来可以申请使用;RegionUsageImage:代表此地址区域被映射到二进制文件的镜像;Region Pdb:代表此地址区域用于保存目标进程的PEB结构;RegionUsageProcessParameters:代表此内存块用于保存目标进程的启动参数;RegionUsageEnviromentBlock:代表此地址区域用于保存目标进程的环境块。

用户环境下可使用下面的命令显示内存统计信息,包括内存用途、内存类型、内存状态。

上图分别以内存使用、内存类型、内存状态显示用户空间内存统计信息。

和!address命令类似的,用户模式下还有下面两个命令可用:

命令!vprot显示指定内存块的信息,侧重于内存保护信息;命令!vadump显示整个内存空间信息,dump者倾泻也,开启-v选项将显示详细(Verbose)信息。

上面讲过,用户环境下使用“!address  –summary”可显示用户空间的内存统计信息;现在再看两个内核命令,在内核环境下显示内存的统计信息:

此命令从物理内存角度显示内存统计信息。无数个页表信息将被打印出来,可以说是“最内存”的信息。此命令会查看所有的页帧,所以运行时会非常地耗时。

此命令从虚拟内存的角度显示内存统计信息,不仅能从全局角度显示虚拟内存的使用情况,还能以进程为单位显示内存使用情况。

内核模式下,查看文件缓存信息,命令格式如下:

            此命令在用户内核模式下,显示文件缓存和页表状态。每一行信息表示一个虚拟地址控制块 (VACB)。虚拟地址控制块可能对应着一个命名文件,也可能对应着一个元数据块。如果对应着一个命名文件,则此文件名称将被显示,否则显示元数据名称。

很多软件都使用文件缓存的方式保存数据,比如Office Word。直接查看WORD文档,由于其
内部格式不透明,故而不便分析。但如果使用WORD打开一个txt文本文档,它就会以文本文档
的方式来处理之,并且依旧使用文件缓存的方式。
读者用WORD打开一个TXT文档(比如:测试.txt)。
运行内核调试器并执行!filecache命令,在打印信息中查找“测试.txt”。

用户模式下查看堆信息,命令格式如下:

下面的清单显示了某个进程中共有4个堆:

本文到此结束。笔者并非调试方面的专家,斗胆写了这许多内容,深心惶恐。本章主要着重于基础知识,和基本指令的介绍,未免挂一漏万,遗漏了许多有用指令。此外本书亦未能深入到调试原理、应用技巧等高级主题,国内著名的调试专家张银奎老师所著的《软件调试》(电子工业出版社 2008),是希望深入学习调试技术的读者首选之作。

注:本文是笔者写作的一书第8章经整理后所发表。

}

BEGIN{} 正式处理数据之前执行

END{} 处理完所有匹配数据后执行

内置变量变量名说明$0整行内容

FNRFile Number Row,多文件处理时,每个文件行号单独计数,都是从 0 开始

FILENAME当前输入的文件名字

ARGC命令行参数个数

ARGV命令行参数数组

示例:# 以 : 分隔,输出第 1 列

# 以 -- 分隔成行,以 : 分隔成列,输出第 1、2 列

# 以 : 分隔列,输出最后一列,因为 NF 变量是总列数

格式化输出(printf)格式符说明修饰符说明%s字符串-左对齐

%f浮点数#八进制前面加 0,十六进制前面加 0x

# - 左对齐;+ 右对齐

# 20 列宽,不足则补空

# .3f 打印保留 3 位数的浮点数

示例:# 打印以 root 开头的行

# 打印空行行号、统计空行数量

# 注意:变量不需要提前声明

字符串函数函数名说明返回值length(str)计算字符串长度整数长度值

# 数组下标从 1 开始

示例:# 引入外部变量

# 把所有操作抽离到一个独立文件

# 建议:复杂操作优先使用这种方式,更易于程序理解和管理

数组遍历for a in ${arr[*]}; do echo $a; done而 awk 中数组的使用略有不同,它使用关联数组提供数组功能,即数组的索引可以是数字或任意字符串。

}

我要回帖

更多关于 c语言中float的有效位数 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信