前一段时间,在看异常处理一章内容的时候,发现这一部分还真的挺有尿水:)所以上网搜了一下有关内容,呦嗬,还挺丰富的。当然有些自己还是看不懂,现在就将这些宝贝拿出来跟大家共享一下。
首先我们看一下使用异常处理的几种情况:
A. 用来处理非致命的错误
B. 对API函数的参数合法性的检验(假设参数都是合法的,只有遇到异常的时候进行合法性检验)
C. 处理致命错误(退出时最好的选择,但是有的时候可以用异常处理函数在程序退出前释放资源,删除临时文件等,甚至可以详细记录产生异常的指令位置和环境)
D. 处理“计划内”的异常(我们可能更关心这种情况,因为可以做很多的手脚,哈哈)
接着我们看看Windows下异常处理的两种方式:1使用筛选器2 SEH异常处理
一、 使用筛选器
因为这里我要重点关注的是SEH的处理方式,所以还是简单的提一下筛选器处理方式。筛选器异常处理是通过异常回调函数来指定程序处理异常。这种方式的回调函数必须是唯一的,设置新的回调函数后以前的将失效。适用于进程范围。看一下这个函数的定义
Invoke SetUnhandledExecpionFilter,offset_Handler
Mov lpPrevHandler,eax
(先到这里吧有些难受,明天接着来)
######题外话:想起“司令”的一句话,觉得挺有道理:明天不一定美好,但是更美好的明天一定会到来!祝福所有的朋友。######
上午有会,什么也没有做,下午?还有会,我tm晕了,中午不睡觉了,不把事情做不完心里不踏实。
回调函数的格式:
_Handlerproc pExecptionInfo
看看pExecptionInfo这个指针参数指向的一个数据结构
EXCEPTION_POINTERS STRUCT
pExceptionRecord DWORD ?
ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS
下面介绍 EXCEPTION_RECORD和CONTEXT结构的定义:
;//===================== 以下是两个成员的详细结构=========================
EXCEPTION_RECORD STRUCT
ExceptionCode DWORD ? ;//异常码
ExceptionFlags DWORD ? ;//异常标志
pExceptionRecord DWORD ? ;//指向另外一个EXCEPTION_RECORD的指针
ExceptionAddress DWORD ? ;//异常发生的地址
NumberParameters DWORD ? ;//下面ExceptionInformation所含有的dword数目
ExceptionInformation DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)
EXCEPTION_RECORDENDS ;//EXCEPTION_MAXIMUM_PARAMETERS ==15
;//=============================具体解释================================
ExceptionCode 异常类型,SDK里面有很多类型,你可以在windows.inc里查找STATUS_来找到更多的异常类型,下面只给出hex值,具体标识定义请查阅windows.inc,你最可能遇到的几种类型如下:
C0000005h----读写内存冲突
C0000094h----非法除0
C00000FDh----堆栈溢出或者说越界
80000001h----由Virtual Alloc建立起来的属性页冲突
C0000025h----不可持续异常,程序无法恢复执行,异常处理例程不应处理这个异 常
C0000026h----在异常处理过程中系统使用的代码,如果系统从某个例程莫名奇妙的返回,则出现此代码, 如果RtlUnwind时没有Exception Record参数也同样会填入这个代码
80000003h----调试时因代码中int3中断
80000004h----处于被单步调试状态
注:也可以自己定义异常代码,遵循如下规则:
____________________________________________________________________
位: 31~30 29~28 27~16 15~0
____________________________________________________________________
含义: 严重程度 29位 功能代码 异常代码
0==成功 0==Mcrosoft MICROSOFT定义 用户定义
1==通知 1==客户
2==警告 28位
3==错误 被保留必须为0
ExceptionFlags 异常标志
0----可修复异常
1----不可修复异常
2----正在展开,不要试图修复什么,需要的话,释放必要的资源
pExceptionRecord 如果程序本身导致异常,指向那个异常结构
ExceptionAddress 发生异常的eip地址
ExceptionInformation 附加消息,在调用RaiseException可指定或者在异常号为C0000005h即内存异常时含义如下
第一个dword 0==读冲突 1==写冲突
第二个dword 读写冲突地址
;//================================解释结束============================
off.
CONTEXT STRUCT ; _
ContextFlags DWORD ? ; | +0
iDr0 DWORD ? ; | +4
iDr1 DWORD ? ; | +8
iDr2 DWORD ? ; >调试寄存器 +C
iDr3 DWORD ? ; | +10
iDr6 DWORD ? ; | +14
iDr7 DWORD ? ; _| +18
FloatSave FLOATING_SAVE_AREA <> ;浮点寄存器区 +1C~~~88h
regGs DWORD ? ;--| +8C
regFs DWORD ? ; |/段寄存器 +90
regEs DWORD ? ; |/ +94
regDs DWORD ? ;--| +98
regEdi DWORD ? ;____________ +9C
regEsi DWORD ? ; | 通用 +A0
regEbx DWORD ? ; | 寄 +A4
regEdx DWORD ? ; | 存 +A8
regEcx DWORD ? ; | 器 +AC
regEax DWORD ? ;_______|___组_ +B0
regEbp DWORD ? ;++++++++++++++++ +B4
regEip DWORD ? ; |控制 +B8
regCs DWORD ? ; |寄存 +BC
regFlag DWORD ? ; |器组 +C0
regEsp DWORD ? ; | +C4
regSs DWORD ? ;++++++++++++++++ +C8
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS
;//============================以上是两个成员的详细结构============
程序使用筛选器异常处理时可以通过查看上面结构中的regEip来找到产生异常的地址!调试的时候可以改变EIP的值以达到越过异常程序,转到“安全”的地方。
最后看一下筛选器异常处理回调函数的返回值
EXECPTION_EXECUTE_HANDLER 1;进程被终止,终止前不会出现提示错误的对话框
EXECPTION_CONTINUE_SEARCH 0;同样终止程序,显示错误对话框
EXECPTION_CONTINUE_EXECUTION -1;系统将CONTECT设置回去,继续执行程序
使用筛选器程序是最简单的处理异常方法,不足:1 不便于封装。2 处理是全局性的也就是无法对每个线程或子程序设置一个私有的异常处理程序进行异常处理。
进入正题:SEH异常处理
首先解释一下什么是SEH异常处理:SEH("Structured Exception Handling"
,即结构化异常处理.是操作系统提供给程序设计者的强有力的处理程序错误或异常的武器。
下面结合冷雨飘心的一个SEH异常处理程序来说明具体的用法:
;//====================================================================
;// ex. 2,by Hume,2001 线程相关的异常处理
;//====================================================================
.386
.model flat, stdcall
option casemap :none ; case sensitive
include hd.h ;//相关的头文件,你自己维护一个吧
;//============================
.data
szCap db "By Hume[AfO],2001...",0
szMsgOK db "It's now in the Per_Thread handler!",0
szMsgERR1 db "It would never Get here!",0
buff db 200 dup(0)
.code
_start:
;//========prog begin====================
ASSUME FS:NOTHING
push offset perThread_Handler
push fs:[0]
mov fs:[0],esp ;//建立SEH的基本ERR结构,如果不明白,就仔细研究一下吧
xor ecx,ecx
mov eax,200
cdq ;//双字扩展到四个字节,因为是除法
div ecx
;//以下永远不会被执行
invoke MessageBox,NULL,addr szMsgERR1,addr szCap,MB_OK+MB_ICONINFORMATION
pop fs:[0]
add esp,4
invoke ExitProcess,NULL
;//============================
perThread_Handler:
invoke MessageBox,NULL,addr szMsgOK,addr szCap,MB_OK+MB_ICONINFORMATION
mov eax,1 ;//ExceptionContinueSearch,不处理,由其他例程或系统处理
;mov eax,0 ;//ExceptionContinueExecution,表示已经修复CONTEXT,可从异常发生处继续执行
ret ;//这里如果返回0,你会陷入死循环,不断跳出对话框....
;//=============================Prog Ends==============
end _start
程序本身很简单,注释也很详细。我们来看看是如何注册回调函数的
push offset perThread_Handler
push fs:[0]
mov fs:[0],esp
仅仅三个语句就解决了~那么为什么要用fs这个段寄存器呢?这里又涉及一个重要的内容:TIB(Thread Information Block线程信息块)。我们来看看这个重要的数据结构(引用了《罗聪浅谈利用SEB实现反跟踪》的部分内容)
TEB(Thread Environment Block) 在 Windows 9x 系 列中被称为 TIB(Thread Information Block),它记录了线程的重要信息,而且每一个线程都会对应一个 TEB 结 构。 Matt Pietrek 大牛已经给我们列出了它的结构,我就不多说啦,见下:(摘 自 Matt Pietrek 的 Under The Hood - MSJ 1996)
//===========================================================
// file: TIB.H
// Author: Matt Pietrek
// From: Microsoft Systems Journal "Under the Hood", May 1996
//===========================================================
#pragma pack(1)
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD * pNext;
FARPROC pfnHandler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
typedef struct _TIB
{
PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list
PVOID pvStackUserTop; // 04h Top of user stack
PVOID pvStackUserBase; // 08h Base of user stack
union // 0Ch (NT/Win95 differences)
{
struct // Win95 fields
{
WORD pvTDB; // 0Ch TDB
WORD pvThunkSS; // 0Eh SS selector used for thunking to 16 bits
DWORD unknown1; // 10h
} WIN95;
struct // WinNT fields
{
PVOID SubSystemTib; // 0Ch
ULONG FiberData; // 10h
} WINNT;
} TIB_UNION1;
PVOID pvArbitrary; // 14h Available for application use
struct _tib *ptibSelf; // 18h Linear address of TIB structure
union // 1Ch (NT/Win95 differences)
{
struct // Win95 fields
{
WORD TIBFlags; // 1Ch
WORD Win16MutexCount; // 1Eh
DWORD DebugContext; // 20h
DWORD pCurrentPriority; // 24h
DWORD pvQueue; // 28h Message Queue selector
} WIN95;
struct // WinNT fields
{
DWORD unknown1; // 1Ch
DWORD processID; // 20h
DWORD threadID; // 24h
DWORD unknown2; // 28h
} WINNT;
} TIB_UNION2;
PVOID* pvTLSArray; // 2Ch Thread Local Storage array
union // 30h (NT/Win95 differences)
{
struct // Win95 fields
{
PVOID* pProcess; // 30h Pointer to owning process database
} WIN95;
} TIB_UNION3;
} TIB, *PTIB;
#pragma pack()
让我们抬头看看上面的 Matt Pietrek 的代码,其中有这么一行:
PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list
注 意到 PEXCEPTION_REGISTRATION_RECORD 这个定义,它表示 pvExcept 这个变量正 是 exception record list 的入口,这个入口位于整个结构的 0 偏移处。同时, 在 M 的 Intel i386 Windows NT/2K/XP 内核中,每当创建一个线程,OS 均会为每个线程分配 TEB ,而 且 TEB 永远放在 fs 段选择器指定的数据段的 0 偏移处。
这样一来,你就明白了 SEH 注册的偏移为什么是在 fs:[0] 了吧?
事实上 Windows 系统都是通过这种方法来为应用程序提供信息的,比如有这样的例子:
struct _tib *ptibSelf; // 18h Linear address of TIB structure
DWORD threadID; // 24h
Windows 提供了一个 API :GetCurrentThreadID(),它的内部工作原理其实是这样的:(利用了上面的这两个地址)
mov eax, fs:[18h] ;因为 18h 偏移处是 TIB 结构的线性偏移地址
mov eax, [eax + 24h] ;因为 24h 偏移处是 threadID 的地址
ret ;把 eax 中储存的 threadID 地址返回
注:为什么要声明assume fs:nothing?因为masm编译器默认将fs段寄存器定义为error,所以程序在使用fs前必须将它启动!
接下来看看SEH的回调函数
_Handler proc _lpExecptionRecord, _lpSEH,lp_context,lp_DispatcherContext
_lpExecptionRecord指向一个EXECPTION_RECORD结构。
lp_context 指向一个CONTEXT结构。
_lpSEH 指向注册回调函数时使用的EXXCEPTION_REGISTRATION结构的地址。
返回值有四种取值:
ExecptionContinueExecution ( 0
:系统将线程环境设置为_lpContext指向的CONTEXT结构并继续执行。
ExceptionContinueSearch(1):回调函数拒绝处理这个异常,系统通过EXECPTION_REGISTRATION结构的prev字段得到前一个回调函数的地址并调用它。
ExecptionNestedExecption (2):发生异常嵌套。
ExecptionCollidedUnwind (3):异常展开操作。这一个部分不做多讲,有兴趣的可以看看罗云彬的书,其实是很重要的一部分。
如果一个程序既有筛选器异常处理又有SEH异常处理,而且系统还有默认的异常处理机制,那么他们被调用的先后次序是怎么样的呢?
发生异常时系统的处理顺序(by Jeremy Gordon):
1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器的存在吗?
2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.
3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程,可交由链起来的其他例程处理.
4.如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.
5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异 常处理例程的话,系统转向对它的调用.
6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框, 你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统就调用ExitProcess终结程序.
7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会.
说了这么多你也许会问SEH异常处理到底有什么用处呢?呵呵,且听小生慢慢道来~~~
第一道菜:病毒程序巧用SEH
这里简单的说一下如何利用SEH异常处理程序来躲避下毒软件的反病毒引擎。一个反病毒引擎在一个程序运行的时候会模拟程序的代码,当发现程序代码的疑点比较多的时候会报告成病毒。看看下面这段程序:
start:call Set_SEH;这句其实就是 push offset CONTINUE
; JMP Set_SEH
CONTINUE:mov esp, [esp+8]; [ESP+8]存储的是旧的堆栈地址。
push offset Start_Virus ;----_ 把Start_Virus 的地址压栈,当作返回地址
ret;----跳到Start_Virus去,是不是很magic?
Set_SEH:sub edx, edx ;Edx =0
Assume fs:nothing
push dword ptr fs:[edx];把指去 _EXCEPTIONAL_REGISTRATION_RECORD 结构的指针入栈
mov fs:[edx], esp;安装一个seh
mov [edx],edx;引起一个内存读写冲突,发生异常因为edx=0
;如果反病毒引擎不处理异常,不进入seh 处理程序(即 CONTINUE:
,继续模
;拟下个指令,也就是jmp start,那么就进入一个死循环,可能会引起死机。
jmp start
Start_Virus: .....
是不是很简单呢?就是让反病毒引擎不处理这个人为的异常时进入死循环~!!
第二道菜:TEB反跟踪初探
如果你的记性够好的话一定记得上面介绍过的TEB(TIB)线程信息块结构中有这么一句:
PVOID* pProcess; // 30h Pointer to owning process database
这 个偏移地址处的内容非常有用,它指向本线程的拥有者的 PDB(Process Database) 的线性地址。当你用动态调试器,例 如 OllyDbg 的时候,调试器是把调试的对象作为一个子线程进行跟踪的,在这种情况下,被调试的对象的“拥有者”就是调试器本身,也就是说,它 的 TEB 的 30h 处的偏移指向的内容肯定不为 0 ,这样,我们就可以利用这一点,判断 30h 偏移指向的内容,来判断是否有调试器跟踪。
最后给出一个 Anti-Debug 的例子程序,用 MASM 编译完成后,请用 OllyDbg 来加载调试一下,看看与正常的运行结果有什么不同。
;*********************************************************
;程序名称:演示利用 TEB 结构进行 Anti-Debug
; 请用 OllyDbg 进行调试
;适用OS:Windows NT/2K/XP
;作者:罗聪
;日期:2003-2-9
;出处:
http://www.LuoCong.com(老罗的缤纷天地) ;注意事项:如欲转载,请保持本程序的完整,并注明:
;转载自“老罗的缤纷天地”(
http://www.LuoCong.com) ;*********************************************************
.386
.model flat, stdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/user32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/user32.lib
.data
szCaption db "Anti-Debug Demo by LC, 2003-2-9", 0
szDebugged db "Hah, let me guess... U r dEBUGGINg me!
", 0
szFine db "Good boy, no dEBUGGEr detected!", 0
.code
main:
assume fs:nothing
mov eax, fs:[30h] ;指向 PDB(Process Database)
movzx eax, byte ptr [eax + 2h];无符号数带零扩展
or al, al
jz _Fine
_Debugged:
push MB_OK or MB_ICONHAND
push offset szCaption
push offset szDebugged
jmp _Output
_Fine:
push MB_OK or MB_ICONINformATION
push offset szCaption
push offset szFine
_Output:
push NULL
call MessageBoxA
invoke ExitProcess, 0
end main
第三道菜:利用SEH执行shellcode
假设异常处理例程入口00401053,程序刚开始执行时esp是0012ffc4,以前的fs:[0]是0012ffe0
建立了TIB结构的第一个成员后堆栈的情况如下:
内存低地址
| E0 |12ffbc(esp)
| FF |
| 12 | --ERR结构的第一个成员
|_00_|
| 53 |12ffc0
| 10 |
| 40 | --ERR结构的第二个成员
| 00 |
内存高地址
好了然后程序CALL一个函数,函数里面有一个局部变量并且在往其分配的空间中写入的数据时产生溢出.这时堆栈如下
____
| |12f000 局部变量分配的空间,并且向12ffc0方向溢出了.
| |
....
....
|_EBP|12ffb4 函数中保存老的EBP
| xx |
| xx |
| xx |
|_EIP|12ffb8 call函数时EIP进栈
| xx |
| xx |
|_xx_|
| E0 |12ffbc(esp) {当SEH起作用的时候EBX刚好指向这个地址(也可说总是指向当前ERR结构)}
| FF |
| 12 | --ERR结构的第一个成员
|_00_|
| 53 |12ffc0
| 10 |
| 40 | --ERR结构的第二个成员
|_00_|
| |12ffc4
继 续看,假设溢出代码一直到了12ffc4,然后call的函数该返回了,因为保存的EIP被溢出代码代替所以程序出错(不会不出错吧?),这样ESH开始 起作用了(注:在这期间系统要执行一些操作,所以EBX才会指向当前ERR).这样一来程序就会跳到12ffc0里的地址去执行!而12ffc0里的东东 早已不是原来的00401053了.这样我们不就改变了程序的流向了么.12ffc0中该写入什么内容呢?应是内存中JMP EBX的代码的地址.这样跳 了3下后最终就会跳到12ffbc去执行.这个四字节可是宝贵的啊
现在假设JMP EBX这个指令在内存中的地址是0x77e33f4d
那下具体看一下现在堆栈的情况:
| EB |12ffbc(esp) {当ESH起作用的时候EBX刚好指向这个地址(也可说总是指向当前ERR结构)}
| 06 |
| 90 | --ERR结构的第一个成员,执行JMP EBX后就到这儿来执行了(EB 06是短跳转JMP 12FFC4的机器码)
|_90_| 后面的90是nop空指令的机器码.
| 4D |12ffc0
| 3F |
| E3 | --ERR结构的第二个成员,出错处理函数的入口地址(现在成了JMP EBX的地址)
|_77_|
| |12ffc4
....
好现在来看看12ffc4里面有些什么代码.(简单的说这段代码的作用是计算真正的shellcode的起始地址,然后跳过去执行.
低地址
| |12f000(shellcode开始地址)
....
....
| 81 |12ffc4
| C3 | add ebx,FFFFF03Ch(ebx=12ffc4,指令长度6,作用计算计算shellcode地址)
| 3C |
| F0 |
| FF |
| FF |
| FF |12ffca jmp ebx
| D3 |
高地址
测试程序
-------------------------SEH.ASM------------------
.386
.model flat,stdcall
option casemap:none
include ../include/user32.inc
includelib ../lib/user32.lib
include ../include/kernel32.inc
includelib ../lib/kernel32.lib
.data
hello db '利用一个读INI文件的API来演示WIN2000本地溢出',0
lpFileName db './seh.ini',0
lpAppName db 'iam',0
lpKeyName db 'czy',0
lpDefault db 'ddd',0
szCap db "SEH TEST",0
szMsgOK db "OK,the exceptoin was handled by final handler!",0
szMsgERR1 db "It would never Get here!",0
.code
testov proc
local lpReturnedString[2224] : byte ;返回的字串搞成本地变量这样就和C语言一样了,它是在栈中
invoke GetPrivateProfileString,offset lpAppName,offset,lpKeyName,offset lpDefault,ADDR lpReturnedString,2249,offset lpFileName
invoke MessageBox,0,addr lpReturnedString,addr lpReturnedString,1
ret
testov endp
start:
ASSUME fs:NOTHING
invoke MessageBox,0,addr szMsgERR1,addr szCap,30h+1000h ;下断点
push offset Final_Handler ;压入正常的出错处理程序入口地址
push FS:[0] ;把前一个TIB的地址压入
mov fs:[0],esp
call testov
pop fs:[0] ;还原FS:[0]
Final_Handler: ;由于溢出了下面的代码不会被执行.
invoke MessageBox,0,addr szMsgOK,addr szCap,30h
invoke ExitProcess,0
mov eax,1
ret
end start
-----------------end-------------
1
如何更好的在内存中找JMP EBX的代码:
在softice中执行S 10:0 L FFFFFFFF FF D3就可以了,但实际上这样找到的
地址可能不能执行代码.所以用下面的方法:
map32 kernel32(在当前进程中查找映射的kernel32 DLL的信息)
一般有如下显示:
Owner Obj Name Obj# Address Size TYPE
kernel32 .text 0001 001b:77b61000 0005d1ae code RO
......
然后
S 77b61000 L 5d1ae FF D3
如果显示如下说明找到了:
Pattern Found at 0023:77e61674 ....
2)关于缓冲区的大小的问题:
利用SEH的办法就起码要设成1000个字节多,你的shellcode才不会被不知哪来的数据覆盖!
这道菜czy做的不好吃:(我感觉理解起来有些困难~!因为关于缓冲区溢出自己接触的太少,不过好东西要保留的,以后回过头看!
第四道菜:用 SEH 技术实现 API Hook
这一部分不想展开了,给大家一个链接吧。
http://www.luocong.com/articles/show_article.asp?Article_ID=25 最后作为结束语说说的缺点吧:)一个人只有正视自己的缺点才能不断地进步!呵呵
在 SEH异常处理链中最后一个被装载的SEH异常处理程序总是被第一个调用,想想如果自己花了一个星期才写出来一个异常处理程序,能够完美处理所有异常,并 希望异常全部由你来处理,但很不幸,比如你调用了一个外部模块,而这个模块自己安装了一个ugly的seh处理例程,他的动作是只要有异常发生就简单地终 止程序,哈哈,那就死悄悄了。又比如你想在你的加壳程序里面加密目标程序代码段,然后发生无效指令异常的时候用你自己安装的处理句柄来解密代码段继续执 行,听起来这的确是一个好主意,但遗憾的是大多数C/C++代码都用_try{}_except{}块来保证其正确运行,而这些异常处理例程是在你壳注册 的例程之后安装的,因而也就在链的前面,无效指令一执行,首先是C/C++编译器本身提供的处理例程或者程序其他的异常处理例程来处理,可能简单结束程序 或者....
好累!~~~~~~
写了两天,错了,应该是剪接+消化了两天,有很多的程序和文字是从hume,老罗,还有czy那里“剽窃”的:)希望高手们不要生气~~天下书籍一大抄。你们的必将是我的,当然我的也会共享给你们的。呵呵,现在还不行,级别不够啊。