一 什么是一般性保护错误(GP)?
一般性保护错误(general protect简称GP),通常只是WINDOWS下的一个术语,我们现在也常把它引用到UNIX下指程序的崩溃。
本文主要探讨WINDOWS下面程序GP的原因,预防和解决办法。
二 GP的原因
WIN32操作系统在发现某应用程序企图访问不属于自己的非法的内存区域时,将触发一般性保护错误,立刻终止该应用程序,防止该程序破坏其他程序的内存数据,对其他程序造成不良影响。
如果该机器上装Dr Washington软件,该软件将捕捉该错误,并出错时的应用程序内存地址情况抓下来,但实际上要分析的话,恐怕需要专门的工具。
如果没有装Dr软件,通常会弹出一个对话框,类似提示为该应用程序非法的访问了0XEFEDCD的错误出来。
如果应用程序为Debug版本,这时候弹出的对话框可能会友善一点点,有时会弹出一个Assert对话框,要你调试还是放弃来着。
以下具体分析导致GP的种种原因:
1 编译器的BUG或编译开关设置不当。
已经有文档和实践证明,VC6.0编译器在缺少补丁的情况,使用最大速度优化将导致程序在某种特殊情况下GP。
编译开关建议一律采用单字节对齐,否则接口程序越多,不一致的对齐方式最后有可能导致GP。
2 库文件或其他配置支撑文件缺少,或者不匹配。
比如原来正常工作的应用程序和配套的DLL,将其中一个DLL用另一个同名但版本不同的DLL替换将导致GP。这是很容易理解的,不同DLL的函数内存映象是不同,应用程序企图通过一个非法的地址访问函数自然会GP。
另外一个常见的例子时winsock的支持版本不一致。使用了winsock的高级特性,但支持库却跟不上。
还有一个例子也很有代表性,几个软件使用一个共用的库文件,其中一个软件升级后,偷偷将该库文件给替换了,导致其他软件不能正常使用。
3 不安全的API函数
分为几类说明
3.1 字符串函数
strlen WIN32下面传入不能为空
sprintf 前面的%和后面的参数不匹配。
建议使用snprintf而不是sprintf,因为前者判断缓冲溢出,后者不判断。
Strcat,在后面叠加时,一定要判断缓冲大小。
Strcpy
3.2 内存函数
memset,memcpy等,一定要判断内存地址有效性,以及大小等。
3.3 时间函数
localtime 不能传入-1
3.4 其他入参有特殊要求的函数,使用时请仔细查看MSDN。
4 数组访问越界,包括上下界。
5 访问到malloc所分配的内存区域后面的内存。
6 用malloc分配了内存,却不检验其合法性。
比如 用子函数分配堆上内存,在子函数中虽然有出错判断,但调用函数却根本没有考虑。
虽然从一般情况下考虑,WINDOWS采用虚拟内存方式,一般不会malloc失败,但是在系统非常繁忙且硬盘空间狭小的情况,一次性分配巨量堆上内存,仍然会可能会malloc失败
7 已经释放malloc分配的内存,却没有把malloc返回的指针置为NULL,另一个地方企图使用该指针指向的内存数据。
8企图重复释放malloc分配的内存。
9 两个函数使用一个全局数据结构,结构中有指针成员,该指针指向第一个函数在在堆栈上分配的一块内存,第二个函数企图通过该指针成员访问那块内存。
10 第一个函数返回一个堆栈上的内存地址为返回值,第二个函数企图使用第一个函数的返回值来获取该内存上的数据。堆栈上分配的内存地址,在函数退出时,该空间内的数据随时可能被其他函数覆盖,所以为不可用数据。
11 数值被0除。(此属于溢出错误,不属于访问内存失败,放在这只是为了提醒)
12 企图改变存储在静态存储区的常量字符串字面值。
13 企图对右值赋值。
14 企图使用一个空指针,经常出现在函数中使用入口指针时不进行判断。
15 消息接口定义不一致,经常出现在各接口程序的头文件定义不一致,导致非法的消息操作。比如 字节序转换时由于对方送来的消息比预期的小,就会访问到后面的内存区域,导致GP。
17 病毒或不同格式文件系统反复拷贝程序导致文件被破坏,注意检查文件大小和时间是否变化。
16 操作系统的bug,通常操作系统和复杂软件比如ORALCE数据库等的兼容性问题,这种问题,只有到微软的站点和相关公司的站点多下点补丁吃吧。
三解决办法
1 预防性校验 包括指针有效性判断,函数入参判断。
2 利用断言assert发现问题。
3 调试时分为两种现象:比如API,数组越界这种问题单步跟踪很快可以发现问题,象第五种有时表现出来非常奇怪,一会在这函数,一会那函数出现,利用函数体简化的方法,可排除干扰项。
注意查看堆栈记录,函数调用顺序。
4 利用调试日志。
5 利用程序崩溃内存映像分析。
6 采用环境比较法。
一般性保护错误(general protect简称GP),通常只是WINDOWS下的一个术语,我们现在也常把它引用到UNIX下指程序的崩溃。
本文主要探讨WINDOWS下面程序GP的原因,预防和解决办法。
二 GP的原因
WIN32操作系统在发现某应用程序企图访问不属于自己的非法的内存区域时,将触发一般性保护错误,立刻终止该应用程序,防止该程序破坏其他程序的内存数据,对其他程序造成不良影响。
如果该机器上装Dr Washington软件,该软件将捕捉该错误,并出错时的应用程序内存地址情况抓下来,但实际上要分析的话,恐怕需要专门的工具。
如果没有装Dr软件,通常会弹出一个对话框,类似提示为该应用程序非法的访问了0XEFEDCD的错误出来。
如果应用程序为Debug版本,这时候弹出的对话框可能会友善一点点,有时会弹出一个Assert对话框,要你调试还是放弃来着。
以下具体分析导致GP的种种原因:
1 编译器的BUG或编译开关设置不当。
已经有文档和实践证明,VC6.0编译器在缺少补丁的情况,使用最大速度优化将导致程序在某种特殊情况下GP。
编译开关建议一律采用单字节对齐,否则接口程序越多,不一致的对齐方式最后有可能导致GP。
2 库文件或其他配置支撑文件缺少,或者不匹配。
比如原来正常工作的应用程序和配套的DLL,将其中一个DLL用另一个同名但版本不同的DLL替换将导致GP。这是很容易理解的,不同DLL的函数内存映象是不同,应用程序企图通过一个非法的地址访问函数自然会GP。
另外一个常见的例子时winsock的支持版本不一致。使用了winsock的高级特性,但支持库却跟不上。
还有一个例子也很有代表性,几个软件使用一个共用的库文件,其中一个软件升级后,偷偷将该库文件给替换了,导致其他软件不能正常使用。
3 不安全的API函数
分为几类说明
3.1 字符串函数
strlen WIN32下面传入不能为空
sprintf 前面的%和后面的参数不匹配。
建议使用snprintf而不是sprintf,因为前者判断缓冲溢出,后者不判断。
Strcat,在后面叠加时,一定要判断缓冲大小。
Strcpy
3.2 内存函数
memset,memcpy等,一定要判断内存地址有效性,以及大小等。
3.3 时间函数
localtime 不能传入-1
3.4 其他入参有特殊要求的函数,使用时请仔细查看MSDN。
4 数组访问越界,包括上下界。
5 访问到malloc所分配的内存区域后面的内存。
6 用malloc分配了内存,却不检验其合法性。
比如 用子函数分配堆上内存,在子函数中虽然有出错判断,但调用函数却根本没有考虑。
虽然从一般情况下考虑,WINDOWS采用虚拟内存方式,一般不会malloc失败,但是在系统非常繁忙且硬盘空间狭小的情况,一次性分配巨量堆上内存,仍然会可能会malloc失败
7 已经释放malloc分配的内存,却没有把malloc返回的指针置为NULL,另一个地方企图使用该指针指向的内存数据。
8企图重复释放malloc分配的内存。
9 两个函数使用一个全局数据结构,结构中有指针成员,该指针指向第一个函数在在堆栈上分配的一块内存,第二个函数企图通过该指针成员访问那块内存。
10 第一个函数返回一个堆栈上的内存地址为返回值,第二个函数企图使用第一个函数的返回值来获取该内存上的数据。堆栈上分配的内存地址,在函数退出时,该空间内的数据随时可能被其他函数覆盖,所以为不可用数据。
11 数值被0除。(此属于溢出错误,不属于访问内存失败,放在这只是为了提醒)
12 企图改变存储在静态存储区的常量字符串字面值。
13 企图对右值赋值。
14 企图使用一个空指针,经常出现在函数中使用入口指针时不进行判断。
15 消息接口定义不一致,经常出现在各接口程序的头文件定义不一致,导致非法的消息操作。比如 字节序转换时由于对方送来的消息比预期的小,就会访问到后面的内存区域,导致GP。
17 病毒或不同格式文件系统反复拷贝程序导致文件被破坏,注意检查文件大小和时间是否变化。
16 操作系统的bug,通常操作系统和复杂软件比如ORALCE数据库等的兼容性问题,这种问题,只有到微软的站点和相关公司的站点多下点补丁吃吧。
三解决办法
1 预防性校验 包括指针有效性判断,函数入参判断。
2 利用断言assert发现问题。
3 调试时分为两种现象:比如API,数组越界这种问题单步跟踪很快可以发现问题,象第五种有时表现出来非常奇怪,一会在这函数,一会那函数出现,利用函数体简化的方法,可排除干扰项。
注意查看堆栈记录,函数调用顺序。
4 利用调试日志。
5 利用程序崩溃内存映像分析。
6 采用环境比较法。