unit uWin64ExceptionStack;

interface

{$IFDEF WIN64}

const
  MAX_NESTED_EXCEPTIONS = 16;

type
  TSavedRaiseFrame = record
    NextRaiseOffset: Integer;
    ExceptAddr: Pointer;
    ExceptObject: TObject;
  end;
  TSavedRaiseFrames = array[0..MAX_NESTED_EXCEPTIONS - 1] of TSavedRaiseFrame;

  TWin64ExceptionStack = record
    ExceptionObjectCount : Integer;
    SavedRaiseFrames : TSavedRaiseFrames;
    RaiseListPtrOffset : Integer;
    procedure LoadFromThreadExceptionStack;
    procedure SaveToThreadExceptionStack;
  end;

{$ENDIF}

implementation

{$IFDEF WIN64}

uses
  Windows;

type
  PPRaiseFrame = ^PRaiseFrame;
  PRaiseFrame = ^TRaiseFrame;
  TRaiseFrame = record
    NextRaise: PRaiseFrame;
    ExceptAddr: Pointer;
    ExceptObject: TObject;
  end;
  PRaiseFrames = ^TRaiseFrames;
  TRaiseFrames = array[0..MAX_NESTED_EXCEPTIONS - 1] of TRaiseFrame;

const
  RAISEFRAMES_TLS_OFFSET = $0;
  EXCEPTIONOBJECTCOUNT_TLS_OFFSET = sizeof(TRaiseFrames);
  RAISELISTPTR_TLS_OFFSET = EXCEPTIONOBJECTCOUNT_TLS_OFFSET + sizeof(NativeUInt);
  NULL_RAISE_FRAME = -1;

// GetTLS function extracted and simplified from SysInit.pas unit
function GetTLS : Pointer;
const
  tlsArray = $58; { offset of tls array from FS: }
type
  PPPointerArray = ^PPointerArray;
var
  P: PPointerArray;
begin
  if ModuleIsLib then
    Result := TlsGetValue(TlsIndex)
  else
  begin
    //P := PPPointerArray(ReadGSQWord(tlsArray));
    P := PPPointerArray(PByte(@GSSegBase) + tlsArray)^;
    Result := P^[TlsIndex];
  end;
end;

procedure TWin64ExceptionStack.LoadFromThreadExceptionStack;
var
  ATLS : Pointer;
  RaiseFrame : PRaiseFrame;
  i : integer;
begin
  ATLS := GetTLS;
  ExceptionObjectCount := PInteger(NativeUInt(ATLS) + EXCEPTIONOBJECTCOUNT_TLS_OFFSET)^;
  for i := 0 to ExceptionObjectCount - 1 do
    begin
      RaiseFrame := @PRaiseFrames(NativeUInt(ATLS) + RAISEFRAMES_TLS_OFFSET)[i];
      SavedRaiseFrames[i].ExceptAddr := RaiseFrame.ExceptAddr;
      SavedRaiseFrames[i].ExceptObject := RaiseFrame.ExceptObject;
      if RaiseFrame.NextRaise <> nil then
        SavedRaiseFrames[i].NextRaiseOffset := NativeUInt(RaiseFrame.NextRaise) - NativeUInt(ATLS) - RAISEFRAMES_TLS_OFFSET
      else SavedRaiseFrames[i].NextRaiseOffset := NULL_RAISE_FRAME;
    end;
  if PPRaiseFrame(NativeUInt(ATLS) + RAISELISTPTR_TLS_OFFSET)^ <> nil then
    RaiseListPtrOffset := NativeUInt(PPRaiseFrame(NativeUInt(ATLS) + RAISELISTPTR_TLS_OFFSET)^) - NativeUInt(ATLS) - RAISEFRAMES_TLS_OFFSET
  else RaiseListPtrOffset := NULL_RAISE_FRAME;
end;

procedure TWin64ExceptionStack.SaveToThreadExceptionStack;
var
  ATLS : Pointer;
  RaiseFrame : PRaiseFrame;
  i : integer;
begin
  ATLS := GetTLS;
  PInteger(NativeUInt(ATLS) + EXCEPTIONOBJECTCOUNT_TLS_OFFSET)^ := ExceptionObjectCount;
  for i := 0 to ExceptionObjectCount - 1 do
    begin
      RaiseFrame := @PRaiseFrames(NativeUInt(ATLS) + RAISEFRAMES_TLS_OFFSET)[i];
      RaiseFrame.ExceptAddr := SavedRaiseFrames[i].ExceptAddr;
      RaiseFrame.ExceptObject := SavedRaiseFrames[i].ExceptObject;
      if SavedRaiseFrames[i].NextRaiseOffset <> NULL_RAISE_FRAME then
        RaiseFrame.NextRaise := PRaiseFrame(NativeUInt(PRaiseFrames(NativeUInt(ATLS) + RAISEFRAMES_TLS_OFFSET)) + SavedRaiseFrames[i].NextRaiseOffset)
      else RaiseFrame.NextRaise := nil;
    end;
  if RaiseListPtrOffset <> NULL_RAISE_FRAME then
    PPRaiseFrame(NativeUInt(ATLS) + RAISELISTPTR_TLS_OFFSET)^ := PRaiseFrame(NativeUInt(PRaiseFrames(NativeUInt(ATLS) + RAISEFRAMES_TLS_OFFSET)) + RaiseListPtrOffset)
  else PPRaiseFrame(NativeUInt(ATLS) + RAISELISTPTR_TLS_OFFSET)^ := nil;
end;

{$ENDIF}

end.