Handling of unwind-protect
The CLRHack compiler maps Lisp unwind-protect semantics directly onto the Structured Exception Handling
(SEH) infrastructure of the .NET Common Language Runtime (CLR). Specifically, it utilizes the try...finally
construct provided by the Common Intermediate Language (CIL).
Lisp semantics require that the cleanup forms in an unwind-protect block be executed regardless of how control
leaves the protected form—whether via normal return, a non-local throw, or a lexical exit like
return-from. The CLR guarantees that a finally block will execute during stack unwinding, which is
exactly the hook required for Lisp. The implementation details are as follows:
- Protected Form: The compiler generates the code for the protected form inside a CIL
tryblock. Upon successful completion, the primary return value is stored in a local variable, and aleaveinstruction is used to exit thetryblock, which automatically triggers the transition to thefinallyblock. - Side-Channel Preservation: A unique challenge in Lisp is that
unwind-protectmust return the values of the protected form, but cleanup forms may themselves perform operations that alter the Multiple Return Value (MRV) side-channel. CLRHack exploits method-local variables to save theReturnCountand the contents ofValue1throughValue63at the very beginning of thefinallyblock and restore them at the very end. - Unwinding: If a
throwor other exception occurs within thetryblock, the CLR stack walker identifies thefinallyblock and executes it before propagating the exception further. This ensures Lisp's "cleanup guarantee" is maintained even during catastrophic or non-local control transfers.
Handling of catch and throw
Lisp's catch and throw are implemented as a Dynamic Non-Local Exit system built on
top of .NET's exception propagation mechanism. While CLR exceptions are typically filtered by type, Lisp requires filtering by a
dynamic "tag" object (compared via eq).
The throw Mechanism
When a (throw tag value) is evaluated, CLRHack does not simply perform a jump. Instead, it performs the following
steps:
- Evaluates the
tagand the primaryvalue. - Captures the current state of the MRV side-channel into an
object[]. - Instantiates a specialized exception class:
[LispBase]Lisp.CatchThrowException. This object acts as a carrier for the tag, the primary value, and the captured MRV array. - Executes the CIL
throwinstruction. This initiates the CLR's SEH stack walk.
The catch Mechanism
The (catch tag body) form is compiled into a try...catch block where the catch handler specifically
targets CatchThrowException:
- Tag Setup: The
catchtag is evaluated and stored in a method-local variable. - Body Execution: The body forms are executed within a
tryblock. - The Catch Handler: When a
CatchThrowExceptionis intercepted, the handler performs a "Dynamic Filter":- It extracts the tag from the exception object and compares it to the local
catchtag usingSystem.Object.Equals(simulating Lisp'seqfor reference types). - Match: If the tags match, the handler "claims" the exception. It extracts the primary value and
the MRV array from the exception, restores them to the thread-local side-channel, and resumes normal execution after the
catchblock. - Mismatch: If the tags do not match, the handler executes the CIL
rethrowinstruction. This allows the exception to continue up the stack to find a matchingcatchtag in a higher frame.
- It extracts the tag from the exception object and compares it to the local
Exploiting SEH for Lisp Semantics
CLRHack exploits the CLR's SEH in three fundamental ways to bridge the gap between .NET and Lisp:
- Automatic Stack Unwinding: By using
throwandtry...catch, the compiler delegates the complex task of cleaning up stack frames, registers, and intermediate states to the highly optimized .NET runtime. - Guaranteed Cleanup: The
finallyblock is the "silicon reality" of Lisp'sunwind-protect. The CLR ensures it runs even if an exception is re-thrown multiple times or if a thread is being terminated. - Payload-Heavy Exceptions: Unlike standard .NET exceptions which often carry only metadata,
CatchThrowExceptionis exploited as a transport mechanism. It carries the entire "return state" of a Lisp expression (primary value + MRV side-channel) across an arbitrary number of stack frames, allowing athrowto behave exactly like a multi-valued return to a dynamic point.
No comments:
Post a Comment