本章内容
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}
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。因为这些语句不会带来局部展开的开销。
DWORD Funcmeister2() {DWORD dwTemp = 0;// 1. Do any processing here.//...__try {// 2. Perform some operation.dwTemp = 5 / dwTemp; // Generates an exceptiondwTemp += 10; // Never executes}__except (/* 3. Evaluate filter. */EXCEPTION_EXECUTE_HANDLER) {// 4. Handle an exception;MessageBeep(0);// ...}// 5. Continue processing.return dwTemp;
}
除零错误会被cpu捕获并抛出一个硬件异常。接着系统定位到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 failurechar * strTemp = NULL; // Assume failure__try {// Allocate a temporary bufferstrTemp = (char*)malloc(strlen(str) + 1);// Copy the original string to the temporary bufferstrcpy(strTemp, str);// Get the first tokenchar * pszToken = strtok(strTemp, " ");// Iterate through all the tokensfor (; 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 blockpbDup = (PBYTE)malloc(cb);memcpy(pbDup, pbSrc, cb);}__except (EXCEPTION_EXECUTE_HANDLER) {free(pbDup);pbDup = NULL;}return pbDup;
}
void FuncOStimpy1() {// 1. Do any processing here.// ...__try{// 2. Call another function.FuncORen1();// Code here never executes.}__except (/* 6. Evaluate filter. */ EXCEPTION_EXECUTE_HANDLER){// 8. Afte rthe unwind, the exception handler executes.MessageBox(...);}// 9. Exception handled -- continue execution.
代码的执行顺序参考注释。
}void FuncORen1() {DWORD dwTemp = 0;// 3. Do any processing here.__try{// 4. Request permission to access protected data.WaitForSingleObject(g_hSem, INFINITE);// 5. Modify the data.// An exception is generated here.g_dwProtectedData = 5 / dwTemp;}__finally {// 7. Global unwind occurs because filter evaluated// to EXCEPTION_EXECUTE_HANDLER.// Allow others to use protected data.ReleaseSemaphore(g_hSem, 1, NULL);}// Continue processing -- never executes.
}
void FuncMonkey(){__try{FuncFish();}__except (EXCEPTION_EXECUTE_HANDLER) {MessageBeep(0);}
}void FuncFish() {FuncPheasant();MessageBox(NULL, TEXT("FuncFish"), TEXT(""), MB_OK);
}void FuncPheasant() {__try{strcpy(NULL, NULL);}__finally{return;}
}
1>------ Build started: Project: SubProcess, Configuration: Debug Win32 ------
1> SubProcess.cpp
1>c:\users\admin\documents\visual studio 2013\projects\consoleapplication3\subprocess\subprocess.cpp(41): error C4532: 'return' : jump out of __finally block has undefined behavior during termination handling
========== Build: 0 succeeded, 1 failed, 2 up-to-date, 0 skipped ==========
TCHAR g_szBuffer[100];
LONG OilFilter1(TCHAR ** ppchBuffer);void FunclinRoosevelt1() {int x = 0;TCHAR *pchBuffer = NULL;__try{*pchBuffer = TEXT('J');x = 5 / x;}__except (OilFilter1(&pchBuffer)) {MessageBox(NULL, TEXT("An exception occured"), NULL, MB_OK);}MessageBox(NULL, TEXT("Function completed"), NULL, MB_OK);
}LONG OilFilter1(TCHAR **ppchBuffer) {if (*ppchBuffer == NULL) {*ppchBuffer = g_szBuffer;return EXCEPTION_CONTINUE_EXECUTION;}return EXCEPTION_EXECUTE_HANDLER;
}
1)第一次给pchBuffer地址赋值会触发异常(访问NULL地址),过滤函数中判断出了异常是由于给空地址赋值。
TCHAR g_szBuffer[100];
LONG OilFilter3(TCHAR ** ppchBuffer);void FuncAtude3(TCHAR * sz) {__try{*sz = TEXT('\0');}__except (EXCEPTION_CONTINUE_SEARCH) {// This never executes.}
}
void FunclinRoosevelt3() {int x = 0;TCHAR *pchBuffer = NULL;__try{FuncAtude3(pchBuffer);}__except (OilFilter3(&pchBuffer)) {MessageBox(NULL, TEXT("An exception occured"), NULL, MB_OK);}MessageBox(NULL, TEXT("Function completed"), NULL, MB_OK);
}
1)在FuncAtude3中试图写入NULL地址会引发异常。
int x, y;__try{x = 0;y = 4 / x;}__except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {// Handle divide by zero exception.}
int x, y;__try{x = 0;y = 4 / x;}__except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { switch (GetExceptionCode()) {case EXCEPTION_INT_DIVIDE_BY_ZERO:// Handle divide by zero exception.break;}}
但是不能在过滤函数内部调用GetExceptionCode()
LONG CoffeeFilter(){// Compilation error: illegal call to GetExceptionCode.return (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
改写过的异常过滤函数
}int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{int x, y;__try{x = 0;y = 4 / x;}__except (CoffeeFilter()){}return 0;
}
LONG CoffeeFilter(DWORD dwExceptionCode){// Compilation error: illegal call to GetExceptionCode.return (dwExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) ?EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
}int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{int x, y;__try{x = 0;y = 4 / x;}__except (CoffeeFilter(GetExceptionCode())){}return 0;
}
typedef struct _EXCEPTION_POINTERS {PEXCEPTION_RECORD ExceptionRecord;PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
void FuncSkunk() {// Declare variables that we can use to save the exception// record and the context if an exception should occur.EXCEPTION_RECORD SavedExceptRec;CONTEXT SavedContext;__try{// ...}__except (SavedExceptRec = *(GetExceptionInformation())->ExceptionRecord,SavedContext =*(GetExceptionInformation())->ContextRecord,EXCEPTION_EXECUTE_HANDLER) {// We can use the SavedExceptRec and SavedContext// variables inside the handler code block.switch (SavedExceptRec.ExceptionCode) {//...}}
}
注意逗号表达式,最终的值是最右边的表达式,前面的表达式会被计算执行。
typedef struct _EXCEPTION_RECORD {DWORD ExceptionCode;DWORD ExceptionFlags;struct _EXCEPTION_RECORD *ExceptionRecord;PVOID ExceptionAddress;DWORD NumberParameters;ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];} EXCEPTION_RECORD;
LONG ExpFltr(LPEXCEPTION_POINTERS pep) {TCHAR szBuf[300], *p;PEXCEPTION_RECORD pER = pep->ExceptionRecord;DWORD dwExceptionCode = pER->ExceptionCode;StringCchPrintf(szBuf, _countof(szBuf),TEXT("Code = %x, Address = %p"),dwExceptionCode, pER->ExceptionAddress);// Find the end of the string.p = _tcschr(szBuf, TEXT('\0'));// I used a switch statement in case Microsoft adds// information for other exception codes in the future.switch (dwExceptionCode) {case EXCEPTION_ACCESS_VIOLATION:StringCchPrintf(p, _countof(szBuf),TEXT("\n--> Attempt to %s data at address %p"),pER->ExceptionInformation[0] ?TEXT("write") : TEXT("read"),pER->ExceptionInformation[1]);break;default:break;}MessageBox(NULL, szBuf, TEXT("Exception"),MB_OK | MB_ICONEXCLAMATION);return EXCEPTION_CONTINUE_SEARCH;
}
例如一下测试代码
int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{__try{// ...// ...char * p = (char *)0xffffffff;char a = *p;}__except (ExpFltr(GetExceptionInformation())) {// ...}return 0;
}
运行结果:
WINBASEAPI
第一个参数是dwExceptionCode是抛出异常的标识符ID。
__analysis_noreturn
VOID
WINAPI
RaiseException(_In_ DWORD dwExceptionCode,_In_ DWORD dwExceptionFlags,_In_ DWORD nNumberOfArguments,_In_reads_opt_(nNumberOfArguments) CONST ULONG_PTR * lpArguments);