summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/debug
diff options
context:
space:
mode:
author AJR <ajrhacker@users.noreply.github.com>2022-03-27 14:23:03 -0400
committer AJR <ajrhacker@users.noreply.github.com>2022-03-27 14:29:51 -0400
commitc7597225b4f0a3531d53dd806048b136956eb850 (patch)
tree3ed5b758cbd0a94a43d9c6fe9f83f402b169657d /src/emu/debug
parentc3162e739d625d31ca207ab8b591ea87c5f1fe03 (diff)
Debugger feature improvements
- Add 'gbt' and 'gbf' debugger commands to step until a true or false conditional branch has been detected. - Update over 100 of the disassemblers in MAME to output a new STEP_COND flag for all conditional branches. Besides being used for execution of the new 'gbt' and 'gbf' commands, this flag also now helps the debugger 'out' command to properly handle conditional return instructions. - Remove STEP_OVER from many instructions that aren't actually subroutine calls (e.g. DJNZ on Z80). A 'gni' debugger command (go next instruction) has been added to accommodate some of the misuse. - Add instruction flag support to several more disassemblers that lacked them entirely (e.g. st62xx) - Don't pass over delay slots for debugging in ASAP core
Diffstat (limited to 'src/emu/debug')
-rw-r--r--src/emu/debug/debugcmd.cpp53
-rw-r--r--src/emu/debug/debugcmd.h2
-rw-r--r--src/emu/debug/debugcpu.cpp109
-rw-r--r--src/emu/debug/debugcpu.h15
-rw-r--r--src/emu/debug/debughlp.cpp62
5 files changed, 220 insertions, 21 deletions
diff --git a/src/emu/debug/debugcmd.cpp b/src/emu/debug/debugcmd.cpp
index bb93f050c95..21d143c7f1d 100644
--- a/src/emu/debug/debugcmd.cpp
+++ b/src/emu/debug/debugcmd.cpp
@@ -234,6 +234,9 @@ debugger_commands::debugger_commands(running_machine& machine, debugger_cpu& cpu
m_console.register_command("gtime", CMDFLAG_NONE, 0, 1, std::bind(&debugger_commands::execute_go_time, this, _1));
m_console.register_command("gt", CMDFLAG_NONE, 0, 1, std::bind(&debugger_commands::execute_go_time, this, _1));
m_console.register_command("gp", CMDFLAG_NONE, 0, 1, std::bind(&debugger_commands::execute_go_privilege, this, _1));
+ m_console.register_command("gbt", CMDFLAG_NONE, 0, 1, std::bind(&debugger_commands::execute_go_branch, this, true, _1));
+ m_console.register_command("gbf", CMDFLAG_NONE, 0, 1, std::bind(&debugger_commands::execute_go_branch, this, false, _1));
+ m_console.register_command("gni", CMDFLAG_NONE, 0, 1, std::bind(&debugger_commands::execute_go_next_instruction, this, _1));
m_console.register_command("next", CMDFLAG_NONE, 0, 0, std::bind(&debugger_commands::execute_next, this, _1));
m_console.register_command("n", CMDFLAG_NONE, 0, 0, std::bind(&debugger_commands::execute_next, this, _1));
m_console.register_command("focus", CMDFLAG_NONE, 1, 1, std::bind(&debugger_commands::execute_focus, this, _1));
@@ -1356,6 +1359,56 @@ void debugger_commands::execute_go_privilege(const std::vector<std::string> &par
m_console.get_visible_cpu()->debug()->go_privilege((condition.is_empty()) ? "1" : condition.original_string());
}
+
+/*-------------------------------------------------
+ execute_go_branch - execute gbt or gbf command
+-------------------------------------------------*/
+
+void debugger_commands::execute_go_branch(bool sense, const std::vector<std::string> &params)
+{
+ parsed_expression condition(m_console.visible_symtable());
+ if (params.size() > 0 && !debug_command_parameter_expression(params[0], condition))
+ return;
+
+ m_console.get_visible_cpu()->debug()->go_branch(sense, (condition.is_empty()) ? "1" : condition.original_string());
+}
+
+
+/*-------------------------------------------------
+ execute_go_next_instruction - execute gni command
+-------------------------------------------------*/
+
+void debugger_commands::execute_go_next_instruction(const std::vector<std::string> &params)
+{
+ u64 count = 1;
+
+ // if we have a parameter, use it instead */
+ if (params.size() > 0 && !validate_number_parameter(params[0], count))
+ return;
+ if (count == 0)
+ return;
+
+ device_state_interface *stateintf;
+ device_t *cpu = m_machine.debugger().console().get_visible_cpu();
+ if (!cpu->interface(stateintf))
+ {
+ m_console.printf("No state interface available for %s\n", cpu->name());
+ return;
+ }
+ u32 pc = stateintf->pcbase();
+
+ debug_disasm_buffer buffer(*cpu);
+ while (count-- != 0)
+ {
+ // disassemble the current instruction and get the length
+ u32 result = buffer.disassemble_info(pc);
+ pc = buffer.next_pc_wrap(pc, result & util::disasm_interface::LENGTHMASK);
+ }
+
+ cpu->debug()->go(pc);
+}
+
+
/*-------------------------------------------------
execute_next - execute the next command
-------------------------------------------------*/
diff --git a/src/emu/debug/debugcmd.h b/src/emu/debug/debugcmd.h
index b50c07bd634..88ae785e643 100644
--- a/src/emu/debug/debugcmd.h
+++ b/src/emu/debug/debugcmd.h
@@ -121,6 +121,8 @@ private:
void execute_go_exception(const std::vector<std::string> &params);
void execute_go_time(const std::vector<std::string> &params);
void execute_go_privilege(const std::vector<std::string> &params);
+ void execute_go_branch(bool sense, const std::vector<std::string> &params);
+ void execute_go_next_instruction(const std::vector<std::string> &params);
void execute_focus(const std::vector<std::string> &params);
void execute_ignore(const std::vector<std::string> &params);
void execute_observe(const std::vector<std::string> &params);
diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp
index 978702094b9..820d9e72688 100644
--- a/src/emu/debug/debugcpu.cpp
+++ b/src/emu/debug/debugcpu.cpp
@@ -427,6 +427,7 @@ device_debug::device_debug(device_t &device)
, m_instrhook(nullptr)
, m_stepaddr(0)
, m_stepsleft(0)
+ , m_delay_steps(0)
, m_stopaddr(0)
, m_stoptime(attotime::zero)
, m_stopirq(0)
@@ -688,11 +689,11 @@ void device_debug::privilege_hook()
if ((m_flags & DEBUG_FLAG_STOP_PRIVILEGE) != 0)
{
bool matched = true;
- if (m_privilege_condition && !m_privilege_condition->is_empty())
+ if (m_stop_condition && !m_stop_condition->is_empty())
{
try
{
- matched = m_privilege_condition->execute();
+ matched = m_stop_condition->execute();
}
catch (expression_error &)
{
@@ -750,19 +751,53 @@ void device_debug::instruction_hook(offs_t curpc)
// handle single stepping
if (!debugcpu.is_stopped() && (m_flags & DEBUG_FLAG_STEPPING_ANY) != 0)
{
+ bool do_step = true;
+ if ((m_flags & (DEBUG_FLAG_CALL_IN_PROGRESS | DEBUG_FLAG_TEST_IN_PROGRESS)) != 0)
+ {
+ if (curpc == m_stepaddr)
+ {
+ if ((~m_flags & (DEBUG_FLAG_TEST_IN_PROGRESS | DEBUG_FLAG_STEPPING_BRANCH_FALSE)) == 0)
+ {
+ debugcpu.set_execution_stopped();
+ do_step = false;
+ }
+
+ // reset the breakpoint
+ m_flags &= ~(DEBUG_FLAG_CALL_IN_PROGRESS | DEBUG_FLAG_TEST_IN_PROGRESS);
+ m_delay_steps = 0;
+ }
+ else if (m_delay_steps != 0)
+ {
+ m_delay_steps--;
+ if (m_delay_steps == 0)
+ {
+ // branch taken or subroutine entered (TODO: interrupt acknowledgment or interleaved multithreading can falsely trigger this)
+ if ((m_flags & DEBUG_FLAG_TEST_IN_PROGRESS) != 0 && (m_flags & (DEBUG_FLAG_STEPPING_OUT | DEBUG_FLAG_STEPPING_BRANCH_TRUE)) != 0)
+ {
+ debugcpu.set_execution_stopped();
+ do_step = false;
+ }
+ if ((m_flags & DEBUG_FLAG_CALL_IN_PROGRESS) != 0)
+ do_step = false;
+ m_flags &= ~DEBUG_FLAG_TEST_IN_PROGRESS;
+ }
+ }
+ else
+ do_step = false;
+ }
+
// is this an actual step?
- if (m_stepaddr == ~0 || curpc == m_stepaddr)
+ if (do_step)
{
- // decrement the count and reset the breakpoint
+ // decrement the count
m_stepsleft--;
- m_stepaddr = ~0;
// if we hit 0, stop
if (m_stepsleft == 0)
debugcpu.set_execution_stopped();
// update every 100 steps until we are within 200 of the end
- else if ((m_flags & DEBUG_FLAG_STEPPING_OUT) == 0 && (m_stepsleft < 200 || m_stepsleft % 100 == 0))
+ else if ((m_flags & (DEBUG_FLAG_STEPPING_OUT | DEBUG_FLAG_STEPPING_BRANCH_TRUE | DEBUG_FLAG_STEPPING_BRANCH_FALSE)) == 0 && (m_stepsleft < 200 || m_stepsleft % 100 == 0))
{
machine.debug_view().update_all();
machine.debug_view().flush_osd_updates();
@@ -849,7 +884,7 @@ void device_debug::instruction_hook(offs_t curpc)
}
// handle step out/over on the instruction we are about to execute
- if ((m_flags & (DEBUG_FLAG_STEPPING_OVER | DEBUG_FLAG_STEPPING_OUT)) != 0 && m_stepaddr == ~0)
+ if ((m_flags & (DEBUG_FLAG_STEPPING_OVER | DEBUG_FLAG_STEPPING_OUT | DEBUG_FLAG_STEPPING_BRANCH)) != 0 && (m_flags & (DEBUG_FLAG_CALL_IN_PROGRESS | DEBUG_FLAG_TEST_IN_PROGRESS)) == 0)
prepare_for_step_overout(m_state->pcbase());
// no longer in debugger code
@@ -928,7 +963,7 @@ void device_debug::single_step(int numsteps)
m_device.machine().rewind_capture();
m_stepsleft = numsteps;
- m_stepaddr = ~0;
+ m_delay_steps = 0;
m_flags |= DEBUG_FLAG_STEPPING;
m_device.machine().debugger().cpu().set_execution_running();
}
@@ -945,7 +980,7 @@ void device_debug::single_step_over(int numsteps)
m_device.machine().rewind_capture();
m_stepsleft = numsteps;
- m_stepaddr = ~0;
+ m_delay_steps = 0;
m_flags |= DEBUG_FLAG_STEPPING_OVER;
m_device.machine().debugger().cpu().set_execution_running();
}
@@ -961,8 +996,9 @@ void device_debug::single_step_out()
assert(m_exec != nullptr);
m_device.machine().rewind_capture();
+ m_stop_condition.reset();
m_stepsleft = 100;
- m_stepaddr = ~0;
+ m_delay_steps = 0;
m_flags |= DEBUG_FLAG_STEPPING_OUT;
m_device.machine().debugger().cpu().set_execution_running();
}
@@ -1060,13 +1096,30 @@ void device_debug::go_privilege(const char *condition)
{
assert(m_exec != nullptr);
m_device.machine().rewind_invalidate();
- m_privilege_condition = std::make_unique<parsed_expression>(*m_symtable, condition);
+ m_stop_condition = std::make_unique<parsed_expression>(*m_symtable, condition);
m_flags |= DEBUG_FLAG_STOP_PRIVILEGE;
m_device.machine().debugger().cpu().set_execution_running();
}
//-------------------------------------------------
+// go_branch - execute until branch taken or
+// not taken
+//-------------------------------------------------
+
+void device_debug::go_branch(bool sense, const char *condition)
+{
+ assert(m_exec != nullptr);
+ m_device.machine().rewind_invalidate();
+ m_stop_condition = std::make_unique<parsed_expression>(*m_symtable, condition);
+ m_stepsleft = 100;
+ m_delay_steps = 0;
+ m_flags |= sense ? DEBUG_FLAG_STEPPING_BRANCH_TRUE : DEBUG_FLAG_STEPPING_BRANCH_FALSE;
+ m_device.machine().debugger().cpu().set_execution_running();
+}
+
+
+//-------------------------------------------------
// halt_on_next_instruction_impl - halt in the
// debugger on the next instruction, internal
// implementation which is necessary solely due
@@ -1385,7 +1438,6 @@ void device_debug::set_track_pc(bool value)
// track_pc_visited - returns a boolean stating
// if this PC has been visited or not. CRC32 is
// done in this function on currently active CPU.
-// TODO: Take a CPU context as input
//-------------------------------------------------
bool device_debug::track_pc_visited(offs_t pc) const
@@ -1399,7 +1451,6 @@ bool device_debug::track_pc_visited(offs_t pc) const
//-------------------------------------------------
// set_track_pc_visited - set this pc as visited.
-// TODO: Take a CPU context as input
//-------------------------------------------------
void device_debug::set_track_pc_visited(offs_t pc)
@@ -1625,12 +1676,32 @@ void device_debug::prepare_for_step_overout(offs_t pc)
// disassemble the current instruction and get the flags
u32 dasmresult = buffer.disassemble_info(pc);
+ if ((dasmresult & util::disasm_interface::SUPPORTED) == 0)
+ return;
+
+ bool step_out = (m_flags & DEBUG_FLAG_STEPPING_OUT) != 0 && (dasmresult & util::disasm_interface::STEP_OUT) != 0;
+ bool test_cond = (dasmresult & util::disasm_interface::STEP_COND) != 0 && ((m_flags & (DEBUG_FLAG_STEPPING_BRANCH_TRUE | DEBUG_FLAG_STEPPING_BRANCH_FALSE)) != 0 || step_out);
+ if (test_cond && m_stop_condition && !m_stop_condition->is_empty())
+ {
+ try
+ {
+ test_cond = m_stop_condition->execute();
+ }
+ catch (expression_error &)
+ {
+ test_cond = false;
+ }
+ }
// if flags are supported and it's a call-style opcode, set a temp breakpoint after that instruction
- if ((dasmresult & util::disasm_interface::SUPPORTED) != 0 && (dasmresult & util::disasm_interface::STEP_OVER) != 0)
+ // (TODO: this completely fails for subroutines that consume inline operands or use alternate returns)
+ if ((dasmresult & util::disasm_interface::STEP_OVER) != 0 || test_cond)
{
int extraskip = (dasmresult & util::disasm_interface::OVERINSTMASK) >> util::disasm_interface::OVERINSTSHIFT;
pc = buffer.next_pc_wrap(pc, dasmresult & util::disasm_interface::LENGTHMASK);
+ m_delay_steps = extraskip + 1;
+ if (m_stepsleft < m_delay_steps)
+ m_stepsleft = m_delay_steps;
// if we need to skip additional instructions, advance as requested
while (extraskip-- > 0) {
@@ -1638,13 +1709,17 @@ void device_debug::prepare_for_step_overout(offs_t pc)
pc = buffer.next_pc_wrap(pc, result & util::disasm_interface::LENGTHMASK);
}
m_stepaddr = pc;
+ if ((dasmresult & util::disasm_interface::STEP_OVER) != 0)
+ m_flags |= DEBUG_FLAG_CALL_IN_PROGRESS;
+ if (test_cond)
+ m_flags |= DEBUG_FLAG_TEST_IN_PROGRESS;
}
// if we're stepping out and this isn't a step out instruction, reset the steps until stop to a high number
- // (TODO: this doesn't work with conditional return instructions)
- if ((m_flags & DEBUG_FLAG_STEPPING_OUT) != 0)
+ if ((m_flags & (DEBUG_FLAG_STEPPING_OUT | DEBUG_FLAG_STEPPING_BRANCH_TRUE | DEBUG_FLAG_STEPPING_BRANCH_FALSE)) != 0)
{
- if ((dasmresult & util::disasm_interface::SUPPORTED) != 0 && (dasmresult & util::disasm_interface::STEP_OUT) == 0)
+ // make sure to also reset the number of steps for conditionals that may be single-instruction loops
+ if (test_cond || !step_out)
m_stepsleft = 100;
else
{
diff --git a/src/emu/debug/debugcpu.h b/src/emu/debug/debugcpu.h
index a7d266df1e8..d9fc3f64eae 100644
--- a/src/emu/debug/debugcpu.h
+++ b/src/emu/debug/debugcpu.h
@@ -82,6 +82,7 @@ public:
void go_exception(int exception, const char *condition);
void go_milliseconds(u64 milliseconds);
void go_privilege(const char *condition);
+ void go_branch(bool sense, const char *condition);
void go_next_device();
template <typename Format, typename... Params>
@@ -184,15 +185,16 @@ private:
debug_instruction_hook_func m_instrhook; // per-instruction callback hook
// stepping information
- offs_t m_stepaddr; // step target address for DEBUG_FLAG_STEPPING_OVER
+ offs_t m_stepaddr; // step target address for DEBUG_FLAG_STEPPING_OVER or DEBUG_FLAG_STEPPING_BRANCH
int m_stepsleft; // number of steps left until done
+ int m_delay_steps; // number of steps until target address check
// execution information
offs_t m_stopaddr; // stop address for DEBUG_FLAG_STOP_PC
attotime m_stoptime; // stop time for DEBUG_FLAG_STOP_TIME
int m_stopirq; // stop IRQ number for DEBUG_FLAG_STOP_INTERRUPT
int m_stopexception; // stop exception number for DEBUG_FLAG_STOP_EXCEPTION
- std::unique_ptr<parsed_expression> m_privilege_condition; // expression to evaluate on privilege change
+ std::unique_ptr<parsed_expression> m_stop_condition; // expression to evaluate on privilege change
std::unique_ptr<parsed_expression> m_exception_condition; // expression to evaluate on exception hit
attotime m_endexectime; // ending time of the current execution
u64 m_total_cycles; // current total cycles
@@ -322,12 +324,17 @@ private:
static constexpr u32 DEBUG_FLAG_SUSPENDED = 0x00004000; // CPU currently suspended
static constexpr u32 DEBUG_FLAG_LIVE_BP = 0x00010000; // there are live breakpoints for this CPU
static constexpr u32 DEBUG_FLAG_STOP_PRIVILEGE = 0x00020000; // run until execution level changes
+ static constexpr u32 DEBUG_FLAG_STEPPING_BRANCH_TRUE = 0x0040000; // run until true branch
+ static constexpr u32 DEBUG_FLAG_STEPPING_BRANCH_FALSE = 0x0080000; // run until false branch
+ static constexpr u32 DEBUG_FLAG_CALL_IN_PROGRESS = 0x01000000; // CPU is in the middle of a subroutine call
+ static constexpr u32 DEBUG_FLAG_TEST_IN_PROGRESS = 0x02000000; // CPU is performing a conditional test and branch
- static constexpr u32 DEBUG_FLAG_STEPPING_ANY = DEBUG_FLAG_STEPPING | DEBUG_FLAG_STEPPING_OVER | DEBUG_FLAG_STEPPING_OUT;
+ static constexpr u32 DEBUG_FLAG_STEPPING_BRANCH = DEBUG_FLAG_STEPPING_BRANCH_TRUE | DEBUG_FLAG_STEPPING_BRANCH_FALSE;
+ static constexpr u32 DEBUG_FLAG_STEPPING_ANY = DEBUG_FLAG_STEPPING | DEBUG_FLAG_STEPPING_OVER | DEBUG_FLAG_STEPPING_OUT | DEBUG_FLAG_STEPPING_BRANCH;
static constexpr u32 DEBUG_FLAG_TRACING_ANY = DEBUG_FLAG_TRACING | DEBUG_FLAG_TRACING_OVER;
static constexpr u32 DEBUG_FLAG_TRANSIENT = DEBUG_FLAG_STEPPING_ANY | DEBUG_FLAG_STOP_PC |
DEBUG_FLAG_STOP_INTERRUPT | DEBUG_FLAG_STOP_EXCEPTION | DEBUG_FLAG_STOP_VBLANK |
- DEBUG_FLAG_STOP_TIME | DEBUG_FLAG_STOP_PRIVILEGE;
+ DEBUG_FLAG_STOP_TIME | DEBUG_FLAG_STOP_PRIVILEGE | DEBUG_FLAG_CALL_IN_PROGRESS | DEBUG_FLAG_TEST_IN_PROGRESS;
};
//**************************************************************************
diff --git a/src/emu/debug/debughlp.cpp b/src/emu/debug/debughlp.cpp
index 87bf8f0f18f..869167e96e8 100644
--- a/src/emu/debug/debughlp.cpp
+++ b/src/emu/debug/debughlp.cpp
@@ -132,6 +132,9 @@ const help_item f_static_help_list[] =
" o[ver] [<count>=1] -- single steps over <count> instructions (F10)\n"
" out -- single steps until the current subroutine/exception handler is exited (Shift-F11)\n"
" g[o] [<address>] -- resumes execution, sets temp breakpoint at <address> (F5)\n"
+ " gbf [<condition>] -- resumes execution until next false branch\n"
+ " gbt [<condition>] -- resumes execution until next true branch\n"
+ " gn[i] [<count>] -- resumes execution, sets temp breakpoint <count> instructions ahead\n"
" ge[x] [<exception>[,<condition>]] -- resumes execution, setting temp breakpoint if <exception> is raised\n"
" gi[nt] [<irqline>] -- resumes execution, setting temp breakpoint if <irqline> is taken (F7)\n"
" gt[ime] <milliseconds> -- resumes execution until the given delay has elapsed\n"
@@ -920,6 +923,65 @@ const help_item f_static_help_list[] =
" Resume execution, stopping at address 1234 unless something else stops us first.\n"
},
{
+ "gbf",
+ "\n"
+ " gbf [<condition>]\n"
+ "\n"
+ "The gbf command resumes execution of the current code. Control will not be returned to the "
+ "debugger until a conditional branch or skip instruction tests false and falls through to the "
+ "next instruction, or the instruction that follows its delay slot. The optional conditional "
+ "expression, if provided, will be evaluated at (not after) each conditional branch; execution "
+ "will not halt regardless of consequent program flow unless its result is true (non-zero).\n"
+ "\n"
+ "Examples:\n"
+ "\n"
+ "gbf\n"
+ " Resume execution until the next break/watchpoint or until the next false branch.\n"
+ "\n"
+ "gbf {pc != 1234}\n"
+ " Resume execution until the next false branch, disregarding the instruction at address 1234.\n"
+ },
+ {
+ "gbt",
+ "\n"
+ " gbt [<condition>]\n"
+ "\n"
+ "The gbt command resumes execution of the current code. Control will not be returned to the "
+ "debugger until a conditional branch or skip instruction tests true and program flow transfers "
+ "to any instruction other than the next one following the delay slot (if any). The optional "
+ "conditional expression, if provided, will be evaluated at (not after) each conditional "
+ "branch; execution will not halt regardless of consequent program flow unless its result is "
+ "true (non-zero).\n"
+ "\n"
+ "Examples:\n"
+ "\n"
+ "gbt\n"
+ " Resume execution until the next break/watchpoint or until the next true branch.\n"
+ "\n"
+ "gbt {pc != 1234}\n"
+ " Resume execution until the next true branch, disregarding the instruction at address 1234.\n"
+ },
+ {
+ "gni",
+ "\n"
+ " gn[i] [<count>]\n"
+ "\n"
+ "The gni command resumes execution of the current code. Control will not be returned to the "
+ "debugger until a breakpoint or watchpoint is hit, or until you manually break in using the "
+ "assigned key. Before executing, the gni command sets a temporary unconditional breakpoint "
+ "<count> instructions sequentially past the current one, which is automatically removed when "
+ "hit. If <count> is omitted, its default value is 1."
+ "\n"
+ "Examples:\n"
+ "\n"
+ "gni\n"
+ " Resume execution, stopping at the address of the next instruction unless something else "
+ "stops us first.\n"
+ "\n"
+ "gni 2\n"
+ " Resume execution, stopping at two instructions past the current one.\n"
+ },
+ {
"gex",
"\n"
" ge[x] [<exception>,[<condition>]]\n"