A typical Gforth error message looks like this:
in file included from *args*:1:1: in file included from xxx-err1.fs:1:9: xxx-err2.fs:3:1: error: Invalid memory address >>>bar<<< Backtrace: xxx-err2.fs:1:15: 0 $7FA610EA7F68 @ 1 $14 xxx-err2.fs:2:7: 2 $7FA610EA7FB0 foo
The line identifying the error is
xxx-err2.fs:3:1: error: Invalid memory address
It says that in file xxx-err2.fs, line 3, character 1, the
error “Invalid memory address” happened; more precisely, the
exception -9 was thrown there, and eventually was caught by Gforth’s
system exception handler (either because the program did not
catch it or caught it and rethrew it).
The next line shows the text-interpreted line where the error
happened, abd the word where the error happened is surrounded by
>>> and <<<.
Before that you find the nesting of source file includes that led to
the inclusion of xxx-err2.fs, with the outermost at the top and
the innermost at the bottom. So in this case xxx-err2.fs was
included from xxx-err1.fs, line 1, character 9, and that was
included from an OS-level command-line argument to Gforth, indicated
by *args*.
At the end of the error message you find the backtrace, actually a
return stack dump. In our example, the middle line of the return
stack dump just contains the number 20 ($14), and is not a
return address. The number 1 before the $14 is the
index of the return stack entry.
The bottom line of the backtrace shows a return address, pushed by the
call to foo; the backtrace also shows foo for this
entry, at the end, and the source location of the call
xxx-err2.fs:2:7 at the beginning of the line.
The top line of the backtrace shows a pseudo-return address, as if the
primitive @ pushed a return address. This return address was
pushed by the signal handler in order to produce this line in the
backtrace, so you know where the exception came from.
So in our example we see that bar called foo, and
foo pushed 20 on the return stack and then called @ (and
@ threw an Invalid memory address exception).
You can see the source code of the backtrace location with nt
(next backtrace entry, initially the one with index 0, i.e., the top
of the return stack), bt (previous backtrace entry, initially
the one with the biggest index, i.e., the bottom of the return stack),
or index tt (the indexed backtrace entry;
see Locating backtrace entry source).
Note that the backtrace is not perfect: We don’t know which return stack entries are return addresses (so we may get false positives).
The return stack dump represents the return stack at the time when a
specific throw was executed. In programs that make use of
catch, it is not necessarily clear which throw should be
used for the return stack dump (e.g., consider one throw that
indicates an error, which is caught, and during recovery another error
happens; which throw should be used for the stack dump?).
Gforth presents the return stack dump for the first throw after
the last executed (not returned-to) catch or nothrow;
this works well in the usual case. To get the right backtrace, you
usually want to insert nothrow or ['] false catch 2drop
after a catch if the error is not rethrown.
The gforth engine is able to do a return stack dump for throws
generated from primitives (e.g., invalid memory address, stack empty
etc.); gforth-fast is only able to do a return stack dump from
a directly called throw (including abort"). Given
an exception caused by a primitive in gforth-fast, you will
typically see no return stack dump at all; however, if the exception
is caught by catch (e.g., for restoring some state), and then
thrown again, the return stack dump will be for the first such
throw.
gforth-fast also does not attempt to differentiate between
division by zero and division overflow, because that costs time in every
division.