/*
Copyright (C) 2006 Evan Teran
                   eteran@alum.rit.edu

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/*
 * NOTE: system specific functions may be implemented in src/os/[*]/Debugger*.cpp
 * this is to isolate the system specific code away from the general code
 */

#include "Debugger.h"
#include "MemoryRegions.h"
#include "DebuggerCoreInterface.h"
#include "DebuggerMain.h"
#include "DialogInputValue.h"
#include "SymbolManager.h"
#include "DialogOptions.h"
#include "Configuration.h"
#include "DialogInputBinaryString.h"
#include "version.h"
#include "Util.h"
#include "Expression.h"
#include "ELF32BinaryInfo.h"
#include "i386ArchProcessor.h"

#include <QMessageBox>
#include <QHBoxLayout>
#include <QFileInfo>
#include <QString>

#include <sstream>
#include <iostream>
#include <ostream>

DebuggerCoreInterface		*Debugger::debuggerBase			= 0;
QWidget 					*Debugger::debuggerGUI			= 0;
DebugEventHandlerInterface	*Debugger::debugEventHandler	= 0;

static void repaintCPUView() {
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(Debugger::debuggerGUI);
	Q_ASSERT(gui != 0);
	gui->ui.cpuView->viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
State &Debugger::currentState() {
	static State g_State;	
	return g_State;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
SymbolManager &Debugger::symbolManager() {
	static SymbolManager g_SymbolManager;
	return g_SymbolManager;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
MemoryRegions &Debugger::memoryRegions() {
	static MemoryRegions g_MemoryRegions;
	return g_MemoryRegions;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
ArchProcessor *Debugger::archProcessor() {
	static ArchProcessor *g_ArchProcessor = 0;
	if(g_ArchProcessor == 0) {
	
		// TODO: don't assume i386!
		static i386ArchProcessor g_i386ArchProcessor;
		g_ArchProcessor = &g_i386ArchProcessor;
	}
	return g_ArchProcessor;
}

//------------------------------------------------------------------------------
// Name: setDebugEventHandler(DebugEventHandlerInterface *p)
// Desc: 
//------------------------------------------------------------------------------
DebugEventHandlerInterface *Debugger::setDebugEventHandler(DebugEventHandlerInterface *p) {
	return reinterpret_cast<DebugEventHandlerInterface *>(q_atomic_set_ptr(&debugEventHandler, p));
}

//------------------------------------------------------------------------------
// Name: intVersion()
// Desc: returns an integer comparable version of our current version string
//------------------------------------------------------------------------------
unsigned long Debugger::intVersion() {
	return intVersion(version_string);
}

//------------------------------------------------------------------------------
// Name: intVersion()
// Desc: returns an integer comparable version of a version string in x.y.z 
//       format, or 0 if error
//------------------------------------------------------------------------------
unsigned long Debugger::intVersion(const QString &s) {
	unsigned int maj = 0;
	unsigned int min = 0;
	unsigned int rev = 0;
	unsigned long ret = 0;
	
	std::istringstream ss(qPrintable(s));
	if(ss >> maj) {
		if(ss.peek() == '.') {
			ss.ignore(1);
			if(ss >> min) {
				if(ss.peek() == '.') {
					ss.ignore(1);	
					if(ss >> rev) {	
						ret = (maj << 12) | (min << 8) | (rev);
					}
				}
			}
		}
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: log(const QString &s)
// Desc: writes a single line to the logger
//------------------------------------------------------------------------------
void Debugger::log(const QString &s) {
	std::cout << qPrintable(s) << std::endl;
}

//------------------------------------------------------------------------------
// Name: jumpToAddress(edb::address_t address)
// Desc: sets the disassembly display to a given address, returning success 
//       status
//------------------------------------------------------------------------------
bool Debugger::jumpToAddress(edb::address_t address) {
	bool ret = false;
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui != 0) {
		MemoryRegions::Region region;
		if(memoryRegions().findRegion(address, region)) {
			gui->doJumpToAddress(address, region, true);
			ret = true;
		}
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: dumpDataRange(edb::address_t address, edb::address_t end_address, bool newTab)
// Desc: shows a given address through a given end address in the data view, 
//       optionally in a new tab
//------------------------------------------------------------------------------
bool Debugger::dumpDataRange(edb::address_t address, edb::address_t end_address, bool newTab) {
	bool ret = false;
	// TODO: remove need for this to see so much of the GUI's private data!
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui != 0) {
		MemoryRegions::Region region;
		if(memoryRegions().findRegion(address, region)) {

			if(newTab) {
				gui->mnuDumpCreateTab();
			}

			const int currentIndex = gui->ui.tabWidget->currentIndex();
			DebuggerMain::DataViewInfo *const info = gui->m_DataRegions[currentIndex];

			if(info != 0) {
				info->region = region;

				if(end_address >= info->region.start && end_address < info->region.end) {
					info->region.end = end_address;
				}

				if(address >= info->region.start && address < info->region.end) {
					info->region.start = address;
				}

				gui->updateData(info);
				ret = true;
			}
		}
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: dumpDataRange(edb::address_t address, edb::address_t end_address)
// Desc: shows a given address through a given end address in the data view
//------------------------------------------------------------------------------
bool Debugger::dumpDataRange(edb::address_t address, edb::address_t end_address) {
	return dumpDataRange(address, end_address, false);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::dumpStack(edb::address_t address) {
	return dumpStack(address, true);
}

//------------------------------------------------------------------------------
// Name: dumpStack(edb::address_t address)
// Desc: shows a given address in the stack view
//------------------------------------------------------------------------------
bool Debugger::dumpStack(edb::address_t address, bool ScrollTo) {

	bool ret = false;
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui != 0) {
		if(memoryRegions().findRegion(address, gui->m_StackViewInfo.region)) {
			gui->m_StackViewInfo.update();
			if(ScrollTo) {
				gui->ui.stackView->scrollTo(address - gui->m_StackViewInfo.region.start);
			}
			ret = true;
		}
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: dumpData(edb::address_t address, bool newTab)
// Desc: shows a given address in the data view, optionally in a new tab
//------------------------------------------------------------------------------
bool Debugger::dumpData(edb::address_t address, bool newTab) {

	bool ret = false;
	// TODO: remove need for this to see so much of the GUI's private data!
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerGUI);
	if(gui != 0) {
		MemoryRegions::Region region;
		if(memoryRegions().findRegion(address, region)) {

			if(newTab) {
				gui->mnuDumpCreateTab();
			}

			const int currentIndex = gui->ui.tabWidget->currentIndex();
			DebuggerMain::DataViewInfo *const info = gui->m_DataRegions[currentIndex];

			if(info != 0) {
				info->region = region;
				gui->updateData(info);
				info->view->scrollTo(address - info->region.start);
				ret = true;
			}
		}
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: dumpData(edb::address_t address)
// Desc: shows a given address in the data view
//------------------------------------------------------------------------------
bool Debugger::dumpData(edb::address_t address) {
	return dumpData(address, false);
}

//------------------------------------------------------------------------------
// Name: createBreakpoint(edb::address_t address)
// Desc: adds a breakpoint at a given address
//------------------------------------------------------------------------------
void Debugger::createBreakpoint(edb::address_t address) {

	MemoryRegions::Region region;
	memoryRegions().sync();
	if(memoryRegions().findRegion(address, region)) {


		int ret = QMessageBox::Yes;

		if(!region.executable() && config()->warnOnNoExecBP) {
			ret = QMessageBox::question(debuggerGUI, "Suspicious breakpoint", 
				"You want to place a breakpoint in a non-executable region.\n"
				"An INT3 breakpoint set on data will not execute and may cause incorrect results or crashes.\n"
				"Do you really want to set a breakpoint here?", 
				QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton);
		}

		if(ret == QMessageBox::Yes) {
			debuggerBase->addBreakpoint(address);
			repaintCPUView();
		}


	} else {
		QMessageBox::information(debuggerGUI, "Error Setting Breakpoint",
			"Sorry, but setting a breakpoint which is not in a valid region is not allowed.");
	}
}

//------------------------------------------------------------------------------
// Name: toggleBreakpoint(edb::address_t address)
// Desc: toggles the existant of a breakpoint at a given address
//------------------------------------------------------------------------------
void Debugger::toggleBreakpoint(edb::address_t address) {
	if(debuggerBase->findBreakpoint(address)) {
		removeBreakpoint(address);
	} else {
		createBreakpoint(address);
	}
}

//------------------------------------------------------------------------------
// Name: toggleBreakpoint(edb::address_t address)
// Desc: toggles the existant of a breakpoint at a given address
//------------------------------------------------------------------------------
void Debugger::removeBreakpoint(edb::address_t address) {
	debuggerBase->removeBreakpoint(address);
	repaintCPUView();
}

//------------------------------------------------------------------------------
// Name: getValueFromUser(edb::reg_t &value, const QString &title)
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getValueFromUser(edb::reg_t &value, const QString &title) {
	static DialogInputValue *dlg = 0;
	bool ret = false;
	
	if(dlg == 0) {
		dlg = new DialogInputValue(debuggerGUI);
	}
	
	dlg->setWindowTitle(title);
	dlg->setValue(value);
	if(dlg->exec() == QDialog::Accepted) {
		value = dlg->value();
		ret = true;	
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: getValueFromUser(edb::reg_t &value)
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getValueFromUser(edb::reg_t &value) {
	return getValueFromUser(value, "Input Value");
}

//------------------------------------------------------------------------------
// Name: getBinaryStringFromUser(QByteArray &value, int maxLength)
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getBinaryStringFromUser(QByteArray &value, int maxLength) {
	return getBinaryStringFromUser(value, "Input Value", maxLength);
}

//------------------------------------------------------------------------------
// Name: getBinaryStringFromUser(QByteArray &value, const QString &title, int maxLength)
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::getBinaryStringFromUser(QByteArray &value, const QString &title, int maxLength) {
	static DialogInputBinaryString *dlg = 0;
	
	bool ret = false;
	
	if(dlg == 0) {
		dlg = new DialogInputBinaryString(debuggerGUI);
	}
	
	dlg->setWindowTitle(title);
	
	BinaryString *const bs = dlg->binaryString();
	
	// set the max length BEFORE the value! (or else we truncate incorrectly)
	if(value.length() <= maxLength) {	
		bs->setMaxLength(maxLength);
	}
	
	bs->setValue(value);
	
	if(dlg->exec() == QDialog::Accepted) {
		value = bs->value();
		ret = true;
	}

	return ret;
}

//------------------------------------------------------------------------------
// Name: dialogOptions()
// Desc: returns a pointer to the options dialog
// TODO: thread safety?
//------------------------------------------------------------------------------
QDialog *Debugger::dialogOptions() {
	static DialogOptions *dlg = 0;
	
	if(dlg == 0) {	
		dlg = new DialogOptions(debuggerGUI);
	}
	
	return dlg;
}

//------------------------------------------------------------------------------
// Name: config()
// Desc: 
//------------------------------------------------------------------------------
const Configuration *Debugger::config() {
	return dynamic_cast<Configuration *>(dialogOptions());
}

//------------------------------------------------------------------------------
// Name: loadedLibraries()
// Desc: 
//------------------------------------------------------------------------------
QStringList Debugger::loadedLibraries() {
	QStringList ret;
	
	memoryRegions().sync();

	const QVector<MemoryRegions::Region> r = memoryRegions().regions();
	foreach(MemoryRegions::Region region, r) {

		// TODO: this line is somewhat system specific...
		if(region.name.startsWith("/")) {
			if(!ret.contains(region.name)) {
				ret.push_back(region.name);
			}
		}
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: getASCIIStringAtAddress(edb::address_t address, QString &s, int minLength, int maxLength, int &foundLength)
// Desc: attemps to get a string at a given address whose length os >= minLength and < maxLength
// Note: strings are comprised of printable characters and whitespace.
// Note: foundLength is needed because we replace characters which need an excape char with the escape sequence 
//       (thus the resultant string may be longer than the original). foundLength is the original length.
//------------------------------------------------------------------------------
bool Debugger::getASCIIStringAtAddress(edb::address_t address, QString &s, int minLength, int maxLength, int &foundLength) {
	
	bool isString = false;
	if(debuggerBase != 0) {
		s.clear();

		if(minLength <= maxLength) {
			while(maxLength--) {
				char ch;
				if(!debuggerBase->readBytes(address++, &ch, 1)) {
					break;
				}

				if(safe_ctype<std::isprint>(ch) || safe_ctype<std::isspace>(ch)) {
					s += ch;
				} else {
					break;
				}
			}
		}

		isString = (s.length() >= minLength);

		if(isString) {
			foundLength = s.length();
			s.replace("\r", "\\r");
			s.replace("\n", "\\n");
			s.replace("\t", "\\t");
			s.replace("\v", "\\v");
			s.replace("\"", "\\\"");
		}
	}
	
	return isString;
}

//------------------------------------------------------------------------------
// Name: findFunctionSymbolBase(edb::address_t address, QString &value, int &offset)
// Desc: 
//------------------------------------------------------------------------------
bool Debugger::findFunctionSymbolBase(edb::address_t address, QString &value, int &offset) {
	bool ret = false;
	const SymbolManager::Symbol *const s = symbolManager().findNearSymbol(address);
	if(s != 0) {
		value = s->name;
		offset = address - s->address;
		ret = true;
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: findFunctionSymbol(edb::address_t address, const QString &defaultValue, int *offset)
// Desc: 
//------------------------------------------------------------------------------
QString Debugger::findFunctionSymbol(edb::address_t address, const QString &defaultValue, int *offset) {
	QString symname(defaultValue);
	int off;
	
	if(findFunctionSymbolBase(address, symname, off)) {
		symname = QString().sprintf("%s+%x", qPrintable(symname), off);
		if(offset != 0) {
			*offset = off;
		}
	}

	return symname;
}

//------------------------------------------------------------------------------
// Name: findFunctionSymbol(edb::address_t address, const QString &defaultValue)
// Desc: 
//------------------------------------------------------------------------------
QString Debugger::findFunctionSymbol(edb::address_t address, const QString &defaultValue) {
	return findFunctionSymbol(address, defaultValue, 0);
}

//------------------------------------------------------------------------------
// Name: basename(const QString &s)
// Desc: 
//------------------------------------------------------------------------------
QString Debugger::basename(const QString &s) {
	
	const QFileInfo fileInfo(s);
	QString ret(fileInfo.baseName());

	if(!fileInfo.completeSuffix().isEmpty()) {
		ret += ".";
		ret += fileInfo.completeSuffix();
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QString Debugger::symlinkTarget(const QString &s) {
	return symlinkTarget(QFileInfo(s));
}

//------------------------------------------------------------------------------
// Name: getVariable(QString &s, void *p)
// Desc: 
//------------------------------------------------------------------------------
edb::address_t Debugger::getVariable(const QString &s, void *p) {

	Q_UNUSED(p);

	bool ok;	
	const edb::address_t ret = currentState().value(s, &ok);
	
	if(!ok) {
		throw Error(Error::UNKNOWN_VARIABLE);
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: getDWORD(edb::address_t address, void *p)
// Desc: 
//------------------------------------------------------------------------------
edb::address_t Debugger::getDWORD(edb::address_t address, void *p) {
	
	Q_UNUSED(p);

	edb::address_t ret = 0;
		
	if(!debuggerBase->readBytes(address, &ret, sizeof(ret))) {
		throw Error(Error::CANNOT_READ_MEMORY);
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: getInstructionBytes(edb::address_t address, uint8_t *buf, int &size)
// Desc: attempts to read at most size bytes, but will retry using smaller sizes as needed
//------------------------------------------------------------------------------
bool Debugger::getInstructionBytes(edb::address_t address, uint8_t *buf, int &size) {
	bool ok = false;
	if(debuggerBase != 0) {
		ok = debuggerBase->readBytes(address, buf, size);
		while(!ok && size) {
			ok = debuggerBase->readBytes(address, buf, --size);
		}
	}
	return ok;
}

//------------------------------------------------------------------------------
// Name: getBinaryInfo(const QString &s)
// Desc: gets an object which knows how to analyze the binary file provided
//       or NULL if none-found
//------------------------------------------------------------------------------
BinaryInfo *Debugger::getBinaryInfo(const QString &s) {
	BinaryInfo *p = 0;
	// TODO: don't assume ELF32
	p = new ELF32BinaryInfo(s);
	if(!p->validateHeader()) {
		delete p;
		p = 0;
	}
	
	return p;
}
