Skip to content

Return stack backtraces and debugging#62

Open
mkobetic wants to merge 3 commits intomainfrom
backtrace
Open

Return stack backtraces and debugging#62
mkobetic wants to merge 3 commits intomainfrom
backtrace

Conversation

@mkobetic
Copy link
Owner

@mkobetic mkobetic commented Mar 5, 2026

Return stack dump and backtrace

Two new words .rs and .bt to support dumping the return stack.

0  > 0 .rs
400018F0 400017A0 4000288C 0 4004059C 40001718 40001620 0  ok
0  > 0 .bt
400018E4+3 interpret+14 catch+10 00000000 4004059C quit+18 warm+20 00000000  ok

Both take a number of top elements to skip; this is useful when the word is invoked in a context that we want to elide from the dump (e.g. debugger). .rs is a simple dump of the cell values with no interpretation. .bt attempts to interpret the cells as XTs, the +x suffix indicates that the address is x cells after the XT field, which is normally the case for colon word IP addresses on the return stack.

Debugger

To support debugging of colon words (which is somewhat unpleasant in GDB), the interpreter is modified to allow interrupting the normal COLON word interpretation cycle after each word. This is achieved by designating register r5 as DEBUG register and checking in each cycle if its value is 0. If not it is expected to point to a debug routine that should run at the point of interruption.

One of the main goals was to allow writing the debugger routines/words in Forth for both convenience and portability. Consequently it is critical that the debug word is executed normally (without further interruptions). Therefore the DEBUG register is cleared when entering the debug word and restored when the debug word exits (exitd). When the debug word exits the interrupted word proceeds being executed.

To enter the debugging regime, the word break sets up the DEBUG register which causes the interpreter to be interrupted in the following cycle (before executing next word). The debug word should normally send debugging information back to the terminal emulator (the parameter stack state and backtrace) and then wait for input from the operator. Several different actions can be available, the fundamental two are "step" and "continue".

Step simply leaves the DEBUG hook in place and resumes interpreter for single cycle and stops again. Continue instead clears the debug hook so the interpreter resumes normal execution until it hits the break word again.
The DEBUG hook is controlled through the USER variable debug.next, which determines the next debug action by being the source of value to restore into the DEBUG register when the debug word exits. Continue sets it to 0, which means the DEBUG register will be cleared when interpreter resumes.

amforth-shell was extended to present the stack state sent by the debugger and to relay user input back to the debugger. It also loads symbols from the symbol table file and re-interprets raw addresses from the debugger; this allows interpreting NONAME word addresses (e.g. the XT_R_WORD_INTERPRET in the example below) and also addresses from other memory regions (e.g. RAM_upper_datastack below).

Here's a transcript of a short debugging session, the fib word has a break at the top of it so it will halt on each entry into the word:

: fib 
    break
    dup 2 <= if drop 1 exit then 
    dup 1- recurse swap 1- 1- recurse +
;

The stacks are presented each on its own line, with the stack label and current depth, e.g. PS(3):, RS(10): , followed by the list of top 10 values. If there are more than 10 values the list is finished with ....
The command prompt lists possible actions, the chosen action is shown as well.

> 5 fib .
PS(2): 5 5
RS(9): fib+2 XT_R_WORD_INTERPRET+3 interpret+14 catch+10 0 RAM_upper_datastack-12 quit+18 warm+20 0
(s)tep | (n)ext | (r)return | (c)ontinue ? c
PS(3): 4 5 5
RS(10): fib+2 fib+13 XT_R_WORD_INTERPRET+3 interpret+14 catch+10 0 RAM_upper_datastack-12 quit+18 warm+20 0
(s)tep | (n)ext | (r)return | (c)ontinue ? c
PS(4): 3 4 5 5
RS(11): fib+2 fib+13 fib+13 XT_R_WORD_INTERPRET+3 interpret+14 catch+10 0 RAM_upper_datastack-12 quit+18 warm+20 ... 
(s)tep | (n)ext | (r)return | (c)ontinue ? r
PS(4): 2 4 5 5
RS(10): fib+13 fib+13 XT_R_WORD_INTERPRET+3 interpret+14 catch+10 0 RAM_upper_datastack-12 quit+18 warm+20 0
(s)tep | (n)ext | (r)return | (c)ontinue ? r
PS(3): 3 5 5
RS(9): fib+13 XT_R_WORD_INTERPRET+3 interpret+14 catch+10 0 RAM_upper_datastack-12 quit+18 warm+20 0
(s)tep | (n)ext | (r)return | (c)ontinue ? c
PS(3): 3 3 5
RS(10): fib+2 fib+17 XT_R_WORD_INTERPRET+3 interpret+14 catch+10 0 RAM_upper_datastack-12 quit+18 warm+20 0
(s)tep | (n)ext | (r)return | (c)ontinue ? r
PS(3): 2 3 5
RS(9): fib+17 XT_R_WORD_INTERPRET+3 interpret+14 catch+10 0 RAM_upper_datastack-12 quit+18 warm+20 0
(s)tep | (n)ext | (r)return | (c)ontinue ? c
5 ok
>

@mkobetic mkobetic force-pushed the backtrace branch 3 times, most recently from 41c9229 to 2b9f030 Compare March 11, 2026 21:30
@mkobetic mkobetic changed the title add support for return stack backtraces Return stack backtraces and debugging Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant