【转载】x64 SEH学习笔记
转自:http://blog.sina.com.cn/s/blog_5d123f940100r9yf.html
Structured Exception Handling (SEH). Basically, SEH is the standard kernel-mode exception handling mechanism that's built into Windows. Because support within the O/S with no way to access it doesn't make much sense, Microsoft compilers provide access to the O/S's exception support via the __try and __except keywords.
To make SEH work, cooperating support must be provided by the compiler, hardware, and OS. Precisely how SEH is implemented on a particular platform can vary based on architecture.
the x86 is said to use frame based exception handling. There are a couple of problems with this approach:
1.Because the exception information is stored on the stack, it is susceptible to buffer overflow attacks.
2.Overhead. Exceptions are, well, exceptional, which means the exception will not occur in the common case. Regardless, every time a function is entered that uses SEH, these extra instructions are executed.
因此On the x64, SEH has become table-based, which means when the source code is compiled, a table is created that fully describes all the exception handling code within the module. This table is then stored as part of the PE header. If an exception occurs, the exception table is parsed by Windows to find the appropriate exception handler to execute. Because exception handling information is tucked safely away in the PE header, it is no longer susceptible to buffer overflow attacks. In addition, because the exception table is generated as part of the compilation process, no runtime overhead (in the form of push and pop instructions) is incurred during normal processing.
//-----------------------
x64的PE头文件里, 多出来了一个exception directory,里面包含一个或更多的RUNTIME_FUNCTION structures
typedef struct _RUNTIME_FUNCTION {
ULONG BeginAddress;
ULONG EndAddress;
ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
The RUNTIME_FUNCTION describes the entire range of a function that contains SEH。
Note the use of ULONGs for addresses even though we're talking about a 64-bit architecture. This is because the values contained in the structure are offsets from the base of the image and not addresses or pointers. Now let's describe each field in turn.
BeginAddress - This value represents an offset into the image where some bit of code of interest to SEH begins.
EndAddress - This value represents an offset into the image where some bit of code of interest to SEH ends.
UnwindData - This value is an offset from the base of the image to an UNWIND_INFO structure that describes why the bit of code encompassed in the BeginAddress and EndAddress is of interest.
typedef struct _UNWIND_INFO {
UBYTE Version : 3;
#define UNW_FLAG_NHANDLER 0x0
#define UNW_FLAG_EHANDLER 0x1
#define UNW_FLAG_UHANDLER 0x2
#define UNW_FLAG_CHAININFO 0x4
UBYTE Flags : 5;
UBYTE SizeOfProlog;
UBYTE CountOfCodes;
UBYTE FrameRegister: 4;
UBYTE FrameOffset : 4;
UNWIND_CODE UnwindCode;//
union {
//
// If (Flags & UNW_FLAG_EHANDLER)
//
OPTIONAL ULONG ExceptionHandler;
//
// Else if (Flags & UNW_FLAG_CHAININFO)
//
OPTIONAL ULONG FunctionEntry;
};
//
// If (Flags & UNW_FLAG_EHANDLER)
//
ULONG ExceptionData;
} UNWIND_INFO, *PUNWIND_INFO;
The ExceptionHandler in the RUNTIME_FUNCTION.UNWIND_INFO is the compiler telling the O/S what to call if an exception is raised while executing the function.
在flag中,UNW_FLAG_CHAININFO是一个特殊的标记,它标识这个UNWIND_INFO只是一个指针,它的FunctionEntry会指向一个RUNTIME_FUNCTION,即这个新RUNTIME_FUNCTION包含了一个和此UNWIND_INFO处理相同代码片的UNWIND_INFO。这个被指向的RUNTIME_FUNCTION里的UNWIND_INFO也可以是UNW_FLAG_CHAININFO的,即这个chain可以是一个链表。
UNWIND_CODE用来在沿着execption handler走,修改了context之后要恢复context时,每个UNWIND_INFO中所做的修改会用它的UNWIND_CODE来恢复。其中的UnwindOp是包含具体操作的元素,其值的意义附在后面。
typedef union _UNWIND_CODE {
struct {
UBYTE CodeOffset;
UBYTE UnwindOp : 4;
UBYTE OpInfo : 4;
};
USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
对于一个 exception handlers而言,它的Flags 是要被置为UNW_FLAG_EHANDLER的
If the UnwindData has the UNW_FLAG_EHANDLER bit set, then the BeginAddress and EndAddress fields of the RUNTIME_FUNCTION describe the location of a function in the image that uses SEH. The UnwindData structure then is going to describe all the places where the __try keyword appears in the function, their associated exception handlers (a.k.a. exception filters), and the location of the code contained in the __except block.
When the UNW_FLAG_EHANDLER bit is set, the ExceptionHandler field of the UNWIND_INFO structure is assumed to be valid. This field is filled in by the compiler and says, "Hey, you, O/S! If an exception ever occurs and the instruction pointer is >= BeginAddress and < EndAddress, call this handler!" This generic exception handler, currently implemented as _C_specific_handler, is then responsible for figuring out exactly what to do with this exception.
It does this by parsing the ExceptionData. On the x64, the ExceptionData is actually an offset to a pointer to a SCOPE_TABLE structure (defined in ntx64.h in build 5308 of the Windows Driver Kit):
typedef struct _SCOPE_TABLE {
ULONG Count;
struct
{
ULONG BeginAddress;
ULONG EndAddress;
ULONG HandlerAddress;
ULONG JumpTarget;
} ScopeRecord;
} SCOPE_TABLE, *PSCOPE_TABLE;
The SCOPE_TABLE describes each of the individual __try/__except blocks within the function.
BeginAddress - This value indicates the offset of the first instruction within a __try block located in the function.
EndAddress - This value indicates the offset to the instruction after the last instruction within the __try block (conceptually the __except statement).
HandlerAddress -这个值指向的是__except()括号中代码的开始,它被叫做"exception handler" or "exception filter". If the code in question specifies the predefined handler EXCEPTION_EXECUTE_HANDLER, this value may simply be "1"
这儿注意一下,exeception filter- exception handler和__except 是不一样的概念,__except是被exception handler dispatch之后调用到的处理routine。
JumpTarget - 这个值是从__try block开始位置到the first instruction in the __except block的offset
Once the exception is raised by the processor, the standard exception handling mechanism in Windows will find the RUNTIME_FUNCTION for the offending instruction pointer and call the ExceptionHandler. This will always result in a call to _C_specific_handler for kernel-mode code running on current versions of Windows. _C_specific_handler will then begin walking all of the SCOPE_TABLE entries searching for a match on the faulting instruction, and will hopefully find an __except statement that covers the offending code.
这个过程如下:
_C_specific_handler {
scopeTable = UwindData->ExceptionData;
For (index = 0; index < scopeTable->Count; index++)
scopeRecord = scopeTable->ScopeRecord;
If (FaultingInstruction >= scopeRecord->BeginAddress &&
FaultingInstruction < scopeRecord->EndAddress) {
If (scopeRecord->HandlerAddress != 1) {
callExceptHandler = (*scopeRecord->HandlerAddress)();
} else {
callExceptHandler = TRUE;
}
If (callExceptHandler) {
(*scopeRecord->JumpTarget)();
}
}
}
}
_C_specific_handler这种language specific handler的原型是:
typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord,
IN ULONG64 EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
ExceptionRecord supplies a pointer to an exception record, which has the standard Win64 definition.
EstablisherFrame is the address of the base of the fixed stack allocation for this function.
ContextRecord points to the exception context at the time the exception was raised (in the exception handler case) or the current "unwind" context (in the termination handler case).
DispatcherContext points to the dispatcher context for this function. 这个DISPATCHER_CONTEXT的结构附在x64 SEH note的结尾。
windows x64处理一个exception的流程如下,从1->5是按顺序执行到的:
1.它首先用当前的context.RIP来查找RUNTIME_FUNCTION表,以期配上一个RUNTIME_FUNCTION的BeginAddress和EndAddress。
2.如果用context.RIP查找不到期望的RUNTIME_FUNCTION的话,就认为当前函数一个leaf function(不包含堆栈保护prolog和epilog函数,这种函数不能调用其他函数),继而把储存的函数调用返回地址更新到exception的context中,然后simulated RSP(exception context中的?)被加8,之后继续去查找新context的RUNTIME_FUNCTION
3.如果查找到了对应的RUNTIME_FUNCTION了,RIP所在代码会出现3种情况
a.RIP位于一个函数结尾的代码中,也即当前函数的流程以及执行完,要返回上层函数了,这个时候会假设当前RIP所在的函数没有配套的exception handler,并且会利用RIP指向的epilog(返回代码)返回到callee(上层)函数中去。这个返回上层是透过模拟执行RIP指向的代码来实现的,RIP到函数具体返回callee位置的代码们都会模拟执行,而模拟之后得到了的新context,就会回到第一步继续查找RUNTIME_FUNCTION
b.如果RIP是位于一个函数开始的(prologue)代码中,就是说当前函数的流程还没执行。这个时候也会假设当前RIP所在的函数没有配套的exception handler。然后RIP到函数开头的代码们都会被反向取它们的效果,把context恢复到callee上层函数里面。这个函数的开头是用UNWIND_INFO.SizeOfProlog和它的Scope_Table来判断出来的,RIP减开头地址的结果比UNWIND_INFO.SizeOfProlog保存的值小或相等时,就是在prolog中了。然后这个prolog的效果都会被撤销,是逆着一个个解析指令直到prolog开头来实现的。等解析完成后,新的context又用来回到第一步,查找对应的RUNTIME_FUNCTION
c.不在开头,也不在结尾时,就是正常流程,像上面_C_specific_handler那样解析UNWIND_INFO具体的处理异常了。If the RIP is not within a prolog or epilog and the function has an exception handler (UNWIND_INFO.flag的UNW_FLAG_EHANDLER is set), then the language specific handler is called. The handler scans its data and calls filter functions as appropriate. The language specific handler can return that the exception was handled or that the search is to be continued。这个language specific handler还可以初始化一个新的只存在于内存中的unwind data(Scope_Table)表出来。
4.如果 language specific handler返回了一个 handled(处理了)的返回值,就会回到原始流程里继续执行。
5.要是这个RUNTIME_FUNCTION里没有一个language specific handler,或者是它的 language specific handler返回了一个"继续搜索"的返回值,那前面2、3步中返回上层函数、修改、虚拟执行、指令撤销都会因为循环执行它所对应UNWIND_INFO中的unwind代码的而抵消掉,直到unwind代码的数组执行完成,context被返回到原始exception context的状态。又会回到step继续查找下一个合适的RUNTIME_FUNCTION。
最后,如果从1->5的循环把整个RUNTIME_FUNCTION表查询完了,也没有得到合适的RUNTIME_FUNCTION,这个exeception就是unhandled(没被处理)的了,这样就会crash程序。
颠倒着处理UNWIND_INFO一步步返回context的流程,和上面寻找RUNTIME_FUNCTION的流程有相同的执行方式。唯一不同的地方是,当沿着unwind code 数组返回虚拟执行的效果时,一碰到unwind code数组的结尾时, unwind code数组它又会链接到上层UNWIND_INFO上去,接着继续沿着上层这个UNWIND_INFO的code数组往以前的context返回. 这个向上链接会一直持续,直到碰到一个 UNW_CHAINED_INFO标识没有被设置的UNWIND_INFO结构,这样把它的unwind code数组返回完成后,就会在此停止往上面链了。
//---------------------
UNWIND_INFO中的UNWIND_CODE的opcode enum表---比较老。
typedef enum _UNWIND_OP_CODES {
UWOP_PUSH_NONVOL = 0,
UWOP_ALLOC_LARGE,
UWOP_ALLOC_SMALL,
UWOP_SET_FPREG,
UWOP_SAVE_NONVOL,
UWOP_SAVE_NONVOL_FAR,
UWOP_SAVE_XMM,
UWOP_SAVE_XMM_FAR,
UWOP_SAVE_XMM128,
UWOP_SAVE_XMM128_FAR,
UWOP_PUSH_MACHFRAME
} UNWIND_CODE_OPS;
//
language specific handler的参数PDISPATCHER_CONTEXT DispatcherContext
typedef struct _DISPATCHER_CONTEXT {
ULONG64 ControlPc;
ULONG64 ImageBase;
PRUNTIME_FUNCTION FunctionEntry;
ULONG64 EstablisherFrame;
ULONG64 TargetIp;
PCONTEXT ContextRecord;
PEXCEPTION_ROUTINE LanguageHandler;
PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;
ControlPc is the value of RIP within this function. This is either an exception address or the address at which control left the establishing function. This is the RIP that will be used to determine if control is within some guarded construct within this function (for example, a __try block for __try/__except or __try/__finally).
ImageBase is the image base (load address) of the module containing this function, to be added to the 32-bit offsets used in the function entry and unwind info to record relative addresses.
FunctionEntry supplies a pointer to the RUNTIME_FUNCTION function entry holding the function and unwind info image-base relative addresses for this function.
EstablisherFrame is the address of the base of the fixed stack allocation for this function.
TargetIp Supplies an optional instruction address that specifies the continuation address of the unwind. This address is ignored if EstablisherFrame is not specified.
ContextRecord points to the exception context, for use by the system exception dispatch/unwind code.
LanguageHandler points to the language-specific language handler routine being called.
HandlerData points to the language-specific handler data for this function.
收藏学习,感谢!
页:
[1]