mirror of
https://git.code.sf.net/p/fuse-emulator/fuse
synced 2026-01-27 01:41:34 +03:00
587 lines
16 KiB
C
587 lines
16 KiB
C
/* debugger.c: The debugger widget
|
|
Copyright (c) 2002-2014 Philip Kendall, Darren Salt
|
|
Copyright (c) 2015 Stuart Brady
|
|
Copyright (c) 2016 BogDan Vatra
|
|
|
|
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 2 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, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
Author contact information:
|
|
|
|
E-mail: philip-fuse@shadowmagic.org.uk
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "libspectrum.h"
|
|
|
|
#include "debugger/debugger.h"
|
|
#include "display.h"
|
|
#include "keyboard.h"
|
|
#include "machine.h"
|
|
#include "peripherals/ide/zxcf.h"
|
|
#include "peripherals/scld.h"
|
|
#include "peripherals/ula.h"
|
|
#include "ui/uidisplay.h"
|
|
#include "widget.h"
|
|
#include "widget_internals.h"
|
|
#include "z80/z80.h"
|
|
#include "z80/z80_macros.h"
|
|
|
|
static enum {
|
|
DB_REGISTERS, DB_BYTES, DB_TEXT, DB_DISASM, DB_BREAKPT
|
|
} display = DB_REGISTERS;
|
|
|
|
struct {
|
|
libspectrum_word value;
|
|
size_t page;
|
|
char x, width, column;
|
|
} db_editv = {
|
|
0, 0, 255, 0
|
|
};
|
|
|
|
static libspectrum_word debugger_memaddr;
|
|
static int breakpt_no = 0, breakpt_show = 0;
|
|
|
|
/* Various data displays */
|
|
static void display_registers( void );
|
|
static void display_bytes( void );
|
|
static void display_text( void );
|
|
static void display_disasm( void );
|
|
static void display_breakpts( void );
|
|
|
|
/* Scrolling for the data displays */
|
|
static void scroll( int step );
|
|
|
|
#define LC(X) ( (X)*8 - DISPLAY_BORDER_ASPECT_WIDTH )
|
|
#define LR(Y) ( (Y)*8 - DISPLAY_BORDER_HEIGHT )
|
|
|
|
static inline const char *format_8_bit( void )
|
|
{
|
|
return debugger_output_base == 10 ? "%-3d" : "%02X";
|
|
}
|
|
|
|
static inline const char *format_16_bit( void )
|
|
{
|
|
return debugger_output_base == 10 ? "%-5d" : "%04X";
|
|
}
|
|
|
|
int ui_debugger_activate( void )
|
|
{
|
|
return widget_do_debugger();
|
|
}
|
|
|
|
int ui_debugger_deactivate( int interruptible GCC_UNUSED )
|
|
{
|
|
/* Refresh the Spectrum's display, including the border */
|
|
display_refresh_all();
|
|
return widget_end_all( WIDGET_FINISHED_OK );
|
|
}
|
|
|
|
int ui_debugger_update( void )
|
|
{
|
|
return widget_debugger_draw( NULL );
|
|
}
|
|
|
|
int ui_debugger_disassemble( libspectrum_word addr )
|
|
{
|
|
debugger_memaddr = addr;
|
|
return 0;
|
|
}
|
|
|
|
/* Debugger update function. The dialog box is created every time it is
|
|
displayed, so no need to do anything here */
|
|
void ui_breakpoints_updated( void )
|
|
{
|
|
}
|
|
|
|
int widget_debugger_draw( void *data )
|
|
{
|
|
static const char state[][8] = {
|
|
"Running", "Halted", "Stepped", "Breakpt"
|
|
};
|
|
int x;
|
|
char pbuf[8];
|
|
|
|
widget_rectangle( LC(0), LR(0), 40 * 8, 17 * 8 + 4, 1 );
|
|
widget_rectangle( LC(0), LR(17) + 2, 320, 1, 7 );
|
|
|
|
switch ( display ) {
|
|
case DB_REGISTERS: display_registers(); break;
|
|
case DB_BYTES: display_bytes(); break;
|
|
case DB_TEXT: display_text(); break;
|
|
case DB_DISASM: display_disasm(); break;
|
|
case DB_BREAKPT: display_breakpts(); break;
|
|
}
|
|
|
|
widget_printstring( LC(0), LR(15) - 4, 6, state[debugger_mode] );
|
|
widget_printstring( LC(10), LR(15) - 4, 6,
|
|
"\022S\021ingle step \022C\021ontinue Co\022m\021mand" );
|
|
|
|
x = LC(-1);
|
|
if( display != DB_REGISTERS )
|
|
x = widget_printstring( x + 8, LR(16), 7, "\022R\021egs" );
|
|
if( display != DB_BYTES )
|
|
x = widget_printstring( x + 8, LR(16), 7, "\022B\021ytes" );
|
|
if( display != DB_TEXT )
|
|
x = widget_printstring( x + 8, LR(16), 7, "\022T\021ext" );
|
|
if( display != DB_DISASM )
|
|
x = widget_printstring( x + 8, LR(16), 7, "\022D\021isasm" );
|
|
if( display != DB_BREAKPT )
|
|
x = widget_printstring( x + 8, LR(16), 7, "Brea\022k\021pts" );
|
|
|
|
widget_printstring_right( LC(25) + 4, LR(16), 5, "PC" );
|
|
sprintf( pbuf, "%04X", PC );
|
|
widget_printstring_fixed( LC(26) / 8, LR(16) / 8, 7, pbuf );
|
|
|
|
widget_printstring_right( LR(35) + 4, LR(16), 5, "Bas\022e\021" );
|
|
sprintf( pbuf, "%d", debugger_output_base );
|
|
widget_printstring( LR(36), LR(16), 7, pbuf );
|
|
|
|
widget_display_lines( LR(0) / 8, 18 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void widget_debugger_keyhandler( input_key key )
|
|
{
|
|
/* Display mode */
|
|
switch ( key ) {
|
|
case INPUT_KEY_Escape: /* Close widget */
|
|
widget_end_widget( WIDGET_FINISHED_CANCEL );
|
|
debugger_run();
|
|
break;
|
|
|
|
case INPUT_KEY_c:
|
|
case INPUT_KEY_Return: /* Close widget */
|
|
case INPUT_KEY_KP_Enter:
|
|
widget_end_all( WIDGET_FINISHED_OK );
|
|
debugger_run();
|
|
break;
|
|
|
|
case INPUT_KEY_s: /* Single step & reopen widget */
|
|
debugger_mode = DEBUGGER_MODE_HALTED;
|
|
widget_end_all( WIDGET_FINISHED_OK );
|
|
break;
|
|
|
|
case INPUT_KEY_r: /* Display the registers */
|
|
display = DB_REGISTERS;
|
|
widget_debugger_draw( NULL );
|
|
break;
|
|
|
|
case INPUT_KEY_b: /* Display a memory dump (bytes) */
|
|
display = DB_BYTES;
|
|
widget_debugger_draw( NULL );
|
|
break;
|
|
|
|
case INPUT_KEY_t: /* Display a memory dump (text) */
|
|
display = DB_TEXT;
|
|
widget_debugger_draw( NULL );
|
|
break;
|
|
|
|
case INPUT_KEY_d: /* Display a disassembly */
|
|
display = DB_DISASM;
|
|
widget_debugger_draw( NULL );
|
|
break;
|
|
|
|
case INPUT_KEY_k: /* Display the breakpoints */
|
|
display = DB_BREAKPT;
|
|
widget_debugger_draw( NULL );
|
|
break;
|
|
|
|
case INPUT_KEY_e: /* Switch base */
|
|
debugger_output_base = 26 - debugger_output_base; /* 10 or 16 */
|
|
widget_debugger_draw( NULL );
|
|
break;
|
|
|
|
case INPUT_KEY_m: /* Enter a command */
|
|
{
|
|
widget_text_t text_data;
|
|
|
|
text_data.title = "Debugger command";
|
|
text_data.allow = WIDGET_INPUT_ASCII;
|
|
text_data.max_length = 63;
|
|
text_data.text[0] = 0;
|
|
if( !widget_do_text( &text_data ) )
|
|
debugger_command_evaluate( widget_text_text );
|
|
}
|
|
break;
|
|
|
|
case INPUT_KEY_Up: /* Back one line */
|
|
scroll( -1 );
|
|
break;
|
|
|
|
case INPUT_KEY_Down: /* Back one instruction or four lines */
|
|
scroll( 1 );
|
|
break;
|
|
|
|
case INPUT_KEY_Page_Up: /* Back eight lines */
|
|
scroll( -8 );
|
|
break;
|
|
|
|
case INPUT_KEY_Page_Down: /* Forward eight lines */
|
|
scroll( 8 );
|
|
break;
|
|
|
|
case INPUT_KEY_Home: /* To start of memory */
|
|
debugger_memaddr = 0;
|
|
scroll( 0 );
|
|
break;
|
|
|
|
case INPUT_KEY_End: /* To end of RAM */
|
|
debugger_memaddr = 0;
|
|
scroll( -8 );
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
}
|
|
|
|
static void show_register0( int x, int y, const char *label, int value )
|
|
{
|
|
char pbuf[8];
|
|
|
|
sprintf( pbuf, "%d", value );
|
|
widget_printstring_right( x - 4, y, 5, label );
|
|
widget_printstring_fixed( x / 8, y / 8, 7, pbuf );
|
|
}
|
|
|
|
static void show_register1( int x, int y, const char *label, int value )
|
|
{
|
|
char pbuf[8];
|
|
|
|
sprintf( pbuf, format_8_bit(), value );
|
|
widget_printstring_right( x - 4, y, 5, label );
|
|
widget_printstring_fixed( x / 8, y / 8, 7, pbuf );
|
|
}
|
|
|
|
static void show_register2( int x, int y, const char *label, int value )
|
|
{
|
|
char pbuf[8];
|
|
|
|
sprintf( pbuf, format_16_bit(), value );
|
|
widget_printstring_right( x - 4, y, 5, label );
|
|
widget_printstring_fixed( x / 8, y / 8, 7, pbuf );
|
|
}
|
|
|
|
static void display_registers( void )
|
|
{
|
|
int source, page_num, writable, contended;
|
|
libspectrum_word offset;
|
|
size_t block;
|
|
char pbuf[16];
|
|
int i, capabilities;
|
|
|
|
show_register2( LC(3), LR(0), "AF", AF );
|
|
show_register2( LC(12), LR(0), "AF'", AF_ );
|
|
show_register2( LC(20), LR(0), "SP", SP );
|
|
show_register2( LC(29), LR(0), "PC", PC );
|
|
show_register1( LC(36), LR(0), "R", ( R & 0x7F ) | ( R7 & 0x80 ) );
|
|
|
|
show_register2( LC(3), LR(1), "BC", BC );
|
|
show_register2( LC(12), LR(1), "BC'", BC_ );
|
|
show_register2( LC(20), LR(1), "IX", IX );
|
|
show_register2( LC(29), LR(1), "IY", IY );
|
|
show_register1( LC(36), LR(1), "I", I );
|
|
|
|
show_register2( LC(3), LR(2), "DE", DE );
|
|
show_register2( LC(12), LR(2), "DE'", DE_ );
|
|
show_register0( LC(20), LR(2), "IM", IM );
|
|
show_register0( LC(29), LR(2), "IFF1", IFF1 );
|
|
show_register0( LC(36), LR(2), "IFF2", IFF2 );
|
|
|
|
show_register0( LC(36), LR(2), "IFF2", IFF2 );
|
|
show_register2( LC(3), LR(3), "HL", HL );
|
|
show_register2( LC(12), LR(3), "HL'", HL_ );
|
|
widget_printstring_fixed( LC(20) / 8, LR(3) / 8, 5, "SZ5H3PNC" );
|
|
show_register0( LC(36), LR(3), "HALTED", z80.halted );
|
|
show_register1( LC(36), LR(4), "ULA", ula_last_byte() );
|
|
|
|
sprintf( pbuf, "%d", tstates );
|
|
widget_printstring_right( LC(12) - 4, LR(4), 5, "Tstates" );
|
|
widget_printstring_fixed( LC(12) / 8, LR(4) / 8, 7, pbuf );
|
|
for( i = 0; i < 8; ++i )
|
|
pbuf[i] = ( F & ( 0x80 >> i ) ) ? '1' : '0';
|
|
pbuf[8] = 0;
|
|
widget_printstring_fixed( LC(20) / 8, LR(4) / 8, 7, pbuf );
|
|
|
|
capabilities = libspectrum_machine_capabilities( machine_current->machine );
|
|
|
|
if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_AY )
|
|
show_register1( LC(37), LR(4), "AY",
|
|
machine_current->ay.current_register );
|
|
|
|
if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY )
|
|
show_register1( LC(6), LR(5), "128Mem",
|
|
machine_current->ram.last_byte );
|
|
|
|
if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY )
|
|
show_register1( LC(15), LR(5), "+3Mem",
|
|
machine_current->ram.last_byte2 );
|
|
|
|
if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_VIDEO ||
|
|
capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY ||
|
|
capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY )
|
|
show_register1( LC(24), LR(5), "TmxDec", scld_last_dec.byte );
|
|
|
|
if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY ||
|
|
capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY )
|
|
show_register1( LC(33), LR(5), "TmxHSR", scld_last_hsr );
|
|
|
|
if( settings_current.zxcf_active )
|
|
show_register1( LC(6), LR(5), "ZXCF", zxcf_last_memctl() );
|
|
|
|
source = page_num = writable = contended = -1;
|
|
offset = 0;
|
|
i = 0;
|
|
|
|
for( block = 0; block < MEMORY_PAGES_IN_64K; block++ ) {
|
|
memory_page *page = &memory_map_read[block];
|
|
|
|
if( page->source != source ||
|
|
page->page_num != page_num ||
|
|
page->offset != offset ||
|
|
page->writable != writable ||
|
|
page->contended != contended ) {
|
|
|
|
int x = LC(5 + 20 * ( i & 1 ) ), y = LR(6 + ( i / 2 ) );
|
|
|
|
sprintf( pbuf, format_16_bit(), (unsigned)block * MEMORY_PAGE_SIZE );
|
|
widget_printstring_right( x, y, 5, pbuf );
|
|
snprintf( pbuf, sizeof( pbuf ), "%s %d",
|
|
memory_source_description( memory_map_read[block].source ),
|
|
memory_map_read[block].page_num );
|
|
x = widget_printstring( x + 4, y, 7, pbuf ) + 4;
|
|
if( memory_map_read[block].writable )
|
|
x = widget_printstring( x, y, 4, "w" );
|
|
if( memory_map_read[block].contended )
|
|
x = widget_printstring( x, y, 4, "c" );
|
|
|
|
i++;
|
|
|
|
source = page->source;
|
|
page_num = page->page_num;
|
|
writable = page->writable;
|
|
contended = page->contended;
|
|
offset = page->offset;
|
|
}
|
|
|
|
/* We expect the next page to have an increased offset */
|
|
offset += MEMORY_PAGE_SIZE;
|
|
}
|
|
}
|
|
|
|
|
|
static void display_bytes( void )
|
|
{
|
|
int x, y;
|
|
char pbuf[36];
|
|
|
|
for( y = 0; y < 8; ++y ) {
|
|
libspectrum_word addr = debugger_memaddr + y * 8;
|
|
|
|
sprintf( pbuf, format_16_bit(), addr );
|
|
widget_printstring_fixed( LC(1) / 8, LR(y) / 8, 7, pbuf );
|
|
widget_printstring( LC(6), LR(y), 5, ":" );
|
|
|
|
for( x = 0; x < 8; ++x ) {
|
|
sprintf( pbuf + x * 4, format_8_bit(),
|
|
readbyte_internal( addr + x ) );
|
|
if( x < 7 )
|
|
strcat( pbuf, " " );
|
|
}
|
|
widget_printstring_fixed( LC(7) / 8, LR(y) / 8, 7, pbuf );
|
|
}
|
|
}
|
|
|
|
|
|
static void display_text( void )
|
|
{
|
|
int x, y;
|
|
char pbuf[8];
|
|
|
|
for( y = 0; y < 8; ++y ) {
|
|
libspectrum_word addr = debugger_memaddr + y * 32;
|
|
|
|
sprintf( pbuf, format_16_bit(), addr );
|
|
widget_printstring_fixed( LC(1) / 8, LR(y) / 8, 7, pbuf );
|
|
widget_printstring( LC(6), LR(y), 5, ":" );
|
|
|
|
for( x = 0; x < 32; ++x )
|
|
widget_printchar_fixed( LC(x + 8) / 8, LR(y) / 8, 7,
|
|
readbyte_internal( addr + x ) );
|
|
}
|
|
}
|
|
|
|
|
|
static void display_disasm( void )
|
|
{
|
|
int y;
|
|
char pbuf[40];
|
|
libspectrum_word addr = debugger_memaddr;
|
|
|
|
for( y = 0; y < 8; ++y ) {
|
|
size_t length;
|
|
char *spc;
|
|
|
|
sprintf( pbuf, format_16_bit(), addr );
|
|
widget_printstring_fixed( LC(1) / 8, LR(y) / 8, 7, pbuf );
|
|
widget_printstring( LC(6), LR(y), 5, ":" );
|
|
|
|
debugger_disassemble( pbuf, sizeof( pbuf ), &length, addr );
|
|
addr += length;
|
|
spc = strchr( pbuf, ' ' );
|
|
if( spc )
|
|
*spc = 0;
|
|
widget_printstring( LC(8), LR(y), 7, pbuf );
|
|
if( spc ) {
|
|
spc += 1 + strspn( spc + 1, " " );
|
|
widget_printstring( LC(12) + 4, LR(y), 7, spc );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void display_breakpts( void )
|
|
{
|
|
GSList *ptr;
|
|
int i = -breakpt_show;
|
|
char pbuf[80], fmt[20];
|
|
|
|
if( i )
|
|
widget_up_arrow( LC(0), LR(0), 7 );
|
|
|
|
for( ptr = debugger_breakpoints; i < 8 && ptr; ptr = ptr->next, ++i ) {
|
|
const debugger_breakpoint *bp = ptr->data;
|
|
|
|
if( i < 0 )
|
|
continue;
|
|
|
|
sprintf( pbuf, "%lu", ( unsigned long )bp->id );
|
|
widget_printstring( LC(1), LR(i), 5, pbuf );
|
|
widget_printstring( LC(6), LR(i), 7,
|
|
debugger_breakpoint_type_abbr[bp->type] );
|
|
|
|
switch ( bp->type ) {
|
|
case DEBUGGER_BREAKPOINT_TYPE_EXECUTE:
|
|
case DEBUGGER_BREAKPOINT_TYPE_READ:
|
|
case DEBUGGER_BREAKPOINT_TYPE_WRITE:
|
|
if( bp->value.address.source == memory_source_any )
|
|
sprintf( pbuf, format_16_bit(), bp->value.address.offset );
|
|
else {
|
|
snprintf( fmt, sizeof( fmt ), "%%s:%s:%s", format_16_bit(),
|
|
format_16_bit() );
|
|
snprintf( pbuf, sizeof( pbuf ), fmt,
|
|
memory_source_description( bp->value.address.source ),
|
|
bp->value.address.page, bp->value.address.offset );
|
|
}
|
|
break;
|
|
|
|
case DEBUGGER_BREAKPOINT_TYPE_PORT_READ:
|
|
case DEBUGGER_BREAKPOINT_TYPE_PORT_WRITE:
|
|
sprintf( fmt, "%s:%s", format_16_bit(), format_16_bit() );
|
|
sprintf( pbuf, fmt, bp->value.port.mask, bp->value.port.port );
|
|
break;
|
|
|
|
case DEBUGGER_BREAKPOINT_TYPE_TIME:
|
|
sprintf( pbuf, "%5d", bp->value.time.tstates );
|
|
break;
|
|
|
|
case DEBUGGER_BREAKPOINT_TYPE_EVENT:
|
|
sprintf( pbuf, "%s:%s", bp->value.event.type, bp->value.event.detail );
|
|
break;
|
|
}
|
|
widget_printstring( LC(10), LR(i), 6, pbuf );
|
|
|
|
sprintf( pbuf, "%lu", ( unsigned long )bp->ignore );
|
|
widget_printstring( LC(18) + 4, LR(i), 7, pbuf );
|
|
|
|
sprintf( pbuf, "%s", debugger_breakpoint_life_abbr[bp->life] );
|
|
widget_printstring( LC(23), LR(i), 7, pbuf );
|
|
|
|
if( bp->condition ) {
|
|
debugger_expression_deparse( pbuf, sizeof( pbuf ), bp->condition );
|
|
widget_printstring( LC(28) + 4, LR(i), 6, pbuf );
|
|
}
|
|
}
|
|
|
|
if( !i )
|
|
widget_printstring( LC(1), LR(0), 5, "(No breakpoints)" );
|
|
else if( ptr )
|
|
widget_down_arrow( LC(0), LC(7), 7 );
|
|
}
|
|
|
|
|
|
static void scroll( int step )
|
|
{
|
|
switch ( display ) {
|
|
case DB_BYTES:
|
|
debugger_memaddr += 8 * step;
|
|
break;
|
|
|
|
case DB_TEXT:
|
|
debugger_memaddr += 32 * step;
|
|
break;
|
|
|
|
case DB_DISASM:
|
|
if( step > 0 )
|
|
for( ; step; --step ) {
|
|
size_t length;
|
|
|
|
debugger_disassemble( NULL, 0, &length, debugger_memaddr );
|
|
debugger_memaddr += length;
|
|
} else
|
|
for( ; step; ++step ) {
|
|
/* For details, see ui/gtk/debugger.c:move_disassembly() */
|
|
size_t i, longest = 1;
|
|
|
|
for( i = 1; i <= 8; ++i ) {
|
|
size_t length;
|
|
|
|
debugger_disassemble( NULL, 0, &length, debugger_memaddr );
|
|
if( length == i )
|
|
longest = i;
|
|
}
|
|
debugger_memaddr -= longest;
|
|
}
|
|
break;
|
|
|
|
case DB_BREAKPT:
|
|
{
|
|
int length = g_slist_length( debugger_breakpoints );
|
|
|
|
breakpt_no += step;
|
|
if( breakpt_no >= length )
|
|
breakpt_no = length - 1;
|
|
if( breakpt_no < 0 )
|
|
breakpt_no = 0;
|
|
if( breakpt_no < breakpt_show )
|
|
breakpt_show = breakpt_no;
|
|
else if( breakpt_no > breakpt_show + 7 )
|
|
breakpt_show = breakpt_no - 7;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
widget_debugger_draw( NULL );
|
|
}
|