关于Windows可执行文件中正确添加SEH异常处理程序的技术问询
Great question—you’re spot-on to dig into this, because while that assembly snippet does add a new exception handler to the chain, it’s missing a critical piece to keep things safe and correct. Let’s break this down step by step.
First, let’s unpack what your current code does:
push ExceptionHandler ; Push the new handler function address onto the stack mov eax, dword ptr fs:[00000000h] ; Get the current head of the exception handler chain (from TEB) push eax ; Save the old chain head as the "Next" pointer for our new node mov dword ptr fs:[0], esp ; Set the new chain head to our new node on the stack
Windows uses the fs:[0] register to point to the head of the per-thread exception handler chain, which is a linked list of _EXCEPTION_REGISTRATION_RECORD structures:
typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD* Next; // Pointer to the next handler in the chain PEXCEPTION_ROUTINE Handler; // The exception handler function } EXCEPTION_REGISTRATION_RECORD;
Does this code overwrite the existing chain?
No—your code doesn’t overwrite the existing chain. By pushing the old fs:[0] value (the original chain head) as the Next pointer of your new node, you’re inserting the new handler at the front of the chain. The rest of the existing handlers are still linked via that Next pointer, so they’re not lost or overwritten.
What’s missing?
The critical oversight here is restoring the original chain head when you’re done with the new handler.
Your code adds the new handler to the chain, but if the function returns normally (or even if an exception is handled and execution resumes), the stack space holding your _EXCEPTION_REGISTRATION_RECORD will be reused or overwritten. If you don’t set fs:[0] back to its original value, the next time an exception occurs, the system will try to traverse the chain and hit a dangling pointer to invalid stack memory—this will almost certainly cause a crash.
How to correctly add an exception handler
To do this safely, you need to ensure the original fs:[0] value is restored, regardless of whether execution completes normally or exits via an exception. Here’s how to fix your assembly:
- Save the original chain head in a safe place (not just on the stack, though the stack works if you manage it properly)
- Add your new handler to the chain
- Execute the code that needs the exception handler
- Restore the original chain head, even if an exception occurs
Here’s a corrected example using assembly with a "finally" style cleanup:
; Set up stack frame and save original chain head push ebp mov ebp, esp sub esp, 4 ; Allocate local space for original_fs0 mov eax, dword ptr fs:[0] mov dword ptr [ebp-4], eax ; Store original chain head safely ; Add new handler to the chain push ExceptionHandler push dword ptr [ebp-4] mov dword ptr fs:[0], esp ; -------------------------- ; Your protected code here ; -------------------------- ; Cleanup: Restore original chain head (runs even if exception occurs) _cleanup: mov eax, dword ptr [ebp-4] mov dword ptr fs:[0], eax mov esp, ebp pop ebp ret ; Exception handler implementation ExceptionHandler: ; Your handler logic here (e.g., handle exception, then resume or unwind) ; If resuming execution, jump back to _cleanup to restore the chain ; If unwinding, the OS will handle chain traversal, but cleanup still ensures validity
Alternatively, if you’re working with C/C++, the compiler handles this automatically when you use __try/__except or __try/__finally—it generates code to add the handler to the chain and restore it when the block exits.
Key takeaways
- Your original code doesn’t overwrite the exception handler chain—it inserts a new handler at the front.
- The missing piece is restoring the original chain head after the protected code runs (to avoid dangling stack pointers).
- Always ensure cleanup happens for both normal and exception paths to keep the chain valid.
内容的提问来源于stack exchange,提问作者Trey

