第24章 异常处理程序与软件异常
本章内容
24.1 通过实例理解异常过滤程序和异常处理程序
24.2 EXCEPTION_EXECUTE_HANDLER
24.3 EXCEPTION_CONTINUE_EXECUTION
24.4 EXCEPTION_CONTINUE_SEARCH
24.5 GetExceptionCode
24.6 GetExceptionInformation
24.7 软件异常
除0或者访问NULL地址,会被cpu捕获。称为硬件异常
操作系统和应用程序也可以自行抛出异常,称为软件异常。
一个异常处理程序的语法结构
__try{
// Guarded body
}
__except (exception filter) {
// Exception handler
}
24.1 通过实例理解异常过滤程序和异常处理程序
24.1.1 Funcmeister1 函数
DWORD dwTemp;
// 1. Do any processing here.
//...
__try {
// 2. Perform some operation.
dwTemp = 0;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// Handle an exception; this never execute.
// ...
}
// 3. Continue processing.
return dwTemp;
}
这个函数不会导致异常,在异常处理程序的try块中可以使用return goto continue和break。因为这些语句不会带来局部展开的开销。
24.1.2 Funcmeister2 函数
DWORD Funcmeister2() {
DWORD dwTemp = 0;
// 1. Do any processing here.
//...
__try {
// 2. Perform some operation.
dwTemp = 5 / dwTemp; // Generates an exception
dwTemp += 10; // Never executes
}
__except (/* 3. Evaluate filter. */EXCEPTION_EXECUTE_HANDLER) {
// 4. Handle an exception;
MessageBeep(0);
// ...
}
// 5. Continue processing.
return dwTemp;
}
除零错误会被cpu捕获并抛出一个硬件异常。接着系统定位到except块,并对异常过滤程序的表达式求值。
可能是一下3个值
异常处理的过程:
24.2 EXCEPTION_EXECUTE_HANDLER
异常处理完毕以后,从哪里开始继续执行代码?
1. 可能是导致异常的那条cpu指令之后的第一条指令开始执行。可能导致连锁异常
2. 从产生异常的那条指令本身开始执行。(
EXCEPTION_CONTINUE_EXECUTION)
3. 从except块后的第一句代码继续(实际情况如此)
24.2.1 一些有用的例子
一个不恰当的try/except
char* RobustStrCpy(char* strDes, const char* src) {
__try {
strcpy(strDes, src);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// Nothing to do here.
}
return strDes;
}
以上代码虽然捕获了异常但是没有做任何处理。这是不可取的。可能会导致隐患和后续问题。
原则:只处理我们知道怎么处理的异常,并且也不要忘记其他的保护措施。以防止程序状态混乱或者安全漏洞。
一个健壮异常处理的例子。
int RobustHowManyToken(const char * str) {
int nHowManyTokens = -1; // -1 indicates failure
char * strTemp = NULL; // Assume failure
__try {
// Allocate a temporary buffer
strTemp = (char*)malloc(strlen(str) + 1);
// Copy the original string to the temporary buffer
strcpy(strTemp, str);
// Get the first token
char * pszToken = strtok(strTemp, " ");
// Iterate through all the tokens
for (; pszToken != NULL; pszToken = strtok(NULL, " "))
nHowManyTokens++;
nHowManyTokens++; // Add 1 since we started at -1
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// Nothing to do here
}
// Free the temporary buffer (guaranteed)
free(strTemp);
return (nHowManyTokens);
}
能过处理常见的内存错误。
另一个内存拷贝例子
PBYTE RobustMemDup(PBYTE pbSrc, size_t cb) {
PBYTE pbDup = NULL; // Assume failure
__try {
// Allocate a buffer for the duplicate memory block
pbDup = (PBYTE)malloc(cb);
memcpy(pbDup, pbSrc, cb);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
free(pbDup);
pbDup = NULL;
}
return pbDup;
}
24.2.2 全局展开
当异常过滤程序的计算结果为
EXCEPTION_EXECUTE_HANDLER时,系统必须执行全局展开(unwind)。
全局展开导致所有已经开始执行但尚未完成的try-finally块得以继续执行,在调用栈中,这些try-finally块位于对异常进行处理的try-except块下方(Call stack窗口中下方就是趋向栈底的函数)。