Introducing Breakpoints window

This commit is contained in:
Guy Perfect 2020-12-21 20:42:28 -06:00
parent edab431a1f
commit d636719775
14 changed files with 879 additions and 102 deletions

View File

@ -8,26 +8,62 @@ locale {
app {
debug {
(menu) Debug
console Console
cpu CPU
memory Memory
(menu) Debug
breakpoints Breakpoints
console Console
cpu CPU
memory Memory
# VIP section
backgrounds Backgrounds
characters Characters
frame_buffers Frame buffers
objects Objects
worlds Worlds
}
file {
(menu) File
debug_mode Debug mode
exit Exit
game_mode Game mode
load_rom Load ROM...
new_window New window
(menu) File
debug_mode Debug mode
exit Exit
game_mode Game mode
load_rom Load ROM...
new_window New window
}
title {
default PVB Emulator
mixed {ctrl.number} {ctrl.filename} - {app.title.default}
number {ctrl.number} {app.title.default}
rom {ctrl.filename} - {app.title.default}
default PVB Emulator
mixed {ctrl.number} {ctrl.filename} - {app.title.default}
number {ctrl.number} {app.title.default}
rom {ctrl.filename} - {app.title.default}
}
}
# Breakpoints window
breakpoints {
address Address
condition Condition
default_name New breakpoint
delete Delete
enabled Enabled
exception Exception
execute Execute
name Name
new New
read Read
title Breakpoints
type Type
write Write
error {
badliteral_a Error:{err.position}: Unable to process address "{err.text}"
badliteral_c Error:{err.position}: Unable to process literal "{err.text}"
badoperand Error:{err.position}: Invalid operand to operator "{err.text}"
badtoken Error:{err.position}: Unrecognized token "{err.text}"
earlyeof Error:{err.position}: Unexpected end of input
invalid Error:{err.position}: Token "{err.text}" cannot appear here
nesting Error:{err.position}: Expected "{err.other}", but found "{err.text}"
unexpected Error:{err.position}: Unexpected "{err.text}"
}
}

View File

@ -1126,6 +1126,10 @@ static vbool cpuExecute(Vue *vue) {
vue->cpu.exception = 0xFF90;
}
/* An application break was requested */
if (vue->breakCode != 0)
return VUE_TRUE;
/* Common processing */
if (vue->cpu.exception == 0) {
vue->cpu.cycles += CYCLES[vue->cpu.inst.id];

View File

@ -0,0 +1,618 @@
package app;
// Java imports
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
// Project imports
import util.*;
import vue.*;
// Memory viewer and hex editor window
class BreakpointsWindow extends ChildWindow {
// Private fields
private Font font; // Display font
private ArrayList<Item> items; // List items
private int selectedIndex; // Selected list item
// UI components
private JButton btnDelete; // Delete button
private JButton btnNew; // New button
private JCheckBox chkEnabled; // Enabled check box
private JCheckBox chkException; // Exception check box
private JCheckBox chkExecute; // Execute check box
private JCheckBox chkRead; // Read check box
private JCheckBox chkWrite; // Write check box
private JPanel client; // Client area
private JLabel errAddress; // Address error text
private JLabel errCondition; // Condition error text
private JPanel lstBreakpoints; // Breakpoint list
private JPanel spacer; // List termination spacer
private JTextField txtAddress; // Address text box
private JTextField txtCondition; // Condition text box
private JTextField txtName; // Name text box
///////////////////////////////////////////////////////////////////////////
// Constants //
///////////////////////////////////////////////////////////////////////////
// Address error keys
private static final String[] KEYS_ADDRESS = {
null , // NONE
"breakpoints.error.badliteral_a", // BADLITERAL
null , // BADOPERAND
null , // BADTOKEN
"breakpoints.error.earlyeof" , // EARLYEOF
null , // INVALID
null , // NESTING
"breakpoints.error.unexpected" // UNEXPECTED
};
// Condition error keys
private static final String[] KEYS_CONDITION = {
null , // NONE
"breakpoints.error.badliteral_c", // BADLITERAL
"breakpoints.error.badoperand" , // BADOPERAND
"breakpoints.error.badtoken" , // BADTOKEN
"breakpoints.error.earlyeof" , // EARLYEOF
"breakpoints.error.invalid" , // INVALID
"breakpoints.error.nesting" , // NESTING
"breakpoints.error.unexpected" // UNEXPECTED
};
// List constraints
private static final GridBagConstraints ENABLED;
private static final GridBagConstraints NAME;
private static final GridBagConstraints SPACER;
private static final GridBagConstraints STATUS;
// Static initializer
static {
ENABLED = new GridBagConstraints();
ENABLED.anchor = GridBagConstraints.CENTER;
ENABLED.insets = new Insets(1, 2, 1, 2);
NAME = new GridBagConstraints();
NAME.fill = GridBagConstraints.HORIZONTAL;
NAME.gridwidth = GridBagConstraints.REMAINDER;
NAME.insets = new Insets(1, 0, 1, 2);
NAME.weightx = 1;
SPACER = new GridBagConstraints();
SPACER.fill = GridBagConstraints.BOTH;
SPACER.gridheight = GridBagConstraints.REMAINDER;
SPACER.gridwidth = GridBagConstraints.REMAINDER;
SPACER.weighty = 1;
STATUS = new GridBagConstraints();
STATUS.fill = GridBagConstraints.BOTH;
STATUS.insets = new Insets(0, 0, 0, 2);
}
///////////////////////////////////////////////////////////////////////////
// Classes //
///////////////////////////////////////////////////////////////////////////
// Text box commit
private interface Commit {
void run();
}
// List item
private class Item {
Breakpoint breakpoint;
JCheckBox chkEnabled;
JLabel lblName;
JLabel lblStatus;
// Constructor
Item(Breakpoint brk) {
var that = this;
breakpoint = brk;
brk.setFetch(false);
chkEnabled = new JCheckBox();
chkEnabled.setBorder(null);
chkEnabled.setFocusable(false);
chkEnabled.addActionListener(e->onEnabled(that));
parent.app.localizer.add(chkEnabled,"null","breakpoints.enabled");
lblName = new JLabel(brk.getName());
lblStatus = new JLabel("", SwingConstants.CENTER);
refresh(null, null);
}
// Update the display
void refresh(Instruction inst, Access acc) {
if (
breakpoint.getAddressError ().code != Breakpoint.NONE ||
breakpoint.getConditionError().code != Breakpoint.NONE
) lblStatus.setText("\u00d7"); // Times symbol
else if (breakpoint.any() && breakpoint.evaluate(inst, acc))
lblStatus.setText("\u2605"); // Star
else lblStatus.setText(" ");
int lineHeight = lblStatus.getPreferredSize().height;
lblStatus.setPreferredSize(new Dimension(lineHeight, lineHeight));
}
// Specify whether or not the item has focus
void setFocused(boolean focused) {
var color = focused ? SystemColor.textHighlightText :
SystemColor.windowText;
lblStatus.setForeground(color);
lblName .setForeground(color);
}
}
///////////////////////////////////////////////////////////////////////////
// Constructors //
///////////////////////////////////////////////////////////////////////////
// Default constructor
BreakpointsWindow(MainWindow parent) {
super(parent, "breakpoints.title");
// Configure instance fields
font = new Font(Util.fontFamily(new String[]
{ "Consolas", Font.MONOSPACED } ), Font.PLAIN, 14);
items = new ArrayList<Item>();
selectedIndex = -1;
// Configure client area
client = new JPanel(new BorderLayout());
client.setBackground(SystemColor.control);
client.setFocusable(true);
client.setPreferredSize(new Dimension(320, 240));
// Configure breakpoint list
lstBreakpoints = new JPanel(new GridBagLayout()) {
public void paintComponent(Graphics g) {
super.paintComponent(g);
onPaint((Graphics2D) g, getWidth(), getHeight());
}
};
lstBreakpoints.setFocusable(true);
lstBreakpoints.setBackground(SystemColor.window);
lstBreakpoints.addFocusListener(Util.onFocus(
e->lstBreakpoints.repaint(), e->lstBreakpoints.repaint()));
lstBreakpoints.addMouseListener(Util.onMouse(e->onMouseDown(e), null));
var scr = new JScrollPane(lstBreakpoints);
client.add(scr, BorderLayout.CENTER);
spacer = new JPanel(null);
spacer.setOpaque(false);
lstBreakpoints.add(spacer, SPACER);
// Configure properties pane
var props = new JPanel(new GridBagLayout());
props.setBackground(SystemColor.control);
props.addMouseListener(
Util.onMouse(e->client.requestFocus(), null));
client.add(props, BorderLayout.SOUTH);
// Configure properties
label(props, "breakpoints.name", 2);
txtName = textBox(props, false, 2, ()->onName());
//label(props, "breakpoints.enabled", 0);
//chkEnabled = checkBox(props, null, true);
chkEnabled = new JCheckBox();
chkEnabled.addActionListener(e->onEnabled(null));
label(props, "breakpoints.type", 0);
chkExecute = checkBox(props, "breakpoints.execute" , false);
chkExecute .addActionListener(e->onType(chkExecute ));
chkRead = checkBox(props, "breakpoints.read" , false);
chkRead .addActionListener(e->onType(chkRead ));
chkWrite = checkBox(props, "breakpoints.write" , false);
chkWrite .addActionListener(e->onType(chkWrite ));
chkException = checkBox(props, "breakpoints.exception", true );
chkException.addActionListener(e->onType(chkException));
label(props, "breakpoints.address", 0);
txtAddress = textBox(props, true, 0, ()->onAddress());
errAddress = error(props);
label(props, "breakpoints.condition", 0);
txtCondition = textBox(props, true, 0, ()->onCondition());
errCondition = error(props);
// Configure buttons
var buttons = new JPanel(new GridBagLayout());
var gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
props.add(buttons, gbc);
var fill = new JPanel(null);
fill.setOpaque(false);
fill.addMouseListener(Util.onMouse(e->onDebug(e), null));
gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
buttons.add(fill, gbc);
btnDelete = button(buttons, "breakpoints.delete", false);
btnDelete.setEnabled(false);
btnDelete.addActionListener(e->onDelete());
btnNew = button(buttons, "breakpoints.new", true);
btnNew.addActionListener(e->onNew());
// Configure component
setContentPane(client);
pack();
}
///////////////////////////////////////////////////////////////////////////
// Package Methods //
///////////////////////////////////////////////////////////////////////////
// Update the display
void refresh() {
var inst = new Instruction(parent.vue.read(
parent.vue.getRegister(Vue.PC, true), Vue.S32));
var acc = inst.access(parent.vue);
for (var item : items)
item.refresh(inst, acc);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
///////////////////////////////////////////////////////////////////////////
// Event Handlers //
///////////////////////////////////////////////////////////////////////////
// Address text box commit
private void onAddress() {
if (selectedIndex == -1)
return;
var item = items.get(selectedIndex);
item.breakpoint.setAddresses(txtAddress.getText());
checkErrors(item);
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// Condition text box commit
private void onCondition() {
if (selectedIndex == -1)
return;
var item = items.get(selectedIndex);
item.breakpoint.setCondition(txtCondition.getText());
checkErrors(item);
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// Debug interaction
private void onDebug(MouseEvent e) {
client.requestFocus();
if (
selectedIndex == -1 ||
e.getButton() != MouseEvent.BUTTON1 ||
(e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == 0
) return;
var brk = items.get(selectedIndex).breakpoint;
System.out.println("Address");
System.out.println(brk.getAddresses());
System.out.println(brk.debugRanges());
System.out.println("Condition");
System.out.println(brk.getCondition());
System.out.println(brk.debugTokens());
System.out.println();
}
// Delete button click
private void onDelete() {
var item = items.remove(selectedIndex);
parent.vue.remove(item.breakpoint);
lstBreakpoints.remove(item.chkEnabled);
lstBreakpoints.remove(item.lblName);
lstBreakpoints.remove(item.lblStatus);
lstBreakpoints.revalidate();
selectedIndex = -1;
select(-1);
}
// Enabled check box toggle
private void onEnabled(Item item) {
boolean enabled;
// Clicked from the properties pane
if (item == null) {
enabled = chkEnabled.isSelected();
item = items.get(selectedIndex);
item.chkEnabled.setSelected(enabled);
client.requestFocus();
}
// Clicked from the breakpoints list
else {
enabled = item.chkEnabled.isSelected();
if (items.indexOf(item) == selectedIndex)
chkEnabled.setSelected(enabled);
lstBreakpoints.requestFocus();
}
// Common processing
item.breakpoint.setEnabled(enabled);
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// List mouse press
private void onMouseDown(MouseEvent e) {
int count = items.size();
// Left clicking
if (count > 0 && e.getButton() == MouseEvent.BUTTON1) {
int newIndex = e.getY() / items.get(0).lblStatus.getHeight();
if (newIndex < count)
select(newIndex);
}
// Update layout
lstBreakpoints.requestFocus();
lstBreakpoints.repaint();
}
// Name text box commit
private void onName() {
if (selectedIndex == -1)
return;
var item = items.get(selectedIndex);
item.lblName.setText(item.breakpoint.setName(txtName.getText()));
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// New button click
private void onNew() {
var brk = parent.vue.breakpoint();
brk.setEnabled(true);
brk.setName (parent.app.localizer.get("breakpoints.default_name"));
var item = new Item(brk);
item.chkEnabled.setSelected(true);
item.lblName .setText(brk.getName());
items.add(item);
int count = items.size();
boolean first = count == 1;
lstBreakpoints.remove(spacer);
lstBreakpoints.add(item.chkEnabled, ENABLED);
lstBreakpoints.add(item.lblStatus , STATUS );
lstBreakpoints.add(item.lblName , NAME );
lstBreakpoints.add(spacer , SPACER );
select(count - 1);
lstBreakpoints.requestFocus();
lstBreakpoints.revalidate();
}
// List paint
private void onPaint(Graphics2D g, int width, int height) {
// There is no selected item
if (selectedIndex == -1)
return;
// Select the display colors
var item = items.get(selectedIndex);
if (lstBreakpoints.isFocusOwner()) {
g.setColor(SystemColor.textHighlight);
item.setFocused(true);
} else {
g.setColor(SystemColor.control);
item.setFocused(false);
}
// Draw the selection background
height = item.lblStatus.getHeight();
g.fillRect(0, selectedIndex * height, width, height);
}
// Type check box toggle
private void onType(JCheckBox chk) {
var item = items.get(selectedIndex);
var brk = item.breakpoint;
boolean selected = chk.isSelected();
// Configure controls
if (chk == chkException)
brk.setException(selected);
else if (chk == chkExecute )
brk.setExecute (selected);
else if (chk == chkRead )
brk.setRead (selected);
else if (chk == chkWrite )
brk.setWrite (selected);
// Common processing
item.refresh(null, null);
client.requestFocus();
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
///////////////////////////////////////////////////////////////////////////
// Private Methods //
///////////////////////////////////////////////////////////////////////////
// Add a button to the form
private JButton button(JPanel panel, String key, boolean last) {
var btn = new JButton();
if (key != null)
parent.app.localizer.add(btn, key);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(0, 0, 2, 2);
if (last)
gbc.gridwidth = GridBagConstraints.REMAINDER;
panel.add(btn, gbc);
return btn;
}
// Add a check box to the form
private JCheckBox checkBox(JPanel panel, String key, boolean last) {
var chk = new JCheckBox();
if (key != null)
parent.app.localizer.add(chk, key);
chk.setBorder(null);
chk.setEnabled(false);
chk.setFocusable(false);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(0, 0, 2, last ? 2 : 6);
if (last)
gbc.gridwidth = GridBagConstraints.REMAINDER;
panel.add(chk, gbc);
return chk;
}
// Check for errors in the selected breakpoint
private void checkErrors(Item item) {
var brk = item.breakpoint;
var loc = parent.app.localizer;
// Check for address errors
var err = brk.getAddressError();
if (err.code != Breakpoint.NONE) {
loc.add(errAddress, KEYS_ADDRESS[err.code]);
loc.put(errAddress, "err.position",
Integer.toString(err.position));
loc.put(errAddress, "err.text", err.text);
errAddress.setVisible(true);
} else errAddress.setVisible(false);
// Check for condition errors
err = brk.getConditionError();
if (err.code != Breakpoint.NONE) {
loc.add(errCondition, KEYS_CONDITION[err.code]);
loc.put(errCondition, "err.position",
Integer.toString(err.position));
loc.put(errCondition, "err.text", err.text);
if (err.code == Breakpoint.NESTING)
loc.put(errCondition, "err.other",
err.text.equals(")") ? "]" : ")");
errCondition.setVisible(true);
} else errCondition.setVisible(false);
}
// Add an error label to the form
private JLabel error(JPanel panel) {
var lbl = new JLabel();
lbl.setForeground(Color.red);
lbl.setVisible(false);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 1;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(0, 0, 2, 2);
panel.add(lbl, gbc);
return lbl;
}
// Add a label to the form
private void label(JPanel panel, String key, int top) {
var lbl = new JLabel();
if (key != null)
parent.app.localizer.add(lbl, key);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(top, 2, 2, 4);
panel.add(lbl, gbc);
}
// Select a particular list item
private void select(int newIndex) {
boolean enabled = newIndex != -1;
// The existing selection was de-selected
if (selectedIndex != -1 && newIndex != selectedIndex)
items.get(selectedIndex).setFocused(false);
// Selecting a new list item
if (newIndex != -1)
items.get(newIndex).setFocused(true);
// Configure instance fields
selectedIndex = newIndex;
// Update control properties
if (enabled) {
var brk = items.get(selectedIndex).breakpoint;
chkEnabled .setSelected(brk.isEnabled ());
chkExecute .setSelected(brk.getExecute ());
chkRead .setSelected(brk.getRead ());
chkWrite .setSelected(brk.getWrite ());
chkException.setSelected(brk.getException());
txtAddress .setText (brk.getAddresses());
txtCondition.setText (brk.getCondition());
txtName .setText (brk.getName ());
}
// Clear control properties
else {
chkEnabled .setSelected(false);
chkExecute .setSelected(false);
chkRead .setSelected(false);
chkWrite .setSelected(false);
chkException.setSelected(false);
errAddress .setText(""); errAddress .setVisible(false);
errCondition.setText(""); errCondition.setVisible(false);
txtAddress .setText("");
txtCondition.setText("");
txtName .setText("");
}
// Enable or disable controls
btnDelete .setEnabled(enabled);
chkEnabled .setEnabled(enabled);
chkExecute .setEnabled(enabled);
chkException.setEnabled(enabled);
chkRead .setEnabled(enabled);
chkWrite .setEnabled(enabled);
txtAddress .setEnabled(enabled);
txtCondition.setEnabled(enabled);
txtName .setEnabled(enabled);
// Common processing
if (selectedIndex != -1)
checkErrors(items.get(selectedIndex));
lstBreakpoints.repaint();
}
// Add a text box to the form
private JTextField textBox(JPanel panel, boolean mono, int top,
Commit handler) {
var txt = new JTextField();
txt.setEnabled(false);
if (mono)
txt.setFont(font);
txt.addActionListener(e->client.requestFocus());
txt.addFocusListener (Util.onFocus(null, e->handler.run()));
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(top, 0, 2, 2);
gbc.weightx = 1;
panel.add(txt, gbc);
return txt;
}
}

View File

@ -28,13 +28,14 @@ class MainWindow extends JFrame {
private File romFile; // Currently loaded ROM file
// UI components
private JPanel client; // Common client container
private ConsoleWindow console; // Console window
private CPUWindow cpu; // CPU window
private JDesktopPane desktop; // Container for child windows
private MemoryWindow memory; // Memory window
private JMenu mnuDebug; // Debug menu
private JPanel video; // Video output
private BreakpointsWindow breakpoints; // Breakpoints window
private JPanel client; // Common client container
private ConsoleWindow console; // Console window
private CPUWindow cpu; // CPU window
private JDesktopPane desktop; // Container for child windows
private MemoryWindow memory; // Memory window
private JMenu mnuDebug; // Debug menu
private JPanel video; // Video output
private JMenuItem mnuFileDebugMode; // File -> Debug mode
private JMenuItem mnuFileGameMode; // File -> Game mode
@ -94,9 +95,10 @@ class MainWindow extends JFrame {
// Configure child windows
desktop = new JDesktopPane();
desktop.setBackground(SystemColor.controlShadow);
desktop.add(console = new ConsoleWindow(this));
desktop.add(cpu = new CPUWindow (this));
desktop.add(memory = new MemoryWindow (this));
desktop.add(breakpoints = new BreakpointsWindow(this));
desktop.add(console = new ConsoleWindow (this));
desktop.add(cpu = new CPUWindow (this));
desktop.add(memory = new MemoryWindow (this));
// Configure internal breakpoints
brkStep = vue.breakpoint();
@ -130,20 +132,52 @@ class MainWindow extends JFrame {
loc.add(mnuDebug, "app.debug.(menu)");
mnuDebug.setVisible(false);
var mnuDebugBreakpoints = new JMenuItem();
loc.add(mnuDebugBreakpoints, "app.debug.breakpoints");
mnuDebugBreakpoints.addActionListener(e->breakpoints.setVisible(true));
mnuDebug.add(mnuDebugBreakpoints);
var mnuDebugConsole = new JMenuItem();
loc.add(mnuDebugConsole, "app.debug.console");
mnuDebugConsole.addActionListener(e->console.setVisible(true));
mnuDebug.add(mnuDebugConsole);
var mnuDebugCPU = new JMenuItem();
loc.add(mnuDebugCPU, "app.debug.cpu");
mnuDebugCPU.addActionListener(e->cpu.setVisible(true));
mnuDebug.add(mnuDebugCPU);
var mnuDebugMemory = new JMenuItem();
loc.add(mnuDebugMemory, "app.debug.memory");
mnuDebugMemory.addActionListener(e->memory.setVisible(true));
mnuDebug.add(mnuDebugMemory);
var mnuDebugCPU = new JMenuItem();
loc.add(mnuDebugCPU, "app.debug.cpu");
mnuDebugCPU.addActionListener(e->cpu.setVisible(true));
mnuDebug.add(mnuDebugCPU);
mnuDebug.addSeparator();
var mnuDebugBackgrounds = new JMenuItem();
mnuDebugBackgrounds.setEnabled(false);
loc.add(mnuDebugBackgrounds, "app.debug.backgrounds");
mnuDebug.add(mnuDebugBackgrounds);
var mnuDebugCharacters = new JMenuItem();
mnuDebugCharacters.setEnabled(false);
loc.add(mnuDebugCharacters, "app.debug.characters");
mnuDebug.add(mnuDebugCharacters);
var mnuDebugFrameBuffers = new JMenuItem();
mnuDebugFrameBuffers.setEnabled(false);
loc.add(mnuDebugFrameBuffers, "app.debug.frame_buffers");
mnuDebug.add(mnuDebugFrameBuffers);
var mnuDebugObjects = new JMenuItem();
mnuDebugObjects.setEnabled(false);
loc.add(mnuDebugObjects, "app.debug.objects");
mnuDebug.add(mnuDebugObjects);
var mnuDebugWorlds = new JMenuItem();
mnuDebugWorlds.setEnabled(false);
loc.add(mnuDebugWorlds, "app.debug.worlds");
mnuDebug.add(mnuDebugWorlds);
return mnuDebug;
}
@ -192,8 +226,9 @@ class MainWindow extends JFrame {
// Refresh all debug views
void refreshDebug(boolean seekToPC) {
cpu .refresh(seekToPC);
memory.refresh();
breakpoints.refresh();
cpu .refresh(seekToPC);
memory .refresh();
}
// A window has been added to or removed from the program state

View File

@ -101,7 +101,8 @@ class Register {
parent.add(gbc, txtValue);
// Value changed
txtValue.addActionListener(e->{
txtValue.addActionListener(e->parent.requestFocus());
txtValue.addFocusListener(Util.onFocus(null, e->{
String text = txtValue.getText();
int val = value;
try { switch (mode) {
@ -112,7 +113,7 @@ class Register {
Float.floatToIntBits(Float.parseFloat(text) ); break;
}} catch (Exception x) { }
setValue(val);
});
}));
// Expansion controls
switch (type) {
@ -208,10 +209,9 @@ class Register {
// Value text box
var txt = new JTextField();
txt.addActionListener(e->{
txt.setText((String) txt.getClientProperty("text"));
parent.requestFocus();
});
txt.addActionListener(e->parent.requestFocus());
txt.addFocusListener(Util.onFocus(null,
e->txt.setText((String) txt.getClientProperty("text"))));
txt.putClientProperty("index",
x == 0 ? Vue.JUMP_FROM : Vue.JUMP_TO);
txt.setBorder(null);
@ -479,13 +479,13 @@ class Register {
controls.add(ctrl);
// Event handlers
ctrl.addActionListener(e->{
parent.requestFocus();
ctrl.addActionListener(e->parent.requestFocus());
ctrl.addFocusListener(Util.onFocus(null, e->{
int val = value >> bit & mask;
try { val = Integer.parseInt(ctrl.getText(), hex ? 16 : 10); }
catch (Exception x) { }
setValue(value & ~(mask << bit) | (val & mask) << bit);
});
}));
// Configure expansion area
var label = new JLabel(name);

View File

@ -265,6 +265,7 @@ public class Localizer {
"Required key not found: 'locale.id'");
if (!ret.containsKey("locale.name")) throw new RuntimeException(
"Required key not found: 'locale.name'");
ret.put("null", "");
return new Locale(ret);
}

View File

@ -533,10 +533,11 @@ static int32_t brkOnBreakpoint(Vue *vue, int32_t breakType) {
}
// Check all breakpoints
int32_t fetch = breakType == BRK_READ && vue->cpu.fetch != -1 ? 1 : 0;
for (int x = 0; x < core->numBreakpoints; x++) {
Breakpoint *brk = &core->breakpoints[x];
if (
brk->enabled &&
brk->enabled && (brk->fetch || !fetch) &&
(breakType == 0 || (breakType & brk->hooks) != 0) &&
(!ranged || brkInRange(brk, start, end)) &&
(brk->numTokens == 0 || brkEvaluate(core, brk) != 0)

View File

@ -13,6 +13,7 @@ public class Breakpoint {
private Error conditionError; // Condition parsing error
private int dataType; // Condition evaluation data type
private int depth; // Stack size for condition evaluation
private boolean fetch; // Breakpoint traps fetch reads
private int hooks; // Applied breakpoint types
private boolean isEnabled; // Breakpoint is active
private String name; // Display name
@ -452,6 +453,7 @@ public class Breakpoint {
addresses = "";
condition = "";
conditionError = new Error(NONE, 0, "");
fetch = true;
name = "";
ranges = new int[0][];
tokens = new Token[0];
@ -464,36 +466,54 @@ public class Breakpoint {
// Public Methods //
///////////////////////////////////////////////////////////////////////////
// Determine whether the breakpoint applies to any break scenarios
public boolean any() {
return hooks != 0;
}
// Evaluate the condition against the emulation context
public boolean evaluate() {
return evaluate(null, null);
}
// Evaluate the condition against the emulation context
public boolean evaluate(Instruction inst, Access acc) {
// Error checking
if (vue == null)
if (vue == null || !isEnabled || hooks == 0)
return false;
// Retrieve state objects
Access acc = vue.getAccess ();
int breakType = vue.getBreakType ();
Instruction inst = vue.getInstruction();
// Working variables
boolean applies = false;
int pc = vue.getRegister(Vue.PC, true);
// Error checking
if (!appliesTo(breakType))
return false;
// Resolve state objects
if (inst == null)
inst = new Instruction(vue.read(pc, Vue.S32));
if (acc == null)
acc = inst.access(vue);
// Check address ranges for execute
if (breakType == EXECUTE) {
int pc = vue.getRegister(Vue.PC, true);
if (!inRange(pc, pc + inst.size - 1))
return false;
// Check whether the breakpoint applies to the current state
if ((hooks & EXCEPTION) != 0)
applies = applies || vue.getExceptionCode() != 0;
if ((hooks & EXECUTE) != 0)
applies = applies || inRange(pc, pc + inst.size - 1);
if ((hooks & (READ | WRITE)) != 0 && acc != null) switch (inst.id) {
case Vue.CAXI :
case Vue.IN_B : case Vue.IN_H : case Vue.IN_W :
case Vue.LD_B : case Vue.LD_H : case Vue.LD_W :
case Vue.OUT_B: case Vue.OUT_H: case Vue.OUT_W:
case Vue.ST_B : case Vue.ST_H : case Vue.ST_W :
applies = applies || inRange(acc.address,
acc.address + JavaVue.TYPE_SIZES[acc.type] - 1);
}
// Check address ranges for read and write
else if (breakType == READ || breakType == WRITE &&
!inRange(acc.address, acc.address+JavaVue.TYPE_SIZES[acc.type]-1))
if (!applies)
return false;
// Evaluate the condition
return isTrue(new int[depth], breakType, inst, acc);
if (acc == null)
acc = new Access();
return isTrue(new int[depth], 0, inst, acc);
}
// Retrieve the most recent address text
@ -526,6 +546,11 @@ public class Breakpoint {
return (hooks & EXECUTE) != 0;
}
// Determine whether the breakpoint hooks fetch reads
public boolean getFetch() {
return fetch;
}
// Determine whether the breakpoint hooks reads
public boolean getRead() {
return (hooks & READ) != 0;
@ -662,7 +687,7 @@ public class Breakpoint {
} // x
// Unexpected end of input
if (mode == 0 && ranges.size() > 0) {
if (mode == 0 && (ranges.size() > 0 || first != null)) {
addressError = new Error(EARLYEOF, x + 1, "");
if (vue != null)
vue.updateRanges(this);
@ -759,9 +784,16 @@ public class Breakpoint {
vue.updateState(this);
}
// Specify whether the breakpoint hooks fetch reads
public void setFetch(boolean fetch) {
this.fetch = fetch;
if (vue != null)
vue.updateState(this);
}
// Specify the display name
public void setName(String name) {
this.name = name == null ? "" : name;
public String setName(String name) {
return this.name = name == null ? "" : name;
}
// Specify whether the breakpoint hooks reads
@ -785,8 +817,9 @@ public class Breakpoint {
///////////////////////////////////////////////////////////////////////////
// Determine whether the breakpoint applies to a break scenario
boolean appliesTo(int breakType) {
return isActive() && (breakType == 0 || (breakType & hooks) != 0);
boolean appliesTo(int breakType, boolean fetch) {
return isActive() && (this.fetch || !fetch) &&
(breakType == 0 || (breakType & hooks) != 0);
}
// Perform a typed evaluation of the condition expression
@ -1145,7 +1178,6 @@ public class Breakpoint {
// Not Logical
case NOT_L:
newType = BOOL;
break;
// Round to Nearest
@ -1289,7 +1321,7 @@ public class Breakpoint {
// Select the ID of the conversion operation
int cvt = 0;
switch (tok.dataType) {
switch (dataType) {
case BOOL : cvt = BOOL_ ; break;
case FLOAT : cvt = FLOAT_; break;
case SIGNED : cvt = S32 ; break;
@ -1305,7 +1337,7 @@ public class Breakpoint {
// Insert a conversion token
else {
var imp = new Token(UNARY, -1, "");
switch (tok.dataType) {
switch (dataType) {
case BOOL : imp.text = "bool" ; break;
case FLOAT : imp.text = "float"; break;
case SIGNED : imp.text = "s32" ; break;
@ -1347,7 +1379,7 @@ public class Breakpoint {
int start = 0;
// Locate the bounds of the innermost nested group
for (int x = 0; x < end; x++) {
for (int x = 0; x <= end; x++) {
var tok = tokens.get(x);
if (tok.type == OPEN)
start = x + 1;
@ -1447,7 +1479,7 @@ public class Breakpoint {
}
// A group was not closed, or a binary operation was incomplete
if (!stack.empty() || mode == 0) {
if (!stack.empty() || mode == 0 && tokens.size() != 0) {
conditionError =
new Error(EARLYEOF, condition.length(), "");
return false;
@ -1927,6 +1959,10 @@ public class Breakpoint {
if (conditionError.code != NONE)
return "Error";
// There is no condition
if (tokens.length == 0)
return "";
// Determine the maximum width of the text fields
int max = 0;
for (var tok : tokens)

View File

@ -443,7 +443,7 @@ class CPU {
// First unit
if (fetch == 0) {
inst.bits = access.value << 16;
inst.bits = access.value & 0xFFFF;
if (Instruction.size(access.value >> 10 & 0x3F) == 4) {
fetch = 1;
return false;
@ -452,7 +452,7 @@ class CPU {
// Second unit
else {
inst.bits |= access.value & 0xFFFF;
inst.bits |= access.value << 16;
fetch = -1;
}

View File

@ -82,6 +82,11 @@ public class Instruction {
// Default constructor
public Instruction() { }
// Decoding constructor
public Instruction(int bits) {
decode(bits);
}
// Cloning constructor
Instruction(Instruction o) {
this();
@ -104,27 +109,67 @@ public class Instruction {
// Public Methods //
///////////////////////////////////////////////////////////////////////////
// Decode an instruction from a byte array
public void decode(byte[] data, int offset) {
bits = (data[offset + 1] & 0xFF) << 24 | (data[offset] & 0xFF) << 16;
if (size(bits >>> 26) == 4)
bits |= (data[offset + 3] & 0xFF) << 8 | data[offset + 2] & 0xFF;
decode(bits);
// Produce an access descriptor from the current instruction state
public Access access(Vue vue) {
boolean read = false;
var ret = new Access();
ret.address = vue.getRegister(reg1, false) + disp;
ret.type = Vue.S32;
// Configure descriptor by ID
switch (id) {
case Vue.CAXI : read = true; break;
case Vue.IN_B : ret.type = Vue.U8 ; read = true; break;
case Vue.IN_H : ret.type = Vue.U16; read = true; break;
case Vue.IN_W : read = true; break;
case Vue.LD_B : ret.type = Vue.S8 ; read = true; break;
case Vue.LD_H : ret.type = Vue.S16; read = true; break;
case Vue.LD_W : read = true; break;
case Vue.OUT_B: ret.type = Vue.U8 ; break;
case Vue.OUT_H: ret.type = Vue.U16; break;
case Vue.OUT_W: break;
case Vue.ST_B : ret.type = Vue.S8 ; break;
case Vue.ST_H : ret.type = Vue.S16; break;
case Vue.ST_W : break;
// TODO: Bit strings
default: return null;
}
// Read the value currently on the bus
if (read)
ret.value = vue.read(ret.address, ret.type);
// Select the value to write to the bus
else ret.value = vue.getRegister(id == Vue.CAXI &&
vue.getRegister(reg2, false) == vue.read(ret.address, Vue.S32) ?
30 : reg2, false);
return ret;
}
// Decode an instruction from its binary encoding
public void decode(int bits) {
// Decode an instruction from a byte array
public Instruction decode(byte[] data, int offset) {
return decode(
(data[offset + 0] & 0xFF) |
(data[offset + 1] & 0xFF) << 8 |
(data[offset + 2] & 0xFF) << 16 |
(data[offset + 3] & 0xFF) << 24
);
}
// Decode an instruction from its binary encoding (swapped halfwords)
public Instruction decode(int bits) {
byte extend; // Sign-extend the immediate operand
int x; // Working variable
// Configure instance fields
this.bits = bits;
opcode = bits >> 26 & 63;
x = opcode << 1;
id = LOOKUP_OPCODE[x ];
extend = LOOKUP_OPCODE[x + 1];
format = extend < 0 ? -extend : extend;
size = format < 4 ? 2 : 4;
this.bits = bits = bits << 16 | bits >>> 16;
opcode = bits >> 26 & 63;
x = opcode << 1;
id = LOOKUP_OPCODE[x ];
extend = LOOKUP_OPCODE[x + 1];
format = extend < 0 ? -extend : extend;
size = format < 4 ? 2 : 4;
// Decode by format
switch (format) {
@ -169,6 +214,7 @@ public class Instruction {
break;
}
return this;
}
}

View File

@ -285,11 +285,12 @@ class JavaVue extends Vue {
}
// Check all breakpoints
int count = breakpoints.size();
int count = breakpoints.size();
boolean fetch = breakType == Breakpoint.READ && cpu.fetch != -1;
for (int x = 0; breakCode == 0 && x < count; x++) {
var brk = breakpoints.get(x);
if (
brk.appliesTo(breakType) &&
brk.appliesTo(breakType, fetch) &&
(!ranged || brk.inRange(start, end)) &&
brk.isTrue(stack, breakType, cpu.inst, cpu.access)
) breakCode = x + 1;

View File

@ -35,6 +35,7 @@ typedef struct {
// Precompiled breakpoint
typedef struct {
int32_t enabled; // The breakpoint is enabled
int32_t fetch; // Breakpoint traps fetch reads
int32_t hooks; // Events hooked by the breakpoint
int32_t numRanges; // Number of address ranges
int32_t numTokens; // Number of condition tokens
@ -106,7 +107,9 @@ JNIEXPORT void JNICALL Java_vue_NativeVue_breakpoint
core->numBreakpoints++;
core->breakpoints =
realloc(core->breakpoints,core->numBreakpoints * sizeof (Breakpoint));
memset(&core->breakpoints[core->numBreakpoints-1], 0,sizeof (Breakpoint));
Breakpoint *brk = &core->breakpoints[core->numBreakpoints - 1];
memset(brk, 0 ,sizeof (Breakpoint));
brk->fetch = VUE_TRUE;
}
// Release any used resources
@ -368,10 +371,11 @@ JNIEXPORT void JNICALL Java_vue_NativeVue_updateRanges
// A breakpoint's enabled/hook state has changed
JNIEXPORT void JNICALL Java_vue_NativeVue_updateState
(JNIEnv *env, jobject vue, jlong handle, jint index, jboolean enabled,
jint hooks) {
jboolean fetch, jint hooks) {
Core *core = *(Core **)&handle;
Breakpoint *brk = &core->breakpoints[index];
brk->enabled = enabled;
brk->fetch = fetch;
brk->hooks = hooks;
}

View File

@ -155,13 +155,13 @@ class NativeVue extends Vue {
}
// A breakpoint's enabled/hook state has changed
private native void updateState(long handle, int index, boolean isEnabled,
int hooks);
private native void updateState(long handle, int index, boolean enabled,
boolean fetch, int hooks);
void updateState(Breakpoint brk) {
super.updateState(brk);
int index = breakpoints.indexOf(brk);
updateState(handle, index, brk.isEnabled(),
brk.getHooks());
updateState(handle, index,
brk.isEnabled(), brk.getFetch(), brk.getHooks());
}
// A breakpoint's condition tokens have changed

View File

@ -183,25 +183,20 @@ public abstract class Vue {
// Evaluate an expression
public Object evaluate(String expression) {
var brk = new Breakpoint(this);
var inst = new Instruction(read(getRegister(PC, true), S32));
var brk = new Breakpoint(this);
brk.setCondition(expression);
brk.setEnabled (true);
return brk.evaluateTyped(new int[brk.getDepth()], 0,
getInstruction(), getAccess());
return brk.evaluateTyped(
new int[brk.getDepth()], 0, inst, inst.access(this));
}
// Retrieve a snapshot of the current state's memory access
public abstract Access getAccess();
// Retrieve the most recent applicaiton break code
public abstract int getBreakCode();
// Retrieve the most recent exception code
public abstract int getExceptionCode();
// Retrieve a snapshot of the current state's instruction
public abstract Instruction getInstruction();
// Retrieve a register value
public abstract int getRegister(int index, boolean system);