// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** points.cpp Debugger breakpoints, watchpoints, etc. ***************************************************************************/ #include "emu.h" #include "points.h" #include "debugger.h" #include "debugcon.h" //************************************************************************** // DEBUG BREAKPOINT //************************************************************************** //------------------------------------------------- // debug_breakpoint - constructor //------------------------------------------------- debug_breakpoint::debug_breakpoint( device_debug *debugInterface, symbol_table &symbols, int index, offs_t address, const char *condition, std::string_view action) : m_debugInterface(debugInterface), m_index(index), m_enabled(true), m_address(address), m_condition(symbols, condition ? condition : "1"), m_action(action) { } //------------------------------------------------- // hit - detect a hit //------------------------------------------------- bool debug_breakpoint::hit(offs_t pc) { // don't hit if disabled if (!m_enabled) return false; // must match our address if (m_address != pc) return false; // must satisfy the condition if (!m_condition.is_empty()) { try { return (m_condition.execute() != 0); } catch (expression_error const &) { return false; } } return true; } //************************************************************************** // DEBUG WATCHPOINT //************************************************************************** //------------------------------------------------- // debug_watchpoint - constructor //------------------------------------------------- debug_watchpoint::debug_watchpoint( device_debug* debugInterface, symbol_table &symbols, int index, address_space &space, read_or_write type, offs_t address, offs_t length, const char *condition, std::string_view action) : m_debugInterface(debugInterface), m_phr(nullptr), m_phw(nullptr), m_space(space), m_index(index), m_enabled(true), m_type(type), m_address(address & space.addrmask()), m_length(length), m_condition(symbols, condition ? condition : "1"), m_action(action), m_installing(false) { std::fill(std::begin(m_start_address), std::end(m_start_address), 0); std::fill(std::begin(m_end_address), std::end(m_end_address), 0); std::fill(std::begin(m_masks), std::end(m_masks), 0); int ashift = m_space.addr_shift(); endianness_t endian = m_space.endianness(); offs_t subamask = m_space.alignment() - 1; offs_t unit_size = ashift <= 0 ? 8 << -ashift : 8 >> ashift; offs_t start = m_address; offs_t end = (m_address + m_length - 1) & space.addrmask(); if (end < start) end = space.addrmask(); offs_t rstart = start & ~subamask; offs_t rend = end | subamask; u64 smask, mmask, emask; smask = mmask = emask = make_bitmask(m_space.data_width()); if (start != rstart) { if (endian == ENDIANNESS_LITTLE) smask &= ~make_bitmask((start - rstart) * unit_size); else smask &= make_bitmask((rstart + subamask + 1 - start) * unit_size); } if (end != rend) { if (endian == ENDIANNESS_LITTLE) emask &= make_bitmask((subamask + 1 + end - rend) * unit_size); else emask &= ~make_bitmask((rend - end) * unit_size); } if (rend == (rstart | subamask) || smask == emask) { m_start_address[0] = rstart; m_end_address[0] = rend; m_masks[0] = smask & emask; } else { int idx = 0; if (smask != mmask) { m_start_address[idx] = rstart; m_end_address[idx] = rstart | subamask; m_masks[idx] = smask; idx++; rstart += subamask + 1; } if (mmask == emask) { m_start_address[idx] = rstart; m_end_address[idx] = rend; m_masks[idx] = emask; } else { if (rstart < rend - subamask) { m_start_address[idx] = rstart; m_end_address[idx] = rend - subamask - 1; m_masks[idx] = mmask; idx++; } m_start_address[idx] = rend - subamask; m_end_address[idx] = rend; m_masks[idx] = emask; } } install(read_or_write::READWRITE); m_notifier = m_space.add_change_notifier( [this] (read_or_write mode) { if (m_enabled) { install(mode); } }); } debug_watchpoint::~debug_watchpoint() { m_notifier.reset(); m_phr.remove(); m_phw.remove(); } void debug_watchpoint::setEnabled(bool value) { if (m_enabled != value) { m_enabled = value; if (m_enabled) install(read_or_write::READWRITE); else { m_installing = true; m_phr.remove(); m_phw.remove(); m_installing = false; } } } void debug_watchpoint::install(read_or_write mode) { if (m_installing) return; m_installing = true; if (u32(mode) & u32(read_or_write::READ)) m_phr.remove(); if (u32(mode) & u32(read_or_write::WRITE)) m_phw.remove(); std::string name = util::string_format("wp@%x", m_address); switch (m_space.data_width()) { case 8: if (u32(m_type) & u32(mode) & u32(read_or_write::READ)) m_phr = m_space.install_read_tap( m_start_address[0], m_end_address[0], name, [this](offs_t offset, u8 &data, u8 mem_mask) { triggered(read_or_write::READ, offset, data, mem_mask); }, &m_phr); if (u32(m_type) & u32(mode) & u32(read_or_write::WRITE)) m_phw = m_space.install_write_tap( m_start_address[0], m_end_address[0], name, [this](offs_t offset, u8 &data, u8 mem_mask) { triggered(read_or_write::WRITE, offset, data, mem_mask); }, &m_phw); break; case 16: for (int i=0; i != 3; i++) if (m_masks[i]) { u16 mask = m_masks[i]; if (u32(m_type) & u32(mode) & u32(read_or_write::READ)) m_phr = m_space.install_read_tap( m_start_address[i], m_end_address[i], name, [this, mask](offs_t offset, u16 &data, u16 mem_mask) { if (mem_mask & mask) triggered(read_or_write::READ, offset, data, mem_mask); }, &m_phr); if (u32(m_type) & u32(mode) & u32(read_or_write::WRITE)) m_phw = m_space.install_write_tap( m_start_address[i], m_end_address[i], name, [this, mask](offs_t offset, u16 &data, u16 mem_mask) { if (mem_mask & mask) triggered(read_or_write::WRITE, offset, data, mem_mask); }, &m_phw); } break; case 32: for (int i=0; i != 3; i++) if (m_masks[i]) { u32 mask = m_masks[i]; if (u32(m_type) & u32(mode) & u32(read_or_write::READ)) m_phr = m_space.install_read_tap( m_start_address[i], m_end_address[i], name, [this, mask](offs_t offset, u32 &data, u32 mem_mask) { if (mem_mask & mask) triggered(read_or_write::READ, offset, data, mem_mask); }, &m_phr); if (u32(m_type) & u32(mode) & u32(read_or_write::WRITE)) m_phw = m_space.install_write_tap( m_start_address[i], m_end_address[i], name, [this, mask](offs_t offset, u32 &data, u32 mem_mask) { if (mem_mask & mask) triggered(read_or_write::WRITE, offset, data, mem_mask); }, &m_phw); } break; case 64: for (int i=0; i != 3; i++) if (m_masks[i]) { u64 mask = m_masks[i]; if (u32(m_type) & u32(mode) & u32(read_or_write::READ)) m_phr = m_space.install_read_tap( m_start_address[i], m_end_address[i], name, [this, mask](offs_t offset, u64 &data, u64 mem_mask) { if (mem_mask & mask) triggered(read_or_write::READ, offset, data, mem_mask); }, &m_phr); if (u32(m_type) & u32(mode) & u32(read_or_write::WRITE)) m_phw = m_space.install_write_tap(m_start_address[i], m_end_address[i], name, [this, mask](offs_t offset, u64 &data, u64 mem_mask) { if (mem_mask & mask) triggered(read_or_write::WRITE, offset, data, mem_mask); }, &m_phw); } break; } m_installing = false; } void debug_watchpoint::triggered(read_or_write type, offs_t address, u64 data, u64 mem_mask) { running_machine &machine = m_debugInterface->device().machine(); debugger_manager &debug = machine.debugger(); // if we're within debugger code, don't trigger if (debug.cpu().within_instruction_hook() || machine.side_effects_disabled()) return; // adjust address, size & value_to_write based on mem_mask. offs_t size = 0; int ashift = m_space.addr_shift(); offs_t unit_size = ashift <= 0 ? 8 << -ashift : 8 >> ashift; u64 unit_mask = make_bitmask(unit_size); offs_t address_offset = 0; if(!mem_mask) mem_mask = 0xff; while (!(mem_mask & unit_mask)) { address_offset++; data >>= unit_size; mem_mask >>= unit_size; } while (mem_mask) { size++; mem_mask >>= unit_size; } data &= make_bitmask(size * unit_size); if (m_space.endianness() == ENDIANNESS_LITTLE) address += address_offset; else address += m_space.alignment() - size - address_offset; // stash the value that will be written or has just been read debug.cpu().set_wpinfo(address, data, size * unit_size); // protect against recursion debug.cpu().set_within_instruction(true); // must satisfy the condition if (!m_condition.is_empty()) { try { if (!m_condition.execute()) { debug.cpu().set_within_instruction(false); return; } } catch (expression_error &) { debug.cpu().set_within_instruction(false); return; } } // halt in the debugger by default bool was_stopped = debug.cpu().is_stopped(); debug.cpu().set_execution_stopped(); // evaluate the action if (!m_action.empty()) debug.console().execute_command(m_action, false); // print a notification, unless the action made us go again if (debug.cpu().is_stopped()) { std::string buffer; if (m_space.is_octal()) buffer = string_format(type == read_or_write::READ ? "Stopped at watchpoint %X reading %0*o from %0*o" : "Stopped at watchpoint %X writing %0*o to %0*o", m_index, (size * unit_size + 2) / 3, data, (m_space.addr_width() + 2) / 3, address); else buffer = string_format(type == read_or_write::READ ? "Stopped at watchpoint %X reading %0*X from %0*X" : "Stopped at watchpoint %X writing %0*X to %0*X", m_index, size * unit_size / 4, data, (m_space.addr_width() + 3) / 4, address); const device_state_interface *state; if (debug.cpu().live_cpu() == &m_debugInterface->device() && m_debugInterface->device().interface(state)) { debug.console().printf("%s (PC=%s)\n", buffer, state->state_string(STATE_GENPCBASE)); m_debugInterface->compute_debug_flags(); } else if (!was_stopped) { debug.console().printf("%s\n", buffer); debug.cpu().set_execution_running(); debug.cpu().set_break_cpu(&m_debugInterface->device()); } m_debugInterface->set_triggered_watchpoint(this); } debug.cpu().set_within_instruction(false); } //************************************************************************** // DEBUG REGISTERPOINT //************************************************************************** //------------------------------------------------- // debug_registerpoint - constructor //------------------------------------------------- debug_registerpoint::debug_registerpoint(symbol_table &symbols, int index, const char *condition, std::string_view action) : m_index(index), m_enabled(true), m_condition(symbols, (condition != nullptr) ? condition : "1"), m_action(action) { } //------------------------------------------------- // hit - detect a hit //------------------------------------------------- bool debug_registerpoint::hit() { // don't hit if disabled if (!m_enabled) return false; // must satisfy the condition if (!m_condition.is_empty()) { try { return (m_condition.execute() != 0); } catch (expression_error &) { return false; } } return true; } //************************************************************************** // DEBUG EXCEPTION POINT //************************************************************************** //------------------------------------------------- // debug_exceptionpoint - constructor //------------------------------------------------- debug_exceptionpoint::debug_exceptionpoint( device_debug *debugInterface, symbol_table &symbols, int index, int type, const char *condition, std::string_view action) : m_debugInterface(debugInterface), m_index(index), m_enabled(true), m_type(type), m_condition(symbols, (condition != nullptr) ? condition : "1"), m_action(action) { } //------------------------------------------------- // hit - detect a hit //------------------------------------------------- bool debug_exceptionpoint::hit(int exception) { // don't hit if disabled if (!m_enabled) return false; // must match our type if (m_type != exception) return false; // must satisfy the condition if (!m_condition.is_empty()) { try { return (m_condition.execute() != 0); } catch (expression_error &) { return false; } } return true; }