1061 lines
30 KiB
C
1061 lines
30 KiB
C
/*
|
|
* TilEm II
|
|
*
|
|
* Copyright (c) 2011 Benjamin Moody
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <ticalcs.h>
|
|
#include <tilem.h>
|
|
#include <tilemdb.h>
|
|
|
|
#include "gui.h"
|
|
#include "disasmview.h"
|
|
#include "fixedtreeview.h"
|
|
|
|
/**************** Add/edit breakpoint dialog ****************/
|
|
|
|
struct hex_entry {
|
|
GtkWidget *addr_label;
|
|
GtkWidget *addr_entry;
|
|
GtkWidget *page_label;
|
|
GtkWidget *page_entry;
|
|
};
|
|
|
|
struct breakpoint_dlg {
|
|
TilemDebugger *dbg;
|
|
|
|
GtkWidget *dlg;
|
|
GtkWidget *box;
|
|
|
|
GtkWidget *type_combo;
|
|
GtkWidget *access_cb[3];
|
|
GtkWidget *single_rb;
|
|
GtkWidget *range_rb;
|
|
GtkWidget *access_label;
|
|
GtkWidget *address_label;
|
|
|
|
struct hex_entry start;
|
|
struct hex_entry end;
|
|
};
|
|
|
|
static const struct {
|
|
char abbrev;
|
|
const char *desc;
|
|
const char *value_label;
|
|
int use_pages;
|
|
guint access_mask;
|
|
} type_info[] = {
|
|
{ 'M', "Memory address (logical)", "Address", 0, 7 },
|
|
{ 'M', "Memory address (absolute)", "Address", 1, 7 },
|
|
{ 'P', "I/O port", "Port Number", 0, 6 },
|
|
{ 'I', "Z80 instruction", "Opcode", 0, 0 }
|
|
};
|
|
|
|
/* Determine currently selected address type */
|
|
static guint get_bp_type(struct breakpoint_dlg *bpdlg)
|
|
{
|
|
int i = gtk_combo_box_get_active(GTK_COMBO_BOX(bpdlg->type_combo));
|
|
return (i < 0 ? 0 : i);
|
|
}
|
|
|
|
/* Format address as a string */
|
|
static void hex_entry_set_value(struct hex_entry *he, TilemDebugger *dbg,
|
|
int type, dword value)
|
|
{
|
|
const TilemCalc *calc;
|
|
char buf[20];
|
|
unsigned int page;
|
|
|
|
g_return_if_fail(dbg->emu != NULL);
|
|
g_return_if_fail(dbg->emu->calc != NULL);
|
|
|
|
calc = dbg->emu->calc;
|
|
|
|
switch (type) {
|
|
case TILEM_DB_BREAK_LOGICAL:
|
|
g_snprintf(buf, sizeof(buf), "%04X", value);
|
|
break;
|
|
|
|
case TILEM_DB_BREAK_PHYSICAL:
|
|
if (value >= calc->hw.romsize) {
|
|
value -= calc->hw.romsize;
|
|
page = (value >> 14) + calc->hw.rampagemask;
|
|
}
|
|
else {
|
|
page = (value >> 14);
|
|
}
|
|
|
|
g_snprintf(buf, sizeof(buf), "%02X", page);
|
|
gtk_entry_set_text(GTK_ENTRY(he->page_entry), buf);
|
|
|
|
g_snprintf(buf, sizeof(buf), "%04X", value & 0x3fff);
|
|
break;
|
|
|
|
case TILEM_DB_BREAK_PORT:
|
|
g_snprintf(buf, sizeof(buf), "%02X", value);
|
|
break;
|
|
|
|
case TILEM_DB_BREAK_OPCODE:
|
|
if (value < 0x100)
|
|
g_snprintf(buf, sizeof(buf), "%02X", value);
|
|
else if (value < 0x10000)
|
|
g_snprintf(buf, sizeof(buf), "%04X", value);
|
|
else if (value < 0x1000000)
|
|
g_snprintf(buf, sizeof(buf), "%06X", value);
|
|
else
|
|
g_snprintf(buf, sizeof(buf), "%08X", value);
|
|
break;
|
|
|
|
default:
|
|
g_return_if_reached();
|
|
}
|
|
|
|
gtk_entry_set_text(GTK_ENTRY(he->addr_entry), buf);
|
|
}
|
|
|
|
/* Parse contents of entry */
|
|
static gboolean parse_num(TilemDebugger *dbg, const char *s, dword *a)
|
|
{
|
|
const char *n;
|
|
char *e;
|
|
|
|
g_return_val_if_fail(s != NULL, FALSE);
|
|
|
|
if (s[0] == '$')
|
|
n = s + 1;
|
|
else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
|
|
n = s + 2;
|
|
else
|
|
n = s;
|
|
|
|
*a = strtol(n, &e, 16);
|
|
if (e != n) {
|
|
if (*e == 'h' || *e == 'H')
|
|
e++;
|
|
if (*e == 0)
|
|
return TRUE;
|
|
}
|
|
|
|
if (dbg->dasm && tilem_disasm_get_label(dbg->dasm, s, a))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Parse user input from hex entry */
|
|
static gboolean hex_entry_parse_value(struct hex_entry *he, TilemDebugger *dbg,
|
|
int type, dword *value)
|
|
{
|
|
const TilemCalc *calc = dbg->emu->calc;
|
|
dword page;
|
|
const char *s;
|
|
|
|
g_return_val_if_fail(calc != NULL, 0);
|
|
|
|
s = gtk_entry_get_text(GTK_ENTRY(he->addr_entry));
|
|
if (!parse_num(dbg, s, value))
|
|
return FALSE;
|
|
|
|
if (type != TILEM_DB_BREAK_PHYSICAL)
|
|
return TRUE;
|
|
|
|
s = gtk_entry_get_text(GTK_ENTRY(he->page_entry));
|
|
if (!parse_num(dbg, s, &page))
|
|
return FALSE;
|
|
|
|
*value &= 0x3fff;
|
|
|
|
if (page >= calc->hw.rampagemask) {
|
|
*value += ((page - calc->hw.rampagemask) << 14);
|
|
*value %= calc->hw.ramsize;
|
|
*value += calc->hw.romsize;
|
|
}
|
|
else {
|
|
*value += (page << 14);
|
|
*value %= calc->hw.romsize;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Parse input fields and check if they make sense */
|
|
static gboolean parse_input(struct breakpoint_dlg *bpdlg,
|
|
TilemDebugBreakpoint *bp)
|
|
{
|
|
int i;
|
|
dword addr0, addr1;
|
|
|
|
bp->mask = bp->start = bp->end = 0xffffffff;
|
|
bp->type = get_bp_type(bpdlg);
|
|
bp->mode = 0;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->access_cb[i])))
|
|
bp->mode += (1 << i);
|
|
|
|
bp->mode &= type_info[bp->type].access_mask;
|
|
if (bp->type == TILEM_DB_BREAK_OPCODE)
|
|
bp->mode = TILEM_DB_BREAK_EXEC;
|
|
else if (bp->mode == 0)
|
|
return FALSE;
|
|
|
|
if (!hex_entry_parse_value(&bpdlg->start, bpdlg->dbg,
|
|
bp->type, &addr0))
|
|
return FALSE;
|
|
|
|
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb))) {
|
|
if (!hex_entry_parse_value(&bpdlg->end, bpdlg->dbg,
|
|
bp->type, &addr1))
|
|
return FALSE;
|
|
}
|
|
else {
|
|
addr1 = addr0;
|
|
}
|
|
|
|
if (bp->type == TILEM_DB_BREAK_LOGICAL)
|
|
bp->mask = 0xffff;
|
|
else if (bp->type == TILEM_DB_BREAK_PORT)
|
|
bp->mask = 0xff;
|
|
else
|
|
bp->mask = 0xffffffff;
|
|
|
|
bp->start = addr0 & bp->mask;
|
|
bp->end = addr1 & bp->mask;
|
|
if (bp->end < bp->start)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Check if input fields are valid, and enable/disable OK response as
|
|
appropriate */
|
|
static void validate(struct breakpoint_dlg *bpdlg)
|
|
{
|
|
TilemDebugBreakpoint tmpbp;
|
|
|
|
if (parse_input(bpdlg, &tmpbp))
|
|
gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
|
|
GTK_RESPONSE_OK, TRUE);
|
|
else
|
|
gtk_dialog_set_response_sensitive(GTK_DIALOG(bpdlg->dlg),
|
|
GTK_RESPONSE_OK, FALSE);
|
|
}
|
|
|
|
/* Enable/disable check buttons for access mode */
|
|
static void set_access_mask(struct breakpoint_dlg *bpdlg, guint mask)
|
|
{
|
|
int i;
|
|
|
|
if (mask)
|
|
gtk_widget_show(bpdlg->access_label);
|
|
else
|
|
gtk_widget_hide(bpdlg->access_label);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
if (mask & (1 << i))
|
|
gtk_widget_show(bpdlg->access_cb[i]);
|
|
else
|
|
gtk_widget_hide(bpdlg->access_cb[i]);
|
|
}
|
|
}
|
|
|
|
/* Combo box changed */
|
|
static void addr_type_changed(G_GNUC_UNUSED GtkComboBox *combo, gpointer data)
|
|
{
|
|
struct breakpoint_dlg *bpdlg = data;
|
|
int type = get_bp_type(bpdlg);
|
|
gboolean range = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bpdlg->range_rb));
|
|
char *s;
|
|
|
|
s = g_strdup_printf("<b>%s</b>", type_info[type].value_label);
|
|
gtk_label_set_markup(GTK_LABEL(bpdlg->address_label), s);
|
|
g_free(s);
|
|
|
|
set_access_mask(bpdlg, type_info[type].access_mask);
|
|
|
|
if (type_info[type].use_pages) {
|
|
gtk_widget_show(bpdlg->start.page_label);
|
|
gtk_widget_show(bpdlg->start.page_entry);
|
|
}
|
|
else {
|
|
gtk_widget_hide(bpdlg->start.page_label);
|
|
gtk_widget_hide(bpdlg->start.page_entry);
|
|
}
|
|
|
|
if (range) {
|
|
gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
|
|
"_Start:");
|
|
gtk_widget_show(bpdlg->end.addr_label);
|
|
gtk_widget_show(bpdlg->end.addr_entry);
|
|
}
|
|
else {
|
|
gtk_label_set_text_with_mnemonic(GTK_LABEL(bpdlg->start.addr_label),
|
|
"_Value:");
|
|
gtk_widget_hide(bpdlg->end.addr_label);
|
|
gtk_widget_hide(bpdlg->end.addr_entry);
|
|
}
|
|
|
|
if (type_info[type].use_pages && range) {
|
|
gtk_widget_show(bpdlg->end.page_label);
|
|
gtk_widget_show(bpdlg->end.page_entry);
|
|
}
|
|
else {
|
|
gtk_widget_hide(bpdlg->end.page_label);
|
|
gtk_widget_hide(bpdlg->end.page_entry);
|
|
}
|
|
|
|
validate(bpdlg);
|
|
}
|
|
|
|
/* Access mode changed */
|
|
static void access_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
|
|
{
|
|
struct breakpoint_dlg *bpdlg = data;
|
|
validate(bpdlg);
|
|
}
|
|
|
|
/* Single/range mode changed */
|
|
static void range_mode_changed(G_GNUC_UNUSED GtkToggleButton *tb, gpointer data)
|
|
{
|
|
struct breakpoint_dlg *bpdlg = data;
|
|
addr_type_changed(NULL, bpdlg);
|
|
}
|
|
|
|
/* Text of entry changed */
|
|
static void entry_edited(G_GNUC_UNUSED GtkEntry *entry,
|
|
gpointer data)
|
|
{
|
|
struct breakpoint_dlg *bpdlg = data;
|
|
validate(bpdlg);
|
|
}
|
|
|
|
/* Key presssed in entry */
|
|
static gboolean entry_key_event(G_GNUC_UNUSED GtkWidget *entry,
|
|
GdkEventKey *ev, gpointer data)
|
|
{
|
|
struct breakpoint_dlg *bpdlg = data;
|
|
TilemDebugBreakpoint tmpbp;
|
|
|
|
if (ev->state & GDK_MODIFIER_MASK)
|
|
return FALSE;
|
|
|
|
if (ev->keyval != GDK_Return
|
|
&& ev->keyval != GDK_KP_Enter
|
|
&& ev->keyval != GDK_ISO_Enter)
|
|
return FALSE;
|
|
|
|
if (parse_input(bpdlg, &tmpbp))
|
|
gtk_dialog_response(GTK_DIALOG(bpdlg->dlg), GTK_RESPONSE_OK);
|
|
else
|
|
gtk_widget_child_focus(bpdlg->box, GTK_DIR_TAB_FORWARD);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void init_hex_entry(struct breakpoint_dlg *bpdlg,
|
|
struct hex_entry *he, const char *label,
|
|
GtkTable *tbl, int ypos)
|
|
{
|
|
GtkWidget *align, *lbl;
|
|
|
|
he->addr_label = lbl = gtk_label_new_with_mnemonic(label);
|
|
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
|
|
align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
|
|
gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 6);
|
|
gtk_container_add(GTK_CONTAINER(align), lbl);
|
|
gtk_table_attach(tbl, align, 0, 1, ypos, ypos + 1,
|
|
GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
he->addr_entry = gtk_entry_new();
|
|
gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->addr_entry);
|
|
gtk_table_attach(tbl, he->addr_entry, 1, 2, ypos, ypos + 1,
|
|
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
g_signal_connect(he->addr_entry, "changed",
|
|
G_CALLBACK(entry_edited), bpdlg);
|
|
g_signal_connect(he->addr_entry, "key-press-event",
|
|
G_CALLBACK(entry_key_event), bpdlg);
|
|
|
|
he->page_label = lbl = gtk_label_new("Page:");
|
|
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
|
|
gtk_table_attach(tbl, lbl, 2, 3, ypos, ypos + 1,
|
|
GTK_FILL, GTK_FILL, 6, 0);
|
|
|
|
he->page_entry = gtk_entry_new();
|
|
gtk_entry_set_width_chars(GTK_ENTRY(he->page_entry), 5);
|
|
gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), he->page_entry);
|
|
gtk_table_attach(tbl, he->page_entry, 3, 4, ypos, ypos + 1,
|
|
GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
g_signal_connect(he->page_entry, "changed",
|
|
G_CALLBACK(entry_edited), bpdlg);
|
|
g_signal_connect(he->page_entry, "key-press-event",
|
|
G_CALLBACK(entry_key_event), bpdlg);
|
|
}
|
|
|
|
static gboolean edit_breakpoint(TilemDebugger *dbg,
|
|
GtkWindow *parent_window,
|
|
const char *title,
|
|
TilemDebugBreakpoint *bp,
|
|
gboolean edit_existing)
|
|
{
|
|
GtkWidget *dlg, *vbox, *frame, *tbl, *hbox, *lbl, *combo, *cb, *rb;
|
|
struct breakpoint_dlg bpdlg;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail(bp != NULL, FALSE);
|
|
g_return_val_if_fail(dbg != NULL, FALSE);
|
|
g_return_val_if_fail(dbg->emu != NULL, FALSE);
|
|
g_return_val_if_fail(dbg->emu->calc != NULL, FALSE);
|
|
|
|
bpdlg.dbg = dbg;
|
|
|
|
dlg = gtk_dialog_new_with_buttons(title, parent_window,
|
|
GTK_DIALOG_MODAL,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OK, GTK_RESPONSE_OK,
|
|
NULL);
|
|
gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
|
|
GTK_RESPONSE_OK,
|
|
GTK_RESPONSE_CANCEL,
|
|
-1);
|
|
gtk_dialog_set_default_response(GTK_DIALOG(dlg),
|
|
GTK_RESPONSE_OK);
|
|
|
|
bpdlg.dlg = dlg;
|
|
|
|
bpdlg.box = gtk_vbox_new(FALSE, 6);
|
|
gtk_container_set_border_width(GTK_CONTAINER(bpdlg.box), 6);
|
|
|
|
tbl = gtk_table_new(2, 2, FALSE);
|
|
gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
|
|
gtk_table_set_col_spacings(GTK_TABLE(tbl), 6);
|
|
|
|
/* Breakpoint type */
|
|
|
|
lbl = gtk_label_new_with_mnemonic("Breakpoint _type:");
|
|
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
|
|
gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 0, 1,
|
|
GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
combo = gtk_combo_box_new_text();
|
|
for (i = 0; i < G_N_ELEMENTS(type_info); i++)
|
|
gtk_combo_box_append_text(GTK_COMBO_BOX(combo), type_info[i].desc);
|
|
|
|
bpdlg.type_combo = combo;
|
|
gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), combo);
|
|
gtk_table_attach(GTK_TABLE(tbl), combo, 1, 2, 0, 1,
|
|
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
gtk_combo_box_set_active(GTK_COMBO_BOX(combo), bp->type);
|
|
|
|
/* Access mode */
|
|
|
|
bpdlg.access_label = lbl = gtk_label_new("Break when:");
|
|
gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
|
|
gtk_table_attach(GTK_TABLE(tbl), lbl, 0, 1, 1, 2,
|
|
GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
hbox = gtk_hbox_new(FALSE, 6);
|
|
|
|
cb = gtk_check_button_new_with_mnemonic("_Reading");
|
|
gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
|
|
bpdlg.access_cb[2] = cb;
|
|
|
|
cb = gtk_check_button_new_with_mnemonic("_Writing");
|
|
gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
|
|
bpdlg.access_cb[1] = cb;
|
|
|
|
cb = gtk_check_button_new_with_mnemonic("E_xecuting");
|
|
gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
|
|
bpdlg.access_cb[0] = cb;
|
|
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[0]),
|
|
bp->mode & 1);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[1]),
|
|
bp->mode & 2);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bpdlg.access_cb[2]),
|
|
bp->mode & 4);
|
|
|
|
gtk_table_attach(GTK_TABLE(tbl), hbox, 1, 2, 1, 2,
|
|
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
frame = new_frame("Breakpoint Condition", tbl);
|
|
gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
|
|
|
|
/* Addresses */
|
|
|
|
tbl = gtk_table_new(3, 4, FALSE);
|
|
gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
|
|
|
|
hbox = gtk_hbox_new(FALSE, 6);
|
|
|
|
rb = gtk_radio_button_new_with_mnemonic(NULL, "Si_ngle");
|
|
gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
|
|
bpdlg.single_rb = rb;
|
|
|
|
rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(rb), "R_ange");
|
|
gtk_box_pack_start(GTK_BOX(hbox), rb, FALSE, FALSE, 0);
|
|
bpdlg.range_rb = rb;
|
|
|
|
if (edit_existing && bp->end != bp->start)
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);
|
|
|
|
gtk_table_attach(GTK_TABLE(tbl), hbox, 0, 2, 0, 1,
|
|
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
|
|
|
|
init_hex_entry(&bpdlg, &bpdlg.start, "S_tart:", GTK_TABLE(tbl), 1);
|
|
init_hex_entry(&bpdlg, &bpdlg.end, "_End:", GTK_TABLE(tbl), 2);
|
|
|
|
frame = new_frame("Address", tbl);
|
|
bpdlg.address_label = gtk_frame_get_label_widget(GTK_FRAME(frame));
|
|
gtk_box_pack_start(GTK_BOX(bpdlg.box), frame, FALSE, FALSE, 0);
|
|
gtk_widget_show_all(bpdlg.box);
|
|
|
|
vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
|
|
gtk_box_pack_start(GTK_BOX(vbox), bpdlg.box, FALSE, FALSE, 0);
|
|
|
|
if (edit_existing) {
|
|
hex_entry_set_value(&bpdlg.start, dbg, bp->type, bp->start);
|
|
hex_entry_set_value(&bpdlg.end, dbg, bp->type, bp->end);
|
|
}
|
|
|
|
g_signal_connect(combo, "changed",
|
|
G_CALLBACK(addr_type_changed), &bpdlg);
|
|
g_signal_connect(bpdlg.access_cb[0], "toggled",
|
|
G_CALLBACK(access_changed), &bpdlg);
|
|
g_signal_connect(bpdlg.access_cb[1], "toggled",
|
|
G_CALLBACK(access_changed), &bpdlg);
|
|
g_signal_connect(bpdlg.access_cb[2], "toggled",
|
|
G_CALLBACK(access_changed), &bpdlg);
|
|
g_signal_connect(bpdlg.single_rb, "toggled",
|
|
G_CALLBACK(range_mode_changed), &bpdlg);
|
|
|
|
addr_type_changed(NULL, &bpdlg);
|
|
|
|
gtk_widget_grab_focus(bpdlg.start.addr_entry);
|
|
|
|
do {
|
|
if (gtk_dialog_run(GTK_DIALOG(dlg)) != GTK_RESPONSE_OK) {
|
|
gtk_widget_destroy(dlg);
|
|
return FALSE;
|
|
}
|
|
} while (!parse_input(&bpdlg, bp));
|
|
|
|
gtk_widget_destroy(dlg);
|
|
return TRUE;
|
|
}
|
|
|
|
/**************** Breakpoint list dialog ****************/
|
|
|
|
enum {
|
|
COL_BP,
|
|
COL_START,
|
|
COL_END,
|
|
COL_TYPE_STR,
|
|
COL_START_STR,
|
|
COL_END_STR,
|
|
COL_ENABLED,
|
|
N_COLUMNS
|
|
};
|
|
|
|
struct bplist_dlg {
|
|
TilemDebugger *dbg;
|
|
GtkWidget *dlg;
|
|
GtkListStore *store;
|
|
GtkWidget *treeview;
|
|
GtkWidget *remove_btn;
|
|
GtkWidget *edit_btn;
|
|
GtkWidget *clear_btn;
|
|
};
|
|
|
|
/* Convert address into a displayable string */
|
|
static void format_address(TilemDebugger *dbg,
|
|
char *buf, int bufsize,
|
|
int type, dword value)
|
|
{
|
|
const TilemCalc *calc;
|
|
unsigned int page;
|
|
|
|
g_return_if_fail(dbg->emu != NULL);
|
|
g_return_if_fail(dbg->emu->calc != NULL);
|
|
|
|
calc = dbg->emu->calc;
|
|
|
|
switch (type) {
|
|
case TILEM_DB_BREAK_LOGICAL:
|
|
g_snprintf(buf, bufsize, "%04X", value);
|
|
break;
|
|
|
|
case TILEM_DB_BREAK_PHYSICAL:
|
|
if (value >= calc->hw.romsize) {
|
|
value -= calc->hw.romsize;
|
|
page = (value >> 14) + calc->hw.rampagemask;
|
|
}
|
|
else {
|
|
page = (value >> 14);
|
|
}
|
|
|
|
g_snprintf(buf, bufsize, "%02X:%04X", page, value & 0x3fff);
|
|
break;
|
|
|
|
case TILEM_DB_BREAK_PORT:
|
|
g_snprintf(buf, bufsize, "%02X", value);
|
|
break;
|
|
|
|
case TILEM_DB_BREAK_OPCODE:
|
|
if (value < 0x100)
|
|
g_snprintf(buf, bufsize, "%02X", value);
|
|
else if (value < 0x10000)
|
|
g_snprintf(buf, bufsize, "%04X", value);
|
|
else if (value < 0x1000000)
|
|
g_snprintf(buf, bufsize, "%06X", value);
|
|
else
|
|
g_snprintf(buf, bufsize, "%08X", value);
|
|
break;
|
|
|
|
default:
|
|
g_return_if_reached();
|
|
}
|
|
}
|
|
|
|
/* Store breakpoint properties in tree model */
|
|
static void set_iter_from_bp(struct bplist_dlg *bpldlg, GtkTreeIter *iter,
|
|
const TilemDebugBreakpoint *bp)
|
|
{
|
|
char tbuf[5], sbuf[10], ebuf[10];
|
|
int i, j;
|
|
|
|
g_return_if_fail(bp != NULL);
|
|
|
|
tbuf[0] = type_info[bp->type].abbrev;
|
|
j = 1;
|
|
for (i = 0; i < 3; i++)
|
|
if (bp->mode & (4 >> i))
|
|
tbuf[j++] = "RWX"[i];
|
|
tbuf[j] = 0;
|
|
|
|
format_address(bpldlg->dbg, sbuf, sizeof(sbuf), bp->type, bp->start);
|
|
format_address(bpldlg->dbg, ebuf, sizeof(ebuf), bp->type, bp->end);
|
|
|
|
gtk_list_store_set(bpldlg->store, iter,
|
|
COL_BP, bp,
|
|
COL_START, bp->start,
|
|
COL_END, bp->end,
|
|
COL_TYPE_STR, tbuf,
|
|
COL_START_STR, sbuf,
|
|
COL_END_STR, ebuf,
|
|
COL_ENABLED, !bp->disabled,
|
|
-1);
|
|
}
|
|
|
|
/* Get breakpoint pointer for the given tree model row */
|
|
static TilemDebugBreakpoint *get_iter_bp(struct bplist_dlg *bpldlg,
|
|
GtkTreeIter *iter)
|
|
{
|
|
gpointer ptr;
|
|
gtk_tree_model_get(GTK_TREE_MODEL(bpldlg->store), iter,
|
|
COL_BP, &ptr, -1);
|
|
return (TilemDebugBreakpoint *) ptr;
|
|
}
|
|
|
|
/* Set buttons sensitive or insensitive depending on whether any BPs
|
|
are selected */
|
|
static void update_buttons(struct bplist_dlg *bpldlg)
|
|
{
|
|
GtkTreeSelection *sel;
|
|
gboolean any_sel;
|
|
|
|
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
|
|
|
|
any_sel = gtk_tree_selection_get_selected(sel, NULL, NULL);
|
|
gtk_widget_set_sensitive(bpldlg->remove_btn, any_sel);
|
|
gtk_widget_set_sensitive(bpldlg->edit_btn, any_sel);
|
|
|
|
gtk_widget_set_sensitive(bpldlg->clear_btn,
|
|
bpldlg->dbg->breakpoints != NULL);
|
|
}
|
|
|
|
/* "Add breakpoint" button clicked */
|
|
static void add_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
|
|
{
|
|
struct bplist_dlg *bpldlg = data;
|
|
TilemDebugBreakpoint tmpbp, *newbp;
|
|
TilemDebugger *dbg = bpldlg->dbg;
|
|
GtkTreeIter iter;
|
|
|
|
memset(&tmpbp, 0, sizeof(tmpbp));
|
|
tmpbp.type = dbg->last_bp_type;
|
|
tmpbp.mode = dbg->last_bp_mode;
|
|
|
|
if (!edit_breakpoint(dbg, GTK_WINDOW(bpldlg->dlg),
|
|
"Add Breakpoint", &tmpbp, FALSE))
|
|
return;
|
|
|
|
dbg->last_bp_type = tmpbp.type;
|
|
dbg->last_bp_mode = tmpbp.mode;
|
|
|
|
newbp = tilem_debugger_add_breakpoint(dbg, &tmpbp);
|
|
gtk_list_store_append(bpldlg->store, &iter);
|
|
set_iter_from_bp(bpldlg, &iter, newbp);
|
|
|
|
update_buttons(bpldlg);
|
|
}
|
|
|
|
/* "Remove breakpoint" button clicked */
|
|
static void remove_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
|
|
{
|
|
struct bplist_dlg *bpldlg = data;
|
|
GtkTreeSelection *sel;
|
|
GtkTreeIter iter;
|
|
TilemDebugBreakpoint *bp;
|
|
|
|
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
|
|
|
|
if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
|
|
return;
|
|
|
|
bp = get_iter_bp(bpldlg, &iter);
|
|
g_return_if_fail(bp != NULL);
|
|
gtk_list_store_remove(bpldlg->store, &iter);
|
|
tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
|
|
|
|
update_buttons(bpldlg);
|
|
}
|
|
|
|
/* Edit an existing breakpoint */
|
|
static void edit_row(struct bplist_dlg *bpldlg, GtkTreeIter *iter)
|
|
{
|
|
TilemDebugBreakpoint *bp, tmpbp;
|
|
|
|
bp = get_iter_bp(bpldlg, iter);
|
|
g_return_if_fail(bp != NULL);
|
|
tmpbp = *bp;
|
|
|
|
if (!edit_breakpoint(bpldlg->dbg, GTK_WINDOW(bpldlg->dlg),
|
|
"Edit Breakpoint", &tmpbp, TRUE))
|
|
return;
|
|
|
|
tmpbp.disabled = 0;
|
|
tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
|
|
set_iter_from_bp(bpldlg, iter, bp);
|
|
|
|
update_buttons(bpldlg);
|
|
}
|
|
|
|
/* "Edit breakpoint" button clicked */
|
|
static void edit_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
|
|
{
|
|
struct bplist_dlg *bpldlg = data;
|
|
GtkTreeSelection *sel;
|
|
GtkTreeIter iter;
|
|
|
|
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(bpldlg->treeview));
|
|
|
|
if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
|
|
return;
|
|
|
|
edit_row(bpldlg, &iter);
|
|
}
|
|
|
|
/* "Clear breakpoints" button clicked */
|
|
static void clear_clicked(G_GNUC_UNUSED GtkButton *btn, gpointer data)
|
|
{
|
|
struct bplist_dlg *bpldlg = data;
|
|
GtkWidget *dlg;
|
|
TilemDebugBreakpoint *bp;
|
|
|
|
dlg = gtk_message_dialog_new(GTK_WINDOW(bpldlg->dlg),
|
|
GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
|
|
GTK_BUTTONS_NONE,
|
|
"Clear all breakpoints?");
|
|
gtk_message_dialog_format_secondary_markup
|
|
(GTK_MESSAGE_DIALOG(dlg),
|
|
"All existing breakpoints will be deleted and"
|
|
" cannot be restored.");
|
|
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_CLEAR, GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
|
|
GTK_RESPONSE_ACCEPT,
|
|
GTK_RESPONSE_CANCEL,
|
|
-1);
|
|
|
|
if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT) {
|
|
while (bpldlg->dbg->breakpoints) {
|
|
bp = bpldlg->dbg->breakpoints->data;
|
|
tilem_debugger_remove_breakpoint(bpldlg->dbg, bp);
|
|
}
|
|
gtk_list_store_clear(bpldlg->store);
|
|
update_buttons(bpldlg);
|
|
}
|
|
|
|
gtk_widget_destroy(dlg);
|
|
}
|
|
|
|
/* Row activated (double-clicked, usually) */
|
|
static void row_activated(G_GNUC_UNUSED GtkTreeView *treeview,
|
|
GtkTreePath *path,
|
|
G_GNUC_UNUSED GtkTreeViewColumn *col,
|
|
gpointer data)
|
|
{
|
|
struct bplist_dlg *bpldlg = data;
|
|
GtkTreeIter iter;
|
|
|
|
if (gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
|
|
&iter, path))
|
|
edit_row(bpldlg, &iter);
|
|
}
|
|
|
|
/* Toggle button clicked for a breakpoint */
|
|
static void enabled_toggled(G_GNUC_UNUSED GtkCellRendererToggle *cell,
|
|
gchar *pathstr, gpointer data)
|
|
{
|
|
struct bplist_dlg *bpldlg = data;
|
|
GtkTreePath *path;
|
|
GtkTreeIter iter;
|
|
TilemDebugBreakpoint *bp, tmpbp;
|
|
|
|
path = gtk_tree_path_new_from_string(pathstr);
|
|
if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(bpldlg->store),
|
|
&iter, path)) {
|
|
gtk_tree_path_free(path);
|
|
return;
|
|
}
|
|
gtk_tree_path_free(path);
|
|
|
|
bp = get_iter_bp(bpldlg, &iter);
|
|
g_return_if_fail(bp != NULL);
|
|
tmpbp = *bp;
|
|
tmpbp.disabled = !tmpbp.disabled;
|
|
tilem_debugger_change_breakpoint(bpldlg->dbg, bp, &tmpbp);
|
|
set_iter_from_bp(bpldlg, &iter, bp);
|
|
}
|
|
|
|
/* Selection changed */
|
|
static void selection_changed(G_GNUC_UNUSED GtkTreeSelection *sel,
|
|
gpointer data)
|
|
{
|
|
struct bplist_dlg *bpldlg = data;
|
|
update_buttons(bpldlg);
|
|
}
|
|
|
|
/* Show a dialog letting the user add, remove, and edit breakpoints */
|
|
void tilem_debugger_edit_breakpoints(TilemDebugger *dbg)
|
|
{
|
|
struct bplist_dlg bpldlg;
|
|
GtkWidget *dlg, *hbox, *treeview, *sw, *bbox, *btn, *vbox, *vbox2,
|
|
*invalid_cb, *undoc_cb;
|
|
GtkListStore *store;
|
|
GtkTreeViewColumn *col;
|
|
GtkCellRenderer *cell;
|
|
GtkTreeIter iter;
|
|
GSList *l;
|
|
GtkTreeSelection *sel;
|
|
unsigned int flags;
|
|
|
|
g_return_if_fail(dbg != NULL);
|
|
g_return_if_fail(dbg->emu != NULL);
|
|
g_return_if_fail(dbg->emu->calc != NULL);
|
|
|
|
bpldlg.dbg = dbg;
|
|
|
|
dlg = gtk_dialog_new_with_buttons("Breakpoints",
|
|
GTK_WINDOW(dbg->window),
|
|
GTK_DIALOG_MODAL,
|
|
GTK_STOCK_CLOSE,
|
|
GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
|
|
gtk_window_set_default_size(GTK_WINDOW(dlg), -1, 300);
|
|
|
|
store = gtk_list_store_new(N_COLUMNS,
|
|
G_TYPE_POINTER,
|
|
G_TYPE_INT,
|
|
G_TYPE_INT,
|
|
G_TYPE_STRING,
|
|
G_TYPE_STRING,
|
|
G_TYPE_STRING,
|
|
G_TYPE_BOOLEAN);
|
|
|
|
bpldlg.dlg = dlg;
|
|
bpldlg.store = store;
|
|
|
|
vbox = gtk_vbox_new(FALSE, 6);
|
|
gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
|
|
|
|
hbox = gtk_hbox_new(FALSE, 6);
|
|
|
|
for (l = dbg->breakpoints; l; l = l->next) {
|
|
gtk_list_store_append(store, &iter);
|
|
set_iter_from_bp(&bpldlg, &iter, l->data);
|
|
}
|
|
|
|
treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
|
|
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
|
|
gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
|
|
gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
|
|
bpldlg.treeview = treeview;
|
|
|
|
fixed_tree_view_init(treeview, 0,
|
|
COL_TYPE_STR, "MRWX ",
|
|
COL_START_STR, "DD:DDDD ",
|
|
COL_END_STR, "DD:DDDD ",
|
|
COL_ENABLED, TRUE,
|
|
-1);
|
|
|
|
g_signal_connect(treeview, "row-activated",
|
|
G_CALLBACK(row_activated), &bpldlg);
|
|
|
|
/* Enabled/type column */
|
|
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_set_title(col, "Type");
|
|
gtk_tree_view_column_set_sort_column_id(col, COL_TYPE_STR);
|
|
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
|
|
|
|
cell = gtk_cell_renderer_toggle_new();
|
|
gtk_tree_view_column_pack_start(col, cell, FALSE);
|
|
gtk_tree_view_column_set_attributes(col, cell,
|
|
"active", COL_ENABLED,
|
|
NULL);
|
|
g_signal_connect(cell, "toggled",
|
|
G_CALLBACK(enabled_toggled), &bpldlg);
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
gtk_tree_view_column_pack_start(col, cell, TRUE);
|
|
gtk_tree_view_column_set_attributes(col, cell,
|
|
"text", COL_TYPE_STR,
|
|
NULL);
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
|
|
|
|
/* Start column */
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
col = gtk_tree_view_column_new_with_attributes
|
|
("Start", cell, "text", COL_START_STR, NULL);
|
|
gtk_tree_view_column_set_sort_column_id(col, COL_START);
|
|
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
|
|
|
|
/* End column */
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
col = gtk_tree_view_column_new_with_attributes
|
|
("End", cell, "text", COL_END_STR, NULL);
|
|
gtk_tree_view_column_set_sort_column_id(col, COL_END);
|
|
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);
|
|
|
|
sw = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
|
|
GTK_POLICY_NEVER,
|
|
GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
|
|
GTK_SHADOW_IN);
|
|
gtk_container_add(GTK_CONTAINER(sw), treeview);
|
|
|
|
gtk_box_pack_start(GTK_BOX(hbox), sw, TRUE, TRUE, 0);
|
|
|
|
/* Buttons */
|
|
|
|
bbox = gtk_vbutton_box_new();
|
|
gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
|
|
gtk_box_set_spacing(GTK_BOX(bbox), 6);
|
|
|
|
btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
|
|
g_signal_connect(btn, "clicked",
|
|
G_CALLBACK(add_clicked), &bpldlg);
|
|
gtk_container_add(GTK_CONTAINER(bbox), btn);
|
|
|
|
btn = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
|
|
g_signal_connect(btn, "clicked",
|
|
G_CALLBACK(remove_clicked), &bpldlg);
|
|
gtk_container_add(GTK_CONTAINER(bbox), btn);
|
|
bpldlg.remove_btn = btn;
|
|
|
|
btn = gtk_button_new_from_stock(GTK_STOCK_EDIT);
|
|
g_signal_connect(btn, "clicked",
|
|
G_CALLBACK(edit_clicked), &bpldlg);
|
|
gtk_container_add(GTK_CONTAINER(bbox), btn);
|
|
bpldlg.edit_btn = btn;
|
|
|
|
btn = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
|
|
g_signal_connect(btn, "clicked",
|
|
G_CALLBACK(clear_clicked), &bpldlg);
|
|
gtk_container_add(GTK_CONTAINER(bbox), btn);
|
|
bpldlg.clear_btn = btn;
|
|
|
|
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
|
|
g_signal_connect(sel, "changed",
|
|
G_CALLBACK(selection_changed), &bpldlg);
|
|
|
|
update_buttons(&bpldlg);
|
|
|
|
gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
|
|
gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
|
|
|
|
invalid_cb = gtk_check_button_new_with_mnemonic
|
|
("Break on _invalid instructions");
|
|
undoc_cb = gtk_check_button_new_with_mnemonic
|
|
("Break on _undocumented instructions");
|
|
|
|
tilem_calc_emulator_lock(dbg->emu);
|
|
flags = dbg->emu->calc->z80.emuflags;
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(invalid_cb),
|
|
(flags & TILEM_Z80_BREAK_INVALID));
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(undoc_cb),
|
|
(flags & TILEM_Z80_BREAK_UNDOCUMENTED));
|
|
tilem_calc_emulator_unlock(dbg->emu);
|
|
|
|
gtk_box_pack_start(GTK_BOX(vbox), invalid_cb, FALSE, FALSE, 0);
|
|
gtk_box_pack_start(GTK_BOX(vbox), undoc_cb, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show_all(vbox);
|
|
|
|
gtk_widget_grab_focus(treeview);
|
|
|
|
vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
|
|
gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
|
|
|
|
gtk_dialog_run(GTK_DIALOG(dlg));
|
|
|
|
tilem_calc_emulator_lock(dbg->emu);
|
|
flags = dbg->emu->calc->z80.emuflags;
|
|
flags &= ~(TILEM_Z80_BREAK_INVALID | TILEM_Z80_BREAK_UNDOCUMENTED);
|
|
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(invalid_cb)))
|
|
flags |= TILEM_Z80_BREAK_INVALID;
|
|
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(undoc_cb)))
|
|
flags |= TILEM_Z80_BREAK_UNDOCUMENTED;
|
|
dbg->emu->calc->z80.emuflags = flags;
|
|
tilem_calc_emulator_unlock(dbg->emu);
|
|
|
|
gtk_widget_destroy(dlg);
|
|
}
|