mirror of
https://github.com/holub/mame
synced 2025-05-18 03:35:03 +03:00

Hi mamedev, This patch continues deglobalifying the MAME core, this time targeting sound.c. The first two patches adds running_machine to apis in sound.h that lack it (the first patch is generated by the perl script, the second patch fixes some cases it didn't handle well). The last patch then removes the globals in the traditional way. ~aa
3084 lines
94 KiB
C
3084 lines
94 KiB
C
/*********************************************************************
|
|
|
|
debugcpu.c
|
|
|
|
Debugger CPU/memory interface engine.
|
|
|
|
Copyright Nicola Salmoria and the MAME Team.
|
|
Visit http://mamedev.org for licensing and usage restrictions.
|
|
|
|
**********************************************************************
|
|
|
|
Future work:
|
|
|
|
- enable history to be enabled/disabled to improve performance
|
|
|
|
*********************************************************************/
|
|
|
|
#include "osdepend.h"
|
|
#include "driver.h"
|
|
#include "debugcpu.h"
|
|
#include "debugcmd.h"
|
|
#include "debugcmt.h"
|
|
#include "debugcon.h"
|
|
#include "express.h"
|
|
#include "debugvw.h"
|
|
#include "debugger.h"
|
|
#include "uiinput.h"
|
|
#include "machine/eeprom.h"
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
#define NUM_TEMP_VARIABLES 10
|
|
|
|
enum
|
|
{
|
|
EXECUTION_STATE_STOPPED,
|
|
EXECUTION_STATE_RUNNING
|
|
};
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
TYPE DEFINITIONS
|
|
***************************************************************************/
|
|
|
|
/* in mame.h: typedef struct _debugcpu_private debugcpu_private; */
|
|
struct _debugcpu_private
|
|
{
|
|
const device_config *livecpu;
|
|
const device_config *visiblecpu;
|
|
const device_config *breakcpu;
|
|
|
|
FILE * source_file; /* script source file */
|
|
|
|
symbol_table * symtable; /* global symbol table */
|
|
|
|
UINT8 within_instruction_hook;
|
|
UINT8 vblank_occurred;
|
|
UINT8 memory_modified;
|
|
UINT8 debugger_access;
|
|
|
|
int execution_state;
|
|
|
|
UINT32 bpindex;
|
|
UINT32 wpindex;
|
|
|
|
UINT64 wpdata;
|
|
UINT64 wpaddr;
|
|
UINT64 tempvar[NUM_TEMP_VARIABLES];
|
|
|
|
osd_ticks_t last_periodic_update_time;
|
|
};
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
FUNCTION PROTOTYPES
|
|
***************************************************************************/
|
|
|
|
/* internal helpers */
|
|
static void debug_cpu_exit(running_machine *machine);
|
|
static void on_vblank(const device_config *device, void *param, int vblank_state);
|
|
static void reset_transient_flags(running_machine *machine);
|
|
static void compute_debug_flags(const device_config *device);
|
|
static void perform_trace(cpu_debug_data *info);
|
|
static void prepare_for_step_overout(cpu_debug_data *info);
|
|
static void process_source_file(running_machine *machine);
|
|
static void breakpoint_update_flags(cpu_debug_data *info);
|
|
static void breakpoint_check(running_machine *machine, cpu_debug_data *info, offs_t pc);
|
|
static void watchpoint_update_flags(const address_space *space);
|
|
static void watchpoint_check(const address_space *space, int type, offs_t address, UINT64 value_to_write, UINT64 mem_mask);
|
|
static void check_hotspots(const address_space *space, offs_t address);
|
|
static UINT32 dasm_wrapped(const device_config *device, char *buffer, offs_t pc);
|
|
|
|
/* expression handlers */
|
|
static UINT64 expression_read_memory(void *param, const char *name, int space, UINT32 address, int size);
|
|
static UINT64 expression_read_address_space(const address_space *space, offs_t address, int size);
|
|
static UINT64 expression_read_program_direct(const address_space *space, int opcode, offs_t address, int size);
|
|
static UINT64 expression_read_memory_region(running_machine *machine, const char *rgntag, offs_t address, int size);
|
|
static UINT64 expression_read_eeprom(running_machine *machine, offs_t address, int size);
|
|
static void expression_write_memory(void *param, const char *name, int space, UINT32 address, int size, UINT64 data);
|
|
static void expression_write_address_space(const address_space *space, offs_t address, int size, UINT64 data);
|
|
static void expression_write_program_direct(const address_space *space, int opcode, offs_t address, int size, UINT64 data);
|
|
static void expression_write_memory_region(running_machine *machine, const char *rgntag, offs_t address, int size, UINT64 data);
|
|
static void expression_write_eeprom(running_machine *machine, offs_t address, int size, UINT64 data);
|
|
static EXPRERR expression_validate(void *param, const char *name, int space);
|
|
|
|
/* variable getters/setters */
|
|
static UINT64 get_wpaddr(void *globalref, void *ref);
|
|
static UINT64 get_wpdata(void *globalref, void *ref);
|
|
static UINT64 get_cpunum(void *globalref, void *ref);
|
|
static UINT64 get_tempvar(void *globalref, void *ref);
|
|
static void set_tempvar(void *globalref, void *ref, UINT64 value);
|
|
static UINT64 get_beamx(void *globalref, void *ref);
|
|
static UINT64 get_beamy(void *globalref, void *ref);
|
|
static UINT64 get_frame(void *globalref, void *ref);
|
|
static UINT64 get_current_pc(void *globalref, void *ref);
|
|
static UINT64 get_cycles(void *globalref, void *ref);
|
|
static UINT64 get_logunmap(void *globalref, void *ref);
|
|
static void set_logunmap(void *globalref, void *ref, UINT64 value);
|
|
static UINT64 get_cpu_reg(void *globalref, void *ref);
|
|
static void set_cpu_reg(void *globalref, void *ref, UINT64 value);
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
GLOBAL CONSTANTS
|
|
***************************************************************************/
|
|
|
|
const express_callbacks debug_expression_callbacks =
|
|
{
|
|
expression_read_memory,
|
|
expression_write_memory,
|
|
expression_validate
|
|
};
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INITIALIZATION AND CLEANUP
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_init - initialize the CPU
|
|
information for debugging
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_init(running_machine *machine)
|
|
{
|
|
const device_config *first_screen = video_screen_first(machine->config);
|
|
debugcpu_private *global;
|
|
const device_config *cpu;
|
|
int regnum;
|
|
|
|
/* allocate and reset globals */
|
|
machine->debugcpu_data = global = auto_alloc_clear(machine, debugcpu_private);
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
global->bpindex = 1;
|
|
global->wpindex = 1;
|
|
|
|
/* create a global symbol table */
|
|
global->symtable = symtable_alloc(NULL, machine);
|
|
|
|
/* add "wpaddr", "wpdata", "cycles", "cpunum", "logunmap" to the global symbol table */
|
|
symtable_add_register(global->symtable, "wpaddr", NULL, get_wpaddr, NULL);
|
|
symtable_add_register(global->symtable, "wpdata", NULL, get_wpdata, NULL);
|
|
symtable_add_register(global->symtable, "cpunum", NULL, get_cpunum, NULL);
|
|
symtable_add_register(global->symtable, "beamx", (void *)first_screen, get_beamx, NULL);
|
|
symtable_add_register(global->symtable, "beamy", (void *)first_screen, get_beamy, NULL);
|
|
symtable_add_register(global->symtable, "frame", (void *)first_screen, get_frame, NULL);
|
|
|
|
/* add the temporary variables to the global symbol table */
|
|
for (regnum = 0; regnum < NUM_TEMP_VARIABLES; regnum++)
|
|
{
|
|
char symname[10];
|
|
sprintf(symname, "temp%d", regnum);
|
|
symtable_add_register(global->symtable, symname, &global->tempvar[regnum], get_tempvar, set_tempvar);
|
|
}
|
|
|
|
/* loop over CPUs and build up their info */
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
{
|
|
cpu_class_header *classheader = cpu_get_class_header(cpu);
|
|
cpu_debug_data *info;
|
|
|
|
/* allocate some information */
|
|
info = auto_alloc_clear(machine, cpu_debug_data);
|
|
classheader->debug = info;
|
|
|
|
/* reset the PC data */
|
|
info->flags = DEBUG_FLAG_OBSERVING | DEBUG_FLAG_HISTORY;
|
|
info->device = cpu;
|
|
info->opwidth = cpu_get_min_opcode_bytes(info->device);
|
|
|
|
/* fetch the memory accessors */
|
|
info->read = (cpu_read_func)device_get_info_fct(info->device, CPUINFO_FCT_READ);
|
|
info->write = (cpu_write_func)device_get_info_fct(info->device, CPUINFO_FCT_WRITE);
|
|
info->readop = (cpu_readop_func)device_get_info_fct(info->device, CPUINFO_FCT_READOP);
|
|
info->translate = (cpu_translate_func)device_get_info_fct(info->device, CPUINFO_FCT_TRANSLATE);
|
|
info->disassemble = (cpu_disassemble_func)device_get_info_fct(info->device, CPUINFO_FCT_DISASSEMBLE);
|
|
|
|
/* allocate a symbol table */
|
|
info->symtable = symtable_alloc(global->symtable, (void *)cpu);
|
|
|
|
/* add a global symbol for the current instruction pointer */
|
|
symtable_add_register(info->symtable, "cycles", NULL, get_cycles, NULL);
|
|
if (cpu->space[ADDRESS_SPACE_PROGRAM] != NULL)
|
|
symtable_add_register(info->symtable, "logunmap", (void *)cpu->space[ADDRESS_SPACE_PROGRAM], get_logunmap, set_logunmap);
|
|
if (cpu->space[ADDRESS_SPACE_DATA] != NULL)
|
|
symtable_add_register(info->symtable, "logunmapd", (void *)cpu->space[ADDRESS_SPACE_DATA], get_logunmap, set_logunmap);
|
|
if (cpu->space[ADDRESS_SPACE_IO] != NULL)
|
|
symtable_add_register(info->symtable, "logunmapi", (void *)cpu->space[ADDRESS_SPACE_IO], get_logunmap, set_logunmap);
|
|
|
|
/* add all registers into it */
|
|
for (regnum = 0; regnum < MAX_REGS; regnum++)
|
|
{
|
|
const char *str = cpu_get_reg_string(info->device, regnum);
|
|
const char *colon;
|
|
char symname[256];
|
|
int charnum;
|
|
|
|
/* skip if we don't get a valid string, or one without a colon */
|
|
if (str == NULL)
|
|
continue;
|
|
if (str[0] == '~')
|
|
str++;
|
|
colon = strchr(str, ':');
|
|
if (colon == NULL)
|
|
continue;
|
|
|
|
/* strip all spaces from the name and convert to lowercase */
|
|
for (charnum = 0; charnum < sizeof(symname) - 1 && str < colon; str++)
|
|
if (!isspace((UINT8)*str))
|
|
symname[charnum++] = tolower((UINT8)*str);
|
|
symname[charnum] = 0;
|
|
|
|
/* add the symbol to the table */
|
|
symtable_add_register(info->symtable, symname, (void *)(FPTR)regnum, get_cpu_reg, set_cpu_reg);
|
|
}
|
|
|
|
/* if no curpc, add one */
|
|
if (symtable_find(info->symtable, "curpc") == NULL)
|
|
symtable_add_register(info->symtable, "curpc", NULL, get_current_pc, 0);
|
|
}
|
|
|
|
/* first CPU is visible by default */
|
|
global->visiblecpu = machine->cpu[0];
|
|
|
|
/* add callback for breaking on VBLANK */
|
|
if (machine->primary_screen != NULL)
|
|
video_screen_register_vblank_callback(machine->primary_screen, on_vblank, NULL);
|
|
|
|
add_exit_callback(machine, debug_cpu_exit);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_flush_traces - flushes all traces;
|
|
this is useful if a trace is going on when we
|
|
fatalerror
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_flush_traces(running_machine *machine)
|
|
{
|
|
const device_config *cpu;
|
|
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(cpu);
|
|
|
|
/* this can be called on exit even when no debugging is enabled, so
|
|
make sure the info is valid before proceeding */
|
|
if (info != NULL && info->trace.file != NULL)
|
|
fflush(info->trace.file);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
DEBUGGING STATUS AND INFORMATION
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
cpu_get_visible_cpu - return the visible CPU
|
|
device (the one that commands should apply to)
|
|
-------------------------------------------------*/
|
|
|
|
const device_config *debug_cpu_get_visible_cpu(running_machine *machine)
|
|
{
|
|
return machine->debugcpu_data->visiblecpu;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_within_instruction_hook - true if
|
|
the debugger is currently live
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_within_instruction_hook(running_machine *machine)
|
|
{
|
|
return machine->debugcpu_data->within_instruction_hook;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_is_stopped - return TRUE if the
|
|
current execution state is stopped
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_is_stopped(running_machine *machine)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
return (global != NULL) ? (global->execution_state == EXECUTION_STATE_STOPPED) : FALSE;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
SYMBOL TABLE INTERFACES
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_get_global_symtable - return the
|
|
global symbol table
|
|
-------------------------------------------------*/
|
|
|
|
symbol_table *debug_cpu_get_global_symtable(running_machine *machine)
|
|
{
|
|
return machine->debugcpu_data->symtable;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_get_visible_symtable - return the
|
|
locally-visible symbol table
|
|
-------------------------------------------------*/
|
|
|
|
symbol_table *debug_cpu_get_visible_symtable(running_machine *machine)
|
|
{
|
|
return cpu_get_debug_data(machine->debugcpu_data->visiblecpu)->symtable;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_get_symtable - return a specific
|
|
CPU's symbol table
|
|
-------------------------------------------------*/
|
|
|
|
symbol_table *debug_cpu_get_symtable(const device_config *device)
|
|
{
|
|
return cpu_get_debug_data(device)->symtable;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
MEMORY AND DISASSEMBLY HELPERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_translate - return the physical
|
|
address corresponding to the given logical
|
|
address
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_translate(const address_space *space, int intention, offs_t *address)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
if (info->translate != NULL)
|
|
return (*info->translate)(space->cpu, space->spacenum, intention, address);
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_disassemble - disassemble a line at
|
|
a given PC on a given CPU
|
|
-------------------------------------------------*/
|
|
|
|
offs_t debug_cpu_disassemble(const device_config *device, char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
offs_t result = 0;
|
|
|
|
/* check for disassembler override */
|
|
if (info->dasm_override != NULL)
|
|
result = (*info->dasm_override)(device, buffer, pc, oprom, opram);
|
|
|
|
/* if we have a disassembler, run it */
|
|
if (result == 0 && info->disassemble != NULL)
|
|
result = (*info->disassemble)(device, buffer, pc, oprom, opram);
|
|
|
|
/* if we still have nothing, output vanilla bytes */
|
|
if (result == 0)
|
|
{
|
|
result = cpu_get_min_opcode_bytes(device);
|
|
switch (result)
|
|
{
|
|
case 1:
|
|
default:
|
|
sprintf(buffer, "$%02X", *(UINT8 *)oprom);
|
|
break;
|
|
|
|
case 2:
|
|
sprintf(buffer, "$%04X", *(UINT16 *)oprom);
|
|
break;
|
|
|
|
case 4:
|
|
sprintf(buffer, "$%08X", *(UINT32 *)oprom);
|
|
break;
|
|
|
|
case 8:
|
|
sprintf(buffer, "$%08X%08X", (UINT32)(*(UINT64 *)oprom >> 32), (UINT32)(*(UINT64 *)oprom >> 0));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* make sure we get good results */
|
|
assert((result & DASMFLAG_LENGTHMASK) != 0);
|
|
#ifdef MAME_DEBUG
|
|
{
|
|
const address_space *space = cpu_get_address_space(device, ADDRESS_SPACE_PROGRAM);
|
|
int bytes = memory_address_to_byte(space, result & DASMFLAG_LENGTHMASK);
|
|
assert(bytes >= cpu_get_min_opcode_bytes(device));
|
|
assert(bytes <= cpu_get_max_opcode_bytes(device));
|
|
(void) bytes; /* appease compiler */
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_set_dasm_override - set an override
|
|
handler for disassembly
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_set_dasm_override(const device_config *device, cpu_disassemble_func dasm_override)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
if (info != NULL)
|
|
info->dasm_override = dasm_override;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CORE DEBUGGER HOOKS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_start_hook - the CPU execution
|
|
system calls this hook before beginning
|
|
execution for the given CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_start_hook(const device_config *device, attotime endtime)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
assert((device->machine->debug_flags & DEBUG_FLAG_ENABLED) != 0);
|
|
|
|
/* stash a pointer to the current live CPU */
|
|
assert(global->livecpu == NULL);
|
|
global->livecpu = device;
|
|
|
|
/* update the target execution end time */
|
|
info->endexectime = endtime;
|
|
|
|
/* if we're running, do some periodic updating */
|
|
if (global->execution_state != EXECUTION_STATE_STOPPED)
|
|
{
|
|
/* check for periodic updates */
|
|
if (device == global->visiblecpu && osd_ticks() > global->last_periodic_update_time + osd_ticks_per_second()/4)
|
|
{
|
|
debug_view_update_all(device->machine);
|
|
debug_view_flush_updates(device->machine);
|
|
global->last_periodic_update_time = osd_ticks();
|
|
}
|
|
|
|
/* check for pending breaks */
|
|
else if (device == global->breakcpu)
|
|
{
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
global->breakcpu = NULL;
|
|
}
|
|
|
|
/* if a VBLANK occurred, check on things */
|
|
if (global->vblank_occurred)
|
|
{
|
|
global->vblank_occurred = FALSE;
|
|
|
|
/* if we were waiting for a VBLANK, signal it now */
|
|
if ((info->flags & DEBUG_FLAG_STOP_VBLANK) != 0)
|
|
{
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
debug_console_printf(device->machine, "Stopped at VBLANK\n");
|
|
}
|
|
|
|
/* check for debug keypresses */
|
|
else if (ui_input_pressed(device->machine, IPT_UI_DEBUG_BREAK))
|
|
debug_cpu_halt_on_next_instruction(global->visiblecpu, "User-initiated break\n");
|
|
}
|
|
}
|
|
|
|
/* recompute the debugging mode */
|
|
compute_debug_flags(device);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_stop_hook - the CPU execution
|
|
system calls this hook when ending execution
|
|
for the given CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_stop_hook(const device_config *device)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
assert(global->livecpu == device);
|
|
|
|
/* if we're stopping on a context switch, handle it now */
|
|
if (info->flags & DEBUG_FLAG_STOP_CONTEXT)
|
|
{
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
reset_transient_flags(device->machine);
|
|
}
|
|
|
|
/* clear the live CPU */
|
|
global->livecpu = NULL;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_interrupt_hook - called when an
|
|
interrupt is acknowledged
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_interrupt_hook(const device_config *device, int irqline)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
/* see if this matches a pending interrupt request */
|
|
if (info != NULL && (info->flags & DEBUG_FLAG_STOP_INTERRUPT) != 0 && (info->stopirq == -1 || info->stopirq == irqline))
|
|
{
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
debug_console_printf(device->machine, "Stopped on interrupt (CPU '%s', IRQ %d)\n", device->tag, irqline);
|
|
compute_debug_flags(device);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_exception_hook - called when an
|
|
exception is generated
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_exception_hook(const device_config *device, int exception)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
/* see if this matches a pending interrupt request */
|
|
if ((info->flags & DEBUG_FLAG_STOP_EXCEPTION) != 0 && (info->stopexception == -1 || info->stopexception == exception))
|
|
{
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
debug_console_printf(device->machine, "Stopped on exception (CPU '%s', exception %d)\n", device->tag, exception);
|
|
compute_debug_flags(device);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_instruction_hook - called by the
|
|
CPU cores before executing each instruction
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_instruction_hook(const device_config *device, offs_t curpc)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
/* note that we are in the debugger code */
|
|
global->within_instruction_hook = TRUE;
|
|
|
|
/* update the history */
|
|
info->pc_history[info->pc_history_index++ % DEBUG_HISTORY_SIZE] = curpc;
|
|
|
|
/* are we tracing? */
|
|
if (info->flags & DEBUG_FLAG_TRACING_ANY)
|
|
perform_trace(info);
|
|
|
|
/* per-instruction hook? */
|
|
if (global->execution_state != EXECUTION_STATE_STOPPED && (info->flags & DEBUG_FLAG_HOOKED) != 0 && (*info->instrhook)(device, curpc))
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
|
|
/* handle single stepping */
|
|
if (global->execution_state != EXECUTION_STATE_STOPPED && (info->flags & DEBUG_FLAG_STEPPING_ANY) != 0)
|
|
{
|
|
/* is this an actual step? */
|
|
if (info->stepaddr == ~0 || curpc == info->stepaddr)
|
|
{
|
|
/* decrement the count and reset the breakpoint */
|
|
info->stepsleft--;
|
|
info->stepaddr = ~0;
|
|
|
|
/* if we hit 0, stop */
|
|
if (info->stepsleft == 0)
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
|
|
/* update every 100 steps until we are within 200 of the end */
|
|
else if ((info->flags & DEBUG_FLAG_STEPPING_OUT) == 0 && (info->stepsleft < 200 || info->stepsleft % 100 == 0))
|
|
{
|
|
debug_view_update_all(device->machine);
|
|
debug_view_flush_updates(device->machine);
|
|
debugger_refresh_display(device->machine);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* handle breakpoints */
|
|
if (global->execution_state != EXECUTION_STATE_STOPPED && (info->flags & (DEBUG_FLAG_STOP_TIME | DEBUG_FLAG_STOP_PC | DEBUG_FLAG_LIVE_BP)) != 0)
|
|
{
|
|
/* see if we hit a target time */
|
|
if ((info->flags & DEBUG_FLAG_STOP_TIME) != 0 && attotime_compare(timer_get_time(device->machine), info->stoptime) >= 0)
|
|
{
|
|
debug_console_printf(device->machine, "Stopped at time interval %.1g\n", attotime_to_double(timer_get_time(device->machine)));
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
}
|
|
|
|
/* check the temp running breakpoint and break if we hit it */
|
|
else if ((info->flags & DEBUG_FLAG_STOP_PC) != 0 && info->stopaddr == curpc)
|
|
{
|
|
debug_console_printf(device->machine, "Stopped at temporary breakpoint %X on CPU '%s'\n", info->stopaddr, device->tag);
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
}
|
|
|
|
/* check for execution breakpoints */
|
|
else if ((info->flags & DEBUG_FLAG_LIVE_BP) != 0)
|
|
breakpoint_check(device->machine, info, curpc);
|
|
}
|
|
|
|
/* if we are supposed to halt, do it now */
|
|
if (global->execution_state == EXECUTION_STATE_STOPPED)
|
|
{
|
|
int firststop = TRUE;
|
|
|
|
/* reset any transient state */
|
|
reset_transient_flags(device->machine);
|
|
global->breakcpu = NULL;
|
|
|
|
/* remember the last visible CPU in the debugger */
|
|
global->visiblecpu = device;
|
|
|
|
/* update all views */
|
|
debug_view_update_all(device->machine);
|
|
debugger_refresh_display(device->machine);
|
|
|
|
/* wait for the debugger; during this time, disable sound output */
|
|
sound_mute(device->machine, TRUE);
|
|
while (global->execution_state == EXECUTION_STATE_STOPPED)
|
|
{
|
|
/* flush any pending updates before waiting again */
|
|
debug_view_flush_updates(device->machine);
|
|
|
|
/* clear the memory modified flag and wait */
|
|
global->memory_modified = FALSE;
|
|
osd_wait_for_debugger(device, firststop);
|
|
firststop = FALSE;
|
|
|
|
/* if something modified memory, update the screen */
|
|
if (global->memory_modified)
|
|
{
|
|
debug_view_update_type(device->machine, DVT_DISASSEMBLY);
|
|
debugger_refresh_display(device->machine);
|
|
}
|
|
|
|
/* check for commands in the source file */
|
|
process_source_file(device->machine);
|
|
|
|
/* if an event got scheduled, resume */
|
|
if (mame_is_scheduled_event_pending(device->machine))
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
sound_mute(device->machine, FALSE);
|
|
|
|
/* remember the last visible CPU in the debugger */
|
|
global->visiblecpu = device;
|
|
}
|
|
|
|
/* handle step out/over on the instruction we are about to execute */
|
|
if ((info->flags & (DEBUG_FLAG_STEPPING_OVER | DEBUG_FLAG_STEPPING_OUT)) != 0 && info->stepaddr == ~0)
|
|
prepare_for_step_overout(info);
|
|
|
|
/* no longer in debugger code */
|
|
global->within_instruction_hook = FALSE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_memory_read_hook - the memory system
|
|
calls this hook when watchpoints are enabled
|
|
and a memory read happens
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_memory_read_hook(const address_space *space, offs_t address, UINT64 mem_mask)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
|
|
/* check watchpoints */
|
|
watchpoint_check(space, WATCHPOINT_READ, address, 0, mem_mask);
|
|
|
|
/* check hotspots */
|
|
if (info->hotspots != NULL)
|
|
check_hotspots(space, address);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_memory_write_hook - the memory
|
|
system calls this hook when watchpoints are
|
|
enabled and a memory write happens
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_memory_write_hook(const address_space *space, offs_t address, UINT64 data, UINT64 mem_mask)
|
|
{
|
|
watchpoint_check(space, WATCHPOINT_WRITE, address, data, mem_mask);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
EXECUTION CONTROL
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_halt_on_next_instruction - halt in
|
|
the debugger on the next instruction
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_halt_on_next_instruction(const device_config *device, const char *fmt, ...)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
va_list arg;
|
|
|
|
/* if something is pending on this CPU already, ignore this request */
|
|
if (info != NULL && device == global->breakcpu)
|
|
return;
|
|
|
|
/* output the message to the console */
|
|
va_start(arg, fmt);
|
|
debug_console_vprintf(device->machine, fmt, arg);
|
|
va_end(arg);
|
|
|
|
/* if we are live, stop now, otherwise note that we want to break there */
|
|
if (device == global->livecpu)
|
|
{
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
if (global->livecpu != NULL)
|
|
compute_debug_flags(global->livecpu);
|
|
}
|
|
else
|
|
global->breakcpu = device;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_ignore_cpu - ignore/observe a given
|
|
CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_ignore_cpu(const device_config *device, int ignore)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
if (ignore)
|
|
info->flags &= ~DEBUG_FLAG_OBSERVING;
|
|
else
|
|
info->flags |= DEBUG_FLAG_OBSERVING;
|
|
|
|
if (device == global->livecpu && ignore)
|
|
debug_cpu_next_cpu(device->machine);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_single_step - single step the visible
|
|
CPU past the requested number of instructions
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_single_step(running_machine *machine, int numsteps)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->stepsleft = numsteps;
|
|
info->stepaddr = ~0;
|
|
info->flags |= DEBUG_FLAG_STEPPING;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_single_step_over - single step the
|
|
visible over the requested number of
|
|
instructions
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_single_step_over(running_machine *machine, int numsteps)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->stepsleft = numsteps;
|
|
info->stepaddr = ~0;
|
|
info->flags |= DEBUG_FLAG_STEPPING_OVER;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_single_step_out - single step the
|
|
visible CPU out of the current function
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_single_step_out(running_machine *machine)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->stepsleft = 100;
|
|
info->stepaddr = ~0;
|
|
info->flags |= DEBUG_FLAG_STEPPING_OUT;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_go - execute the visible CPU until
|
|
it hits the given address
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_go(running_machine *machine, offs_t targetpc)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->stopaddr = targetpc;
|
|
info->flags |= DEBUG_FLAG_STOP_PC;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_go_vblank - execute until the next
|
|
VBLANK
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_go_vblank(running_machine *machine)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
global->vblank_occurred = FALSE;
|
|
info->flags |= DEBUG_FLAG_STOP_VBLANK;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_go_interrupt - execute until the
|
|
specified interrupt fires on the visible CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_go_interrupt(running_machine *machine, int irqline)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->stopirq = irqline;
|
|
info->flags |= DEBUG_FLAG_STOP_INTERRUPT;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_go_exception - execute until the
|
|
specified exception fires on the visible CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_go_exception(running_machine *machine, int exception)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->stopexception = exception;
|
|
info->flags |= DEBUG_FLAG_STOP_EXCEPTION;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_go_milliseconds - execute until the
|
|
specified delay elapses
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_go_milliseconds(running_machine *machine, UINT64 milliseconds)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->stoptime = attotime_add(timer_get_time(machine), ATTOTIME_IN_MSEC(milliseconds));
|
|
info->flags |= DEBUG_FLAG_STOP_TIME;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_next_cpu - execute until we hit
|
|
the next CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_next_cpu(running_machine *machine)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(global->visiblecpu);
|
|
|
|
assert(info != NULL);
|
|
|
|
info->flags |= DEBUG_FLAG_STOP_CONTEXT;
|
|
global->execution_state = EXECUTION_STATE_RUNNING;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
BREAKPOINTS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_breakpoint_set - set a new
|
|
breakpoint, returning its index
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_breakpoint_set(const device_config *device, offs_t address, parsed_expression *condition, const char *action)
|
|
{
|
|
debugcpu_private *global = device->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
debug_cpu_breakpoint *bp;
|
|
|
|
assert_always(device != NULL, "debug_cpu_breakpoint_set() called with invalid cpu!");
|
|
|
|
/* allocate breakpoint */
|
|
bp = alloc_or_die(debug_cpu_breakpoint);
|
|
bp->index = global->bpindex++;
|
|
bp->enabled = TRUE;
|
|
bp->address = address;
|
|
bp->condition = condition;
|
|
bp->action = NULL;
|
|
if (action != NULL)
|
|
{
|
|
bp->action = alloc_array_or_die(char, strlen(action) + 1);
|
|
strcpy(bp->action, action);
|
|
}
|
|
|
|
/* hook us in */
|
|
bp->next = info->bplist;
|
|
info->bplist = bp;
|
|
|
|
/* ensure the live breakpoint flag is set */
|
|
breakpoint_update_flags(info);
|
|
return bp->index;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_breakpoint_clear - clear a
|
|
breakpoint by index
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_breakpoint_clear(running_machine *machine, int bpnum)
|
|
{
|
|
debug_cpu_breakpoint *bp, *pbp;
|
|
const device_config *cpu;
|
|
|
|
/* loop over CPUs and find the requested breakpoint */
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(cpu);
|
|
for (pbp = NULL, bp = info->bplist; bp != NULL; pbp = bp, bp = bp->next)
|
|
if (bp->index == bpnum)
|
|
{
|
|
/* unlink us from the list */
|
|
if (pbp == NULL)
|
|
info->bplist = bp->next;
|
|
else
|
|
pbp->next = bp->next;
|
|
|
|
/* free the memory */
|
|
if (bp->condition != NULL)
|
|
expression_free(bp->condition);
|
|
if (bp->action != NULL)
|
|
free(bp->action);
|
|
free(bp);
|
|
|
|
/* update the flags */
|
|
breakpoint_update_flags(info);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* we didn't find it; return an error */
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_breakpoint_enable - enable/disable
|
|
a breakpoint by index
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_breakpoint_enable(running_machine *machine, int bpnum, int enable)
|
|
{
|
|
debug_cpu_breakpoint *bp;
|
|
const device_config *cpu;
|
|
|
|
/* loop over CPUs and find the requested breakpoint */
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(cpu);
|
|
for (bp = info->bplist; bp != NULL; bp = bp->next)
|
|
if (bp->index == bpnum)
|
|
{
|
|
bp->enabled = (enable != 0);
|
|
breakpoint_update_flags(info);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
WATCHPOINTS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_watchpoint_set - set a new
|
|
watchpoint, returning its index
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_watchpoint_set(const address_space *space, int type, offs_t address, offs_t length, parsed_expression *condition, const char *action)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
debug_cpu_watchpoint *wp = alloc_or_die(debug_cpu_watchpoint);
|
|
|
|
/* fill in the structure */
|
|
wp->index = global->wpindex++;
|
|
wp->enabled = TRUE;
|
|
wp->type = type;
|
|
wp->address = memory_address_to_byte(space, address) & space->bytemask;
|
|
wp->length = memory_address_to_byte(space, length);
|
|
wp->condition = condition;
|
|
wp->action = NULL;
|
|
if (action != NULL)
|
|
{
|
|
wp->action = alloc_array_or_die(char, strlen(action) + 1);
|
|
strcpy(wp->action, action);
|
|
}
|
|
|
|
/* hook us in */
|
|
wp->next = info->wplist[space->spacenum];
|
|
info->wplist[space->spacenum] = wp;
|
|
|
|
watchpoint_update_flags(space);
|
|
|
|
return wp->index;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_watchpoint_clear - clear a
|
|
watchpoint by index
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_watchpoint_clear(running_machine *machine, int wpnum)
|
|
{
|
|
debug_cpu_watchpoint *wp, *pwp;
|
|
const device_config *cpu;
|
|
int spacenum;
|
|
|
|
/* loop over CPUs and find the requested watchpoint */
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(cpu);
|
|
|
|
for (spacenum = 0; spacenum < ADDRESS_SPACES; spacenum++)
|
|
for (pwp = NULL, wp = info->wplist[spacenum]; wp != NULL; pwp = wp, wp = wp->next)
|
|
if (wp->index == wpnum)
|
|
{
|
|
/* unlink us from the list */
|
|
if (pwp == NULL)
|
|
info->wplist[spacenum] = wp->next;
|
|
else
|
|
pwp->next = wp->next;
|
|
|
|
/* free the memory */
|
|
if (wp->condition != NULL)
|
|
expression_free(wp->condition);
|
|
if (wp->action != NULL)
|
|
free(wp->action);
|
|
free(wp);
|
|
|
|
watchpoint_update_flags(cpu_get_address_space(cpu, spacenum));
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* we didn't find it; return an error */
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_watchpoint_enable - enable/disable a
|
|
watchpoint by index
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_watchpoint_enable(running_machine *machine, int wpnum, int enable)
|
|
{
|
|
debug_cpu_watchpoint *wp;
|
|
const device_config *cpu;
|
|
int spacenum;
|
|
|
|
/* loop over CPUs and address spaces and find the requested watchpoint */
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(cpu);
|
|
|
|
for (spacenum = 0; spacenum < ADDRESS_SPACES; spacenum++)
|
|
for (wp = info->wplist[spacenum]; wp != NULL; wp = wp->next)
|
|
if (wp->index == wpnum)
|
|
{
|
|
wp->enabled = (enable != 0);
|
|
watchpoint_update_flags(cpu_get_address_space(cpu, spacenum));
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
MISC DEBUGGER FUNCTIONS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_source_script - specifies a debug
|
|
command script to execute
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_source_script(running_machine *machine, const char *file)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
|
|
/* close any existing source file */
|
|
if (global->source_file != NULL)
|
|
{
|
|
fclose(global->source_file);
|
|
global->source_file = NULL;
|
|
}
|
|
|
|
/* open a new one if requested */
|
|
if (file != NULL)
|
|
{
|
|
global->source_file = fopen(file, "r");
|
|
if (!global->source_file)
|
|
{
|
|
if (mame_get_phase(machine) == MAME_PHASE_RUNNING)
|
|
debug_console_printf(machine, "Cannot open command file '%s'\n", file);
|
|
else
|
|
fatalerror("Cannot open command file '%s'", file);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_trace - trace execution of a given
|
|
CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_trace(const device_config *device, FILE *file, int trace_over, const char *action)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
/* close existing files and delete expressions */
|
|
if (info->trace.file != NULL)
|
|
fclose(info->trace.file);
|
|
info->trace.file = NULL;
|
|
|
|
if (info->trace.action != NULL)
|
|
free(info->trace.action);
|
|
info->trace.action = NULL;
|
|
|
|
/* open any new files */
|
|
info->trace.file = file;
|
|
info->trace.action = NULL;
|
|
info->trace.trace_over_target = ~0;
|
|
if (action != NULL)
|
|
{
|
|
info->trace.action = alloc_array_or_die(char, strlen(action) + 1);
|
|
strcpy(info->trace.action, action);
|
|
}
|
|
|
|
/* update flags */
|
|
if (info->trace.file != NULL)
|
|
info->flags |= trace_over ? DEBUG_FLAG_TRACING_OVER : DEBUG_FLAG_TRACING;
|
|
else
|
|
info->flags &= ~DEBUG_FLAG_TRACING_ANY;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_trace_printf - output data into the
|
|
given CPU's tracefile, if tracing
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_trace_printf(const device_config *device, const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
if (info->trace.file)
|
|
{
|
|
va_start(va, fmt);
|
|
vfprintf(info->trace.file, fmt, va);
|
|
va_end(va);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_set_instruction_hook - set a hook to
|
|
be called on each instruction for a given CPU
|
|
-------------------------------------------------*/
|
|
|
|
void debug_cpu_set_instruction_hook(const device_config *device, debug_instruction_hook_func hook)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
/* set the hook and also the CPU's flag for fast knowledge of the hook */
|
|
info->instrhook = hook;
|
|
if (hook != NULL)
|
|
info->flags |= DEBUG_FLAG_HOOKED;
|
|
else
|
|
info->flags &= ~DEBUG_FLAG_HOOKED;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_hotspot_track - enable/disable
|
|
tracking of hotspots
|
|
-------------------------------------------------*/
|
|
|
|
int debug_cpu_hotspot_track(const device_config *device, int numspots, int threshhold)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
|
|
/* if we already have tracking info, kill it */
|
|
if (info->hotspots)
|
|
free(info->hotspots);
|
|
info->hotspots = NULL;
|
|
|
|
/* only start tracking if we have a non-zero count */
|
|
if (numspots > 0)
|
|
{
|
|
/* allocate memory for hotspots */
|
|
info->hotspots = alloc_array_or_die(debug_hotspot_entry, numspots);
|
|
memset(info->hotspots, 0xff, sizeof(*info->hotspots) * numspots);
|
|
|
|
/* fill in the info */
|
|
info->hotspot_count = numspots;
|
|
info->hotspot_threshhold = threshhold;
|
|
}
|
|
|
|
watchpoint_update_flags(cpu_get_address_space(device, ADDRESS_SPACE_PROGRAM));
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
DEBUGGER MEMORY ACCESSORS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_read_byte - return a byte from the
|
|
the specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
UINT8 debug_read_byte(const address_space *space, offs_t address, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
UINT64 custom;
|
|
UINT8 result;
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, return 0xff */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_READ_DEBUG, &address))
|
|
result = 0xff;
|
|
|
|
/* if there is a custom read handler, and it returns TRUE, use that value */
|
|
else if (info->read != NULL && (*info->read)(space->cpu, space->spacenum, address, 1, &custom))
|
|
result = custom;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
result = memory_read_byte(space, address);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_read_word - return a word from the
|
|
specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
UINT16 debug_read_word(const address_space *space, offs_t address, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
UINT16 result;
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* if this is misaligned read, or if there are no word readers, just read two bytes */
|
|
if ((address & 1) != 0)
|
|
{
|
|
UINT8 byte0 = debug_read_byte(space, address + 0, apply_translation);
|
|
UINT8 byte1 = debug_read_byte(space, address + 1, apply_translation);
|
|
|
|
/* based on the endianness, the result is assembled differently */
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = byte0 | (byte1 << 8);
|
|
else
|
|
result = byte1 | (byte0 << 8);
|
|
}
|
|
|
|
/* otherwise, this proceeds like the byte case */
|
|
else
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
UINT64 custom;
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, return 0xffff */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_READ_DEBUG, &address))
|
|
result = 0xffff;
|
|
|
|
/* if there is a custom read handler, and it returns TRUE, use that value */
|
|
else if (info->read != NULL && (*info->read)(space->cpu, space->spacenum, address, 2, &custom))
|
|
result = custom;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
result = memory_read_word(space, address);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_read_dword - return a dword from the
|
|
specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
UINT32 debug_read_dword(const address_space *space, offs_t address, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
UINT32 result;
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* if this is misaligned read, or if there are no dword readers, just read two words */
|
|
if ((address & 3) != 0)
|
|
{
|
|
UINT16 word0 = debug_read_word(space, address + 0, apply_translation);
|
|
UINT16 word1 = debug_read_word(space, address + 2, apply_translation);
|
|
|
|
/* based on the endianness, the result is assembled differently */
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = word0 | (word1 << 16);
|
|
else
|
|
result = word1 | (word0 << 16);
|
|
}
|
|
|
|
/* otherwise, this proceeds like the byte case */
|
|
else
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
UINT64 custom;
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, return 0xffffffff */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_READ_DEBUG, &address))
|
|
result = 0xffffffff;
|
|
|
|
/* if there is a custom read handler, and it returns TRUE, use that value */
|
|
else if (info->read != NULL && (*info->read)(space->cpu, space->spacenum, address, 4, &custom))
|
|
result = custom;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
result = memory_read_dword(space, address);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_read_qword - return a qword from the
|
|
specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
UINT64 debug_read_qword(const address_space *space, offs_t address, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
UINT64 result;
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* if this is misaligned read, or if there are no qword readers, just read two dwords */
|
|
if ((address & 7) != 0)
|
|
{
|
|
UINT32 dword0 = debug_read_dword(space, address + 0, apply_translation);
|
|
UINT32 dword1 = debug_read_dword(space, address + 4, apply_translation);
|
|
|
|
/* based on the endianness, the result is assembled differently */
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = dword0 | ((UINT64)dword1 << 32);
|
|
else
|
|
result = dword1 | ((UINT64)dword0 << 32);
|
|
}
|
|
|
|
/* otherwise, this proceeds like the byte case */
|
|
else
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
UINT64 custom;
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, return 0xffffffffffffffff */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_READ_DEBUG, &address))
|
|
result = ~(UINT64)0;
|
|
|
|
/* if there is a custom read handler, and it returns TRUE, use that value */
|
|
else if (info->read != NULL && (*info->read)(space->cpu, space->spacenum, address, 8, &custom))
|
|
result = custom;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
result = memory_read_qword(space, address);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_write_byte - write a byte to the
|
|
specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
void debug_write_byte(const address_space *space, offs_t address, UINT8 data, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, we're done */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_WRITE_DEBUG, &address))
|
|
;
|
|
|
|
/* if there is a custom write handler, and it returns TRUE, use that */
|
|
else if (info->write != NULL && (*info->write)(space->cpu, space->spacenum, address, 1, data))
|
|
;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
memory_write_byte(space, address, data);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
global->memory_modified = TRUE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_write_word - write a word to the
|
|
specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
void debug_write_word(const address_space *space, offs_t address, UINT16 data, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* if this is a misaligned write, or if there are no word writers, just read two bytes */
|
|
if ((address & 1) != 0)
|
|
{
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
{
|
|
debug_write_byte(space, address + 0, data >> 0, apply_translation);
|
|
debug_write_byte(space, address + 1, data >> 8, apply_translation);
|
|
}
|
|
else
|
|
{
|
|
debug_write_byte(space, address + 0, data >> 8, apply_translation);
|
|
debug_write_byte(space, address + 1, data >> 0, apply_translation);
|
|
}
|
|
}
|
|
|
|
/* otherwise, this proceeds like the byte case */
|
|
else
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, we're done */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_WRITE_DEBUG, &address))
|
|
;
|
|
|
|
/* if there is a custom write handler, and it returns TRUE, use that */
|
|
else if (info->write != NULL && (*info->write)(space->cpu, space->spacenum, address, 2, data))
|
|
;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
memory_write_word(space, address, data);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
global->memory_modified = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_write_dword - write a dword to the
|
|
specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
void debug_write_dword(const address_space *space, offs_t address, UINT32 data, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* if this is a misaligned write, or if there are no dword writers, just read two words */
|
|
if ((address & 3) != 0)
|
|
{
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
{
|
|
debug_write_word(space, address + 0, data >> 0, apply_translation);
|
|
debug_write_word(space, address + 2, data >> 16, apply_translation);
|
|
}
|
|
else
|
|
{
|
|
debug_write_word(space, address + 0, data >> 16, apply_translation);
|
|
debug_write_word(space, address + 2, data >> 0, apply_translation);
|
|
}
|
|
}
|
|
|
|
/* otherwise, this proceeds like the byte case */
|
|
else
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, we're done */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_WRITE_DEBUG, &address))
|
|
;
|
|
|
|
/* if there is a custom write handler, and it returns TRUE, use that */
|
|
else if (info->write != NULL && (*info->write)(space->cpu, space->spacenum, address, 4, data))
|
|
;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
memory_write_dword(space, address, data);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
global->memory_modified = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_write_qword - write a qword to the
|
|
specified memory space
|
|
-------------------------------------------------*/
|
|
|
|
void debug_write_qword(const address_space *space, offs_t address, UINT64 data, int apply_translation)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
|
|
/* mask against the logical byte mask */
|
|
address &= space->logbytemask;
|
|
|
|
/* if this is a misaligned write, or if there are no qword writers, just read two dwords */
|
|
if ((address & 7) != 0)
|
|
{
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
{
|
|
debug_write_dword(space, address + 0, data >> 0, apply_translation);
|
|
debug_write_dword(space, address + 4, data >> 32, apply_translation);
|
|
}
|
|
else
|
|
{
|
|
debug_write_dword(space, address + 0, data >> 32, apply_translation);
|
|
debug_write_dword(space, address + 4, data >> 0, apply_translation);
|
|
}
|
|
}
|
|
|
|
/* otherwise, this proceeds like the byte case */
|
|
else
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
|
|
/* all accesses from this point on are for the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* translate if necessary; if not mapped, we're done */
|
|
if (apply_translation && !debug_cpu_translate(space, TRANSLATE_WRITE_DEBUG, &address))
|
|
;
|
|
|
|
/* if there is a custom write handler, and it returns TRUE, use that */
|
|
else if (info->write != NULL && (*info->write)(space->cpu, space->spacenum, address, 8, data))
|
|
;
|
|
|
|
/* otherwise, call the byte reading function for the translated address */
|
|
else
|
|
memory_write_qword(space, address, data);
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
global->memory_modified = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_read_opcode - read 1,2,4 or 8 bytes at
|
|
the given offset from opcode space
|
|
-------------------------------------------------*/
|
|
|
|
UINT64 debug_read_opcode(const address_space *space, offs_t address, int size, int arg)
|
|
{
|
|
UINT64 result = ~(UINT64)0 & (~(UINT64)0 >> (64 - 8*size)), result2;
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
|
|
/* keep in logical range */
|
|
address &= space->logbytemask;
|
|
|
|
/* shortcut if we have a custom routine */
|
|
if (info->readop != NULL)
|
|
{
|
|
UINT64 result;
|
|
|
|
/* return early if we got the result directly */
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
if ((*info->readop)(space->cpu, address, size, &result))
|
|
{
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/* if we're bigger than the address bus, break into smaller pieces */
|
|
if (size > space->dbits / 8)
|
|
{
|
|
int halfsize = size / 2;
|
|
UINT64 r0 = debug_read_opcode(space, address + 0, halfsize, arg);
|
|
UINT64 r1 = debug_read_opcode(space, address + halfsize, halfsize, arg);
|
|
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
return r0 | (r1 << (8 * halfsize));
|
|
else
|
|
return r1 | (r0 << (8 * halfsize));
|
|
}
|
|
|
|
/* translate to physical first */
|
|
if (!debug_cpu_translate(space, TRANSLATE_FETCH_DEBUG, &address))
|
|
return result;
|
|
|
|
/* keep in physical range */
|
|
address &= space->bytemask;
|
|
switch (space->dbits / 8 * 10 + size)
|
|
{
|
|
/* dump opcodes in bytes from a byte-sized bus */
|
|
case 11:
|
|
break;
|
|
|
|
/* dump opcodes in bytes from a word-sized bus */
|
|
case 21:
|
|
address ^= (space->endianness == ENDIANNESS_LITTLE) ? BYTE_XOR_LE(0) : BYTE_XOR_BE(0);
|
|
break;
|
|
|
|
/* dump opcodes in words from a word-sized bus */
|
|
case 22:
|
|
break;
|
|
|
|
/* dump opcodes in bytes from a dword-sized bus */
|
|
case 41:
|
|
address ^= (space->endianness == ENDIANNESS_LITTLE) ? BYTE4_XOR_LE(0) : BYTE4_XOR_BE(0);
|
|
break;
|
|
|
|
/* dump opcodes in words from a dword-sized bus */
|
|
case 42:
|
|
address ^= (space->endianness == ENDIANNESS_LITTLE) ? WORD_XOR_LE(0) : WORD_XOR_BE(0);
|
|
break;
|
|
|
|
/* dump opcodes in dwords from a dword-sized bus */
|
|
case 44:
|
|
break;
|
|
|
|
/* dump opcodes in bytes from a qword-sized bus */
|
|
case 81:
|
|
address ^= (space->endianness == ENDIANNESS_LITTLE) ? BYTE8_XOR_LE(0) : BYTE8_XOR_BE(0);
|
|
break;
|
|
|
|
/* dump opcodes in words from a qword-sized bus */
|
|
case 82:
|
|
address ^= (space->endianness == ENDIANNESS_LITTLE) ? WORD2_XOR_LE(0) : WORD2_XOR_BE(0);
|
|
break;
|
|
|
|
/* dump opcodes in dwords from a qword-sized bus */
|
|
case 84:
|
|
address ^= (space->endianness == ENDIANNESS_LITTLE) ? DWORD_XOR_LE(0) : DWORD_XOR_BE(0);
|
|
break;
|
|
|
|
/* dump opcodes in qwords from a qword-sized bus */
|
|
case 88:
|
|
break;
|
|
|
|
default:
|
|
fatalerror("debug_read_opcode: unknown type = %d", space->dbits / 8 * 10 + size);
|
|
break;
|
|
}
|
|
|
|
/* turn on debugger access */
|
|
if (!global->debugger_access)
|
|
memory_set_debugger_access(space, global->debugger_access = TRUE);
|
|
|
|
/* switch off the size and handle unaligned accesses */
|
|
switch (size)
|
|
{
|
|
case 1:
|
|
result = (arg) ? memory_raw_read_byte(space, address) : memory_decrypted_read_byte(space, address);
|
|
break;
|
|
|
|
case 2:
|
|
result = (arg) ? memory_raw_read_word(space, address & ~1) : memory_decrypted_read_word(space, address & ~1);
|
|
if ((address & 1) != 0)
|
|
{
|
|
result2 = (arg) ? memory_raw_read_word(space, (address & ~1) + 2) : memory_decrypted_read_word(space, (address & ~1) + 2);
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = (result >> (8 * (address & 1))) | (result2 << (16 - 8 * (address & 1)));
|
|
else
|
|
result = (result << (8 * (address & 1))) | (result2 >> (16 - 8 * (address & 1)));
|
|
result &= 0xffff;
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
result = (arg) ? memory_raw_read_dword(space, address & ~3) : memory_decrypted_read_dword(space, address & ~3);
|
|
if ((address & 3) != 0)
|
|
{
|
|
result2 = (arg) ? memory_raw_read_dword(space, (address & ~3) + 4) : memory_decrypted_read_dword(space, (address & ~3) + 4);
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = (result >> (8 * (address & 3))) | (result2 << (32 - 8 * (address & 3)));
|
|
else
|
|
result = (result << (8 * (address & 3))) | (result2 >> (32 - 8 * (address & 3)));
|
|
result &= 0xffffffff;
|
|
}
|
|
break;
|
|
|
|
case 8:
|
|
result = (arg) ? memory_raw_read_qword(space, address & ~7) : memory_decrypted_read_qword(space, address & ~7);
|
|
if ((address & 7) != 0)
|
|
{
|
|
result2 = (arg) ? memory_raw_read_qword(space, (address & ~7) + 8) : memory_decrypted_read_qword(space, (address & ~7) + 8);
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = (result >> (8 * (address & 7))) | (result2 << (64 - 8 * (address & 7)));
|
|
else
|
|
result = (result << (8 * (address & 7))) | (result2 >> (64 - 8 * (address & 7)));
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* no longer accessing via the debugger */
|
|
memory_set_debugger_access(space, global->debugger_access = FALSE);
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INTERNAL HELPERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cpu_exit - free all memory
|
|
-------------------------------------------------*/
|
|
|
|
static void debug_cpu_exit(running_machine *machine)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
const device_config *cpu;
|
|
int spacenum;
|
|
|
|
/* loop over all watchpoints and breakpoints to free their memory */
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(cpu);
|
|
|
|
/* close any tracefiles */
|
|
if (info->trace.file != NULL)
|
|
fclose(info->trace.file);
|
|
if (info->trace.action != NULL)
|
|
free(info->trace.action);
|
|
|
|
/* free the symbol table */
|
|
if (info->symtable != NULL)
|
|
symtable_free(info->symtable);
|
|
|
|
/* free all breakpoints */
|
|
while (info->bplist != NULL)
|
|
debug_cpu_breakpoint_clear(machine, info->bplist->index);
|
|
|
|
/* loop over all address spaces */
|
|
for (spacenum = 0; spacenum < ARRAY_LENGTH(info->wplist); spacenum++)
|
|
{
|
|
/* free all watchpoints */
|
|
while (info->wplist[spacenum] != NULL)
|
|
debug_cpu_watchpoint_clear(machine, info->wplist[spacenum]->index);
|
|
}
|
|
}
|
|
|
|
/* free the global symbol table */
|
|
if (global != NULL && global->symtable != NULL)
|
|
symtable_free(global->symtable);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
on_vblank - called when a VBLANK hits
|
|
-------------------------------------------------*/
|
|
|
|
static void on_vblank(const device_config *device, void *param, int vblank_state)
|
|
{
|
|
/* just set a global flag to be consumed later */
|
|
if (vblank_state)
|
|
device->machine->debugcpu_data->vblank_occurred = TRUE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
reset_transient_flags - reset the transient
|
|
flags on all CPUs
|
|
-------------------------------------------------*/
|
|
|
|
static void reset_transient_flags(running_machine *machine)
|
|
{
|
|
const device_config *cpu;
|
|
|
|
/* loop over CPUs and reset the transient flags */
|
|
for (cpu = machine->cpu[0]; cpu != NULL; cpu = cpu->typenext)
|
|
cpu_get_debug_data(cpu)->flags &= ~DEBUG_FLAG_TRANSIENT;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
compute_debug_flags - compute the global
|
|
debug flags for optimal efficiency
|
|
-------------------------------------------------*/
|
|
|
|
static void compute_debug_flags(const device_config *device)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(device);
|
|
running_machine *machine = device->machine;
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
|
|
/* clear out all global flags by default */
|
|
machine->debug_flags = DEBUG_FLAG_ENABLED;
|
|
|
|
/* if we are ignoring this CPU, or if events are pending, we're done */
|
|
if ((info->flags & DEBUG_FLAG_OBSERVING) == 0 || mame_is_scheduled_event_pending(machine) || mame_is_save_or_load_pending(machine))
|
|
return;
|
|
|
|
/* many of our states require us to be called on each instruction */
|
|
if (global->execution_state == EXECUTION_STATE_STOPPED)
|
|
machine->debug_flags |= DEBUG_FLAG_CALL_HOOK;
|
|
if ((info->flags & (DEBUG_FLAG_HISTORY | DEBUG_FLAG_TRACING_ANY | DEBUG_FLAG_HOOKED |
|
|
DEBUG_FLAG_STEPPING_ANY | DEBUG_FLAG_STOP_PC | DEBUG_FLAG_LIVE_BP)) != 0)
|
|
machine->debug_flags |= DEBUG_FLAG_CALL_HOOK;
|
|
|
|
/* if we are stopping at a particular time and that time is within the current timeslice, we need to be called */
|
|
if ((info->flags & DEBUG_FLAG_STOP_TIME) && attotime_compare(info->endexectime, info->stoptime) <= 0)
|
|
machine->debug_flags |= DEBUG_FLAG_CALL_HOOK;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
perform_trace - log to the tracefile the
|
|
data for a given instruction
|
|
-------------------------------------------------*/
|
|
|
|
static void perform_trace(cpu_debug_data *info)
|
|
{
|
|
offs_t pc = cpu_get_pc(info->device);
|
|
int offset, count, i;
|
|
char buffer[100];
|
|
offs_t dasmresult;
|
|
|
|
/* are we in trace over mode and in a subroutine? */
|
|
if ((info->flags & DEBUG_FLAG_TRACING_OVER) != 0 && info->trace.trace_over_target != ~0)
|
|
{
|
|
if (info->trace.trace_over_target != pc)
|
|
return;
|
|
info->trace.trace_over_target = ~0;
|
|
}
|
|
|
|
/* check for a loop condition */
|
|
for (i = count = 0; i < TRACE_LOOPS; i++)
|
|
if (info->trace.history[i] == pc)
|
|
count++;
|
|
|
|
/* if no more than 1 hit, process normally */
|
|
if (count <= 1)
|
|
{
|
|
const address_space *space = cpu_get_address_space(info->device, ADDRESS_SPACE_PROGRAM);
|
|
|
|
/* if we just finished looping, indicate as much */
|
|
if (info->trace.loops != 0)
|
|
fprintf(info->trace.file, "\n (loops for %d instructions)\n\n", info->trace.loops);
|
|
info->trace.loops = 0;
|
|
|
|
/* execute any trace actions first */
|
|
if (info->trace.action != NULL)
|
|
debug_console_execute_command(info->device->machine, info->trace.action, 0);
|
|
|
|
/* print the address */
|
|
offset = sprintf(buffer, "%0*X: ", space->logaddrchars, pc);
|
|
|
|
/* print the disassembly */
|
|
dasmresult = dasm_wrapped(info->device, &buffer[offset], pc);
|
|
|
|
/* output the result */
|
|
fprintf(info->trace.file, "%s\n", buffer);
|
|
|
|
/* do we need to step the trace over this instruction? */
|
|
if ((info->flags & DEBUG_FLAG_TRACING_OVER) != 0 && (dasmresult & DASMFLAG_SUPPORTED) != 0 && (dasmresult & DASMFLAG_STEP_OVER) != 0)
|
|
{
|
|
int extraskip = (dasmresult & DASMFLAG_OVERINSTMASK) >> DASMFLAG_OVERINSTSHIFT;
|
|
offs_t trace_over_target = pc + (dasmresult & DASMFLAG_LENGTHMASK);
|
|
|
|
/* if we need to skip additional instructions, advance as requested */
|
|
while (extraskip-- > 0)
|
|
trace_over_target += dasm_wrapped(info->device, buffer, trace_over_target) & DASMFLAG_LENGTHMASK;
|
|
|
|
info->trace.trace_over_target = trace_over_target;
|
|
}
|
|
|
|
/* log this PC */
|
|
info->trace.nextdex = (info->trace.nextdex + 1) % TRACE_LOOPS;
|
|
info->trace.history[info->trace.nextdex] = pc;
|
|
}
|
|
|
|
/* else just count the loop */
|
|
else
|
|
info->trace.loops++;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
prepare_for_step_overout - prepare things for
|
|
stepping over an instruction
|
|
-------------------------------------------------*/
|
|
|
|
static void prepare_for_step_overout(cpu_debug_data *info)
|
|
{
|
|
offs_t pc = cpu_get_pc(info->device);
|
|
char dasmbuffer[100];
|
|
offs_t dasmresult;
|
|
|
|
/* disassemble the current instruction and get the flags */
|
|
dasmresult = dasm_wrapped(info->device, dasmbuffer, pc);
|
|
|
|
/* if flags are supported and it's a call-style opcode, set a temp breakpoint after that instruction */
|
|
if ((dasmresult & DASMFLAG_SUPPORTED) != 0 && (dasmresult & DASMFLAG_STEP_OVER) != 0)
|
|
{
|
|
int extraskip = (dasmresult & DASMFLAG_OVERINSTMASK) >> DASMFLAG_OVERINSTSHIFT;
|
|
pc += dasmresult & DASMFLAG_LENGTHMASK;
|
|
|
|
/* if we need to skip additional instructions, advance as requested */
|
|
while (extraskip-- > 0)
|
|
pc += dasm_wrapped(info->device, dasmbuffer, pc) & DASMFLAG_LENGTHMASK;
|
|
info->stepaddr = pc;
|
|
}
|
|
|
|
/* if we're stepping out and this isn't a step out instruction, reset the steps until stop to a high number */
|
|
if ((info->flags & DEBUG_FLAG_STEPPING_OUT) != 0)
|
|
{
|
|
if ((dasmresult & DASMFLAG_SUPPORTED) != 0 && (dasmresult & DASMFLAG_STEP_OUT) == 0)
|
|
info->stepsleft = 100;
|
|
else
|
|
info->stepsleft = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
process_source_file - executes commands from
|
|
a source file
|
|
-------------------------------------------------*/
|
|
|
|
static void process_source_file(running_machine *machine)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
|
|
/* loop until the file is exhausted or until we are executing again */
|
|
while (global->source_file != NULL && global->execution_state == EXECUTION_STATE_STOPPED)
|
|
{
|
|
char buf[512];
|
|
int i;
|
|
char *s;
|
|
|
|
/* stop at the end of file */
|
|
if (feof(global->source_file))
|
|
{
|
|
fclose(global->source_file);
|
|
global->source_file = NULL;
|
|
return;
|
|
}
|
|
|
|
/* fetch the next line */
|
|
memset(buf, 0, sizeof(buf));
|
|
fgets(buf, sizeof(buf), global->source_file);
|
|
|
|
/* strip out comments (text after '//') */
|
|
s = strstr(buf, "//");
|
|
if (s)
|
|
*s = '\0';
|
|
|
|
/* strip whitespace */
|
|
i = (int)strlen(buf);
|
|
while((i > 0) && (isspace((UINT8)buf[i-1])))
|
|
buf[--i] = '\0';
|
|
|
|
/* execute the command */
|
|
if (buf[0])
|
|
debug_console_execute_command(machine, buf, 1);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
breakpoint_update_flags - update the CPU's
|
|
breakpoint flags
|
|
-------------------------------------------------*/
|
|
|
|
static void breakpoint_update_flags(cpu_debug_data *info)
|
|
{
|
|
debugcpu_private *global = info->device->machine->debugcpu_data;
|
|
debug_cpu_breakpoint *bp;
|
|
|
|
/* see if there are any enabled breakpoints */
|
|
info->flags &= ~DEBUG_FLAG_LIVE_BP;
|
|
for (bp = info->bplist; bp != NULL; bp = bp->next)
|
|
if (bp->enabled)
|
|
{
|
|
info->flags |= DEBUG_FLAG_LIVE_BP;
|
|
break;
|
|
}
|
|
|
|
/* push the flags out globally */
|
|
if (global->livecpu != NULL)
|
|
compute_debug_flags(global->livecpu);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
breakpoint_check - check the breakpoints for
|
|
a given CPU
|
|
-------------------------------------------------*/
|
|
|
|
static void breakpoint_check(running_machine *machine, cpu_debug_data *info, offs_t pc)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
debug_cpu_breakpoint *bp;
|
|
UINT64 result;
|
|
|
|
/* see if we match */
|
|
for (bp = info->bplist; bp != NULL; bp = bp->next)
|
|
if (bp->enabled && bp->address == pc)
|
|
|
|
/* if we do, evaluate the condition */
|
|
if (bp->condition == NULL || (expression_execute(bp->condition, &result) == EXPRERR_NONE && result != 0))
|
|
{
|
|
/* halt in the debugger by default */
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
|
|
/* if we hit, evaluate the action */
|
|
if (bp->action != NULL)
|
|
debug_console_execute_command(machine, bp->action, 0);
|
|
|
|
/* print a notification, unless the action made us go again */
|
|
if (global->execution_state == EXECUTION_STATE_STOPPED)
|
|
debug_console_printf(machine, "Stopped at breakpoint %X\n", bp->index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
watchpoint_update_flags - update the CPU's
|
|
watchpoint flags
|
|
-------------------------------------------------*/
|
|
|
|
static void watchpoint_update_flags(const address_space *space)
|
|
{
|
|
const cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
debug_cpu_watchpoint *wp;
|
|
int enablewrite = FALSE;
|
|
int enableread = FALSE;
|
|
|
|
/* if hotspots are enabled, turn on all reads */
|
|
if (info->hotspots != NULL)
|
|
enableread = TRUE;
|
|
|
|
/* see if there are any enabled breakpoints */
|
|
for (wp = info->wplist[space->spacenum]; wp != NULL; wp = wp->next)
|
|
if (wp->enabled)
|
|
{
|
|
if (wp->type & WATCHPOINT_READ)
|
|
enableread = TRUE;
|
|
if (wp->type & WATCHPOINT_WRITE)
|
|
enablewrite = TRUE;
|
|
}
|
|
|
|
/* push the flags out globally */
|
|
memory_enable_read_watchpoints(space, enableread);
|
|
memory_enable_write_watchpoints(space, enablewrite);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
watchpoint_check - check the watchpoints
|
|
for a given CPU and address space
|
|
-------------------------------------------------*/
|
|
|
|
static void watchpoint_check(const address_space *space, int type, offs_t address, UINT64 value_to_write, UINT64 mem_mask)
|
|
{
|
|
const cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
debug_cpu_watchpoint *wp;
|
|
offs_t size = 0;
|
|
UINT64 result;
|
|
|
|
/* if we're within debugger code, don't stop */
|
|
if (global->within_instruction_hook || global->debugger_access)
|
|
return;
|
|
|
|
global->within_instruction_hook = TRUE;
|
|
|
|
/* adjust address, size & value_to_write based on mem_mask. */
|
|
if (mem_mask != 0)
|
|
{
|
|
int bus_size = space->dbits / 8;
|
|
int address_offset = 0;
|
|
|
|
while (address_offset < bus_size && (mem_mask & 0xff) == 0)
|
|
{
|
|
address_offset++;
|
|
value_to_write >>= 8;
|
|
mem_mask >>= 8;
|
|
}
|
|
|
|
while (mem_mask != 0)
|
|
{
|
|
size++;
|
|
mem_mask >>= 8;
|
|
}
|
|
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
address += address_offset;
|
|
else
|
|
address += bus_size - size - address_offset;
|
|
}
|
|
|
|
/* if we are a write watchpoint, stash the value that will be written */
|
|
global->wpaddr = address;
|
|
if (type & WATCHPOINT_WRITE)
|
|
global->wpdata = value_to_write;
|
|
|
|
/* see if we match */
|
|
for (wp = info->wplist[space->spacenum]; wp != NULL; wp = wp->next)
|
|
if (wp->enabled && (wp->type & type) != 0 && address + size > wp->address && address < wp->address + wp->length)
|
|
|
|
/* if we do, evaluate the condition */
|
|
if (wp->condition == NULL || (expression_execute(wp->condition, &result) == EXPRERR_NONE && result != 0))
|
|
{
|
|
/* halt in the debugger by default */
|
|
global->execution_state = EXECUTION_STATE_STOPPED;
|
|
|
|
/* if we hit, evaluate the action */
|
|
if (wp->action != NULL)
|
|
debug_console_execute_command(space->machine, wp->action, 0);
|
|
|
|
/* print a notification, unless the action made us go again */
|
|
if (global->execution_state == EXECUTION_STATE_STOPPED)
|
|
{
|
|
static const char *const sizes[] =
|
|
{
|
|
"0bytes", "byte", "word", "3bytes", "dword", "5bytes", "6bytes", "7bytes", "qword"
|
|
};
|
|
char buffer[100];
|
|
|
|
if (type & WATCHPOINT_WRITE)
|
|
{
|
|
sprintf(buffer, "Stopped at watchpoint %X writing %s to %08X (PC=%X)", wp->index, sizes[size], memory_byte_to_address(space, address), cpu_get_pc(space->cpu));
|
|
if (value_to_write >> 32)
|
|
sprintf(&buffer[strlen(buffer)], " (data=%X%08X)", (UINT32)(value_to_write >> 32), (UINT32)value_to_write);
|
|
else
|
|
sprintf(&buffer[strlen(buffer)], " (data=%X)", (UINT32)value_to_write);
|
|
}
|
|
else
|
|
sprintf(buffer, "Stopped at watchpoint %X reading %s from %08X (PC=%X)", wp->index, sizes[size], memory_byte_to_address(space, address), cpu_get_pc(space->cpu));
|
|
debug_console_printf(space->machine, "%s\n", buffer);
|
|
compute_debug_flags(space->cpu);
|
|
}
|
|
break;
|
|
}
|
|
|
|
global->within_instruction_hook = FALSE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
check_hotspots - check for
|
|
hotspots on a memory read access
|
|
-------------------------------------------------*/
|
|
|
|
static void check_hotspots(const address_space *space, offs_t address)
|
|
{
|
|
cpu_debug_data *info = cpu_get_debug_data(space->cpu);
|
|
offs_t pc = cpu_get_pc(space->cpu);
|
|
int hotindex;
|
|
|
|
/* see if we have a match in our list */
|
|
for (hotindex = 0; hotindex < info->hotspot_count; hotindex++)
|
|
if (info->hotspots[hotindex].access == address && info->hotspots[hotindex].pc == pc && info->hotspots[hotindex].space == space)
|
|
break;
|
|
|
|
/* if we didn't find any, make a new entry */
|
|
if (hotindex == info->hotspot_count)
|
|
{
|
|
/* if the bottom of the list is over the threshhold, print it */
|
|
debug_hotspot_entry *spot = &info->hotspots[info->hotspot_count - 1];
|
|
if (spot->count > info->hotspot_threshhold)
|
|
debug_console_printf(space->machine, "Hotspot @ %s %08X (PC=%08X) hit %d times (fell off bottom)\n", space->name, spot->access, spot->pc, spot->count);
|
|
|
|
/* move everything else down and insert this one at the top */
|
|
memmove(&info->hotspots[1], &info->hotspots[0], sizeof(info->hotspots[0]) * (info->hotspot_count - 1));
|
|
info->hotspots[0].access = address;
|
|
info->hotspots[0].pc = pc;
|
|
info->hotspots[0].space = space;
|
|
info->hotspots[0].count = 1;
|
|
}
|
|
|
|
/* if we did find one, increase the count and move it to the top */
|
|
else
|
|
{
|
|
info->hotspots[hotindex].count++;
|
|
if (hotindex != 0)
|
|
{
|
|
debug_hotspot_entry temp = info->hotspots[hotindex];
|
|
memmove(&info->hotspots[1], &info->hotspots[0], sizeof(info->hotspots[0]) * hotindex);
|
|
info->hotspots[0] = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
dasm_wrapped - wraps calls to the disassembler
|
|
by fetching the opcode bytes to a temporary
|
|
buffer and then disassembling them
|
|
-------------------------------------------------*/
|
|
|
|
static UINT32 dasm_wrapped(const device_config *device, char *buffer, offs_t pc)
|
|
{
|
|
const address_space *space = cpu_get_address_space(device, ADDRESS_SPACE_PROGRAM);
|
|
int maxbytes = cpu_get_max_opcode_bytes(device);
|
|
UINT8 opbuf[64], argbuf[64];
|
|
offs_t pcbyte;
|
|
int numbytes;
|
|
|
|
/* fetch the bytes up to the maximum */
|
|
pcbyte = memory_address_to_byte(space, pc) & space->bytemask;
|
|
for (numbytes = 0; numbytes < maxbytes; numbytes++)
|
|
{
|
|
opbuf[numbytes] = debug_read_opcode(space, pcbyte + numbytes, 1, FALSE);
|
|
argbuf[numbytes] = debug_read_opcode(space, pcbyte + numbytes, 1, TRUE);
|
|
}
|
|
|
|
return debug_cpu_disassemble(device, buffer, pc, opbuf, argbuf);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
EXPRESSION HANDLERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
expression_cpu_index - return the CPU index
|
|
based on a case insensitive tag search
|
|
-------------------------------------------------*/
|
|
|
|
static const device_config *expression_cpu_index(running_machine *machine, const char *tag)
|
|
{
|
|
int index;
|
|
|
|
for (index = 0; index < ARRAY_LENGTH(machine->cpu); index++)
|
|
if (machine->cpu[index] != NULL && mame_stricmp(machine->cpu[index]->tag, tag) == 0)
|
|
return machine->cpu[index];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_read_memory - read 1,2,4 or 8 bytes
|
|
at the given offset in the given address
|
|
space
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 expression_read_memory(void *param, const char *name, int space, UINT32 address, int size)
|
|
{
|
|
running_machine *machine = (running_machine *)param;
|
|
const device_config *cpu = NULL;
|
|
|
|
switch (space)
|
|
{
|
|
case EXPSPACE_PROGRAM:
|
|
case EXPSPACE_DATA:
|
|
case EXPSPACE_IO:
|
|
if (name != NULL)
|
|
cpu = expression_cpu_index(machine, name);
|
|
if (cpu == NULL)
|
|
cpu = debug_cpu_get_visible_cpu(machine);
|
|
return expression_read_address_space(cpu_get_address_space(cpu, ADDRESS_SPACE_PROGRAM + (space - EXPSPACE_PROGRAM)), address, size);
|
|
|
|
case EXPSPACE_OPCODE:
|
|
case EXPSPACE_RAMWRITE:
|
|
if (name != NULL)
|
|
cpu = expression_cpu_index(machine, name);
|
|
if (cpu == NULL)
|
|
cpu = debug_cpu_get_visible_cpu(machine);
|
|
return expression_read_program_direct(cpu_get_address_space(cpu, ADDRESS_SPACE_PROGRAM), (space == EXPSPACE_OPCODE), address, size);
|
|
|
|
case EXPSPACE_EEPROM:
|
|
return expression_read_eeprom(machine, address, size);
|
|
|
|
case EXPSPACE_REGION:
|
|
if (name == NULL)
|
|
break;
|
|
return expression_read_memory_region(machine, name, address, size);
|
|
}
|
|
return ~(UINT64)0 >> (64 - 8*size);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_read_address_space - read memory
|
|
from a specific CPU's address space
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 expression_read_address_space(const address_space *space, offs_t address, int size)
|
|
{
|
|
UINT64 result = ~(UINT64)0 >> (64 - 8*size);
|
|
|
|
if (space != NULL)
|
|
{
|
|
/* adjust the address into a byte address */
|
|
address = memory_address_to_byte(space, address);
|
|
|
|
/* switch contexts and do the read */
|
|
switch (size)
|
|
{
|
|
case 1: result = debug_read_byte(space, address, TRUE); break;
|
|
case 2: result = debug_read_word(space, address, TRUE); break;
|
|
case 4: result = debug_read_dword(space, address, TRUE); break;
|
|
case 8: result = debug_read_qword(space, address, TRUE); break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_read_program_direct - read memory
|
|
directly from an opcode or RAM pointer
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 expression_read_program_direct(const address_space *space, int opcode, offs_t address, int size)
|
|
{
|
|
UINT64 result = ~(UINT64)0 >> (64 - 8*size);
|
|
|
|
if (space != NULL)
|
|
{
|
|
UINT8 *base;
|
|
|
|
/* adjust the address into a byte address, but not if being called recursively */
|
|
if ((opcode & 2) == 0)
|
|
address = memory_address_to_byte(space, address);
|
|
|
|
/* call ourself recursively until we are byte-sized */
|
|
if (size > 1)
|
|
{
|
|
int halfsize = size / 2;
|
|
UINT64 r0, r1;
|
|
|
|
/* read each half, from lower address to upper address */
|
|
r0 = expression_read_program_direct(space, opcode | 2, address + 0, halfsize);
|
|
r1 = expression_read_program_direct(space, opcode | 2, address + halfsize, halfsize);
|
|
|
|
/* assemble based on the target endianness */
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = r0 | (r1 << (8 * halfsize));
|
|
else
|
|
result = r1 | (r0 << (8 * halfsize));
|
|
}
|
|
|
|
/* handle the byte-sized final requests */
|
|
else
|
|
{
|
|
/* lowmask specified which address bits are within the databus width */
|
|
offs_t lowmask = space->dbits / 8 - 1;
|
|
|
|
/* get the base of memory, aligned to the address minus the lowbits */
|
|
if (opcode & 1)
|
|
base = (UINT8 *)memory_decrypted_read_ptr(space, address & ~lowmask);
|
|
else
|
|
base = (UINT8 *)memory_get_read_ptr(space, address & ~lowmask);
|
|
|
|
/* if we have a valid base, return the appropriate byte */
|
|
if (base != NULL)
|
|
{
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
result = base[BYTE8_XOR_LE(address) & lowmask];
|
|
else
|
|
result = base[BYTE8_XOR_BE(address) & lowmask];
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_read_memory_region - read memory
|
|
from a memory region
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 expression_read_memory_region(running_machine *machine, const char *rgntag, offs_t address, int size)
|
|
{
|
|
UINT8 *base = memory_region(machine, rgntag);
|
|
UINT64 result = ~(UINT64)0 >> (64 - 8*size);
|
|
|
|
/* make sure we get a valid base before proceeding */
|
|
if (base != NULL)
|
|
{
|
|
UINT32 length = memory_region_length(machine, rgntag);
|
|
UINT32 flags = memory_region_flags(machine, rgntag);
|
|
|
|
/* call ourself recursively until we are byte-sized */
|
|
if (size > 1)
|
|
{
|
|
int halfsize = size / 2;
|
|
UINT64 r0, r1;
|
|
|
|
/* read each half, from lower address to upper address */
|
|
r0 = expression_read_memory_region(machine, rgntag, address + 0, halfsize);
|
|
r1 = expression_read_memory_region(machine, rgntag, address + halfsize, halfsize);
|
|
|
|
/* assemble based on the target endianness */
|
|
if ((flags & ROMREGION_ENDIANMASK) == ROMREGION_LE)
|
|
result = r0 | (r1 << (8 * halfsize));
|
|
else
|
|
result = r1 | (r0 << (8 * halfsize));
|
|
}
|
|
|
|
/* only process if we're within range */
|
|
else if (address < length)
|
|
{
|
|
/* lowmask specified which address bits are within the databus width */
|
|
UINT32 lowmask = (1 << ((flags & ROMREGION_WIDTHMASK) >> 8)) - 1;
|
|
base += address & ~lowmask;
|
|
|
|
/* if we have a valid base, return the appropriate byte */
|
|
if ((flags & ROMREGION_ENDIANMASK) == ROMREGION_LE)
|
|
result = base[BYTE8_XOR_LE(address) & lowmask];
|
|
else
|
|
result = base[BYTE8_XOR_BE(address) & lowmask];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_read_eeprom - read EEPROM data
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 expression_read_eeprom(running_machine *machine, offs_t address, int size)
|
|
{
|
|
UINT64 result = ~(UINT64)0 >> (64 - 8*size);
|
|
UINT32 eelength, eesize;
|
|
void *base;
|
|
|
|
/* make sure we get a valid base before proceeding */
|
|
base = eeprom_get_data_pointer(&eelength, &eesize);
|
|
if (base != NULL && address < eelength)
|
|
{
|
|
/* switch off the size */
|
|
switch (eesize)
|
|
{
|
|
case 1: result &= ((UINT8 *)base)[address]; break;
|
|
case 2: result &= BIG_ENDIANIZE_INT16(((UINT16 *)base)[address]); break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_write_memory - write 1,2,4 or 8
|
|
bytes at the given offset in the given address
|
|
space
|
|
-------------------------------------------------*/
|
|
|
|
static void expression_write_memory(void *param, const char *name, int space, UINT32 address, int size, UINT64 data)
|
|
{
|
|
running_machine *machine = (running_machine *)param;
|
|
const device_config *cpu = NULL;
|
|
|
|
switch (space)
|
|
{
|
|
case EXPSPACE_PROGRAM:
|
|
case EXPSPACE_DATA:
|
|
case EXPSPACE_IO:
|
|
if (name != NULL)
|
|
cpu = expression_cpu_index(machine, name);
|
|
if (cpu == NULL)
|
|
cpu = debug_cpu_get_visible_cpu(machine);
|
|
expression_write_address_space(cpu_get_address_space(cpu, ADDRESS_SPACE_PROGRAM + (space - EXPSPACE_PROGRAM)), address, size, data);
|
|
break;
|
|
|
|
case EXPSPACE_OPCODE:
|
|
case EXPSPACE_RAMWRITE:
|
|
if (name != NULL)
|
|
cpu = expression_cpu_index(machine, name);
|
|
if (cpu == NULL)
|
|
cpu = debug_cpu_get_visible_cpu(machine);
|
|
expression_write_program_direct(cpu_get_address_space(cpu, ADDRESS_SPACE_PROGRAM), (space == EXPSPACE_OPCODE), address, size, data);
|
|
break;
|
|
|
|
case EXPSPACE_EEPROM:
|
|
expression_write_eeprom(machine, address, size, data);
|
|
break;
|
|
|
|
case EXPSPACE_REGION:
|
|
if (name == NULL)
|
|
break;
|
|
expression_write_memory_region(machine, name, address, size, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_write_address_space - write memory
|
|
to a specific CPU's address space
|
|
-------------------------------------------------*/
|
|
|
|
static void expression_write_address_space(const address_space *space, offs_t address, int size, UINT64 data)
|
|
{
|
|
if (space != NULL)
|
|
{
|
|
/* adjust the address into a byte address */
|
|
address = memory_address_to_byte(space, address);
|
|
|
|
/* switch contexts and do the write */
|
|
switch (size)
|
|
{
|
|
case 1: debug_write_byte(space, address, data, TRUE); break;
|
|
case 2: debug_write_word(space, address, data, TRUE); break;
|
|
case 4: debug_write_dword(space, address, data, TRUE); break;
|
|
case 8: debug_write_qword(space, address, data, TRUE); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_write_program_direct - write memory
|
|
directly to an opcode or RAM pointer
|
|
-------------------------------------------------*/
|
|
|
|
static void expression_write_program_direct(const address_space *space, int opcode, offs_t address, int size, UINT64 data)
|
|
{
|
|
if (space != NULL)
|
|
{
|
|
debugcpu_private *global = space->machine->debugcpu_data;
|
|
UINT8 *base;
|
|
|
|
/* adjust the address into a byte address, but not if being called recursively */
|
|
if ((opcode & 2) == 0)
|
|
address = memory_address_to_byte(space, address);
|
|
|
|
/* call ourself recursively until we are byte-sized */
|
|
if (size > 1)
|
|
{
|
|
int halfsize = size / 2;
|
|
UINT64 r0, r1, halfmask;
|
|
|
|
/* break apart based on the target endianness */
|
|
halfmask = ~(UINT64)0 >> (64 - 8 * halfsize);
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
{
|
|
r0 = data & halfmask;
|
|
r1 = (data >> (8 * halfsize)) & halfmask;
|
|
}
|
|
else
|
|
{
|
|
r0 = (data >> (8 * halfsize)) & halfmask;
|
|
r1 = data & halfmask;
|
|
}
|
|
|
|
/* write each half, from lower address to upper address */
|
|
expression_write_program_direct(space, opcode | 2, address + 0, halfsize, r0);
|
|
expression_write_program_direct(space, opcode | 2, address + halfsize, halfsize, r1);
|
|
}
|
|
|
|
/* handle the byte-sized final case */
|
|
else
|
|
{
|
|
/* lowmask specified which address bits are within the databus width */
|
|
offs_t lowmask = space->dbits / 8 - 1;
|
|
|
|
/* get the base of memory, aligned to the address minus the lowbits */
|
|
if (opcode & 1)
|
|
base = (UINT8 *)memory_decrypted_read_ptr(space, address & ~lowmask);
|
|
else
|
|
base = (UINT8 *)memory_get_read_ptr(space, address & ~lowmask);
|
|
|
|
/* if we have a valid base, write the appropriate byte */
|
|
if (base != NULL)
|
|
{
|
|
if (space->endianness == ENDIANNESS_LITTLE)
|
|
base[BYTE8_XOR_LE(address) & lowmask] = data;
|
|
else
|
|
base[BYTE8_XOR_BE(address) & lowmask] = data;
|
|
global->memory_modified = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_write_memory_region - write memory
|
|
from a memory region
|
|
-------------------------------------------------*/
|
|
|
|
static void expression_write_memory_region(running_machine *machine, const char *rgntag, offs_t address, int size, UINT64 data)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
UINT8 *base = memory_region(machine, rgntag);
|
|
|
|
/* make sure we get a valid base before proceeding */
|
|
if (base != NULL)
|
|
{
|
|
UINT32 length = memory_region_length(machine, rgntag);
|
|
UINT32 flags = memory_region_flags(machine, rgntag);
|
|
|
|
/* call ourself recursively until we are byte-sized */
|
|
if (size > 1)
|
|
{
|
|
int halfsize = size / 2;
|
|
UINT64 r0, r1, halfmask;
|
|
|
|
/* break apart based on the target endianness */
|
|
halfmask = ~(UINT64)0 >> (64 - 8 * halfsize);
|
|
if ((flags & ROMREGION_ENDIANMASK) == ROMREGION_LE)
|
|
{
|
|
r0 = data & halfmask;
|
|
r1 = (data >> (8 * halfsize)) & halfmask;
|
|
}
|
|
else
|
|
{
|
|
r0 = (data >> (8 * halfsize)) & halfmask;
|
|
r1 = data & halfmask;
|
|
}
|
|
|
|
/* write each half, from lower address to upper address */
|
|
expression_write_memory_region(machine, rgntag, address + 0, halfsize, r0);
|
|
expression_write_memory_region(machine, rgntag, address + halfsize, halfsize, r1);
|
|
}
|
|
|
|
/* only process if we're within range */
|
|
else if (address < length)
|
|
{
|
|
/* lowmask specified which address bits are within the databus width */
|
|
UINT32 lowmask = (1 << ((flags & ROMREGION_WIDTHMASK) >> 8)) - 1;
|
|
base += address & ~lowmask;
|
|
|
|
/* if we have a valid base, set the appropriate byte */
|
|
if ((flags & ROMREGION_ENDIANMASK) == ROMREGION_LE)
|
|
base[BYTE8_XOR_LE(address) & lowmask] = data;
|
|
else
|
|
base[BYTE8_XOR_BE(address) & lowmask] = data;
|
|
global->memory_modified = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_write_eeprom - write EEPROM data
|
|
-------------------------------------------------*/
|
|
|
|
static void expression_write_eeprom(running_machine *machine, offs_t address, int size, UINT64 data)
|
|
{
|
|
debugcpu_private *global = machine->debugcpu_data;
|
|
UINT32 eelength, eesize;
|
|
void *vbase = eeprom_get_data_pointer(&eelength, &eesize);
|
|
|
|
/* make sure we get a valid base before proceeding */
|
|
if (vbase != NULL && address < eelength)
|
|
{
|
|
UINT64 mask = ~(UINT64)0 >> (64 - 8*size);
|
|
|
|
/* switch off the size */
|
|
switch (eesize)
|
|
{
|
|
case 1:
|
|
{
|
|
UINT8 *base = (UINT8 *)vbase + address;
|
|
*base = (*base & ~mask) | (data & mask);
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
UINT16 *base = (UINT16 *)vbase + address;
|
|
UINT16 value = BIG_ENDIANIZE_INT16(*base);
|
|
value = (value & ~mask) | (data & mask);
|
|
*base = BIG_ENDIANIZE_INT16(value);
|
|
break;
|
|
}
|
|
}
|
|
global->memory_modified = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
expression_validate - validate that the
|
|
provided expression references an
|
|
appropriate name
|
|
-------------------------------------------------*/
|
|
|
|
static EXPRERR expression_validate(void *param, const char *name, int space)
|
|
{
|
|
running_machine *machine = (running_machine *)param;
|
|
const device_config *cpu = NULL;
|
|
|
|
switch (space)
|
|
{
|
|
case EXPSPACE_PROGRAM:
|
|
case EXPSPACE_DATA:
|
|
case EXPSPACE_IO:
|
|
if (name != NULL)
|
|
{
|
|
cpu = expression_cpu_index(machine, name);
|
|
if (cpu == NULL)
|
|
return EXPRERR_INVALID_MEMORY_NAME;
|
|
}
|
|
if (cpu == NULL)
|
|
cpu = debug_cpu_get_visible_cpu(machine);
|
|
if (cpu_get_address_space(cpu, ADDRESS_SPACE_PROGRAM + (space - EXPSPACE_PROGRAM)) == NULL)
|
|
return EXPRERR_NO_SUCH_MEMORY_SPACE;
|
|
break;
|
|
|
|
case EXPSPACE_OPCODE:
|
|
case EXPSPACE_RAMWRITE:
|
|
if (name != NULL)
|
|
{
|
|
cpu = expression_cpu_index(machine, name);
|
|
if (cpu == NULL)
|
|
return EXPRERR_INVALID_MEMORY_NAME;
|
|
}
|
|
if (cpu == NULL)
|
|
cpu = debug_cpu_get_visible_cpu(machine);
|
|
if (cpu_get_address_space(cpu, ADDRESS_SPACE_PROGRAM) == NULL)
|
|
return EXPRERR_NO_SUCH_MEMORY_SPACE;
|
|
break;
|
|
|
|
case EXPSPACE_EEPROM:
|
|
if (name != NULL)
|
|
return EXPRERR_INVALID_MEMORY_NAME;
|
|
break;
|
|
|
|
case EXPSPACE_REGION:
|
|
if (name == NULL)
|
|
return EXPRERR_MISSING_MEMORY_NAME;
|
|
if (memory_region(machine, name) == NULL)
|
|
return EXPRERR_INVALID_MEMORY_NAME;
|
|
break;
|
|
}
|
|
return EXPRERR_NONE;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
VARIABLE GETTERS/SETTERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
get_wpaddr - getter callback for the
|
|
'wpaddr' symbol
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_wpaddr(void *globalref, void *ref)
|
|
{
|
|
running_machine *machine = (running_machine *)globalref;
|
|
return machine->debugcpu_data->wpaddr;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_wpdata - getter callback for the
|
|
'wpdata' symbol
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_wpdata(void *globalref, void *ref)
|
|
{
|
|
running_machine *machine = (running_machine *)globalref;
|
|
return machine->debugcpu_data->wpdata;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_cpunum - getter callback for the
|
|
'cpunum' symbol
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_cpunum(void *globalref, void *ref)
|
|
{
|
|
running_machine *machine = (running_machine *)globalref;
|
|
return cpu_get_index(machine->debugcpu_data->visiblecpu);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_tempvar - getter callback for the
|
|
'tempX' symbols
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_tempvar(void *globalref, void *ref)
|
|
{
|
|
return *(UINT64 *)ref;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
set_tempvar - setter callback for the
|
|
'tempX' symbols
|
|
-------------------------------------------------*/
|
|
|
|
static void set_tempvar(void *globalref, void *ref, UINT64 value)
|
|
{
|
|
*(UINT64 *)ref = value;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_beamx - get beam horizontal position
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_beamx(void *globalref, void *ref)
|
|
{
|
|
const device_config *screen = (const device_config *)ref;
|
|
return (screen != NULL) ? video_screen_get_hpos(screen) : 0;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_beamy - get beam vertical position
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_beamy(void *globalref, void *ref)
|
|
{
|
|
const device_config *screen = (const device_config *)ref;
|
|
return (screen != NULL) ? video_screen_get_vpos(screen) : 0;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_frame - get current frame number
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_frame(void *globalref, void *ref)
|
|
{
|
|
const device_config *screen = (const device_config *)ref;
|
|
return (screen != NULL) ? video_screen_get_frame_number(screen) : 0;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_current_pc - getter callback for a CPU's
|
|
current instruction pointer
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_current_pc(void *globalref, void *ref)
|
|
{
|
|
const device_config *device = (const device_config *)globalref;
|
|
return cpu_get_pc(device);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_cycles - getter callback for the
|
|
'cycles' symbol
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_cycles(void *globalref, void *ref)
|
|
{
|
|
const device_config *device = (const device_config *)globalref;
|
|
return *cpu_get_icount_ptr(device);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_logunmap - getter callback for the logumap
|
|
symbols
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_logunmap(void *globalref, void *ref)
|
|
{
|
|
const address_space *space = (const address_space *)ref;
|
|
return (space != NULL) ? memory_get_log_unmap(space) : TRUE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
set_logunmap - setter callback for the logumap
|
|
symbols
|
|
-------------------------------------------------*/
|
|
|
|
static void set_logunmap(void *globalref, void *ref, UINT64 value)
|
|
{
|
|
const address_space *space = (const address_space *)ref;
|
|
if (space != NULL)
|
|
memory_set_log_unmap(space, value ? 1 : 0);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_cpu_reg - getter callback for a CPU's
|
|
register symbols
|
|
-------------------------------------------------*/
|
|
|
|
static UINT64 get_cpu_reg(void *globalref, void *ref)
|
|
{
|
|
const device_config *device = (const device_config *)globalref;
|
|
return cpu_get_reg(device, (FPTR)ref);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
set_cpu_reg - setter callback for a CPU's
|
|
register symbols
|
|
-------------------------------------------------*/
|
|
|
|
static void set_cpu_reg(void *globalref, void *ref, UINT64 value)
|
|
{
|
|
const device_config *device = (const device_config *)globalref;
|
|
cpu_set_reg(device, (FPTR)ref, value);
|
|
}
|