windows核心编程-第一章 对程序错误的处理
第一章-对程序错误的处理
在开始介绍Microsoft Windows
的特性之前,必须首先了解 Wi n d o w s的各个函数是如何进行错误处理的。
当调用一个Wi n d o w s函数时,它首先要检验传递给它的的各个参数的有效性,然后再设法执行任务。如果传递了一个无效参数,或者由于某种原因无法执行这项操作,那么操作系统就会返回一个值,指明该函数在某种程度上运行失败了。表
1 - 1列出了大多数Wi n d o w s函数使用的返回值的数据类型。
一个Wi n d o w s函数返回的错误代码对了解该函数为什么会运行失败常常很有用。
M i c r o s o f t公司编译了一个所有可能的错误代码的列表,并且为每个错误代码分配了一个
3 2位的号码。
从系统内部来讲,当一个Wi n d o w s函数检测到一个错误时,它会使用一个称为线程本地存储器(thread-local storage)
的机制,将相应的错误代码号码与调用的线程关联起来(
线程本地存储器将在第2 1章中介绍) 。这将使线程能够互相独立地运行,而不会影响各自的错误代码。当函数返回时,它的返回值就能指明一个错误已经发生。若要确定这是个什么错误,请调用G e t L a s t E
r r o r函数:
DWORD GetLastError();
该函数只返回线程的3 2位错误代码。当你拥有3 2位错误代码的号码时,必须将该号码转换成更有用的某种对象。
Wi n E r r o r. h头文件包含了M i c r o s o f t公司定义的错误代码的列表。下面显示了该列表的某些内容,使你能够看到它的大概样子:
如你所见,每个错误都有3种表示法:一个消息I
D(这是你可以在源代码中使用的一个宏,以便与G e t L a s t E r r o r的返回值进行比较) ,消息文本(对错误的英文描述)和一个号码(应该避免使用这个号码,可使用消息I
D) 。请记住,这里只显示了Wi n E r r o r. h头文件中的很少一部分内容,整个文件的长度超过2
1 0 0 0行。
当Wi n d o w s函数运行失败时,应该立即调用G e t L a s t E r r o r函数。如果调用另一个Wi
n d o w s函数,它的值很可能被改写。
注意 G e t L a s t E r r o r能返回线程产生的最后一个错误。如果该线程调用的Wi n d o w s函数运行成功,那么最后一个错误代码就不被改写,并且不指明运行成功。有少数Wi
n d o w s函数并不遵循这一规则,它会更改最后的错误代码;但是 Platform SDK文档通常指明,当函数运行成功时,该函数会更改最后的错误代码。
Wi n d o w s 9 8 许多Windows 98的函数实际上是用M i c r o s o f t公司的1
6位Windows 3.1产品产生的1 6位代码来实现的。这种比较老的代码并不通过
G e t L a s t E r r o r之类的函数来报告错误,而且M i c r o s o f t公司并没有在Windows
98中修改1 6位代码,以支持这种错误处理方式。对于我们来说,这意味着Windows 98中的许多Wi
n 3 2函数在运行失败时不能设置最后的错误代码。该函数将返回一个值,指明运行失败,这样你就能够发现该函数确实已经运行失败,但是你无法确定运行失败的原因。
有些Wi n d o w s函数之所以能够成功运行,其中有许多原因。例如,创建指明的事件内核对象之所以能够取得成功,是因为你实际上创建了该对象,或者因为已经存在带有相同名字的事件内核对象。你应搞清楚成功的原因。为了将该信息返回,
M i c r o s o f t公司选择使用最后错误代码机制。这样,当某些函数运行成功时,就能够通过调用
G e t L a d t E r r o r函数来确定其他的一些信息。对于具有这种行为特性的函数来说,
Platform SDK文档清楚地说明了G e t L a s t E r r o r函数可以这样使用。请参见该文档,找出C
r e a t e E v e n t函数的例子。
进行调试的时候,监控线程的最后错误代码是非常有用的。 在Microsoft Visual studio 6.0中,M i c r o s o f t的调试程序支持一个非常有用的特性,即可以配置
Wa t c h窗口,以便始终都能显示线程的最后错误代码的号码和该错误的英文描述。通过选定
Wa t c h窗口中的一行,并键入“@ e r r, h r” ,就能够做到这一点。观察图1 - 1,你会看到已经调用了C
r e a t e F i l e函数。该函数返回I N VA L I D _ H A N D L E _ VA L U E(- 1)的H
A N D L E,表示它未能打开指定的文件。但是Wa t c h窗口向我们显示最后错误代码(即如果调用
G e t L a s t E r r o r函数,该函数返回的错误代码)是0 x 0 0 0 0 0 0 0 2。该Wa
t c h窗口又进一步指明错误代码2是指“系统不能找到指定的文件。 ”你会发现它与Wi n E r r o r. h头文件中的错误代码2所指的字符串是相同的。
OK,然后我就照着做,但是发现了一个尴尬的事情,我在vs2012上找不大watch窗口,搜索了下,说是在调试->窗口->监视(Debug->Window->Watch),然后我就去找,但是并没有找到,一开始看到的是这个:
额...后来无意中发现了,其实是当你点击运行的时候,那个地方才会出现Watch的选项:
然后就是我在vs2012中写了下上面说的那个:
然后又在网上找了一些常用技巧:
1.eg: int *p = new int[100];
如果只查看p的话,只能看到一个结果。可以使用:p,n
查看n个结果。
2.格式化数据和表达式赋值语句.
常用变量格式化符(表达式的值后跟逗号,接格式化符,如”(int)0xFFFF,d”):
d,I:有符号的十进制数.
u :无符号的十进制数.
o :无符号的八
x,X:十六进制数.
l,h:d,i,u,o,x,X的长前缀或短前缀.
f :有符号浮点数.
e :有符号的科学计数法.
g :有符号的浮点或有符号的科学计数法,用其中较短的一个.
c :单字符.
s :字符串.
su :双字节字符串.
st :双字节字符串或ANSI字符串,取决于AUTOEXP.DAT中的Unicode
String设置.
hr :Windows类标记.
wm :Windows消息码.
常用内存转储对象的格式化符(用法同变量格式化符):
ma :64个ASCII码字符.
m :以16进制书写的16字节,后跟16个ASCII字符.
mb :以16进制书写的16字节,后跟16个ASCII字符.
mw :8个字长.
md :4个双精度字.
mq :4个四倍字长的字.
mu :2字节字符(Unicode标准).
# :将指针扩展到指定的数值数目的内存存储单元上.(#代表一个数字)
WATCH窗口允许重新设置数据变量的格式,
如:可用BY,DW表达式来定位指针的偏移量;
可用&和*运算符,且两运算符都可直接操作内存地址;
甚至可用上下说明符明确指定变量的上下文.
总之,所有格式化方法和指定方法在WATCH窗口都有效
WATCH窗口是一个完整的表达式求值程序,可以在其中查看任何条件语句.
表达式中可用的伪寄存器(可当普通变量进行查看):
@ERR:最后一个错误值,GetLastError API返回相同的值.
@TIB:当前线程的线程信息块.(调试器不能处理”FS:0″格式).
@CLK:时钟寄存器.
@EAX,@EBX,@ECX,@EDX,@ESI,@EDI,@DIP,@ESP,@EBP,@EFL
:Intel CPU寄存器.
@CS,@DS,@ES,@SS,@FS,@GS
:Intel CPU段寄存器.
@ST0,@ST1,@ST2,@ST3,@ST4,@ST5,@ST6,@ST7
:Intel CPU浮点寄存器.
3.适时编码
许多时候只想对两断点间的执行时间有个大致印象,可用@CLK得出两断点间所需执行时间(包括调试器占用的时间).
需要输入两个@CLK观察符,第一个是@CLK,第二个是@CLK=0.第二个的目的是重新运行时将定时器清0.
时间以微秒为单位,大多数情况下需要格式化为毫秒:”@CLK/1000,d”.
4.在WATCH窗口中调用函数
大多数情况下用于执行专门编写的校验数据结构,保证数据的相关性的函数.在释放构件中,从未调用过的函数不会被链接,因此不必担
心这类函数会对影响发布构件.
如函数没有参数,也要求使用括号”()”,调用时像用普通函数一样传送参数.WATCH右边将显示函数返回值.
这里有些限制:
1.只能在一个单线程上下文中执行函数.如是多线程程序,将函数输入到WATCH窗口中检查结果后应立即从WATCH窗口清除,否则,如调
试函数在第二个线程上下文中执行,会立即终止第二个线程的运行.
2.调试函数必须在20秒内执行.如执行过程中出现异常,程序会在调试器中中止.
3.(常识)只对数据验证进行内存读取,如有问题,调用OutputDebugString类的函数.如更改内存或调用API函数—-尽管这是可能的,但
无法预知可能会发生什么.
只要在WATCH窗口中重新计算表达式,已输入WATCH窗口的调试函数就会执行:
.程序处于运行状态并触发某一断点时.
.单步调试某一代码行或某一指令时.
.在WATCH窗口左边编辑完成调试函数的文本并按下回车时.
.在运行程序时出现异常情况,并让你返回调试器中时.
使用调试函数的建议:输入调试函数并查看值后,立即从WATCH窗口清除;只为最关键的数据结构编写调试函数;不要更改个别结构的转
储内像.
5.自动扩展自己的类型
常见的自动扩展是RECT,输入RECT型的变量后直接显示其中的某些数据成员的值.
自定义类型扩展时,只需将自己的类型入口加入<VS Common>\MSDev98\Bin目录的AUTOEXP.DAT文件中.
例:
扩展CreateProcess()所用到的PROCESS_INFORMATION结构
(1).检查调试器将该类型识别为什么.将PROCESS_INFORMATION变量输入WATCH窗口,右击变量,选择Properties,在这里它被标注为
_PROCESS_INFORMATION类型.
(2).打开AUTOEXP.DAT文本文件,加入扩展入口.语法如下:
Type=[text]<member[format]>
本例中要查看hProcess和hThread值,故输入:
_PROCESS_INFORMATION=hProcess=<hProcess,X> hThread=<hThread,X>
其中X表示以16进制查看.有个特殊的格式化符<,t>,用于通知调试器输入最易派生类型的类型名.如B派生至A,只有B有自动扩展规则,
则B的自动扩展将会是后面跟随着类A的自动扩展规则的类型名B.
6.Set Next Statement命令
可以在调试时从菜单运行,但也可在WATCH窗口中直接设置EIP寄存器—-小心,可能很容易摧毁程序.在最优化的释放构件中,最安全的
方法是在Disassembly窗口中使用该命令.如代码在堆栈上创建了临时变量,更要多加小心.
最常用的情况是:在出问题的函数前设置一个断点,检查进入的参数,单步调试整个函数;如问题不是重复的,使用Set
Next Statement
设置返回到断点的执行点,并更改参数.这样可在一个调试会话中测试多个假设,节省测试时间,但它不能用于所有场合,因为函数执行
会破坏其状态.另一个常用地点是测试时填充数据结构,如表和数组,可用它输入额外的数据并查看代码如何处理–当某些数据条件难于复制时更为方便.
Visual studio还配有一个小的实用程序,称为Error Lookup。可以使用Error Lookup
将错误代码的号码转换成相应文本描述(见图1 - 2) 。如果在编写的应用程序中发现一个错误,可能想要向用户显示该错误的文本描述。Wi n d o w s提供了一个函数,可以将错误代码转换成它的文本描述。该函数称为
F o r m a t -M e s s a g e,显示如下:
F o r m a t M e s s a g e函数的功能实际上是非常丰富的,在创建向用户显示的字符串信息时,它是首选函数。该函数之所以有这样大的作用,原因之一是它很容易用多种语言进行操作。该函数能够检测出用户首选的语言(在Regional
Settings Control Panel小应用程序中设定) ,并返回相应的文本。当然,首先必须自己转换字符串,然后将已转换的消息表资源嵌入你的
. e x e文件或D L L模块中,然后该函数会选定正确的嵌入对象。
E r r o r S h o w示例应用程序(本章后面将加以介绍)展示了如何调用该函数,以便将M i c r o s o f t公司定义的错误代码转换成它的文本描述。
有些人常常问我,M i c r o s o f t公司是否建立了一个主控列表,以显示每个
Wi n d o w s函数可能返回的所有错误代码。可惜,回答是没有这样的列表,而且
M i c r o s o f t公司将永远不会建立这样的一个列表。因为在创建系统的新版本时,建立和维护该列表实在太困难了。建立这样一个列表存在的问题是,你可以调用一个
Wi n d o w s函数,但是该函数能够在内部调用另一个函数,而这另一个函数又可以调用另一个函数,如此类推。由于各种不同的原因,这些函数中的任何一个函数都可能运行失败。有时,当一个函数运行失败时,较高级的函数对它进行恢复,并且仍然可以执行你想执行的操作。为了创建该主控列表,
M i c r o s o f t公司必须跟踪每个函数的运行路径,并建立所有可能的错误代码的列表。这项工作很困难。而且,当创建系统的新版本时,这些函数的运行路径还会改变。
1.1 定义自己的错误代码
前面已经说明 Wi n d o w s函数是如何向函数的调用者指明发生的错误,你也能够将该机制用于自己的函数。比如说,你编写了一个希望其他人调用的函数,你的函数可能因为这样或那样的原因而运行失败,你必须向函数的调用者说明它已经运行失败。
若要指明函数运行失败,只需要设定线程的最后的错误代码,然后让你的函数返回FA L S E、I N VA L I D _ H A N D L E _ VA L U E、N
U L L或者返回任何合适的信息。若要设定线程的最后错误代码,只需调用下面的代码:
请将你认为合适的任何 3 2位号码传递给该函数。尝试使用
Wi n E r r o r. h中已经存在的代码,图1-2 Error Lookup窗口只要该代码能够正确地指明想要报告的错误即可。如果你认为
Wi n E r r o r. h中的任何代码都不能正确地反映该错误的性质,那么可以创建你自己的代码。错误代码是个
3 2位的数字,划分成表
1-2 所示的各个域。
表1-2 错误代码的域
这些域将在第2 4章中详细讲述。现在,需要知道的重要域是第
2 9位。M i c r o s o f t公司规定,他们建立的所有错误代码的这个信息位均使用
0。如果创建自己的错误代码,必须使
2 9位为1。这样,就可以确保你的错误代码与M i c r o s o f t公司目前或者将来定义的错误代码不会发生冲突。
来一个自定义的例子:SetLastError((1<<29) + (1<<30) * 0 + 1);
1.2 ErrorShow示例应用程序
E r r o r S h o w应用程序“01 ErrorShow. e x e”(在清单1 - 1中列出)展示了如何获取错误代码的文本描述的方法。该应用程序的源代码和资源文件位于本书所附光盘上的 0 1 - E r r o r S h o w目录下。一般来说,该应用程序用于显示调试程序的 Wa t c h窗口和Error Lookup程序是如何运行的。当启动该程序时,就会出现
如图1 - 3所示的窗口。
可以将任何错误代码键入该编辑控件。当单击 Look up按钮时,在底部的滚动窗口中就会显示该错误的文本描述。该应用程序唯一令人感兴趣的特性是如何调用F o r m a t M e s s a g e函数。下面是使用该函数的方法:
第一个代码行用于从编辑控件中检索错误代码的号码。然后,建立一个内存块的句柄并将它初始化为N U L L。F o r m a t M e s s a g e函数在内部对内存块进行分配,并将它的句柄返回给我们。当调用F
o r m a t M e s s a g e函数时,传递了F O R M AT _ M E S S A G E _ F R O M _ S Y S T E M标志。该标志告诉F o r m a t M
e s s a g e函数,我们想要系统定义的错误代码的字符串。还传递了
F O R M AT _M E S S A G E _ A L L O C AT E _ B U F F E R标志,告诉该函数为错误代码的文本描述分配足够大的内存块。该内存块的句柄将在
h l o c a l变量中返回。第三个参数指明想要查找的错误代码的号码,第四个参数指明想要文本描述使用什么语言。
如果F o r m a t M e s s a g e函数运行成功,那么错误代码的文本描述就位于内存块中,将它拷贝到对话框底部的滚动窗口中。如果F o r m a t M e s s a g e函数运行失败,设法查看N
e t M s g . d l l模块中的消息代码,以了解该错误是否与网络有关。使用
N e t M s g . d l l模块的句柄,再次调用F o r m a t M e s s a g e函数。你会看到,每个
D L L(或. e x e)都有它自己的一组错误代码,可以使用Message Compiler(M
C . e x e)将这组错误代码添加给该模块,并将一个资源添加给该模块。这就是Visual Studio的Error Lookup工具允许你用M
o d u l e s对话框进行的操作。
windows核心编程-第一章 对程序错误的处理的更多相关文章
- Windows核心编程第一章.错误处理
Windows核心编程第一章,错误处理. 一丶错误处理 1.核心编程学习总结 不管是做逆向,开始做开发.在Windows下.你都需要看一下核心编程这本书.这本书确实写得很好.所以自己在学习这本书的同时 ...
- windows核心编程---第一章 谈谈windows中的错误处理机制
我们写的函数会用返回值表示程序执行的正确与否,使用void,就意味着程序一定不会出错.Bool类型标识true时为真,false时为假.其他类型根据需要可以定义成不同意义. Win ...
- windows核心编程---第二章 字符和字符串处理
使用vc编程时项目-->属性-->常规栏下我们可以设置项目字符集合,它可以是ANSI(多字节)字符集,也可以是unicode字符集.一般情况下说Unicode都是指UTF-16.也 ...
- windows核心编程---第九章 同步设备IO与异步设备IO之同步IO
同步设备IO 所谓同步IO是指线程在发起IO请求后会被挂起,IO完成后继续执行. 异步IO是指:线程发起IO请求后并不会挂起而是继续执行.IO完毕后会得到设备的通知.而IO完成端口就是实现这种通知的很 ...
- Windows核心编程第二章,字符串的表示以及宽窄字符的转换
目录 Windows核心编程,字符串的表示以及宽窄字符的转换 1.字符集 1.1.双字节字符集DBCS 1.2 Unicode字符集 1.3 UTF-8编码 1.4 UTF - 32编码. 1.5 U ...
- windows核心编程-第二章 Unicode
第2章U n i c o d e 随着M i c r o s o f t公司的Wi n d o w s操作系统在全世界日益广泛的流行,对于软件开发人员来说,将目标瞄准国际上的各个不同市场,已经成为一个 ...
- Windows核心编程 第九章 线程与内核对象的同步(下)
9.4 等待定时器内核对象 等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象.它们通常用来在某个时间执行某个操作. 若要创建等待定时器,只需要调用C r e a t e Wa i ...
- Windows核心编程 第九章 线程与内核对象的同步(上)
第9章 线程与内核对象的同步 上一章介绍了如何使用允许线程保留在用户方式中的机制来实现线程同步的方法.用户方式同步的优点是它的同步速度非常快.如果强调线程的运行速度,那么首先应该确定用户方式的线程同步 ...
- 《windows核心编程》 在应用程序中使用虚拟内存
Microsoft Windows 提供了以下三种机制来对内存进行操控: 虚拟内存 最适合用来管理大型对象数组或大型结构数组 内存映射文件 最适合用来管理大型数据流(通常是文件),以及在同一台机器上运 ...
随机推荐
- WEB服务-Nginx之十-keepalived
WEB服务-Nginx之10-keepalived 目录 WEB服务-Nginx之10-keepalived Keepalived和高可用 基本概述 Keepalived安装配置 Keepalived ...
- 聊一聊和Nacos 2.0.0对接那些事
前言 nacos 2.0.0 已经发布了 alpha1, alpha2 和 beta 三个版本了,部分测试报告也已经出来了. Nacos2.0.0-ALPHA2 服务发现性能测试报告 Nacos 2. ...
- SSRF漏洞利用之Redis大神赐予shell
0x00实验环境 1.centos靶机(IP为:192.168.11.205,桥接模式) 2.kali黑客攻击主机(IP为:192.168.172.129,NAT模式) 0x01实验原理 这段 ...
- 一文搞懂 this、apply、call、bind
码文不易,转载请带上本文链接,感谢~ https://www.cnblogs.com/echoyya/p/14506269.html 目录 码文不易,转载请带上本文链接,感谢~ https://www ...
- 如何获取下载 FreeBSD
『如何获取下载 FreeBSD 』 『如何获取下载 FreeBSD 』 FreeBSD 是免费获取的. [下载地址] O网页链接 版本选择,尽量选择较新版本,桌面用户可选择 current 版本.st ...
- Redis工具收费后新的开源已出现
作者:三十三重天 博客: zhouhuibo.club 引言 Redis工具哪家强,中国山东找蓝翔.哎呀,串台了. 众所周知,开源的最终还是收费. Reids Desktop 秉承了这一理念,苦逼的程 ...
- C# 基础 - 文件对话框
using System.Windows.Forms; ... /// <summary> /// 选择保存文件的名称以及路径 取消返回 空""; /// </s ...
- 深入理解Java并发框架AQS系列(一):线程
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.概述 1.1.前言 重剑无锋,大巧不工 读j.u.c包下的源码,永远无法绕开的经典 ...
- BST(二叉搜索树)的基本操作
BST(二叉搜索树) 首先,我们定义树的数据结构如下: public class TreeNode { int val; TreeNode left; TreeNode right; public T ...
- SpringBoot-11 扩展功能
SpringBoot-11 扩展功能 异步 同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列.要么成功都成功,失败都失败,两个任务 ...