/*
Copyright (C) 2006 - 2009 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.
*/

#include "Analyzer.h"
#include "Debugger.h"
#include "State.h"
#include "Util.h"
#include "MD5.h"
#include "DebuggerCoreInterface.h"
#include "ArchProcessorInterface.h"
#include "SymbolManager.h"
#include "MemoryRegions.h"
#include "Instruction.h"
#include "AnalyzerOptionsPage.h"

#include <QtDebug>
#include <QMenu>
#include <QStack>
#include <QHash>
#include <QProgressDialog>
#include <QCoreApplication>
#include <QtDebug>
#include <QSettings>
#include <QMessageBox>

#include <boost/bind.hpp>

#ifdef USE_QT_CONCURRENT
#include <QtConcurrentMap>
#endif

#include <cstring>

#ifdef Q_OS_LINUX
#include <elf.h>
#endif

#define MIN_REFCOUNT 2

namespace {
#if defined(EDB_X86)
	const edb::Operand::Register STACK_REG = edb::Operand::REG_ESP;
	const edb::Operand::Register FRAME_REG = edb::Operand::REG_EBP;
#elif defined(EDB_X86_64)
	const edb::Operand::Register STACK_REG = edb::Operand::REG_RSP;
	const edb::Operand::Register FRAME_REG = edb::Operand::REG_RBP;
#endif
}

//------------------------------------------------------------------------------
// Name: Analyzer(
// Desc: 
//------------------------------------------------------------------------------
Analyzer::Analyzer() : m_Menu(0) {
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QWidget *Analyzer::optionsPage() {
	return new AnalyzerOptionsPage;
}

//------------------------------------------------------------------------------
// Name: menu(QWidget *parent)
// Desc: 
//------------------------------------------------------------------------------
QMenu *Analyzer::menu(QWidget *parent) {
	if(m_Menu == 0) {
		m_Menu = new QMenu(tr("Analyzer"), parent);
		m_Menu->addAction(tr("&Analyzer Current Region"), this, SLOT(doMenu()), QKeySequence(tr("Ctrl+A")));
	}

	return m_Menu;
}

//------------------------------------------------------------------------------
// Name: privateInit()
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::privateInit() {
	edb::v1::setAnalyzer(this);
}

//------------------------------------------------------------------------------
// Name: doMenu()
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::doMenu() {
	MemRegion region;
	const State &state = edb::v1::currentState();
	const edb::address_t eip = state.instructionPointer();
	if(edb::v1::memoryRegions().findRegion(eip, region)) {
	
		QProgressDialog progress(tr("Performing Analysis"), 0, 0, 100, edb::v1::debuggerUI);
		connect(this, SIGNAL(updateProgress(int)), &progress, SLOT(setValue(int)));
		
		progress.show();
		progress.setValue(0);
	
		analyze(region);
		
		edb::v1::repaintCPUView();
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::findFunctionCalls(const MemRegion &region, FunctionMap &found_functions) {
	static const edb::address_t pageSize = edb::v1::debuggerBase->pageSize();

	const edb::address_t size_in_pages = region.size() / pageSize;
	
	try {
		QVector<quint8> pages(size_in_pages * pageSize);

		if(edb::v1::debuggerBase->readPages(region.start, &pages[0], size_in_pages)) {
			for(edb::address_t i = 0; i < size_in_pages * pageSize; ++i) {

				const edb::Instruction insn(&pages[i], region.size() - i, region.start + i, std::nothrow);

				if(insn.valid() && insn.type() == edb::Instruction::OP_CALL) {

					const edb::address_t ip = region.start + i;
					const edb::Operand &op = insn.operand(0);

					if(op.generalType() == edb::Operand::TYPE_REL) {
						const edb::address_t ea = op.relativeTarget();

						// skip over ones which are : call <label>; label:
						if(ea != ip + insn.size()) {
							if(region.contains(ea)) {							
								// avoid calls which land in the middle of a function...
								// this may or may not be the best approach
								if(!isInsideKnown(region, ea)) {
									found_functions[ea].entry_address	= ea;
									found_functions[ea].end_address		= ea;
									found_functions[ea].reference_count++;
								}
							}
						}
					}

					emit updateProgress(util::percentage(5, 10, i, region.size()));
				}
			}
		}
	} catch(const std::bad_alloc &) {
		QMessageBox::information(0, QObject::tr("Memroy Allocation Error"),
			QObject::tr("Unable to satisfy memory allocation request for requested region."));
	}
}

//------------------------------------------------------------------------------
// Name: isStackFrame(edb::address_t addr)
// Desc: 
//------------------------------------------------------------------------------
bool Analyzer::isStackFrame(edb::address_t addr) const {

	quint8 buf[edb::Instruction::MAX_SIZE];

	unsigned int i = 0;

	while(i < 2) {
		// gets the bytes for the instruction
		int buf_size = sizeof(buf);
		if(!edb::v1::getInstructionBytes(addr, buf, buf_size)) {
			break;
		}

		// decode it
		const edb::Instruction insn(buf, buf_size, addr, std::nothrow);
		if(!insn.valid()) {
			break;;
		}

		// which part of the sequence are we looking at?
		switch(i++) {
		case 0:
			// are we looking at a 'push rBP' ?
			if(insn.type() == edb::Instruction::OP_PUSH) {
				Q_ASSERT(insn.operandCount() == 1);
				const edb::Operand &op = insn.operand(0);
				if(op.completeType() == edb::Operand::TYPE_REGISTER) {
					if(op.reg == FRAME_REG) {
						break;
					}
				}
			// if it is an 'enter 0,0' instruction, then it's a stack frame right away
			} else if(insn.type() == edb::Instruction::OP_ENTER) {
				Q_ASSERT(insn.operandCount() == 2);
				const edb::Operand &op0 = insn.operand(0);
				const edb::Operand &op1 = insn.operand(1);
				if(op0.immediate() == 0 && op1.immediate() == 0) {
					return true;
				}
			}
			++i;
			break;
		case 1:
			// are we looking at a 'mov rBP, rSP' ?
			if(insn.type() == edb::Instruction::OP_MOV) {
				Q_ASSERT(insn.operandCount() == 2);
				const edb::Operand &op0 = insn.operand(0);
				const edb::Operand &op1 = insn.operand(1);
				if(op0.completeType() == edb::Operand::TYPE_REGISTER && op1.completeType() == edb::Operand::TYPE_REGISTER) {
					if(op0.reg == FRAME_REG && op1.reg == STACK_REG) {
						return true;
					}
				}
			}
			break;
		default:
			break;
		}

		addr += insn.size();
	}
	
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::bonusStackFramesHelper(Function &info) const {
	const edb::address_t addr = info.entry_address;
	if(isStackFrame(addr)) {
		info.reference_count++;
	}
}

//------------------------------------------------------------------------------
// Name: bonusStackFrames(FunctionMap &results)
// Desc: give bonus if we see a "push ebp; mov ebp, esp;"
//------------------------------------------------------------------------------
void Analyzer::bonusStackFrames(FunctionMap &results) {
#ifdef USE_QT_CONCURRENT
	QtConcurrent::blockingMap(results, boost::bind(&Analyzer::bonusStackFramesHelper, this, _1));
#else
	std::for_each(results.begin(), results.end(), boost::bind(&Analyzer::bonusStackFramesHelper, this, _1));
#endif
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::updateResultEntry(FunctionMap &results, edb::address_t address) const {
	results[address].entry_address		= address;
	results[address].end_address		= address;
	if(results[address].reference_count == 0) {
		results[address].reference_count = MIN_REFCOUNT;
	} else {
		results[address].reference_count++;
	}
}

//------------------------------------------------------------------------------
// Name: bonusMain(const MemRegion &region, FunctionMap &results)
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::bonusMain(const MemRegion &region, FunctionMap &results) const {
	const QString s = edb::v1::getProcessExecutable();
	if(!s.isEmpty()) {
		const edb::address_t main = edb::v1::locateMainFunction();
		
		if(main != edb::nullptr && region.contains(main)) {
			// make sure we have an entry for this function
			updateResultEntry(results, main);
		}
	}
}

//------------------------------------------------------------------------------
// Name: bonusSymbols(FunctionMap &results)
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::bonusSymbols(const MemRegion &region, FunctionMap &results) {
	// give bonus if we have a symbol for the address
	
	QList<SymbolManager::Symbol> symbols = edb::v1::symbolManager().symbols();
	
	Q_FOREACH(const SymbolManager::Symbol &sym, symbols) {
		const edb::address_t addr = sym.address;
		
		if(region.contains(addr)) {
			if(sym.isCode()) {

				qDebug("[Analyzer] adding: %s <%p>", qPrintable(sym.name), reinterpret_cast<void *>(addr));

				// make sure we have an entry for this function
				updateResultEntry(results, addr);
			}
		}
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int Analyzer::walkAllFunctions(FunctionMap &results, const MemRegion &region, QSet<edb::address_t> &walked_functions) {
	int updates = 0;
	
	QSet<edb::address_t> found_functions;
		
	FunctionMap::iterator it = results.begin();
	while(it != results.end()) {
		Function &info = it.value();
		
		const FunctionMap::iterator next = ++it;

		if(info.reference_count >= MIN_REFCOUNT) {
			if(!walked_functions.contains(info.entry_address)) {
		
				// the function's upper bound is either the entry point of the next function
				// or the region's end which is the absolute max end this function can have
				const edb::address_t next_entry = (next != results.end()) ? next.value().entry_address : region.end;

				// walk the function and collect some results
				
				findFunctionEnd(info, next_entry, found_functions, results);
				walked_functions.insert(info.entry_address);
				
	#if 1
				// if the very last instruction happens to be a jmp, then this may
				// be a call/ret -> jmp optimization. This isn't always the case
				// but often enough that it's probably right
				quint8 buf[edb::Instruction::MAX_SIZE];
				int buf_size = sizeof(buf);
				if(edb::v1::getInstructionBytes(info.last_instruction, buf, buf_size)) {
					const edb::Instruction insn(buf, buf_size, info.last_instruction, std::nothrow);
					if(insn.valid() && insn.type() == edb::Instruction::OP_JMP) {

						Q_ASSERT(insn.operandCount() == 1);
						const edb::Operand &op = insn.operand(0);

						if(op.generalType() == edb::Operand::TYPE_REL) {
							const edb::address_t target = op.relativeTarget();

							Function func;

							if(!findContainingFunction(target, func)) {
								found_functions.insert(target);
							}
						}
					}
				}
	#endif
			}
		}
		
		it = next;
	}
	
	// add the newly found functions to the list and report the number of "updates"
	Q_FOREACH(edb::address_t func, found_functions) {
		if(!results.contains(func)) {
			results[func].entry_address		= func;
			results[func].end_address		= func;
			results[func].reference_count	= MIN_REFCOUNT;
			++updates;
		}
	}
	
	return updates;
}

//------------------------------------------------------------------------------
// Name: fixOverlaps(FunctionMap &function_map)
// Desc: ensures that no function overlaps another
//------------------------------------------------------------------------------
void Analyzer::fixOverlaps(FunctionMap &function_map) {
	for(FunctionMap::iterator it = function_map.begin(); it != function_map.end(); ) {
		Function &func = *it++;
		if(it != function_map.end()) {
			const Function &next_func = *it;
			if(next_func.entry_address <= func.end_address) {				
				func.end_address = next_func.entry_address - 1;
			}
		}
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::findFunctionEnd(Function &function, edb::address_t end_address, QSet<edb::address_t> &found_functions, const FunctionMap &results) {
	QStack<edb::address_t>		jump_targets;
	QHash<edb::address_t, int>	visited_addresses;
		
	// we start with the entry point of the function
	jump_targets.push(function.entry_address);
	
	// while no more jump targets... (includes entry point)
	while(!jump_targets.empty()) {
		edb::address_t addr = jump_targets.pop();
				
		// for certain forward jump scenarioes this is possible.
		if(visited_addresses.contains(addr)) {
			continue;
		}
						
		// keep going until we go out of bounds	
		while(addr >= function.entry_address && addr < end_address) {
								
			quint8 buf[edb::Instruction::MAX_SIZE];
			int buf_size = sizeof(buf);
			if(!edb::v1::getInstructionBytes(addr, buf, buf_size)) {
				break;
			}
			
			const edb::Instruction insn(buf, buf_size, addr, std::nothrow);
			if(!insn.valid()) {
				break;
			}

			// ok, it was a valid instruction, let's add it to the
			// list of possible 'code addresses'
			visited_addresses.insert(addr, insn.size());
			
			const edb::Instruction::Type type = insn.type();

			if(type == edb::Instruction::OP_RET || type == edb::Instruction::OP_HLT) {
				break;
			} else if(type == edb::Instruction::OP_JCC) {

				const edb::Operand &op = insn.operand(0);
				if(op.generalType() == edb::Operand::TYPE_REL) {
					const edb::address_t ea = op.relativeTarget();

					if(!visited_addresses.contains(ea) && !jump_targets.contains(ea)) {
						jump_targets.push(ea);
					}
				}
			} else if(type == edb::Instruction::OP_JMP) {

				const edb::Operand &op = insn.operand(0);
				if(op.generalType() == edb::Operand::TYPE_REL) {
					const edb::address_t ea = op.relativeTarget();
					
					// is it a jump to another function's entry point?
					// if so, this is a dead end, resume from other branches
					// but give the target a bonus reference
					FunctionMap::const_iterator it = results.find(ea);
					if(it != results.end()) {
						found_functions.insert(ea);
						break;
					}
					
					// ok, it is a jmp to an address we haven't seen yet
					// so just continue processing from there
					if(!visited_addresses.contains(ea)) {
						addr = ea;
						continue;
					} else {
						break;
					}
				}
			} else if(type == edb::Instruction::OP_CALL) {

				const edb::Operand &op = insn.operand(0);
				if(op.generalType() == edb::Operand::TYPE_REL) {
					const edb::address_t ea = op.relativeTarget();

					// skip over ones which are: "call <label>; label:"
					if(ea != addr + insn.size()) {
						found_functions.insert(ea);
					}
				} else if(op.generalType() == edb::Operand::TYPE_EXPRESSION) {
					// looks like: "call [...]", if it is of the form, call [N + REG]
					// then it may be a jump table using REG as an offset
					// TODO: is this a jump table?
				}
			}
			
			addr += insn.size();
		}
	}
	
	function.last_instruction	= function.entry_address;
	function.end_address		= function.entry_address;
	
	// get the last instruction and the last byte of the function
	for(QHash<edb::address_t, int>::const_iterator it = visited_addresses.begin(); it != visited_addresses.end(); ++it) {
		function.end_address        = qMax(function.end_address,      it.key() + it.value() - 1);
		function.last_instruction	= qMax(function.last_instruction, it.key());
	}
}

//------------------------------------------------------------------------------
// Name: isThunk(edb::address_t address)
// Desc: basically returns true if the first instruction of the function is a 
//       jmp
//------------------------------------------------------------------------------
bool Analyzer::isThunk(edb::address_t address) const {
	quint8 buf[edb::Instruction::MAX_SIZE];
	int buf_size = sizeof(buf);
	if(edb::v1::getInstructionBytes(address, buf, buf_size)) {
		const edb::Instruction insn(buf, buf_size, address, std::nothrow);
		return insn.valid() && insn.type() == edb::Instruction::OP_JMP;
	}
	
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::setFunctionTypesHelper(Function &info) const {
	const edb::address_t addr = info.entry_address;

	if(isThunk(addr)) {
		info.type = Function::FUNCTION_THUNK;
	} else {
		info.type = Function::FUNCTION_STANDARD;
	}
}

//------------------------------------------------------------------------------
// Name: setFunctionTypes(FunctionMap &results)
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::setFunctionTypes(FunctionMap &results) {
	// give bonus if we have a symbol for the address
#ifdef USE_QT_CONCURRENT
	QtConcurrent::blockingMap(results, boost::bind(&Analyzer::setFunctionTypesHelper, this, _1));
#else
	std::for_each(results.begin(), results.end(), boost::bind(&Analyzer::setFunctionTypesHelper, this, _1));
#endif
}

//------------------------------------------------------------------------------
// Name: isInsideKnown(const MemRegion &region, edb::address_t address)
// Desc: 
//------------------------------------------------------------------------------
bool Analyzer::isInsideKnown(const MemRegion &region, edb::address_t address) {
	
	const FunctionMap &funcs = functions(region);
	Q_FOREACH(const Function &func, funcs) {
		if(address >= func.entry_address && address <= func.end_address) {
			return true;
		}
	}
	
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::findCallsFromFunctions(const MemRegion &region, FunctionMap &results, QSet<edb::address_t> &walked_functions) {
	int updates;
	do {
		updates = walkAllFunctions(results, region, walked_functions);
		qDebug() << "[Analyzer] got" << updates << "updates";
	} while(updates != 0);
}

//------------------------------------------------------------------------------
// Name: analyze(const MemRegion &region)
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::analyze(const MemRegion &region_ref) {


	// NOTE: through a series of craziness, 
	// bonusMain, calls 
	// edb::v1::locateMainFunction, which calls
	// edb::v1::primaryCodeRegion, which calls
	// MemoryRegions::sync()
	// which happens to invalidate the references to regions we are potentially passed..
	// so just to be sure, we make a copy! wow, that was an annoying bug!
	MemRegion region(region_ref);

	RegionInfo &region_info = m_AnalysisInfo[region];

	QSettings settings;
	const bool fuzzy			= settings.value("Analyzer/fuzzy_logic_functions.enabled", true).toBool();
	const QByteArray md5		= md5Region(region);
	const QByteArray prev_md5	= region_info.md5;

	if(md5 != prev_md5 || fuzzy != region_info.fuzzy) {
		FunctionMap &function_map = region_info.analysis;

		function_map.clear();
				
		QSet<edb::address_t> walked_functions;

		// start with some obvious functions...
		emit updateProgress(util::percentage(0, 11));
		qDebug("[Analyzer] adding entry points to the list...");
		bonusEntryPoint(region, function_map);
		emit updateProgress(util::percentage(1, 11));
		
		qDebug("[Analyzer] attempting to add 'main' to the list...");
		bonusMain(region, function_map);
		emit updateProgress(util::percentage(2, 11));

		qDebug("[Analyzer] attempting to add functions with symbols to the list...");
		bonusSymbols(region, function_map);
		emit updateProgress(util::percentage(3, 11));

		qDebug("[Analyzer] calculating function bounds... (pass 1)");
		findCallsFromFunctions(region, function_map, walked_functions);
		emit updateProgress(util::percentage(4, 11));
		
		fixOverlaps(function_map);

		// ok, at this point, we've done the best we can with knowns
		// we should have a full analysis of all functions which are
		// reachable from known entry points in the code
		// let's start looking for unknowns
		if(fuzzy) {
			qDebug("[Analyzer] finding possible function calls...");

			// put these into a temporary bin
			FunctionMap found_functions;
			findFunctionCalls(region, found_functions);
			emit updateProgress(util::percentage(5, 11));

			qDebug("[Analyzer] bonusing stack frames...");
			bonusStackFrames(found_functions);
			emit updateProgress(util::percentage(6, 11));

			// collect the ones which have enough references
			qDebug("[Analyzer] collecting high reference answers ...");
			for(FunctionMap::iterator it = found_functions.begin(); it != found_functions.end(); ) {
				if(it->reference_count >= MIN_REFCOUNT) {
					function_map[it->entry_address] = *it;
					found_functions.erase(it++);
				} else {
					++it;
				}
			}
			emit updateProgress(util::percentage(7, 11));

			// walk the newly found functions!
			qDebug("[Analyzer] calculating function bounds... (pass 2)");
			findCallsFromFunctions(region, function_map, walked_functions);
			emit updateProgress(util::percentage(8, 11));

			// ok, after this the only ones left should be low reference 
			// count ones...
			// but by now we can gaurantee they won't overlap with 
			// known good functions at all
			qDebug("[Analyzer] collecting low reference answers...");
			
			// promote weak symbols...
			SymbolManager &syms = edb::v1::symbolManager();
			Q_FOREACH(const Function &func, found_functions) {
				if(!isInsideKnown(region, func.entry_address)) {
					if(!function_map.contains(func.entry_address)) {
						function_map[func.entry_address] = func;
						
						const SymbolManager::Symbol *const s = syms.find(func.entry_address);
						if(s != 0 && s->isWeak()) {
							function_map[func.entry_address].reference_count++;
						}
						
					}
				}
			}
			emit updateProgress(util::percentage(9, 11));
			
			// walk the newly found functions!
			qDebug("[Analyzer] calculating function bounds... (pass 3)");
			findCallsFromFunctions(region, function_map, walked_functions);
			emit updateProgress(util::percentage(10, 11));
		}
		
		qDebug("[Analyzer] determining function types...");
		setFunctionTypes(function_map);
		emit updateProgress(util::percentage(11, 11));

		qDebug("[Analyzer] complete");
		emit updateProgress(100);

		region_info.md5		= md5;
		region_info.fuzzy	= fuzzy;
	} else {
		qDebug("[Analyzer] region unchanged, using previous analysis");
	}
}

//------------------------------------------------------------------------------
// Name: catagory(edb::address_t address) const
// Desc: 
//------------------------------------------------------------------------------
AnalyzerInterface::AddressCatagory Analyzer::catagory(edb::address_t address) const {

	Function func;
	if(findContainingFunction(address, func)) {
		if(address == func.entry_address) {
			return ADDRESS_FUNC_START;
		} else if(address == func.end_address) {
			return ADDRESS_FUNC_END;
		} else {
			return ADDRESS_FUNC_BODY;
		}
	}
	return ADDRESS_FUNC_UNKNOWN;
}

//------------------------------------------------------------------------------
// Name: functions(const MemRegion &region) const
// Desc: 
//------------------------------------------------------------------------------
AnalyzerInterface::FunctionMap Analyzer::functions(const MemRegion &region) const {
	return m_AnalysisInfo[region].analysis;
}

//------------------------------------------------------------------------------
// Name: findContainingFunction(edb::address_t address, AnalyzerInterface::Function &function) const
// Desc: 
//------------------------------------------------------------------------------
bool Analyzer::findContainingFunction(edb::address_t address, AnalyzerInterface::Function &function) const {

	MemRegion region;
	if(edb::v1::memoryRegions().findRegion(address, region)) {
		const FunctionMap &funcs = functions(region);
		Q_FOREACH(const Function &f, funcs) {
			if(address >= f.entry_address && address <= f.end_address) {
				function = f;
				return true;
			}
		}
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: md5Region(const MemRegion &region) const
// Desc: returns a byte array representing the MD5 of a region
//------------------------------------------------------------------------------
QByteArray Analyzer::md5Region(const MemRegion &region) const{

	static const edb::address_t pageSize = edb::v1::debuggerBase->pageSize();

	MD5 md5Digest;
	QByteArray ret;

	const edb::address_t size_in_pages = region.size() / pageSize;
	try {
		QVector<quint8> pages(size_in_pages * pageSize);
	
		if(edb::v1::debuggerBase->readPages(region.start, &pages[0], size_in_pages)) {
			md5Digest.update(&pages[0], size_in_pages * pageSize);
			md5Digest.finalize();
			
			// make a deep copy because MD5 is about to go out of scope
			ret = QByteArray(reinterpret_cast<const char *>(md5Digest.digest()), 16);
		}
	} catch(const std::bad_alloc &) {
		QMessageBox::information(0, QObject::tr("Memroy Allocation Error"),
			QObject::tr("Unable to satisfy memory allocation request for requested region."));
	}

	return ret;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
edb::address_t Analyzer::moduleEntryPoint(const MemRegion &region) const {
	edb::address_t entry = edb::nullptr;
#ifdef Q_OS_LINUX
	const edb::address_t pageSize = edb::v1::debuggerBase->pageSize();

	try {
		QVector<quint8> pages(pageSize);
		if(edb::v1::debuggerBase->readPages(region.start, &pages[0], 1)) {

			union {
				void		*ptr;
				Elf32_Ehdr	*elf32;
				Elf64_Ehdr	*elf64;
			} code = { &pages[0] };

			if(std::memcmp(code.elf32->e_ident, ELFMAG, SELFMAG) == 0) {
				if(code.elf32->e_ident[EI_CLASS] == ELFCLASS32) {
					entry = code.elf32->e_entry;
				}
			}

			if(entry == edb::nullptr && std::memcmp(code.elf64->e_ident, ELFMAG, SELFMAG) == 0) {
				if(code.elf64->e_ident[EI_CLASS] == ELFCLASS64) {
					entry = code.elf64->e_entry;
				}
			}
		}
	} catch(const std::bad_alloc &) {
		QMessageBox::information(0, QObject::tr("Memroy Allocation Error"),
			QObject::tr("Unable to satisfy memory allocation request for requested region."));
	}
#else
	Q_UNUSED(region);
#endif
	return entry;
}


//------------------------------------------------------------------------------
// Name: bonusEntryPoint(const MemRegion &region, FunctionMap &results) const
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::bonusEntryPoint(const MemRegion &region, FunctionMap &results) const {
	edb::address_t entry = moduleEntryPoint(region);

	if(entry != edb::nullptr) {
	
		// if the entry seems like a relative one (like for a library)
		// then add the base of its image
		if(entry < region.start) {
			entry += region.start;
		}
	
		// make sure we have an entry for this function
		if(region.contains(entry)) {
			updateResultEntry(results, entry);	
		}
	}
}

//------------------------------------------------------------------------------
// Name: invalidateAnalysis(const MemRegion &region)
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::invalidateAnalysis(const MemRegion &region) {
	m_AnalysisInfo[region] = RegionInfo();
}

//------------------------------------------------------------------------------
// Name: invalidateAnalysis()
// Desc: 
//------------------------------------------------------------------------------
void Analyzer::invalidateAnalysis() {
	m_AnalysisInfo.clear();
}

Q_EXPORT_PLUGIN2(Analyzer, Analyzer)
