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: