/*
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 "DebuggerPluginInterface.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 "ArchProcessor.h"
#include "ByteShiftArray.h"
#include "State.h"
#include "BinaryInfo.h"
#include "MD5.h"

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

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

DebuggerCoreInterface					*edb::v1::debuggerBase	= 0;
QWidget 								*edb::v1::debuggerUI	= 0;
static DebugEventHandlerInterface		*g_DebugEventHandler	= 0;
static QList<DebuggerPluginInterface *>	g_GeneralPlugins;

//------------------------------------------------------------------------------
// Name: repaintCPUView()
// Desc: 
//------------------------------------------------------------------------------
static void repaintCPUView() {
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(edb::v1::debuggerUI);
	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;
	return g_MemoryRegions;
}

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

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

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

//------------------------------------------------------------------------------
// Name: intVersion()
// Desc: returns an integer comparable version of our current version string
//------------------------------------------------------------------------------
unsigned long edb::v1::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 edb::v1::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 char *format, ...)
// Desc: 
//------------------------------------------------------------------------------
int edb::v1::log(const char *format, ...) {
	char buf[1024];
	va_list ap;
	va_start(ap, format);	
	const int ret = vsnprintf(buf, sizeof(buf), format, ap);
	va_end(ap);
	puts(buf);
	return ret;
}

//------------------------------------------------------------------------------
// 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) {
	bool ret = false;
	// TODO: remove need for this to see so much of the GUI's private data!
	
	MemoryRegions::Region region;
	if(memoryRegions().findRegion(address, region)) {
		DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerUI);
		Q_CHECK_PTR(gui);
		
		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 edb::v1::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!
		
	MemoryRegions::Region region;
	if(memoryRegions().findRegion(address, region)) {

		DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerUI);
		Q_CHECK_PTR(gui);

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

		DebuggerMain::DataViewInfo *const info = gui->currentDataViewInfo();

		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 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) {

	bool ret = false;
	
	DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerUI);
	Q_CHECK_PTR(gui);
	
	const MemoryRegions::Region last_region = gui->m_StackViewInfo.region;

	if(memoryRegions().findRegion(address, gui->m_StackViewInfo.region)) {
		gui->m_StackViewInfo.update();
		if(ScrollTo || gui->m_StackViewInfo.region != last_region) {
			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 edb::v1::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!
	
	MemoryRegions::Region region;
	if(memoryRegions().findRegion(address, region)) {

		DebuggerMain *const gui = dynamic_cast<DebuggerMain *>(debuggerUI);
		Q_CHECK_PTR(gui);
	
		if(newTab) {
			gui->mnuDumpCreateTab();
		}

		DebuggerMain::DataViewInfo *const info = gui->currentDataViewInfo();

		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 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) {

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

		if(!region.executable() && config().warnOnNoExecBP) {
			ret = QMessageBox::question(debuggerUI, "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(debuggerUI, "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 edb::v1::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 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: getValueFromUser(edb::reg_t &value)
// Desc: 
//------------------------------------------------------------------------------
bool edb::v1::getValueFromUser(edb::reg_t &value) {
	return getValueFromUser(value, "Input Value");
}

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

//------------------------------------------------------------------------------
// 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 DialogOptions *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 = dynamic_cast<Configuration *>(dialogOptions());
	Q_CHECK_PTR(p);
	return *p;
}

//------------------------------------------------------------------------------
// Name: loadedLibraries()
// Desc: 
//------------------------------------------------------------------------------
QStringList edb::v1::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 << 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 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, 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 edb::v1::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 edb::v1::findFunctionSymbol(edb::address_t address, const QString &defaultValue, int *offset) {
	QString symname(defaultValue);
	int off;
	
	if(findFunctionSymbolBase(address, symname, off)) {
		symname.sprintf("%s+%x", qPrintable(symname), off);
		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: basename(const QString &s)
// Desc: 
//------------------------------------------------------------------------------
QString edb::v1::basename(const QString &s) {
	
	const QFileInfo fileInfo(s);
	QString ret = fileInfo.baseName();

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

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

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

	Q_UNUSED(p);
	Q_CHECK_PTR(debuggerBase);

	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 edb::v1::getDWORD(edb::address_t address, void *p) {
	
	Q_UNUSED(p);
	Q_CHECK_PTR(debuggerBase);

	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, 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);
	
	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) {
	BinaryInfo *p = 0;

	const BinaryInfo::BinaryInfoList &list = BinaryInfo::HandlerList();
	for(BinaryInfo::BinaryInfoList::const_iterator it = list.begin(); it != list.end(); ++it) {
	
		p = (*it)(s);
		
		if(p->validateHeader()) {
			break;
		}
	
		release(p);
	}
		
	return p;
}

//------------------------------------------------------------------------------
// Name: parseCommandLine(const QString &cmdline)
// Desc: 
//------------------------------------------------------------------------------
QStringList edb::v1::parseCommandLine(const QString &cmdline) {
	
	QStringList args;
	QString arg;

	int bcount = 0;
	bool in_quotes = false;

	QString::const_iterator s = cmdline.begin();

	while(s != cmdline.end()) {
		if(!in_quotes && s->isSpace()) {

			// Close the argument and copy it
			args << arg;
			arg.clear();

			// skip the remaining spaces
			do {
				++s;
			} while(s->isSpace());

			// Start with a new argument
			bcount = 0;
		} else if(*s == '\\') {

			// '\\'
			arg += *s++;
			++bcount;

		} else if (*s == '"') {

			// '"'
			if ((bcount & 1) == 0) {
				/* Preceded by an even number of '\', this is half that
				 * number of '\', plus a quote which we erase.
				 */

				arg.chop(bcount / 2);
				in_quotes = !in_quotes;
			} else {
				/* Preceded by an odd number of '\', this is half that
				 * number of '\' followed by a '"'
				 */

				arg.chop(bcount / 2 + 1);
				arg += '"';
			}
			
			++s;
			bcount = 0;
		} else {
			arg += *s++;
			bcount = 0;
		}
	}

	if(!arg.isEmpty()) {
		args << arg;
	}

	return args;
}

//------------------------------------------------------------------------------
// Name: locateMainFunction()
// Desc: 
//------------------------------------------------------------------------------
edb::address_t edb::v1::locateMainFunction(const QString &file) {
	edb::address_t entryPoint = 0;
	BinaryInfo *binfo = getBinaryInfo(file);
	if(binfo) {
		entryPoint = binfo->getEntryPoint();
		if(entryPoint != 0) {

			// TODO: this currently only works for glibc linked elf files
			ByteShiftArray ba(6);
			for(int i = 0; i < 50; ++i) {
				quint8 byte;
				if(debuggerBase->readBytes(entryPoint + i, &byte, sizeof(byte))) {
					ba.shl();
					ba[ba.size() - 1] = byte;

					// begining of a call preceeded by a push

					// TODO: x86 only, portability warning!!!
					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;
						log("No main symbol found, calculated it to be " EDB_FMT_PTR " using heuristic", entryPoint);
						break;
					}
				} else {
					break;
				}
			}
		}
	}
	release(binfo);
	return entryPoint;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
bool edb::v1::registerPlugin(DebuggerPluginInterface *plugin) {
	if(!g_GeneralPlugins.contains(plugin)) {
		g_GeneralPlugins.push_back(plugin);
		return true;
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
const QList<DebuggerPluginInterface *> &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) {
	DebuggerPluginInterface *ret = 0;
	foreach (DebuggerPluginInterface *plugin, g_GeneralPlugins) {
		
		// get a QObject from the plugin
		QObject *const p = dynamic_cast<QObject *>(plugin);
		
		if(p != 0) {
			if(name == p->metaObject()->className()) {
				ret = plugin;
				break;
			}
		}
	}
	return ret;	
}

//------------------------------------------------------------------------------
// Name: getFileMD5(const QString &s)
// Desc: returns a byte array representing the MD5 of a file
//------------------------------------------------------------------------------
QByteArray edb::v1::getFileMD5(const QString &s) {

	QFile libFile(s);
	libFile.open(QIODevice::ReadOnly);
	if(libFile.isOpen()) {
		const QByteArray libBytes = libFile.readAll();
		
		MD5 md5Digest;
		md5Digest.update(libBytes.data(), libBytes.size());
		md5Digest.finalize();
		
		// make a deep copy because MD5 is about to go out of scope
		return QByteArray(reinterpret_cast<const char *>(md5Digest.digest()), 16);
	}
	
	return QByteArray();
}
