欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《Windows核心编程》读书笔记二十三章 终止处理程序

程序员文章站 2022-05-12 23:08:23
...

第二十三章 终止处理程序


本章内容

通过实例理解终止处理程序


VC++支持SEH(Structured exception handling)来使得开发代码过程能够把代码和错误处理的逻辑分离开来。

SEH是编译器在编译代码过程中加入了特殊的处理代码,生成SEH的数据结构和操作系统相关的代码。参考深入解析Win32结构化异常

《Windows核心编程》读书笔记二十三章 终止处理程序


SEH实际上包含两方面的功能:终止处理(termination handling)和异常处理(exception handling)


终止处理确保不管一个代码块(被保护的代码块)是如何退出的,另一个代码块(终止处理程序)总能被调用和执行。终止处理的语法如下:

	__try{
		// Guard body
		// ...
	}
	__finally{
		// Termination handler
		// ...
	}


编译器和操作系统协同工作保障了无论被保护的代码是如何退出的(return, goto, longjump, 除非调用了ExitProcess , ExitThread, TerminateProcess, TerminateThread来终止进程或线程) ,终止处理程序都会表调用(__finally语句块中的代码)


通过实例理解终止处理程序

1. Funcenstein1函数

DWORD Funcenstein1() {
	DWORD dwTemp;

	// 1. Do any processing here.
	__try{
		// 2. Request permission to access
		// protected data, and then use it.
		WaitForSingleObject(g_hSem, INFINITE);

		g_dwProtectedData = 5;
		dwTemp = g_dwProtectedData;
	}
	__finally{
		// 3. Allow other to use protected data.
		ReleaseSemaphore(g_hSem, 1, NULL);
	}

	// 4. continue processing
	return dwTemp;
}

2. Funcenstein2函数

DWORD Funcenstein2() {
	DWORD dwTemp;

	// 1. Do any processing here.
	__try{
		// 2. Request permission to access
		// protected data, and then use it.
		WaitForSingleObject(g_hSem, INFINITE);

		g_dwProtectedData = 5;
		dwTemp = g_dwProtectedData;

		// return the new value.
		return dwTemp;
	}
	__finally{
		// 3. Allow other to use protected data.
		ReleaseSemaphore(g_hSem, 1, NULL);
	}

	// 4. continue processing
	// will never execute in this version
	dwTemp = 9;
	return dwTemp;
}

通过__finally保证了信号量被即使释放。

在__try语句块中返回return的时候,由于存在__finally语句块,会强制执行__finally块中的代码然后再return


编译器在编译__try语句块中发现存在一个return语句(类似ret指令),编译器会先将返回值保存在一个临时变量中(通常是push xxx)。然后再调转到finally代码块(这个过程称为局部展开)一旦finally代码块执行完毕,编译器所创建的临时变量的值就会返回给函数调用者。

《Windows核心编程》读书笔记二十三章 终止处理程序

可以看到ret语句被编译成了一系列语句,最后通过一系列跳转到了_finally语句块内并执行其中的代码。

同时__finally语句块执行以后最后有一条ret指令最终会返回所有调用然后让函数返回。

注意在try语句块中使用return语句会生成大量了代码,多次跳转才能回到finally语句块,这会损失性能。应该改用__leave


另外异常处理通常用来捕获哪些本来不应该发生的异常。如果是常见问题,应该显示地检查这些问题以提供运行效率(例如空指针访问,除0错误等)


通常让__try语句正常执行以后进入__finally这样的开销是最低的


3. Funcenstein3

DWORD Funcenstein3() {
	DWORD dwTemp;

	// 1. Do any processing here.
	__try{
		// 2. Request permission to access
		// protected data, and then use it.
		WaitForSingleObject(g_hSem, INFINITE);

		g_dwProtectedData = 5;
		dwTemp = g_dwProtectedData;

		// return the new value.
		goto ReturnValue;
	}
	__finally{
		// 3. Allow other to use protected data.
		ReleaseSemaphore(g_hSem, 1, NULL);
	}

	// 4. continue processing
	// will never execute in this version
ReturnValue:
	dwTemp = 9;
	return dwTemp;
}

goto 语句试图直接跳出到return语句。编译器会检查出并进行局部展开保证执行了__finally块以后才会跳转到ReturnValue处。


4. Funcfurter1

DWORD Funcfurter1() {
	DWORD dwTemp;

	// 1. Do any processing here.
	__try{
		// 2. Request permission to access
		// protected data, and then use it.
		WaitForSingleObject(g_hSem, INFINITE);

		g_dwProtectedData = 5;
		dwTemp = g_dwProtectedData;
	}
	__finally{
		// 3. Allow other to use protected data.
		ReleaseSemaphore(g_hSem, 1, NULL);
	}

	// 4. continue processing
	// will never execute in this version
	return dwTemp;
}

假设try代码块会有语句导致访问非法内存,如果没有SEH,会被Windows错误报告程序截获,弹出一个对话框然后终止应用程序。

但finally语句也不是总是能保证被执行。例如栈被破坏了,finally语句块所在的内存可能已经被覆盖了。这种情况下应用程序得不到任何提示被强制终止。

或者有异常导致SEH链的中断,__finally处理块也不一定能得到执行。

如果异常发生在异常过滤程序里,终止处理也不会被执行。

尽量限制在catch或者finally块中代码所做的工作,否则进程可能会在finally块执行前突然终止。


5. 突击测验:FuncaDoodleDoo

DWORD FuncaDoodleDoo() {
	DWORD dwTemp = 0;

	while (dwTemp < 10) {

		__try{
			if (dwTemp == 2)
				continue;

			if (dwTemp == 3)
				break;
		}
		__finally{
			dwTemp++;
		}
		dwTemp++;
	}

	dwTemp += 10;
	return dwTemp;
}

最后dwTemp返回4.

每个循环代码都会执行一次__finally块。无论continue, break; (甚至longjump 和setjump也能被SEH展开)

只有ExitProcess,ExitThread, TerminateProcess, TerminateThread可以强制跳过finally块。(应该尽量避免调用这些函数)

6. Funcenstein4 函数


DWORD Funcenstein4() {
	DWORD dwTemp;

	// 1. Do any processing here.
	__try{
		// 2. Request permission to access
		// protected data, and then use it.
		WaitForSingleObject(g_hSem, INFINITE);

		g_dwProtectedData = 5;
		dwTemp = g_dwProtectedData;

		// Return the new value.
		return dwTemp;
	}
	__finally{
		// 3. Allow other to use protected data.
		ReleaseSemaphore(g_hSem, 1, NULL);
		return 103;
	}

	// 4. continue processing
	// will never execute in this version
	return dwTemp;
}

以上代码最后将返回103,因为Finally语句块中的103会替换默认的返回值。

一条好的法则,不要在try块中存放return语句。


7. Funcarama1 ~10. Funcarama4

包含错误处理程序的代码如何使用try finally改进使代码易读

DWORD Funcarama1() {
	// IMPORTANT: Initialize all variables to assume failure.
	HANDLE hFile = INVALID_HANDLE_VALUE;
	PVOID pvBuf = NULL;
	DWORD dwNumBytesRead;
	BOOL bOk;

	hFile = CreateFile(TEXT("SOMEDATA.DAT"), GENERIC_READ,
		FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	pvBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
	if (pvBuf == NULL) {
		CloseHandle(hFile);
		return FALSE;
	}

	bOk = ReadFile(hFile, pvBuf, 1024, &dwNumBytesRead, NULL);
	if (!bOk || (dwNumBytesRead != 1024)) {
		VirtualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
		CloseHandle(hFile);
		return FALSE;
	}

	// Do some calculation on the data.
	// ...

	// Clean up all the resources.
	VirtualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
	CloseHandle(hFile);

	return TRUE;
}

这种代码非常不好,如果代码需要操作的句柄数量多,后面需要增加大量的释放语句,非常容易出错。而且可读性也很差。

DWORD Funcarama2() {
	// IMPORTANT: Initialize all variables to assume failure.
	HANDLE hFile = INVALID_HANDLE_VALUE;
	PVOID pvBuf = NULL;
	DWORD dwNumBytesRead;
	BOOL bOk, bSuccess = FALSE;

	hFile = CreateFile(TEXT("SOMEDATA.DAT"), GENERIC_READ,
		FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile != INVALID_HANDLE_VALUE) {
		pvBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);

		if (pvBuf != NULL) {
			bOk = ReadFile(hFile, pvBuf, 1024, &dwNumBytesRead, NULL);
			if (bOk && (dwNumBytesRead != 1024)) {
				// Do some calculation on the data.
				// ...
				bSuccess = TRUE;
			}
			VirtualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
		}
		CloseHandle(hFile);
	}

	return bSuccess;
}

这种代码比第一种略微简洁一些,但是会产生大量的代码缩进。也难以维护。


DWORD Funcarama3() {
	// IMPORTANT: Initialize all variables to assume failure.
	HANDLE hFile = INVALID_HANDLE_VALUE;
	PVOID pvBuf = NULL;

	__try{
		DWORD dwNumBytesRead;
		BOOL bOk;

		hFile = CreateFile(TEXT("SOMEDATA.DAT"), GENERIC_READ,
			FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile == INVALID_HANDLE_VALUE) {
			return FALSE;
		}

		pvBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
		if (pvBuf == NULL) {
			return FALSE;
		}

		bOk = ReadFile(hFile, pvBuf, 1024, &dwNumBytesRead, NULL);
		if (!bOk || (dwNumBytesRead != 1024)) {
			return FALSE;
		}

		// Do some calculation on the data.
		// ...
	}
	__finally{
		// Clean up all the resources.
		if (pvBuf != NULL)
			VirtualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
		if (hFile != INVALID_HANDLE_VALUE)
			CloseHandle(hFile);
	}

	return TRUE;
}

Funcarama3的精髓在于所有清理工作都放在finally快。以后添加新的代码,可以只在finally块添加相应的清理代码。

由于在try语句块使用了return 局部展开,因此性能可能会有所损失。


最终改进版

DWORD Funcarama4() {
	// IMPORTANT: Initialize all variables to assume failure.
	HANDLE hFile = INVALID_HANDLE_VALUE;
	PVOID pvBuf = NULL;

	// Assume that the function will not execute successfully.
	BOOL bFunctionOk = FALSE;

	__try{
		DWORD dwNumBytesRead;
		BOOL bOk;

		hFile = CreateFile(TEXT("SOMEDATA.DAT"), GENERIC_READ,
			FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hFile == INVALID_HANDLE_VALUE) {
			__leave;
		}

		pvBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
		if (pvBuf == NULL) {
			__leave;
		}

		bOk = ReadFile(hFile, pvBuf, 1024, &dwNumBytesRead, NULL);
		if (!bOk || (dwNumBytesRead != 1024)) {
			__leave;
		}

		// Do some calculation on the data.
		// ...
		// Indicate that the entire function executed successfully.
		bFunctionOk = TRUE;
	}
	__finally{
		// Clean up all the resources.
		if (pvBuf != NULL)
			VirtualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
		if (hFile != INVALID_HANDLE_VALUE)
			CloseHandle(hFile);
	}

	return bFunctionOk;
}

将return改成__leave 减少性能开销。

不过这种方式在进入try语句块之前需要将所有句柄都初始化为无效值。


11. Finally快注意事项:

1. try块到finally的正常代码控制流

2. 局部展开:从try块中提前退出(goto, longjump, continue, break, return)将程序控制流强制转入finally块

第三种--全局展开。如果触发了异常


可以在finally快中使用内在函数AbnormalTermination来确定是哪种情况进入finally块

int           __cdecl _abnormal_termination(void);
《Windows核心编程》读书笔记二十三章 终止处理程序

如果代码从正常流入finally块,该函数返回FALSE

否则返回TRUE


12. Funcfurter2函数

演示如何使用AbnormalTermination

DWORD Funcfurther2() {
	DWORD dwTemp;

	// 1. Do any processing here.
	__try{
		// 2. Request permission to access
		// protected data, and then use it.
		WaitForSingleObject(g_hSem, INFINITE);

		dwTemp = Funcinator(g_dwProtectedData);
	}
	__finally{
		// 3. Allow other to use protected data.
		ReleaseSemaphore(g_hSem, 1, NULL);
		if (!AbnormalTermination()) {
			// No errors occurred in the try block, and
			// control flowed naturally from try into finally.
		}
		else {
			// Something caused an exception, and
			// because there is no code in the try block
			// that would cause a premature exit, we must
			// be executing in the finally block
			// because of a global unwind.
			// If there were a goto in the try block,
			// we wouldn't know how we got there.
		}
	}

	// 4. continue processing
	return dwTemp;
}

总结使用终止处理程序的理由:

1)因为清理工作集中在一个地方执行,并保证能得到执行,从而简化了错误处理。

2)提高代码可读性

3)让代码更容易维护

4)如果正确使用,它们对程序性能和体积影响是微小的。


13. SEH终止处理示例程序

演示了如何处理异常的流程。

在vista以前的操作系统中。若try块异常退出,finally块还是有机会被执行的。finally块因为全局展开而被执行(触发异常)一个线程入口点(entry point)被try/except块所保护。为了触发全局展开__except块的异常过滤程序应该返回EXCEPTION_EXECUTE_HANDLER

在Vista以后,为了提高错误记录和报告的可靠性,从架构上对未处理异常的处理过程做了一个重大的改动(参考25章)。一个明显的缺点是用来作为保护的异常过滤程序返回的是EXCEPTION_CONTINUE_SEARCH , 进程将马上终止,从而导致finally块没有机会执行。


SEHTerm.exe会检查系统是不是Vista。将弹出一个消息框让我们选择是否使用try/except来保护错误函数。

try/finally将会被一个异常过滤程序所保护起来,该过滤返回EXCEPTION_EXECUTE_HANDLER.其作用是保证当异常抛出时,全局展开会被触发,让finally块代码可以执行。


笔者将代码稍作修改支持Vista以上的版本(Win7)

/******************************************************************************
Module:  SEHTerm.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"     /* See Appendix A. */
#include <windows.h>
#include <tchar.h>


///////////////////////////////////////////////////////////////////////////////

BOOL IsWindowsVista() {

	// Code from Chapter 4
	// Prepare the OSVERSIONINFOEX structure to indicate Windows Vista.
	OSVERSIONINFOEX osver = { 0 };
	osver.dwOSVersionInfoSize = sizeof(osver);
	osver.dwMajorVersion = 6;
	osver.dwMinorVersion = 0;
	osver.dwPlatformId = VER_PLATFORM_WIN32_NT;

	// Prepare the condition mask.
	DWORDLONG dwConditionMask = 0;		// Your MUST initialize this to 0.
	VER_SET_CONDITION(dwConditionMask, VER_MAJORVERSION, VER_EQUAL);
	VER_SET_CONDITION(dwConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
	VER_SET_CONDITION(dwConditionMask, VER_PLATFORMID, VER_EQUAL);

	// Perform the version test.
	if (VerifyVersionInfo(&osver, VER_MAJORVERSION | VER_MINORVERSION |
		VER_PLATFORMID, dwConditionMask)) {
		// The host system is Windows Vista or newer.
		return TRUE;
	}
	else {
		// The host system is NOT Windows Vista.
		return FALSE;
	}
}

void TriggerException() {

	__try{
		int n = MessageBox(NULL, TEXT("Perform invalid memory access?"),
			TEXT("SEHTerm: In try block"), MB_YESNO);

		if (n == IDYES) {
			*(PBYTE)NULL = 5;	// This causes an access violation
		}
	}
	__finally {
		PCTSTR psz = AbnormalTermination()
			? TEXT("Abnormal termination") : TEXT("Normal termination");
		MessageBox(NULL, psz, TEXT("SEHTerm: In finally block"), MB_OK);
	}

	MessageBox(NULL, TEXT("Normal function termination"),
		TEXT("SEHTerm: After finally block"), MB_OK);
}

int WINAPI _tWinMain(HINSTANCE, HINSTANCE, PTSTR, int) {

	// In Windows Vista, a global unwind occurs if an except filter
	// returns EXCEPTION_EXECUTE_HANDLER. If an unhandled exception
	// occurs, the process is simply terminated and the finally blocks
	// are not executed.
	if (IsWindowsVista()) {

		DWORD n = MessageBox(NULL, TEXT("Protect with try/except?"),
			TEXT("SEHTerm: workflow"), MB_YESNO);

		if (n == IDYES) {
			__try{
				TriggerException();
			}
			__except (EXCEPTION_EXECUTE_HANDLER) {
				// But the system dialog will not appear.
				// So, popup a message box.
				MessageBox(NULL, TEXT("Abnormal process termination"),
					TEXT("Process entry point try/except handler"), MB_OK);

				// Exit with a dedicated error code
				return -1;
			}
		}
		else {
			TriggerException();
		}
	}
	else {
		TriggerException();
	}

	MessageBox(NULL, TEXT("Normal process termination"),
		TEXT("SEHTerm: before leaving the main thread"), MB_OK);

	return 0;
}


相关标签: exception