// 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,
										const char *action)
	: m_debugInterface(debugInterface),
		m_index(index),
		m_enabled(true),
		m_address(address),
		m_condition(symbols, (condition != nullptr) ? condition : "1"),
		m_action((action != nullptr) ? 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 &)
		{
			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,
										const char *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 != nullptr) ? condition : "1"),
	  m_action((action != nullptr) ? 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<u64>(m_space.data_width());
	if (start != rstart)
	{
		if (endian == ENDIANNESS_LITTLE)
			smask &= ~make_bitmask<u64>((start - rstart) * unit_size);
		else
			smask &= make_bitmask<u64>((rstart + subamask + 1 - start) * unit_size);
	}
	if (end != rend)
	{
		if (endian == ENDIANNESS_LITTLE)
			emask &= make_bitmask<u64>((subamask + 1 + end - rend) * unit_size);
		else
			emask &= ~make_bitmask<u64>((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_space.remove_change_notifier(m_notifier);
	if (m_phr)
		m_phr->remove();
	if (m_phw)
		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;
			if(m_phr)
				m_phr->remove();
			if(m_phw)
				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)
		m_phr->remove();
	if ((u32(mode) & u32(read_or_write::WRITE)) && m_phw)
		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<u64>(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<u64>(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, const char *action)
	: m_index(index),
		m_enabled(true),
		m_condition(symbols, (condition != nullptr) ? condition : "1"),
		m_action((action != nullptr) ? 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;
}
