SwiftForth Exception Handling

SwiftForth extends the Standard Forth CATCH and THROW exception handling to include exceptions signaled by the underlying OS (Windows vectored exceptions, Linux signals, macOS signals).

Kernel-Level Exception Handling

The SwiftForth kernel implements CATCH and THROW, common to all platforms (Windows, Linux, macOS):

SwiftForth/src/kernel/except.f
{ --------------------------------------------------------------------
Debug info

IOR_EXCEPT is the throw code for a system exception (a SIGNAL in MacOS
and Linux). The debug info that follows is captured by the system
exception handler before returning to its THROWBACK vector.

In the case where THROW is called directly (the ior is not
IOR_EXCEPT), THROW fills in the stack frame debug info.
-------------------------------------------------------------------- }

$DEADBEEF CONSTANT IOR_EXCEPT   \ Throw code for SIGNAL
VARIABLE EXCEPTION#             \ Exception code for debug handler

 20 CONSTANT #TRACE_REGS        \ Registers from ucontext record
256 CONSTANT #TRACE_RSTACK      \ Return stack trace cells
 20 CONSTANT #TRACE_DSTACK      \ Data stack trace cells

CREATE TRACEBACK
   #TRACE_REGS CELLS ALLOT      \ Registers
   #TRACE_RSTACK CELLS ALLOT    \ Return stack
   #TRACE_DSTACK CELLS ALLOT    \ Datastack

TRACEBACK #TRACE_REGS CELLS + EQU TRACE_RSTACK

{ --------------------------------------------------------------------
Exception frame operators

CATCH links a new exception frame on the return stack into the list
pointed to by user variable CATCHER, calls the execution token on the
stack.  If the called xt returns here, the exception frame is unlinked
and discarded, and 0 is returned on the data stack.

THROW takes a throw code n from the data stack.  If n is 0 or there is
no exception frame (user variable CATCHER contains 0), THROW simply
returns to its caller.  If n is non-zero, the exception is thrown via
the exception frame set up by the most recent call to CATCH.  The
return stack is set to the address held in CATCHER which is then
updated from the address on top of the return stack.  The parameter
stack pointer is also popped from the exception frame, the throw code
n is placed on the data stack, and control is returned immediately
after the call to CATCH.

THROWBACK is used by the system <EXCEPTION> handler. It returns
IOR_EXCEPT to indicate that a system exception occurred. Note that
THROW makes a copy of the stacks then the throw code is not
IOR_EXCEPT.
-------------------------------------------------------------------- }

28 EQU |FPENV|

CODE CATCH ( i*x xt -- j*x 0 | i*x n )
   |FPENV| # ESP SUB                    \ room for fpenv
   0 [ESP] FSTENV                       \ save fpenv
   0 [ESP] FLDCW                        \ restore control word
   'N [ESI] PUSH                        \ floating stack pointer
   'PERSONALITY [ESI] PUSH              \ personality
   'THIS [ESI] PUSH                     \ current class
   'SELF [ESI] PUSH                     \ current object
   'LF [ESI] PUSH                       \ current local variable frame
   'OB [ESI] PUSH                       \ current local objects frame
   EBP PUSH   ESI PUSH   EDI PUSH       \ Save Forth VM registers S, U, ORIGIN
   CATCHER [ESI] PUSH                   \ link to previous exception frame
   ESP CATCHER [ESI] MOV                \ CATCHER points to this frame
   EBX EAX MOV   POP(EBX)               \ Save xt, pop new TOS
   EDI EAX ADD   EAX CALL               \ Convert xt to addr, call it
   BEGIN ( *)                           \ Return point from call
   CATCHER [ESI] POP                    \ Unlink frame
   |FPENV| 9 CELLS + # ESP ADD          \ Discard it
   PUSH(EBX)   EBX EBX XOR              \ Return 0
   RET   END-CODE

( *) CONSTANT 'CATCHING

CODE THROW ( k*x n -- k*x | i*x n )
   EBX EBX TEST   0= NOT IF             \ Skip if n=0
      IOR_EXCEPT # EBX CMP   0= NOT IF  \ Capture debug info if not IOR_EXCEPT
         ESI PUSH   EDI PUSH
         TRACE_RSTACK [EDI] EDI LEA
         ESP ESI MOV
         #TRACE_RSTACK # ECX MOV   REP MOVSD
         EBP ESI MOV
         #TRACE_DSTACK # ECX MOV   REP MOVSD
      EDI POP   ESI POP   THEN
      CATCHER [ESI] EAX MOV             \ Get pointer to exception frame
      EAX EAX TEST   0= NOT IF          \ Skip if no frame
         EAX ESP MOV   CATCHER [ESI] POP  \ Unlink frame
         EDI POP   ESI POP   EBP POP    \ Restore Forth VM registers
         'OB [ESI] POP                  \ local objects frame
         'LF [ESI] POP                  \ local variable frame
         'SELF [ESI] POP                \ current object
         'THIS [ESI] POP                \ current class
         'PERSONALITY [ESI] POP         \ personality
         'N [ESI] POP                   \ floating stack pointer
         FINIT                          \ floating stack init
         0 [ESP] FLDENV                 \ restore fpenv
         |FPENV| # ESP ADD              \ discarding its memory
         RET                            \ Return n
   THEN THEN
   POP(EBX)   RET   END-CODE            \ Bail out (n=0 or no frame) exits here

LABEL THROWBACK
   IOR_EXCEPT # EBX MOV              \ Return unique throw code
   ' THROW JMP   END-CODE

Each platform adds a handler to “trap” the system exceptions:

SwiftForth/src/kernel/win32/trap.f
{ ====================================================================
Trap handler

Copyright 2020  FORTH, Inc.

Memory structures documented at the end of this file.
==================================================================== }

TARGET

{ ----------------------------------------------------------------------
Vectored exception handler

IOR_EXCEPT is the reserved throw code for Windows exceptions.
EXCEPTION# holds the ExceptionCode field received in ExceptionRecord.
TRACEBACK holds the register and stack contexts for the exception.

THROWBACK does the equivalent of IOR_WINEXCEPT THROW. The THROWBACK
code address is stuffed into the ContextRecord Eip field in
<EXCEPTION> below.

<EXCEPTION> is the SwiftForth PvectoredExceptionHandler (see
documentation below). It receives a pointer to ExceptionInfo on the
stack when called. It pulls the two pointers (ExceptionRecord and
ContextRecord) from ExceptionInfo, saving ExceptionCode in variable
EXCEPTION#, and copying the register and stack context to the
TRACEBACK buffer. EXCEPTION# and TRACEBACK are used by the throw code
debug display in the IDE. The address of THROWBACK replaces the EIP
value in ContextRecord and we return with EXCEPTION_CONTINUE_EXECUTION
so Windows will attempt to continue execution in that context, but
returning to THROWBACK instead, which does IOR_WINEXCEPT THROW back to
the CATCH of the code that caused the exception.

/EXCEPTION sets <EXCEPTION> as the vectored exception handler with the
flag 1 to prioritize this handler as first. This is called at startup.
---------------------------------------------------------------------- }

LABEL <EXCEPTION>
   4 [ESP] EDX MOV                      \ ExceptionInfo addr
   EBX PUSH   ESI PUSH   EDI PUSH       \ Save registers for return to Windows
   0 [EDX] EAX MOV   0 [EAX] EAX MOV    \ EAX = ExceptionCode
   4 [EDX] EBX MOV                      \ EBX = ContextRecord
   BEGIN ( *)  0 # EDX MOV              \ EDX = ORIGIN (filled in by /SIGNAL)
   $C0000000 # EAX TEST   0= IF         \ Bail out if status not error
      EDI POP   ESI POP   EBX POP
      EXCEPTION_CONTINUE_SEARCH # EAX MOV
   4 # RET   THEN
   EAX EXCEPTION# [EDX] MOV             \ Save exception code
   140 [EBX] ESI LEA                    \ ESI = start of registers in context record
   TRACEBACK [EDX] EDI LEA              \ EDI = traceback buffer in data space
   #TRACE_REGS # ECX MOV   REP MOVSD    \ Copy registers to traceback buffer
   196 [EBX] ESI MOV                    \ ESI = return stack addr ESP from context record
   #TRACE_RSTACK # ECX MOV   REP MOVSD  \ Copy return stack to traceback buffer
   180 [EBX] ESI MOV                    \ ESI = data stack addr EBP from context record
   #TRACE_DSTACK # ECX MOV   REP MOVSD  \ Copy return stack to traceback buffer
   'CATCHING [EDX] EAX LEA   ESP EDI MOV
   $8000 # ECX MOV   REPNE SCASD        \ Find CATCH stack frame
   0= IF   8 [EDI] EAX MOV              \ Get pushed ESI (user pointer)
   EAX 160 [EBX] MOV   THEN             \ Replace ESI in context record so THROW will work correctly
   THROWBACK [EDX] EAX LEA              \ Code address of THROWBACK
   EAX 184 [EBX] MOV                    \ Stuff THROWBACK addr in place of EIP in context record
   EDI POP   ESI POP   EBX POP          \ Restore registers
   EXCEPTION_CONTINUE_EXECUTION # EAX MOV
   4 # RET   END-CODE                   \ Return back to Windows and eventually to THROWBACK

( *) 1+ EQU 'ORIGIN                    \ Address of immediate value for ORIGIN

| : /SIGNAL ( -- )
   ORIGIN 'ORIGIN +ORIGIN !             \ Set our origin at ( *) above
   1 <EXCEPTION> +ORIGIN AddVectoredExceptionHandler DROP ;


\\ *********************************************************************

Function and structure documentation from Microsoft Win32 API:

<EXCEPTION> follows this function prototype:

LONG PvectoredExceptionHandler(
  _EXCEPTION_POINTERS *ExceptionInfo
)


ExceptionInfo structure passed to <EXCEPTION> handler by Windows:

typedef struct _EXCEPTION_POINTERS {
  PEXCEPTION_RECORD ExceptionRecord;
  PCONTEXT          ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;


Exception record structure pointed to by first cell of ExceptionRecord:

typedef struct _EXCEPTION_RECORD {
  DWORD                    ExceptionCode;
  DWORD                    ExceptionFlags;
  struct _EXCEPTION_RECORD *ExceptionRecord;
  PVOID                    ExceptionAddress;
  DWORD                    NumberParameters;
  ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;


Machine-dependent context record structure pointed to by second cell of ExceptionRecord:

typedef struct _CONTEXT {
    DWORD   ContextFlags;
    /* Debug registers */
    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;
    /* Floating-point context */
    DWORD   ControlWord;
    DWORD   StatusWord;
    DWORD   TagWord;
    DWORD   ErrorOffset;
    DWORD   ErrorSelector;
    DWORD   DataOffset;
    DWORD   DataSelector;
    BYTE    RegisterArea[80];
    DWORD   Cr0NpxState;
    /* CPU registers -- offset=140 */
    DWORD   SegGs;      // 140
    DWORD   SegFs;      // 144
    DWORD   SegEs;      // 148
    DWORD   SegDs;      // 152
    DWORD   Edi;        // 156
    DWORD   Esi;        // 160
    DWORD   Ebx;        // 164
    DWORD   Edx;        // 168
    DWORD   Ecx;        // 172
    DWORD   Eax;        // 176
    DWORD   Ebp;        // 180
    DWORD   Eip;        // 184
    DWORD   SegCs;      // 188
    DWORD   EFlags;     // 192
    DWORD   Esp;        // 196
    DWORD   SegSs;      // 200
    BYTE    ExtendedRegisters[512];
} CONTEXT;
SwiftForth/src/kernel/linux/trap.f
{ ====================================================================
Trap handler

Copyright 2008  FORTH, Inc.

Memory structures documented at the end of this file.
====================================================================

Top of machine stack on entry to <EXCEPTION>

 +0  Return addr
 +4  SIG#
 +8  Pointer to siginfo_t structure
+12  Pointer to ucontext_t structure (see below)

==================================================================== }

TARGET

LABEL <EXCEPTION>
   4 [ESP] EAX MOV                      \ EAX = EXCEPTION#
   EBX PUSH   ESI PUSH   EDI PUSH       \ Save registers for return to OS
   24 [ESP] EBX MOV   36 [EBX] EDX MOV  \ EBX = context pointer, EDX = ORIGIN addr
   BEGIN ( *)  0 # EDX MOV              \ Data space origin (updated by /EXCEPTION)
   EAX EXCEPTION# [EDX] MOV             \ Save exception#
   TRACEBACK [EDX] EDI LEA              \ EDI = Address of TRACEBACK buffer in our data space
   20 [EBX] ESI LEA                     \ ESI = Address of first register in machine context
   #TRACE_REGS # ECX MOV   REP MOVSD    \ Copy registers to traceback
   48 [EBX] ESI MOV                     \ ESI = ESP from uc_mcontext
   #TRACE_RSTACK # ECX MOV   REP MOVSD  \ Copy return stack to traceback
   44 [EBX] ESI MOV                     \ EDI = EBP from uc_mcontext
   #TRACE_DSTACK # ECX MOV   REP MOVSD  \ Copy data stack to traceback
   'CATCHING [EDX] EAX LEA   ESP EDI MOV
   $8000 # ECX MOV   REPNE SCASD        \ Find CATCH stack frame
   0= IF   8 [EDI] EAX MOV              \ Get pushed ESI (user pointer)
   EAX 40 [EBX] MOV   THEN              \ Replace ESI in context record so THROW will work correctly
   THROWBACK [EDX] EAX LEA              \ Code address of THROWBACK
   EAX 76 [EBX] MOV                     \ Stuff THROWBACK addr in place of EIP in context record
   EDI POP   ESI POP   EBX POP          \ Restore registers
   EAX EAX XOR   RET   END-CODE         \ Return 0 result

( *) 1+ EQU 'ORIGIN

4 EQU SA_SIGINFO

| : /EXCEPTION ( -- )
   ORIGIN 'ORIGIN +ORIGIN !             \ Stuff ORIGIN address into literal
   R-BUF  R@ 140 ERASE                  \ This is struct sigaction
   <EXCEPTION> +ORIGIN R@ !             \ sa_siginfo handler
   SA_SIGINFO R@ 132 + !                \ sa_flags (skip 128-bit sa_mask field)
   4 R@ 0 sigaction DROP                \ SIGILL - Illegal instruction
   8 R@ 0 sigaction DROP                \ SIGFPE - Arithmetic exception
   11 R@ 0 sigaction DROP               \ SIGSEGV - Seg fault
   2 R@ 0 sigaction DROP                \ SIGINT - User interrupt (Ctrl-C)
   R> DROP ;


\\ =====================================================================

If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of
sa_handler) specifies the  signal-handling function  for  signum.
This function receives the signal number as its first argument, a
pointer to a siginfo_t as its second argument and a pointer to a
ucontext_t (cast to void *) as its third argument.

/* Userlevel context.  */
typedef struct ucontext
  {
    unsigned long int uc_flags;         [4]
    struct ucontext *uc_link;           [4]
    stack_t uc_stack;                   [12]
    mcontext_t uc_mcontext;             [88]
    __sigset_t uc_sigmask;              [128]
    struct _libc_fpstate __fpregs_mem;  [112]
  } ucontext_t;                                 [348]

typedef struct sigaltstack
  {
    void *ss_sp;                        [4]
    int ss_flags;                       [4]
    size_t ss_size;                     [4]
  } stack_t;                                    [12]


/* Context to describe whole processor state.  */
typedef struct
  {
    gregset_t gregs;                    [76]
    fpregset_t fpregs;                  [4]
    unsigned long int oldmask;          [4]
    unsigned long int cr2;              [4]
  } mcontext_t;                                 [88]


/* Type for general register.  */
typedef int greg_t;

/* Number of general registers.  */
#define NGREG   19

/* Container for all general registers.  */
typedef greg_t gregset_t[NGREG];

/* Number of each register in the `gregset_t' array.  */
enum
{
  REG_GS = 0,   // +20 (offset into ucontext above)
  REG_FS,       // +24
  REG_ES,       // +28
  REG_DS,       // +32
  REG_EDI,      // +36
  REG_ESI,      // +40
  REG_EBP,      // +44
  REG_ESP,      // +48
  REG_EBX,      // +52
  REG_EDX,      // +56
  REG_ECX,      // +60
  REG_EAX,      // +64
  REG_TRAPNO,   // +68
  REG_ERR,      // +72
  REG_EIP,      // +76
  REG_CS,       // +80
  REG_EFL,      // +84
  REG_UESP,     // +88
  REG_SS        // +92
};
#endif
SwiftForth/src/kernel/osx/trap.f
{ ====================================================================
Trap handler

Copyright 2008  FORTH, Inc.

Memory structures documented at the end of this file.
====================================================================

Top of machine stack on entry to <EXCEPTION>

 +0  Return addr
 +4  SIG#
 +8  Pointer to siginfo_t structure
+12  Pointer to ucontext_t structure (see below)

==================================================================== }

TARGET

LABEL <EXCEPTION>
   04 [ESP] EAX MOV                     \ EAX = EXCEPTION#
   EBX PUSH   ESI PUSH   EDI PUSH       \ Save registers for return to OS
   24 [ESP] EBX MOV   28 [EBX] EBX MOV  \ EBX = Machine context
   BEGIN ( *)  0 # EDX MOV              \ Data space origin (updated by /EXCEPTION)
   EAX EXCEPTION# [EDX] MOV             \ Save exception#
   TRACEBACK [EDX] EDI LEA              \ EDI = Address of TRACEBACK buffer in our data space
   12 [EBX] ESI LEA                     \ ESI = ddress of first register in machine context
   #TRACE_REGS # ECX MOV   REP MOVSD    \ Copy registers to TRACEBACK buffer
   40 [EBX] ESI MOV                     \ ESI = Return stack pointed by ESP in machine context
   #TRACE_RSTACK # ECX MOV   REP MOVSD  \ Copy return stack to TRACEBACK buffer
   36 [EBX] ESI MOV                     \ ESI = Data stack pointed by EBP in machine context
   #TRACE_DSTACK # ECX MOV   REP MOVSD  \ Copy data stack to TRACEBACK buffer
   'CATCHING [EDX] EAX LEA   ESP EDI MOV
   $8000 # ECX MOV   REPNE SCASD        \ Find CATCH stack frame
   0= IF   8 [EDI] EAX MOV              \ Get pushed ESI (user pointer)
   EAX 32 [EBX] MOV   THEN              \ Replace ESI in context record so THROW will work correctly
   THROWBACK [EDX] EAX LEA              \ CStuff THROWBACK addr in place of EIP in context record
   EAX 52 [EBX] MOV                     \ Stuff THROWBACK in place of EIP
   EDI POP   ESI POP   EBX POP          \ Restore registers
   EAX EAX XOR   RET   END-CODE         \ Return 0 result

( *) 1+ EQU 'ORIGIN

CREATE SA   <EXCEPTION> ORIGIN + , 0 , $40 , \ struct sigaction

| : /EXCEPTION ( -- )
   ORIGIN 'ORIGIN +ORIGIN !     \ Stuff ORIGIN address into literal
   4 SA 0 sigaction DROP        \ SIGILL - Illegal instruction
   8 SA 0 sigaction DROP        \ SIGFPE - Arithmetic exception
   10 SA 0 sigaction DROP       \ SIGBUS - Bus error
   11 SA 0 sigaction DROP       \ SIGSEGV - Seg fault
   2 SA 0 sigaction DROP        \ SIGINT - User interrupt (Ctrl-C)
;


\\ =====================================================================

_STRUCT_UCONTEXT
{
        int                     uc_onstack;     // +0
        __darwin_sigset_t       uc_sigmask;     // +4
        _STRUCT_SIGALTSTACK     uc_stack;       // +8
        _STRUCT_UCONTEXT        *uc_link;       // +20
        __darwin_size_t         uc_mcsize;      // +24
        _STRUCT_MCONTEXT        *uc_mcontext;   // +28
};

_STRUCT_SIGALTSTACK
{
        void            *ss_sp;
        __darwin_size_t ss_size;
        int             ss_flags;
};


_STRUCT_MCONTEXT32
{
        _STRUCT_X86_EXCEPTION_STATE32   __es;   // +0
        _STRUCT_X86_THREAD_STATE32      __ss;   // +12
        _STRUCT_X86_FLOAT_STATE32       __fs;   // +76
};

_STRUCT_X86_EXCEPTION_STATE32
{
        __uint16_t      __trapno;               // +0
        __uint16_t      __cpu;                  // +2
        __uint32_t      __err;                  // +4
        __uint32_t      __faultvaddr;           // +8
};

_STRUCT_X86_THREAD_STATE32
{
    unsigned int        __eax;                  // +12
    unsigned int        __ebx;                  // +16
    unsigned int        __ecx;                  // +20
    unsigned int        __edx;                  // +24
    unsigned int        __edi;                  // +28
    unsigned int        __esi;                  // +32
    unsigned int        __ebp;                  // +36
    unsigned int        __esp;                  // +40
    unsigned int        __ss;                   // +44
    unsigned int        __eflags;               // +48
    unsigned int        __eip;                  // +52
    unsigned int        __cs;                   // +56
    unsigned int        __ds;                   // +60
    unsigned int        __es;                   // +64
    unsigned int        __fs;                   // +68
    unsigned int        __gs;                   // +72
};


   2 # AL CMP   0= IF   -28 # EBX MOV   THEN    \ SIGINT
   8 # AL CMP   0= IF   -10 # EBX MOV   THEN    \ SIGFPE
   10 # AL CMP   0= IF   -9 # EBX MOV   THEN    \ SIGBUS
   11 # AL CMP   0= IF   -9 # EBX MOV   THEN    \ SIGSEGV

Each of them registers its exception handler, named <EXCEPTION> with the system. In Linux and macOS, this is done with multiple calls to sigaction(), one for each “signal” we’re willing to handle. In Windows, there’s a single call to AddVectoredExceptionHandler() and it’s up to the handler to determine if it will process the exception or pass it along to be handled by another member of the chain of handlers.

If the exception is for us, we receive from the OS the user context for the exception. We capture the CPU registers from that context and save it in the TRACEBACK buffer. We also save some return stack and Forth data stack (as pointed to by ESP and EBP in the CPU register context) for debug display.

We replace the EIP within the context with the address of THROWBACK and return, indicating with our return status in EAX that we have handled the exception and that the OS should resume execution from the stored user context. The OS will then return back to the user-level code using the stored context, which now will continue execution at THROWBACK, which simply does IOR_EXCEPT THROW.

THROW will also capture return and data stacks in the same way if the throw code passed to it is not IOR_EXCEPT. So no matter how you got a THROW (system exception or user code calling THROW directly), you can dump out the stacks for debug in a consistent way.

Exception Debug Display

Over on the IDE side, we extend the system’s .CATCH display to perform .THROW, which dumps the captured CPU context and stacks if the throw code is IOR_EXCEPT. All three platforms do this the same way in this shared source file:

SwiftForth/src/ide/exception.f
PACKAGE ERROR-HANDLERS

{ --------------------------------------------------------------------
Traceback display

The kernel's exception handler captures error frame info into the
TRACEBACK buffer.  This consists of three things:

1) The CPU registers from the user context passed to the exception handler
2) The return stack pointed by ESP in the user context
3) The Forth data stack pointed to by EBP in the user context

The display words below decode the error frame data. The register
contents, the top 20 items on the data stack, and the top 20 entities
on the return stack are presented.

The return stack is used to instantiate many different temporary
things in SwiftForth; each of these is decoded separately and
displayed in some pretty fashion. This means that some items on the
return stack aren't displayed in full, but a much better picture of
the overall error reference is made.

Each token to be decoded requires a known return stack address to
identify it, so each entity has a code-space label.

Local entities don't have persistent names, but local objects have a
discernible class and so the class names are presented.  Data pools
defined by R-ALLOC and R-BUF aren't displayed, but their existence and
size are.  Local variables are likewise not displayed but their
existence is reported.
-------------------------------------------------------------------- }

: DICTIONARY? ( addr -- flag )
   ORIGIN 1+   UP0 @REL [ H UP@ - ] LITERAL + @  WITHIN ;

: (ID.') ( addr -- )
   DUP (.')  DUP ORIGIN <  IF  DROP  C" <unknown>"  THEN
   ( a nfa)  COUNT 2DUP TYPE SPACE  + 1+ -
   ?DUP IF  DUP 0> IF ." +" THEN  . THEN ;

: ID.' ( addr -- )
   DUP DICTIONARY? IF  (ID.') EXIT THEN
   +ORIGIN  DUP DICTIONARY? IF  (ID.') EXIT  THEN  DROP ;

: @REG ( r -- x )
   CELLS TRACEBACK + @ ;

: @RS ( i -- x )
   #TRACE_REGS + CELLS  TRACEBACK + @ ;

: .LOCAL-OBJECT ( i1 -- i2 )
   DUP DUP 1+ @RS CELL / + >R 3 + @RS  ID.'  R> ;

: .LOCAL-OBJECTS ( i1 -- i2 )
   BEGIN
      DUP #TRACE_RSTACK U< WHILE
      DUP @RS IOR_EXCEPT <> WHILE
      .LOCAL-OBJECT
   REPEAT THEN ;

: .RSTACK-SKIP ( i1 -- i2 n )
   DUP @RS  REG_ESP CELLS TRACEBACK + @ - CELL /
   TUCK SWAP - 1- ;

: .REGX ( n -- )
   CASE
      0 OF  ." EAX "  REG_EAX  ENDOF
      1 OF  ." EBX "  REG_EBX  ENDOF
      2 OF  ." ECX "  REG_ECX  ENDOF
      3 OF  ." EDX "  REG_EDX  ENDOF
      4 OF  ." ESI "  REG_ESI  ENDOF
      5 OF  ." EDI "  REG_EDI  ENDOF
      6 OF  ." EBP "  REG_EBP  ENDOF
      7 OF  ." ESP "  REG_ESP  ENDOF
      8 OF  ." EFL "  REG_EFL  ENDOF
      9 OF  ." EIP "  REG_EIP  ENDOF
   DROP  14 SPACES  EXIT  ENDCASE
   @REG H.8  SPACE SPACE ;

: .DS ( offset -- )
   #TRACE_REGS + #TRACE_RSTACK + CELLS
   TRACEBACK + @ H.8  SPACE SPACE ;

: .RS ( i1 -- i2 )
   #TRACE_RSTACK OVER < ?EXIT
   DUP 1+ SWAP @RS
   DUP H.8 SPACE  -ORIGIN CASE
      'R-ALLOC OF ." <<r-alloc>> "  .RSTACK-SKIP .  ENDOF
      'R-BUF   OF ." <<r-buf>> "    .RSTACK-SKIP .  ENDOF
      'LSPACE  OF ." <<locals>> "   .RSTACK-SKIP .  1+  ENDOF
      'OSPACE  OF ." <<object>> "   .LOCAL-OBJECTS  2+  ENDOF
   DUP ID.'  ENDCASE ;

: .TRACEBACK1 ( -- )     \ Dump TRACEBACK info including registers
   ."  at "  REG_EIP @REG DUP H.8  SPACE  ID.'
   CR ." Registers     Dstack    esp+ Rstack  "
   0  20 0 DO
      CR  I .REGX  I .DS  DUP CELLS 4 H.0 SPACE .RS
   LOOP DROP ;

: .TRACEBACK2 ( -- )    \ Dump TRACEBACK stacks only
   CR ." Dstack    esp+ Rstack  "
   0  20 0 DO
     CR  I .DS  DUP CELLS 4 H.0 SPACE .RS
   LOOP DROP ;

{ ----------------------------------------------------------------------
Display exception frames

IOR_EXCEPT is added to the (THROW) switch to properly decode the
"signal" exception events.

.THROW extends the system .CATCH behavior to include .TRACEBACK debug
info for IOR_EXCEPT events.
---------------------------------------------------------------------- }

PUBLIC

: EXCEPTION-DECODE ( -- addr u )
   EXCEPTION# @ EXCEPTION-TYPE ;

[+SWITCH (THROW)
   IOR_EXCEPT RUN: EXCEPTION-DECODE ;
SWITCH]

: .THROW ( ior -- )
   DUP ERRORMSG  IOR_EXCEPT = IF  .TRACEBACK1  THEN ;

' .THROW IS .CATCH

END-PACKAGE

The system-specific exception decode and mapping of the CPU context registers is supplied in that platform’s exception-map.f.

This is an example of the debug output from a memory access fault in Windows:

ACCESS_VIOLATION at 0040B24F ?
Registers     Dstack    esp+ Rstack
EAX 0000724F  00310000  0000 0040D037 WORD-INTERPRETER +24
EBX FFFFFFFF  76FE6359  0004 0040D0C8 (INTERPRETING) +73
ECX 0040B24F  00310000  0008 0040D1EB INTERPRET +28
EDX 00404000  76FE6340  000C 0040D35F (QUIT) +16
ESI 0041233C  0019FFDC  0010 004052DD CATCH +62
EDI 00404000  77DA7C24  0014 00197EC4
EBP 0019FF70  00310000  0018 0019FF70
ESP 00197F20  754C483C  001C 00000000
EFL 00010202  00000000  0020 00000000
EIP 0040B24F  00000000  0024 00000000
              00310000  0028 00000000
              00000000  002C 00456FC4 SIMPLE-GUI +5
              00000000  0030 0041497C %SwiftForth\src\kernel\win32\start.f +9801
              00000000  0034 FFFF027F
              00000000  0038 FFFF4020
              00000000  003C FFFFFFFF
              00000000  0040 76EF9D82
              00000000  0044 00000000
              00000000  0048 00000000
              00000000  004C FFFF0000

In Windows, we add the display of both kinds of debug info (THROW, which shows stacks but not registers) and system exception (which shows all of it) in a dialog box that replaces the default CAUGHT behavior for Windows callbacks and threads:

SwiftForth\src\ide\win32\exception-dialog.f

This is an example of the dialog box when a system exception occurs in a Windows callback or in a thread (Forth TASK) whose behavior is assigned by ACTIVATE: