/*
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 "ArchProcessor.h"
#include "ByteShiftArray.h"
#include "Configuration.h"
#include "DebuggerCoreInterface.h"
#include "DebuggerMain.h"
#include "DebuggerPluginInterface.h"
#include "DialogInputBinaryString.h"
#include "DialogInputValue.h"
#include "DialogOptions.h"
#include "Expression.h"
#include "FunctionDB.h"
#include "State.h"
#include "Core.h"
#include "version.h"
#include "QHexView.h"
#include <QMessageBox>
#include <QString>
#include <QMap>
#include <boost/smart_ptr.hpp>

DebuggerCoreInterface	*edb::v1::debuggerBase	= 0;
QWidget					*edb::v1::debuggerUI	= 0;

namespace {

	typedef QList<BinaryInfo::createFuncPtr> BinaryInfoList;

	ATOMIC_POINTER(DebugEventHandlerInterface)	g_DebugEventHandler		= 0;
	ATOMIC_POINTER(FunctionDB)					g_FunctionDB			= 0;
	ATOMIC_POINTER(AnalyzerInterface)			g_Analyzer 				= 0;
	ATOMIC_POINTER(SessionFileInterface)		g_SessionHandler		= 0;
	QMap<QString, QObject *>					g_GeneralPlugins;
	BinaryInfoList								g_BinaryInfoList;
	
	DebuggerMain *ui() {
		return qobject_cast<DebuggerMain *>(edb::v1::debuggerUI);
	}

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

//------------------------------------------------------------------------------
// Name: repaintCPUView()
// Desc: 
//------------------------------------------------------------------------------
void edb::v1::repaintCPUView() {
	DebuggerMain *const gui = ui();
	Q_CHECK_PTR(gui);
	gui->ui.cpuView->viewport()->repaint();
}

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

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

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

//------------------------------------------------------------------------------
// Name: archProcessor()
// Desc: 
//------------------------------------------------------------------------------
ArchProcessorInterface &edb::v1::archProcessor() {
	static ArchProcessor g_ArchProcessor;
	return g_ArchProcessor;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
AnalyzerInterface *edb::v1::setAnalyzer(AnalyzerInterface *p) {
	Q_CHECK_PTR(p);

#if QT_VERSION >= 0x040400
	return g_Analyzer.fetchAndStoreAcquire(p);
#else
	AnalyzerInterface *const oldptr = g_Analyzer;
	g_Analyzer = p;
	return oldptr;
#endif	
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
AnalyzerInterface *edb::v1::analyzer() {
	return g_Analyzer;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
SessionFileInterface *edb::v1::setSessionFileHandler(SessionFileInterface *p) {
	Q_CHECK_PTR(p);

#if QT_VERSION >= 0x040400
	return g_SessionHandler.fetchAndStoreAcquire(p);
#else
	SessionFileInterface *const oldptr = g_SessionHandler;
	g_SessionHandler = p;
	return oldptr;
#endif
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
SessionFileInterface *edb::v1::sessionFileHandler() {
	return g_SessionHandler;;
}

//------------------------------------------------------------------------------
// Name: setDebugEventHandler(DebugEventHandlerInterface *p)
// Desc: 
//------------------------------------------------------------------------------
DebugEventHandlerInterface *edb::v1::setDebugEventHandler(DebugEventHandlerInterface *p) {

	Q_CHECK_PTR(p);
	
#if QT_VERSION >= 0x040400
	return g_DebugEventHandler.fetchAndStoreAcquire(p);
#else
	DebugEventHandlerInterface *const oldptr = g_DebugEventHandler;
	g_DebugEventHandler = p;
	return oldptr;
#endif
}

//------------------------------------------------------------------------------
// Name: getDebugEventHandler();
// Desc: 
//------------------------------------------------------------------------------
DebugEventHandlerInterface *edb::v1::debugEventHandler() {
	return g_DebugEventHandler;
}

//------------------------------------------------------------------------------
// Name: jumpToAddress(edb::address_t address)
// Desc: sets the disassembly display to a given address, returning success 
//       status
//------------------------------------------------------------------------------
bool edb::v1::jumpToAddress(edb::address_t address) {
	DebuggerMain *const gui = ui();
	Q_CHECK_PTR(gui);
	return gui->jumpToAddress(address);
}

//------------------------------------------------------------------------------
// 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 edb::v1::dumpDataRange(edb::address_t address, edb::address_t end_address, bool newTab) {
	DebuggerMain *const gui = ui();
	Q_CHECK_PTR(gui);
	return gui->dumpDataRange(address, end_address, newTab);
}

//------------------------------------------------------------------------------
// 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 edb::v1::dumpDataRange(edb::address_t address, edb::address_t end_address) {
	return dumpDataRange(address, end_address, false);
}

//------------------------------------------------------------------------------
// Name: dumpStack(edb::address_t address)
// Desc: 
//------------------------------------------------------------------------------
bool edb::v1::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 edb::v1::dumpStack(edb::address_t address, bool ScrollTo) {
	DebuggerMain *const gui = ui();
	Q_CHECK_PTR(gui);
	return gui->dumpStack(address, ScrollTo);
}

//------------------------------------------------------------------------------
// Name: dumpData(edb::address_t address, bool newTab)
// Desc: shows a given address in the data view, optionally in a new tab
//------------------------------------------------------------------------------
bool edb::v1::dumpData(edb::address_t address, bool newTab) {
	DebuggerMain *const gui = ui();
	Q_CHECK_PTR(gui);
	return gui->dumpData(address, newTab);
}

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

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

	MemRegion region;
	memoryRegions().sync();
	if(memoryRegions().findRegion(address, region)) {
		int ret = QMessageBox::Yes;

		if(!region.executable() && config().warnOnNoExecBP) {
			ret = QMessageBox::question(0, QObject::tr("Suspicious breakpoint"), 
				QObject::tr(
				"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);
		}

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


	} else {
		QMessageBox::information(0, QObject::tr("Error Setting Breakpoint"),
			QObject::tr("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 edb::v1::toggleBreakpoint(edb::address_t address) {
	if(debuggerBase->findBreakpoint(address)) {
		removeBreakpoint(address);
	} else {
		createBreakpoint(address);
	}
}

//------------------------------------------------------------------------------
// Name: removeBreakpoint(edb::address_t address)
// Desc: removes a breakpoint
//------------------------------------------------------------------------------
void edb::v1::removeBreakpoint(edb::address_t address) {
	debuggerBase->removeBreakpoint(address);
	repaintCPUView();
}

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

//------------------------------------------------------------------------------
// Name: getBinaryStringFromUser(QByteArray &value, const QString &title, int maxLength)
// Desc: 
//------------------------------------------------------------------------------
bool edb::v1::getBinaryStringFromUser(QByteArray &value, const QString &title, int maxLength) {
	static DialogInputBinaryString *const dlg = new DialogInputBinaryString(debuggerUI);
	
	bool ret = false;
		
	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
//------------------------------------------------------------------------------
QDialog *edb::v1::dialogOptions() {
	static QDialog *const dlg = new DialogOptions(debuggerUI);
	return dlg;
}

//------------------------------------------------------------------------------
// Name: config()
// Desc: 
//------------------------------------------------------------------------------
const Configuration &edb::v1::config() {

	// NOTE: this particular optimization is only valid because we know that
	// dialogOptions is the same object every time (see above function)
	static Configuration *const p = qobject_cast<DialogOptions *>(dialogOptions());
	Q_CHECK_PTR(p);
	return *p;
}

//------------------------------------------------------------------------------
// 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 
//       escape char with the escape sequence (thus the resultant string may be 
//       longer than the original). foundLength is the original length.
//------------------------------------------------------------------------------
bool edb::v1::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, sizeof(ch))) {
					break;
				}

				if(static_cast<unsigned char>(ch) < 0x80 && (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: getUTF16StringAtAddress(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 
//       escape char with the escape sequence (thus the resultant string may be 
//       longer than the original). foundLength is the original length.
//------------------------------------------------------------------------------
bool edb::v1::getUTF16StringAtAddress(edb::address_t address, QString &s, int minLength, int maxLength, int &foundLength) {
	bool isString = false;
#ifdef SUPPORT_UNICODE	
	if(debuggerBase != 0) {
		s.clear();

		if(minLength <= maxLength) {
			while(maxLength--) {

				quint16 val;
				if(!debuggerBase->readBytes(address, &val, sizeof(val))) {
					break;
				}
				
				address += sizeof(val);
				
				QChar ch(val);

				if(ch.isPrint() || ch.isSpace()) {
					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("\"", "\\\"");
		}
	}
#else
	Q_UNUSED(address);
	Q_UNUSED(s);
	Q_UNUSED(minLength);
	Q_UNUSED(maxLength);
	Q_UNUSED(foundLength);
#endif
	return isString;
}

//------------------------------------------------------------------------------
// Name: findFunctionSymbol(edb::address_t address, const QString &defaultValue, int *offset)
// Desc: 
//------------------------------------------------------------------------------
QString edb::v1::findFunctionSymbol(edb::address_t address, const QString &defaultValue, int *offset) {
	QString symname(defaultValue);
	int off;
	
	if(findFunctionSymbolBase(address, symname, off)) {
		symname = QString("%1+%2").arg(symname).arg(off, 0, 16);
		if(offset != 0) {
			*offset = off;
		}
	}

	return symname;
}

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

//------------------------------------------------------------------------------
// Name: findFunctionSymbol(edb::address_t address)
// Desc: 
//------------------------------------------------------------------------------
QString edb::v1::findFunctionSymbol(edb::address_t address) {
	return findFunctionSymbol(address, QString(), 0);
}

//------------------------------------------------------------------------------
// Name: getVariable(QString &s, bool &ok, ExpressionError &err, void *p)
// Desc: 
//------------------------------------------------------------------------------
edb::address_t edb::v1::getVariable(const QString &s, bool &ok, ExpressionError &err, void *p) {

	Q_UNUSED(p);
	Q_CHECK_PTR(debuggerBase);

	const edb::address_t ret = currentState().value(s, ok);
	if(!ok) {
		err = ExpressionError(ExpressionError::UNKNOWN_VARIABLE);
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: getDWORD(edb::address_t address, bool &ok, ExpressionError &err, void *p)
// Desc: 
//------------------------------------------------------------------------------
edb::address_t edb::v1::getDWORD(edb::address_t address, bool &ok, ExpressionError &err, void *p) {
	
	Q_UNUSED(p);
	Q_CHECK_PTR(debuggerBase);

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

//------------------------------------------------------------------------------
// Name: getInstructionBytes(edb::address_t address, quint8 *buf, int &size)
// Desc: attempts to read at most size bytes, but will retry using smaller sizes as needed
//------------------------------------------------------------------------------
bool edb::v1::getInstructionBytes(edb::address_t address, quint8 *buf, int &size) {
	
	Q_CHECK_PTR(debuggerBase);
	Q_ASSERT(size >= 0);
	
	bool 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 *edb::v1::getBinaryInfo(const QString &s) {
	foreach(BinaryInfo::createFuncPtr f, g_BinaryInfoList) {	
		BinaryInfo *const p = (*f)(s);
		
		if(p->validateHeader()) {
			return p;
		}
	
		delete p;
	}
		
	return 0;
}

//------------------------------------------------------------------------------
// Name: locateMainFunction()
// Desc: 
// Note: this currently only works for glibc linked elf files
//------------------------------------------------------------------------------
edb::address_t edb::v1::locateMainFunction(const QString &file) {
	edb::address_t entryPoint = 0;
	boost::scoped_ptr<BinaryInfo> binfo(getBinaryInfo(file));
	if(binfo) {	
		entryPoint = binfo->getEntryPoint();
		if(entryPoint != 0) {
		
			// TODO: rewrite this so that it uses edisassm

#if defined(EDB_X86)
			ByteShiftArray ba(6);
			for(int i = 0; i < 50; ++i) {
				quint8 byte;
				if(debuggerBase->readBytes(entryPoint + i, &byte, sizeof(byte))) {
					ba << byte;

					// begining of a call preceeded by a push
					if(ba[0] == 0x68 && ba[5] == 0xe8) {
						edb::address_t address = *reinterpret_cast<const edb::address_t *>(ba.data() + 1);
						// TODO: make sure that this address resides in an executable region
						entryPoint = address;
						qDebug("No main symbol found, calculated it to be " EDB_FMT_PTR " using heuristic", entryPoint);
						break;
					}
				} else {
					break;
				}
			}
#elif defined(EDB_X86_64)
			ByteShiftArray ba(12);
			for(int i = 0; i < 50; ++i) {
				quint8 byte;
				if(debuggerBase->readBytes(entryPoint + i, &byte, sizeof(byte))) {
					ba << byte;

					// begining of a call preceeded by a 64-bit mov				
					if(ba[0] == 0x48 && ba[1] == 0xc7 && ba[7] == 0xe8) {
						// Seems that this 64-bit mov still has a 32-bit immediate
						edb::address_t address = *reinterpret_cast<const edb::address_t *>(ba.data() + 3) & 0xffffffff;
						// TODO: make sure that this address resides in an executable region
						entryPoint = address;
						qDebug("No main symbol found, calculated it to be " EDB_FMT_PTR " using heuristic", entryPoint);
						break;
					}
				} else {
					break;
				}
			}
#endif
		}
	}
	return entryPoint;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool edb::v1::registerPlugin(const QString &filename, QObject *plugin) {
	if(!g_GeneralPlugins.contains(filename)) {
		g_GeneralPlugins[filename] = plugin;
		return true;
	}

	return false;
}

//------------------------------------------------------------------------------
// Name: pluginList()
// Desc: 
//------------------------------------------------------------------------------
const QMap<QString, QObject *> &edb::v1::pluginList() {
	return g_GeneralPlugins;
}

//------------------------------------------------------------------------------
// Name: findPluginByName(const QString &name)
// Desc: gets a pointer to a plugin based on it's classname
//------------------------------------------------------------------------------
DebuggerPluginInterface *edb::v1::findPluginByName(const QString &name) {
	foreach(QObject *p, g_GeneralPlugins) {
		if(name == p->metaObject()->className()) {
			return qobject_cast<DebuggerPluginInterface *>(p);
		}
	}
	return 0;
}

//------------------------------------------------------------------------------
// Name: reloadSymbols()
// Desc: 
//------------------------------------------------------------------------------
void edb::v1::reloadSymbols() {
	symbolManager().loadSymbols(edb::v1::config().symbolPath);
}

//------------------------------------------------------------------------------
// Name: getFunctionInfo(const QString &functionName)
// Desc: 
//------------------------------------------------------------------------------
const FunctionInfo *edb::v1::getFunctionInfo(const QString &functionName) {
	const FunctionInfo *ret = 0;
		
	FunctionDB *const p = g_FunctionDB;
	if(p != 0) {
		ret = p->find(functionName);
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: primaryDataRegion()
// Desc: returns the main .data section of the main executable module
// Note: make sure that memory regions has been sync'd first or you will like
//       get a null-region result
//------------------------------------------------------------------------------
MemRegion edb::v1::primaryDataRegion() {
	const QVector<MemRegion> &l = edb::v1::memoryRegions().regions();

	if(l.size() >= 2) {
		return l[1];
	} else {
		return MemRegion();
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void edb::v1::popValue(State &state) {
	state.adjustStack(sizeof(edb::reg_t));
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void edb::v1::pushValue(State &state, edb::reg_t value) {
	state.adjustStack(- static_cast<int>(sizeof(edb::reg_t)));
	edb::v1::debuggerBase->writeBytes(state.stackPointer(), &value, sizeof(edb::reg_t));
}

//------------------------------------------------------------------------------
// Name: registerBinaryInfo(createFuncPtr fptr)
// Desc: 
//------------------------------------------------------------------------------
void edb::v1::registerBinaryInfo(BinaryInfo::createFuncPtr fptr) {
	if(!g_BinaryInfoList.contains(fptr)) {
		g_BinaryInfoList.push_back(fptr);
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
FunctionDB *edb::v1::setFunctionDB(FunctionDB *p) {
	Q_CHECK_PTR(p);

#if QT_VERSION >= 0x040400
	return g_FunctionDB.fetchAndStoreAcquire(p);
#else
	FunctionDB *const oldptr = g_FunctionDB;
	g_FunctionDB = p;
	return oldptr;
#endif	
}

//------------------------------------------------------------------------------
// Name: edbVersion()
// Desc: returns an integer comparable version of our current version string
//------------------------------------------------------------------------------
ulong edb::v1::edbVersion() {
	return edb::core::intVersion(version_string);
}
