While reading up on Win64 structured exception tracing (from Programming against the x64 exception handling support, part 7: Putting it all together, or building a stack walk routine), I converted the code StackWalk64.cpp.
procedure DumpExceptionStack();
var
LContext : CONTEXT;
LUnwindHistoryTable : _UNWIND_HISTORY_TABLE;
LRuntimeFunction : Pointer;
LImageBase : ULONGLONG;
HandlerData : Pointer;
EstablisherFrame : ULONG64;
NvContext : KNONVOLATILE_CONTEXT_POINTERS;
LLineNumber : integer;
LModuleName : UnicodeString;
LPublicAddr : pointer;
LPublicName : UnicodeString;
LUnitName : UnicodeString;
begin
//
// First, we'll get the caller's context.
//
RtlCaptureContext(LContext);
//
// Initialize the (optional) unwind history table.
//
LUnwindHistoryTable := Default(_UNWIND_HISTORY_TABLE);
// LUnwindHistoryTable.Unwind := True;
//
// This unwind loop intentionally skips the first call frame, as it shall
// correspond to the call to StackTrace64, which we aren't interested in.
//
repeat
//
// Try to look up unwind metadata for the current function.
//
LRuntimeFunction := RtlLookupFunctionEntry(LContext.Rip,
LImageBase,
LUnwindHistoryTable);
NvContext := Default(KNONVOLATILE_CONTEXT_POINTERS);
if not Assigned(LRuntimeFunction) then
begin
//
// If we don't have a RUNTIME_FUNCTION, then we've encountered
// a leaf function. Adjust the stack approprately.
//
//LContext.Rip := (ULONG64)(*(PULONG64)Context.Rsp);
LContext.Rip := ULONG64(Pointer(LContext.Rsp)^);
LContext.Rsp := LContext.Rsp + 8;
end
else
begin
//
// Otherwise, call upon RtlVirtualUnwind to execute the unwind for
// us.
//
RtlVirtualUnwind(UNW_FLAG_NHANDLER,
LImageBase,
LContext.Rip,
LRuntimeFunction,
LContext,
HandlerData,
EstablisherFrame,
NvContext);
end;
//
// If we reach an RIP of zero, this means that we've walked off the end
// of the call stack and are done.
//
if LContext.Rip = 0 then
Break;
//
// Display the context. Note that we don't bother showing the XMM
// context, although we have the nonvolatile portion of it.
//
if madMapFile.GetMapFileInfos(Pointer(LContext.Rip),
LModuleName,
LUnitName,
LPublicName,
LPublicAddr,
LLineNumber) then
begin
Writeln(Format('%p %s.%s %d', [Pointer(LContext.Rip), LUnitName, LPublicName, LLineNumber{, LSEHType}]));
end;
until LContext.Rip = 0;
end;
Then I call it with the following:
procedure Main();
begin
try
try
try
try
DumpExceptionStack();
finally
//
end;
except
on E : Exception do
raise
end;
except
on E : Exception do
raise
end;
except
on E : Exception do
raise
end;
end;
When I run the application (just a console application), I only get one entry for Main
but I was expecting there to be four (three nested exceptions and one finally).
Could it be that I have mis-interpretted and that DumpExceptionStack
will only give the result that I am interested in when an exception is thrown? If this is so, what would the required changes be to get all the exception stacks (if it possible) - ie. have four outputs for Main
?
The x64 exception model is table based, in contrast to the stack based x86 model. Which means that exception stacks do not exist. In any case, I've never seen a stalk walk routine that attempts to include exception and finally blocks. This one is no different. It walks the function call stack.
The exception flow within a single function is controlled by the scope tables. In your function, if your code raised an exception at the point at which is calls DumpExceptionStack
, then multiple scope table entries match the exception location. The exception is handled by the innermost matching scope. The distance between the scope's begin and end addresses can be used to infer which scope is the innermost. If that innermost scope does not handle the exception, or re-raises it, then the next most innermost scope is asked to handle it. And so on until all the matching scopes for the function are exhausted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With