optText) {
+ g.setFont(smallFont);
+
+ boolean textAbove = optText.isPresent();
+ String text = optText.orElseGet(() -> String.format("0x%08x", addr));
+
+ Insets insets = getInsets();
+ int y = addrToY(addr);
+ Rectangle2D bounds = g.getFontMetrics().getStringBounds(text, g);
+ int textWidth = (int) Math.ceil(bounds.getWidth());
+ if (textWidth + stackLPad > stackLeft) {
+ stackLeft = textWidth + stackLPad;
+ update();
+ }
+ int left = insets.left + stackLeft - stackLPad - textWidth;
+ g.setColor(Color.BLACK);
+ g.drawString(text, left, y + (int) Math.ceil(textAbove
+ ? -(bounds.getHeight() + bounds.getY())
+ : -bounds.getY()));
+
+ g.drawLine(left, y, insets.left + stackLeft, y);
+ Polygon arrowHead = new Polygon();
+ arrowHead.addPoint(insets.left + stackLeft, y);
+ arrowHead.addPoint(insets.left + stackLeft - 6, y - 3);
+ arrowHead.addPoint(insets.left + stackLeft - 6, y + 3);
+ g.fillPolygon(arrowHead);
+ }
+
+ private void drawFrameSize(Graphics2D g, Frame frame) {
+ AffineTransform transform = g.getTransform();
+ g.setFont(smallFont);
+
+ Insets insets = getInsets();
+ int mid = addrToY(frame.getBase()) - (frame.getBase() - frame.getTop()) * scale / 2;
+ String text = String.format("s:%d", frame.getBase() - frame.getTop());
+ Rectangle2D bounds = g.getFontMetrics().getStringBounds(text, g);
+ int textHeight = (int) Math.ceil(bounds.getHeight());
+ if (textHeight > stackLPad) {
+ stackLPad = textHeight;
+ update();
+ }
+ int right = insets.left + stackLeft;
+ int textHalfWidth = (int) Math.ceil(bounds.getWidth() / 2);
+ int textDescend = (int) Math.ceil(bounds.getY() + bounds.getHeight());
+ g.rotate(Math.toRadians(-90), right, mid);
+ g.setColor(Color.WHITE);
+ int outlineRadius = 2;
+ int outlineMax = 3;
+ for (int i = -outlineRadius; i <= outlineRadius; i++) {
+ for (int j = -outlineRadius; j <= outlineRadius; j++) {
+ if (Math.abs(i) + Math.abs(j) <= outlineMax) {
+ g.drawString(text, right - textHalfWidth + i, mid - textDescend + j);
+ }
+ }
+ }
+ g.setColor(Color.BLACK);
+ g.drawString(text, right - textHalfWidth, mid - textDescend);
+
+ g.setTransform(transform);
+ }
+
+ private void drawFrameName(Graphics2D g, Frame frame, String name) {
+ AffineTransform transform = g.getTransform();
+ g.setFont(smallFont);
+
+ Insets insets = getInsets();
+ int mid = addrToY(frame.getBase()) - (frame.getBase() - frame.getTop()) * scale / 2;
+ Rectangle2D bounds = g.getFontMetrics().getStringBounds(name, g);
+ int textHeight = (int) Math.ceil(bounds.getHeight());
+ if (textHeight > stackRPad) {
+ stackRPad = textHeight;
+ update();
+ }
+ int left = insets.left + stackLeft + stackWidth;
+ int textHalfWidth = (int) Math.ceil(bounds.getWidth() / 2);
+ int textAscend = (int) Math.ceil(-bounds.getY());
+ g.rotate(Math.toRadians(-90), left, mid);
+ g.setColor(Color.BLACK);
+ g.drawString(name, left - textHalfWidth, mid + textAscend);
+
+ g.setTransform(transform);
+ }
+ }
+
+ private static class HelpDialog extends JDialog {
+ public HelpDialog() {
+ super();
+ String helpContent = """
+ Callstack Analyzer
+ The Callstack Analyzer is an additional tool for RARS which allows for
+ tracking the state of the call stack while executing a program.
+
+ User Interface
+ On the left, there are the settings. Below that, special events are being
+ logged. Below that, the stack pointer, frame pointer, and program counter
+ registers are being displayed.
+ On the right, there is a call stack diagram. Depending on the selection in
+ the settings, there may be multiple call stack diagrams to be compared by the
+ user. It is also possible to choose whether the "s0" register should be called
+ by its alternative name "fp" to clarify its function as the frame pointer. "fp"
+ will be treated as the default name from now on.
+ At the bottom, there is a control bar which is supplied by RARS itself. To
+ use the Callstack Analyzer, it is necessary to press Connect
+ before starting the program. Before starting the program again, Reset
+ should be pressed.
+
+ Functionality
+ The structure of the call stack is not directly included in the program. It
+ is only possible to employ different strategies in odrder to try reconstructing
+ the call stack as well as possible. The Callstack Analyzer therefore
+ provides multiple different strategies which each require different knowledge
+ about the running program.
+
+ Strategy: No Frames
+ required knowledge:
+
+ - current value of the "sp" register
+
+ behavior:
+
+ - The whole call stack is being displayed.
+
+ advantage:
+
+ - The program could do this analysis on its own in order to measure its
+ own stack memory usage.
+ - The program does not have to use "s0" as frame pointer.
+
+ limitations:
+
+ - This strategy cannot differentiate between multiple stack frames.
+
+
+ Strategy: Only Stack Pointer
+ required knowledge:
+
+ - every change of the "sp" register
+
+ behavior:
+
+ - Each time the "sp" register changes, a decrease is being treated as a
+ new stack frame and an increase as removing a stack frame.
+
+ advantages:
+
+ - The program does not have to use "s0" as frame pointer.
+ - Nevertheless, all stack frames are mostly recognizable
+
+ limitations:
+
+ - If a function chooses to increase the size of its frame, this will be
+ treated as an entirely new frame. (This behavior is not usual though.)
+ - The program itself could not do this analysis on its own.
+
+
+ Strategy: Call And Return
+ required knowledge:
+
+ - current value of the "sp" register and the "fp" register
+ - value of the "sp" register and the "fp" register each time "call" or
+ "ret" is being executed.
+
+ behavior:
+
+ - top stack frame is always being updated with "sp" and "fp"
+ - "call" creates new stack frame, "ret" removes the top stack frame
+
+ advantages:
+
+ - Each function call corresponds with exactly one stack frame.
+
+ limitations:
+
+ - The program itself could not do this analysis on its own.
+
+
+ Strategy: Stack Unwinding
+ required knowledge:
+
+ - current value of the "sp" register and the "fp" register
+ - position of the saved "fp" register in the stack frame
+ - content of the stack memory
+
+ behavior:
+
+ - The algorithm starts at the current stack frame as specified by "fp" and
+ retrieves the previous value of "fp", the pointer to the previous stack
+ frame.
+ - Using this principle, for each frame, the previous frame is being
+ discovered until the algorithm has reached the upper end of the stack
+ memory, the first stack frame.
+ - Basically, the stack is being treated as a linked list of frames.
+
+ advantages:
+
+ - The program could do this analysis on its own. (Indeed, similar
+ approaches are being taken to create stack traces etc.)
+
+ limitations:
+
+ - The program must use "s0" as frame pointer.
+ - "fp" must be saved as the second word in the stack frame, addressable
+ via
+8(fp).
+
+
+ Example
+ The following example illustrates a simple use case. The program start at
+ _start. The function printnumbers is being called
+ which in turn calls the function printdigit multiple times.
+
+ .eqv SYS_PrintInt, 1
+ .eqv SYS_Exit, 10
+ .eqv SYS_PrintChar, 11
+
+ _start:
+ mv fp, sp
+
+ li a0, 4
+ call printnumbers
+
+ li a7, SYS_Exit
+ ecall
+
+ # void printnumbers(int n)
+ printnumbers:
+ addi sp, sp, -16
+ sw ra, 12(sp)
+ sw fp, 8(sp)
+ sw s1, 4(sp)
+ sw s2, 0(sp)
+ addi fp, sp, 16
+
+ mv s1, a0
+ li s2, 0
+
+ loop:
+ mv a0, s2
+ call printdigit
+ addi s2, s2, 1
+ blt s2, s1, loop
+
+ lw ra, 12(sp)
+ lw fp, 8(sp)
+ lw s1, 4(sp)
+ lw s2, 0(sp)
+ addi sp, sp, 16
+ ret
+
+ # void printdigit(int n)
+ printdigit:
+ addi sp, sp, -16
+ sw ra, 12(sp)
+ sw fp, 8(sp)
+ addi fp, sp, 16
+
+ addi a0, a0, '0'
+ li a7, SYS_PrintChar
+ ecall
+
+ lw ra, 12(sp)
+ lw fp, 8(sp)
+ addi sp, sp, 16
+ ret
+
+ All strategies can be used to reconstruct the call stack for this program.
+
+ """;
+ JEditorPane helpEditorPane = new JEditorPane("text/html", helpContent);
+ helpEditorPane.setEditable(false);
+ helpEditorPane.setCaretPosition(0);
+ JScrollPane helpScrollPane = new JScrollPane(helpEditorPane);
+ helpScrollPane.setPreferredSize(new Dimension(500, 400));
+ add(helpScrollPane);
+ pack();
+ }
+ }
+}
From ad349e56e4ee31a52a3cb6841b5b51221d7fc8c7 Mon Sep 17 00:00:00 2001
From: BenCrafterRED <73169588+BenCrafterRED@users.noreply.github.com>
Date: Wed, 21 Jan 2026 11:51:40 +0100
Subject: [PATCH 2/2] Fixed Callstack Analyzer GUI update
---
src/rars/tools/CallstackAnalyzer.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/rars/tools/CallstackAnalyzer.java b/src/rars/tools/CallstackAnalyzer.java
index 7e08c6ce9..e48dd83d6 100644
--- a/src/rars/tools/CallstackAnalyzer.java
+++ b/src/rars/tools/CallstackAnalyzer.java
@@ -204,7 +204,7 @@ protected void addAsObserver() {
}
@Override
- public void update(Observable resource, Object notice) {
+ public void processRISCVUpdate(Observable resource, AccessNotice notice) {
switch (notice) {
case RegisterAccessNotice regNotice -> {
if (regNotice.getAccessType() == AccessNotice.WRITE) {
@@ -291,8 +291,6 @@ private void addEvent(Event event) {
@Override
protected void updateDisplay() {
- System.out.println("update display");
-
int selectStart = eventLogArea.getSelectionStart();
int selectEnd = eventLogArea.getSelectionEnd();
boolean follow = selectStart == selectEnd && selectStart == eventLogArea.getText().length();