/*
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.
*/


#include "DialogHeap.h"
#include "DebuggerCoreInterface.h"
#include "Debugger.h"
#include "MemoryRegions.h"
#include "SymbolManager.h"

#include <QHeaderView>
#include <QMessageBox>
#include <QVector>
#include <QString>
#include <QFileInfo>


#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
#define NON_MAIN_ARENA 0x4
#define SIZE_BITS (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)

#define next_chunk(p, c) ((p) + (c).chunksize())
#define prev_chunk(p, c) ((p) - ((c).prev_size)))


struct malloc_chunk {
	unsigned int	prev_size;	/* Size of previous chunk (if free).  */
	unsigned int	size;		/* Size in bytes, including overhead. */

	struct malloc_chunk* fd;	/* double links -- used only if free. */
	struct malloc_chunk* bk;

	unsigned int chunksize() const	{ return size & ~(SIZE_BITS); }
	bool prev_inuse() const			{ return size & PREV_INUSE; }
};


//------------------------------------------------------------------------------
// Name: DialogHeap(QWidget *parent)
// Desc: 
//------------------------------------------------------------------------------
DialogHeap::DialogHeap(QWidget *parent) : QDialog(parent) {
	ui.setupUi(this);
	
	ui.resultTable->verticalHeader()->hide();
	ui.resultTable->horizontalHeader()->setStretchLastSection(true);
}


//------------------------------------------------------------------------------
// Name: showEvent(QShowEvent *)
// Desc: 
//------------------------------------------------------------------------------
void DialogHeap::showEvent(QShowEvent *) {
	ui.progressBar->setValue(0);
	ui.resultTable->setRowCount(0);
}

//------------------------------------------------------------------------------
// Name: on_resultTable_cellDoubleClicked(int, int)
// Desc: 
//------------------------------------------------------------------------------
void DialogHeap::on_resultTable_cellDoubleClicked(int, int) {
	
	const QList<QTableWidgetItem *> sel = ui.resultTable->selectedItems();
	
	BaseTypes::address_t start_address	= sel[0]->text().toUInt(0, 16);
	BaseTypes::address_t end_address	= sel[1]->text().toUInt(0, 16) + start_address;
	
	Debugger::dumpDataRange(start_address, end_address);
}

//------------------------------------------------------------------------------
// Name: on_btnFind_clicked()
// Desc: 
//------------------------------------------------------------------------------
void DialogHeap::getLibraryNames(QString &libcName, QString &ldName) {
	QList<QString> libs = Debugger::loadedLibraries();

	for(QList<QString>::const_iterator it = libs.begin(); it != libs.end() && (ldName == "" || libcName == ""); ++it) {

		const QFileInfo fileinfo(*it);

		// this tries its best to cover all possible libc library versioning possibilities
		// we need to find out if this is 100% accurate, so far seems correct based on my system

		if(fileinfo.completeBaseName().startsWith("libc-")) {
			libcName = fileinfo.completeBaseName() + "." + fileinfo.suffix();
			Debugger::log(QString().sprintf("libc library appears to be: %s", qPrintable(libcName)));
			continue;
		}

		if(fileinfo.completeBaseName().startsWith("ld-")) {
			ldName = fileinfo.completeBaseName() + "." + fileinfo.suffix();
			Debugger::log(QString().sprintf("ld library appears to be: %s", qPrintable(ldName)));
			continue;
		}

		if(fileinfo.completeBaseName().startsWith("libc.so")) {
			libcName = fileinfo.completeBaseName() + "." + fileinfo.suffix();
			Debugger::log(QString().sprintf("libc library appears to be: %s", qPrintable(libcName)));
			continue;
		}
	}
	
}

//------------------------------------------------------------------------------
// Name: on_btnFind_clicked()
// Desc: 
//------------------------------------------------------------------------------
void DialogHeap::on_btnFind_clicked() {
	
	ui.resultTable->setRowCount(0);
	ui.resultTable->setSortingEnabled(false);

	// get both the libc and ld symbols of __curbrk
	// this will be the "before/after libc addresses
	if(Debugger::symbolManager != 0) {
	
		// we should get them from the regions we see
		const SymbolManager::Symbol *s;
		
		BaseTypes::address_t start_address	= 0;
		BaseTypes::address_t end_address	= 0;
		
		QString libcName;
		QString ldName;
		
		getLibraryNames(libcName, ldName);

		s = Debugger::symbolManager->findSymbol(libcName + "::__curbrk");
		if(s != 0) {
			end_address = s->address;
		} else {
			QMessageBox::information(this, "__curbrk symbol not found in libc", "Could not find the symbol for __curbrk in your libc, perhaps you need to regenerate your symbols?");
			Debugger::log("__curbrk symbol not found in libc");
			return;
		}

		s = Debugger::symbolManager->findSymbol(ldName + "::__curbrk");
		if(s != 0) {
			start_address = s->address;
		} else {
			QMessageBox::information(this, "__curbrk symbol not found in ld", "Could not find the symbol for __curbrk in your ld, perhaps you need to regenerate your symbols?");
			Debugger::log("__curbrk symbol not found in ld");
			return;
		}

		Debugger::log(QString().sprintf("heap start symbol : %08x", start_address));
		Debugger::log(QString().sprintf("heap end symbol   : %08x", end_address));

		// read the contents of those symbols
		Debugger::debuggerBase->readBytes(end_address, &end_address, sizeof(end_address));
		Debugger::debuggerBase->readBytes(start_address, &start_address, sizeof(start_address));
		
		Debugger::log(QString().sprintf("heap start: %08x", start_address));
		Debugger::log(QString().sprintf("heap end  : %08x", end_address));
		
		if(start_address != 0x00000000 && end_address != 0x00000000) {

			malloc_chunk currentChunk;
			malloc_chunk nextChunk;
			BaseTypes::address_t currentChunkAddress = start_address;

			const BaseTypes::address_t howMany = end_address - start_address;
			do {
				// read in the current chunk..
				Debugger::debuggerBase->readBytes(currentChunkAddress, &currentChunk, sizeof(currentChunk));

				// figure out the address of the next chunk
				const BaseTypes::address_t nextChunkAddress = next_chunk(currentChunkAddress, currentChunk);


				int result_count = ui.resultTable->rowCount();
				ui.resultTable->insertRow(result_count);

				if(nextChunkAddress == end_address) {
					ui.resultTable->setItem(result_count, 0, new QTableWidgetItem(QString().sprintf("%08x", currentChunkAddress)));
					ui.resultTable->setItem(result_count, 1, new QTableWidgetItem(QString().sprintf("%08x", currentChunk.chunksize())));
					ui.resultTable->setItem(result_count, 2, new QTableWidgetItem("Top"));
				} else {

					// make sure we aren't following a broken heap...
					if(nextChunkAddress > end_address || nextChunkAddress < start_address) {
						break;
					}

					// read in the next chunk
					Debugger::debuggerBase->readBytes(nextChunkAddress, &nextChunk, sizeof(nextChunk));
					ui.resultTable->setItem(result_count, 0, new QTableWidgetItem(QString().sprintf("%08x", currentChunkAddress)));
					ui.resultTable->setItem(result_count, 1, new QTableWidgetItem(QString().sprintf("%08x", currentChunk.chunksize() + sizeof(unsigned int))));
					ui.resultTable->setItem(result_count, 2, new QTableWidgetItem(nextChunk.prev_inuse() ? "Busy" : "Free"));
					
				}

				if(currentChunkAddress == nextChunkAddress) {
					break;
				}

				currentChunkAddress = nextChunkAddress;

				const BaseTypes::address_t howManyDone	= currentChunkAddress - start_address;
				const int percent = static_cast<int>(static_cast<double>(howManyDone) / static_cast<double>(howMany) * 100);			

				ui.progressBar->setValue(percent);

			} while(currentChunkAddress != end_address);
		}
	}
	
	ui.resultTable->setSortingEnabled(true);
}
