Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Win64 exception stack walking not displaying entries

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 ?

like image 203
Nicholas Ring Avatar asked Oct 06 '22 10:10

Nicholas Ring


1 Answers

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.

like image 71
David Heffernan Avatar answered Oct 10 '22 02:10

David Heffernan