Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
build/
dist/
/toolchain
__pycache__
**/words/build-info.s
.DS_Store

146 changes: 146 additions & 0 deletions arm/dev/gdb_backtrace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import gdb
from gdb_shared import *
from gdb.unwinder import Unwinder, FrameId


class ForthUnwinder(Unwinder):
"""Unwinder for Forth return stack.

GDB uses the unwinder to interpret the return stack. It seeds the process with a first pending_frame
that includes the state of all registers, the PC is set to whatever the current PC value is.
SP points at the top of the return stack.
The unwinder then builds and return unwind_info for the next pending frame (in the __call__ method).
The __call__ method is called repeatedly until a None is returned which ends the backtracce building process.
"""

def __init__(self):
super().__init__("ForthUnwinder")
self.first_frame_done = False

def __call__(self, pending_frame):
try:
sp = pending_frame.read_register("sp")
pc = pending_frame.read_register("pc")

# Stop if we hit bottom of the stack
if not (RAM_lower_returnstack <= int(sp) < RAM_upper_returnstack):
return None

if self.first_frame_done:
sp_ptr = sp.cast(gdb.lookup_type("unsigned int").pointer())
ret_addr = sp_ptr.dereference()
else:
# Adjust SP for first frame (FORTHIP effectively extends RSP by one slot)
sp = gdb.Value(int(sp) - 4).cast(sp.type)
ret_addr = pending_frame.read_register("r9")
self.first_frame_done = True

# Validate the return address
ret_addr_int = int(ret_addr)
if not (FlashStart <= ret_addr_int < FlashEnd):
return None

# Create frame ID: use SP (as identity) and ret_addr (as code address)
frame_id = FrameId(int(sp), ret_addr_int)
unwind_info = pending_frame.create_unwind_info(frame_id)

# Next frame's SP is current SP + 4 (pop one return address)
new_sp = gdb.Value(int(sp) + 4).cast(sp.type)

# Next frame's PC is the return address we just popped
unwind_info.add_saved_register("pc", ret_addr)
unwind_info.add_saved_register("sp", new_sp)

return unwind_info
except Exception as e:
print(f"[ForthUnwinder] Exception: {e}")
import traceback
traceback.print_exc()
return None

# Register the unwinder
gdb.unwinder.register_unwinder(None, ForthUnwinder(), replace=False)

# Keeping the code below in case we do need FrameDecorators later.
# This shows how to put it together.

# Frame decorator to enhance Forth frame information
# class ForthFrameDecorator(gdb.FrameDecorator.FrameDecorator):
# """Decorator to enhance Forth frame display with symbol information."""

# def __init__(self, frame):
# super().__init__(frame)

# def function(self):
# """Get function name from symbol."""
# try:
# frame = self.inferior_frame()
# if frame is None:
# return None
# pc = frame.pc()
# # Look up the symbol at this address
# try:
# block = gdb.block_for_pc(pc)
# if block and block.function:
# return block.function.name
# except:
# pass
# # Try to get just the symbol name
# try:
# result = gdb.execute(f"info symbol 0x{pc:x}", to_string=True).strip()
# if result and "not in any" not in result:
# # Extract just the symbol name (before any +offset)
# parts = result.split()
# if parts:
# return parts[0]
# except:
# pass
# except:
# pass
# return None

# def filename(self):
# """Get source filename if available."""
# try:
# frame = self.inferior_frame()
# if frame is None:
# return None
# pc = frame.pc()
# sal = gdb.find_pc_line(pc)
# # Only return if we have valid debug info
# if sal and sal.symtab and sal.line > 0:
# return sal.symtab.filename
# except:
# pass
# return None

# def line(self):
# """Get source line number if available."""
# try:
# frame = self.inferior_frame()
# if frame is None:
# return None
# pc = frame.pc()
# sal = gdb.find_pc_line(pc)
# # Only return if we have valid debug info
# if sal and sal.line > 0:
# return sal.line
# except:
# pass
# return None

# class ForthFrameFilter:
# """Filter to apply ForthFrameDecorator to all frames."""

# def __init__(self):
# self.name = "ForthFrameFilter"
# self.priority = 100
# self.enabled = True
# gdb.frame_filters[self.name] = self

# def filter(self, frame_iter):
# # Wrap each frame with our decorator
# for frame in frame_iter:
# yield ForthFrameDecorator(frame)

# ForthFrameFilter()
26 changes: 26 additions & 0 deletions arm/dev/gdb_shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import gdb
import sys

# Append the GDB search directory to Python module search path
# so that the dev/ files can be imported as modules.
# `info directories` returns something like:
# Source directories searched: /Users/martin/forth/amforth/arm/mcu/lm4f120/../../dev:$cdir:$cwd
# Grab the first directory in the list and append it to sys.path.
# Note that this file still has to be sourced explicitly.
sys.path.append(gdb.execute("show directories", to_string=True).split(':')[1].strip())

def get_sym_val(name, default):
try:
return int(gdb.parse_and_eval(f"&{name}"))
except gdb.error:
return default

FlashStart = get_sym_val("flash.low", 0x00000000)
FlashEnd = get_sym_val("FlashEnd", 0x00040000)
RAM_lower_datastack = get_sym_val("RAM_lower_datastack", 0x20000000)
RAM_upper_datastack = get_sym_val("RAM_upper_datastack", 0x20000080)
RAM_lower_returnstack = get_sym_val("RAM_lower_returnstack", 0x20000080)
RAM_upper_returnstack = get_sym_val("RAM_upper_returnstack", 0x20000100)
RAM_lower_userarea = get_sym_val("RAM_lower_userarea", 0x20000100)
RAM_upper_userarea = get_sym_val("RAM_upper_userarea", 0x20000188)

130 changes: 80 additions & 50 deletions arm/dev/gdb-amforth.py → arm/dev/gdb_tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,7 @@
# Loaded by tui-full.gdb

import gdb

# TODO: we should be able to read these from the symbol table
# FIXME: These are now wrong with amforth32.ld in the picture
FlashStart = 0x00004000
FlashEnd = 0x00040000
RAM_lower_datastack = 0x20000000
RAM_upper_datastack = 0x20000080
RAM_lower_returnstack = 0x20000080
RAM_upper_returnstack = 0x20000100
RAM_lower_userarea = 0x20000100
RAM_upper_userarea = 0x20000188
from gdb_shared import *

# GdbCommandWindow can be used to define a TUI window
# that invokes a GDB command to produce its contents.
Expand All @@ -35,18 +25,18 @@ def render(self):
return
self._tui_window.write(self.get_contents(), True)

class ForthParameterStack(GdbCommandWindow):
title = "Parameter Stack"
gdb_command = ".s"
# class ForthParameterStack(GdbCommandWindow):
# title = "Parameter Stack"
# gdb_command = ".s"

class ForthReturnStack(GdbCommandWindow):
title = "Return Stack"
gdb_command = ".r"
# class ForthReturnStack(GdbCommandWindow):
# title = "Return Stack"
# gdb_command = ".r"

def value(val):
# If val is in the flash code range, treat it as address
if FlashStart <= val and val < FlashEnd:
return val.format_string(format="a")
return address(val)
dec = val.format_string(format="d")
hex = val.format_string(format="x")
if 0 <= val and val <= 0xFFFF:
Expand All @@ -60,6 +50,9 @@ def value(val):
else:
return f"{dec} {hex}"

def address(val):
return val.format_string(format="a")

# ForthRegisterWindow is a custom register view
# showing registers based on what they are used for in AmForth.
class ForthRegisterWindow:
Expand Down Expand Up @@ -118,7 +111,7 @@ def get_contents(self):
self.addres_register(frame, "r10", "UP"),
self.value_register(frame, "r11", "RLINDEX"),
self.value_register(frame, "r12", "RLLIMIT"),
self.addres_register(frame, "sp"),
self.addres_register(frame, "sp", "RSP"),
self.addres_register(frame, "lr"),
self.addres_register(frame, "pc"),
# xPSR on Cortex-M3 is named xpsr
Expand All @@ -136,42 +129,79 @@ def render(self):
self._tui_window.write(contents, True)

# ForthParameterStack shows the contents of the PSP
# class ForthParameterStack:

# def __init__(self, tui_window):
# self._tui_window = tui_window
# tui_window.title = "Forth Parameter Stack"
# gdb.events.before_prompt.connect(self.render)

# def get_contents(self):
# frame = gdb.selected_frame()
# if frame is None:
# return "no frame selected"
# tos = frame.read_register("r6")
# # TODO: need to detect when stack is empty
# lines = [ f"r6/TOS:\t\t{value(tos)}" ]
# psp = frame.read_register("r7")
# # cast psp from int to int* so that we can dereference it
# psp = psp.cast(psp.type.pointer())
# while psp < RAM_upper_datastack:
# addr = psp.format_string(format="x")
# lines.append(f"{addr}:\t{value(psp.dereference())}")
# psp += 1
# return "\n".join(lines)

# def render(self):
# if not self._tui_window.is_valid():
# return
# try:
# contents = self.get_contents()
# except gdb.error as exc:
# contents = str(exc)
# self._tui_window.write(contents, True)
class ForthParameterStack:

def __init__(self, tui_window):
self._tui_window = tui_window
tui_window.title = "Forth Parameter Stack"
gdb.events.before_prompt.connect(self.render)

def get_contents(self):
frame = gdb.selected_frame()
if frame is None:
return "no frame selected"
tos = frame.read_register("r6")
# TODO: need to detect when stack is empty
lines = [ f"r6/TOS:\t\t{value(tos)}" ]
psp = frame.read_register("r7")
# cast psp from int to int* so that we can dereference it
psp = psp.cast(psp.type.pointer())
while psp < RAM_upper_datastack:
addr = psp.format_string(format="x")
lines.append(f"{addr}:\t{value(psp.dereference())}")
psp += 1
return "\n".join(lines)

def render(self):
if not self._tui_window.is_valid():
return
try:
contents = self.get_contents()
except gdb.error as exc:
contents = str(exc)
self._tui_window.write(contents, True)

# ForthReturnStack shows the contents of the RSP
class ForthReturnStack:

def __init__(self, tui_window):
self._tui_window = tui_window
tui_window.title = "Forth Return Stack"
gdb.events.before_prompt.connect(self.render)

def get_contents(self):
frame = gdb.selected_frame()
if frame is None:
return "no frame selected"
w = frame.read_register("r8") # FORTHW
lines = [ f"r8/FORTHW:\t{address(w)}" ]
ip = frame.read_register("r9") # FORTHIP
lines.append(f"r9/FORTHIP:\t{address(ip)}")
rsp = frame.read_register("sp")
# cast rsp from int to int* so that we can dereference it
rsp = rsp.cast(rsp.type.pointer())
while rsp < RAM_upper_returnstack:
addr = rsp.format_string(format="x")
lines.append(f"{addr}:\t{gdb.format_address(int(rsp.dereference()))}")
rsp += 1
return "\n".join(lines)

def render(self):
if not self._tui_window.is_valid():
return
try:
contents = self.get_contents()
except gdb.error as exc:
contents = str(exc)
self._tui_window.write(contents, True)


gdb.register_window_type("fps", ForthParameterStack)
gdb.register_window_type("frs", ForthReturnStack)
gdb.register_window_type("fregs", ForthRegisterWindow)



# GDB Python API Notes
#
# To read memory: gdb.selected_inferior().read_memory(addr, length)
Expand Down
4 changes: 3 additions & 1 deletion arm/dev/tui-full.gdb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ source amforth.gdb
# This requires Python enabled GDB and the correct version of Python installed on the system.
# ref: https://undo.io/resources/enhance-gdb-with-tui/
# Make sure the sourced files are on GDB search path
source gdb-amforth.py
source gdb_shared.py
source gdb_backtrace.py
source gdb_tui.py
tui new-layout forth {-horizontal { {-horizontal src 2 asm 3 } 1 status 0 cmd 1 } 3 { fregs 2 fps 1 frs 1 } 1 } 1

# Enable the forth layout and set focus on the command window
Expand Down
Loading