
#include "HardwareBreakpoints.h"
#include "PluginAPI.h"
#include "DialogHWBreakpoints.h"
#include "DebuggerCoreInterface.h"
#include "State.h"
#include "Core.h"

#include <QMenu>
#include <QDialog>

// TODO: at the moment, nearly this entire file is x86/x86-64 specific
//       we need to figure out a proper way to support (if at all) non
//       x86 arches

//------------------------------------------------------------------------------
// Name: HardwareBreakpoints()
// Desc: 
//------------------------------------------------------------------------------
HardwareBreakpoints::HardwareBreakpoints() : m_Menu(0), m_Dialog(0), m_OldEventHandler(0) {
}

//------------------------------------------------------------------------------
// Name: menu(QWidget *parent)
// Desc: 
//------------------------------------------------------------------------------
QMenu *HardwareBreakpoints::menu(QWidget *parent) {
	if(m_Menu == 0) {
		m_Menu = new QMenu(tr("Hardware BreakpointManager"), parent);
		m_Menu->addAction(tr("&Hardware Breakpoints"), this, SLOT(doMenu()), QKeySequence(tr("Ctrl+Shift+H")));
	}
	
	return m_Menu;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void HardwareBreakpoints::setupBP(State &state, int num, bool enabled, edb::address_t addr, int type, int size) {

	const int N1 = 16 + (num * 4);
	const int N2 = 18 + (num * 4);

	// default to disabled
	state.dr[7] = (state.dr[7] & ~(0x01 << (num * 2)));
	
	if(enabled) {
		// set the address
		state.dr[num] = addr;

		// enable this breakpoint
		state.dr[7] |= (0x01 << (num * 2));

		// setup the type
		switch(type) {
		case 2:
			// read/write
			state.dr[7] = (state.dr[7] & ~(0x03 << N1)) | (0x03 << N1);
			break;
		case 1:
			// write
			state.dr[7] = (state.dr[7] & ~(0x03 << N1)) | (0x01 << N1);
			break;
		case 0:
			// execute
			state.dr[7] = (state.dr[7] & ~(0x03 << N1));
			break;
		}

		if(type != 0) {
			// setup the size
			switch(size) {
			case 2:
				// 4 bytes
				state.dr[7] = (state.dr[7] & ~(0x03 << N2)) | (0x03 << N2);
				break;
			case 1:
				// 2 bytes
				state.dr[7] = (state.dr[7] & ~(0x03 << N2)) | (0x01 << N2);
				break;
			case 0:
				// 1 byte
				state.dr[7] = (state.dr[7] & ~(0x03 << N2));
				break;
			}
		} else {
			state.dr[7] = (state.dr[7] & ~(0x03 << N2));
		}			
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void HardwareBreakpoints::setupBreakPoints(DialogHWBreakpoints *p) {
	
	// TODO: assert that we are paused
	
	const bool enabled = 
		p->ui.chkBP1->isChecked() ||
		p->ui.chkBP2->isChecked() ||
		p->ui.chkBP3->isChecked() ||
		p->ui.chkBP4->isChecked();

	if(enabled) {
		// we want to be enabled, if we aren't already hooked, 
		// hook it
		if(m_OldEventHandler == 0) {
			m_OldEventHandler = m_API.setDebugEventHandler(this);
		}

		State state;
		bool ok;
		m_API.debuggerCore->getState(state);
		
		edb::address_t addr;
		
		addr = edb::core::stringToAddress(p->ui.txtBP1->text(), ok);
		if(ok) {	
			setupBP(state, 0, p->ui.chkBP1->isChecked(), addr, p->ui.cmbType1->currentIndex(), p->ui.cmbSize1->currentIndex());
		}
		
		addr = edb::core::stringToAddress(p->ui.txtBP2->text(), ok);
		if(ok) {	
			setupBP(state, 1, p->ui.chkBP2->isChecked(), addr, p->ui.cmbType2->currentIndex(), p->ui.cmbSize2->currentIndex());
		}
		
		addr = edb::core::stringToAddress(p->ui.txtBP3->text(), ok);
		if(ok) {	
			setupBP(state, 2, p->ui.chkBP3->isChecked(), addr, p->ui.cmbType3->currentIndex(), p->ui.cmbSize3->currentIndex());
		}
		
		addr = edb::core::stringToAddress(p->ui.txtBP4->text(), ok);
		if(ok) {	
			setupBP(state, 3, p->ui.chkBP4->isChecked(), addr, p->ui.cmbType4->currentIndex(), p->ui.cmbSize4->currentIndex());
		}

		m_API.debuggerCore->setState(state);

	} else {
	
		State state;
		m_API.debuggerCore->getState(state);
		state.dr[7] = 0;
		m_API.debuggerCore->setState(state);
	
		// we want to be disabled and we have hooked, so unhook
		if(m_OldEventHandler != 0) {
			m_API.setDebugEventHandler(m_OldEventHandler);
			m_OldEventHandler = 0;
		}			
	}
}

//------------------------------------------------------------------------------
// Name: doMenu()
// Desc: 
//------------------------------------------------------------------------------
void HardwareBreakpoints::doMenu() {

	if(m_Dialog == 0) {
		m_Dialog = new DialogHWBreakpoints(m_API, m_API.debuggerUI);
	}
	
	DialogHWBreakpoints *const p = qobject_cast<DialogHWBreakpoints *>(m_Dialog);
	
	if(p != 0) {	
		if(m_Dialog->exec() == QDialog::Accepted) {
			setupBreakPoints(p);
		}
	}
}

//------------------------------------------------------------------------------
// Name: handleEvent(DebugEvent event)
// Desc: this hooks the debug event handler so we can make the breakpoints
//       able to be resumed
//------------------------------------------------------------------------------
void HardwareBreakpoints::handleEvent(DebugEvent event) {

	if(event.reason() == DebugEvent::EVENT_STOPPED) {
		if(event.stopCode() == DebugEvent::sigtrap) {
			// check DR6 to see if it was a HW BP event
			// if so, set the resume flag
			State state;
			m_API.debuggerCore->getState(state);
			
			if((state.dr[6] & 0x0f) != 0x00) {
#if defined(EDB_X86_64)
				state.rflags |= (1 << 16);
#elif defined(EDB_X86)
				state.eflags |= (1 << 16);
#else
#error "Unsupported Platform"
#endif
				m_API.debuggerCore->setState(state);
			}
		}
	}
	
	m_OldEventHandler->handleEvent(event);
}


Q_EXPORT_PLUGIN2(HardwareBreakpoints, HardwareBreakpoints)
