/*
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 "MemoryRegions.h"
#include "SymbolManager.h"
#include "DebuggerCoreInterface.h"
#include "Util.h"
#include "State.h"
#include "Debugger.h"
#include <QtGlobal>
#include <QApplication>
#include <QDebug>
#include <QStringList>

#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/exec.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <sys/namei.h>
#include <sys/sysctl.h>

#include <uvm/uvm.h>
#include <uvm/uvm_device.h>

#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>
#undef doff_t
#undef IN_ACCESS
#undef i_size
#undef i_devvp
//#include <isofs/cd9660/iso.h>
#include <isofs/cd9660/cd9660_node.h>

#include <kvm.h>
#include <fcntl.h>
#include <err.h>
#include <unistd.h>

struct kbit {
	/*
	 * size of data chunk
	 */
	size_t k_size;

	/*
	 * something for printf() and something for kvm_read()
	 */
	union {
		void *k_addr_p;
		u_long k_addr_ul;
	};

	/*
	 * where we actually put the "stuff"
	 */
	union {
		char data[1];
		struct vmspace		vmspace;
		struct vm_map		vm_map;
		struct vm_map_entry	vm_map_entry;
		struct vnode		vnode;
		struct uvm_object	uvm_object;
		struct mount		mount;
		struct inode		inode;
		struct iso_node		iso_node;
		struct uvm_device	uvm_device;
	};
};

/* the size of the object in the kernel */
#define S(x)	((x)->k_size)

/* the address of the object in kernel, two forms */
#define A(x)	((x)->k_addr_ul)
#define P(x)	((x)->k_addr_p)

/* the data from the kernel */
#define D(x,d)	(&((x)->d))

void kernel_read(kvm_t *kd, u_long addr, void *dst, size_t sz) {
	assert(addr != 0);
	assert(dst != 0);
	
	const ssize_t len = kvm_read(kd, addr, dst, sz);
	if (len != static_cast<ssize_t>(sz)) {
		errx(
			1,
			"%s == %ld vs. %lu @ %lx",
			kvm_geterr(kd),
			(long)len,
			(unsigned long)sz,
			addr);
	}
}

void kernel_read_item(kvm_t *kd, struct kbit *item) {
	kernel_read(kd, A(item), D(item, data), S(item));
}


class KernelVM {
public:
	KernelVM() {
		char errbuf[_POSIX2_LINE_MAX];
		gid_t gid = getgid();
		kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
		if (kd == NULL) {
			errx(1, "%s", errbuf);
		}
		
		setresgid(gid, gid, gid);
		load_symbols();
	}
	
	~KernelVM() {
			kvm_close(kd);
	}
	
	struct kinfo_proc* open_process(edb::pid_t pid, int *count) {
		return kvm_getprocs(kd, KERN_PROC_PID, pid, count);
	}
	
	void load_symbols() {

		enum {
			NL_MAXSSIZ			= 0,
			NL_UVM_VNODEOPS		= 1,
			NL_UVM_DEVICEOPS	= 2,
			NL_AOBJ_PAGER		= 3,
			NL_NCHASHTBL		= 4,
			NL_NCHASH			= 5,
			NL_KENTER			= 6
		};

		struct nlist nl[] = {
			{ "_maxsmap", 0, 0, 0, 0 },
			{ "_uvm_vnodeops", 0, 0, 0, 0 },
			{ "_uvm_deviceops", 0, 0, 0, 0 },
			{ "_aobj_pager", 0, 0, 0, 0 },
			{ "_nchashtbl", 0, 0, 0, 0 },
			{ "_nchash", 0, 0, 0, 0 },
			{ "_kernel_text", 0, 0, 0, 0 },
			{ NULL, 0, 0, 0, 0  }
		};

		int rc = kvm_nlist(kd, &nl[0]);
		if (rc == -1) {
			errx(1, "%s == %d", kvm_geterr(kd), rc);
		}

		for (size_t i = 0; i < sizeof(nl)/sizeof(nl[0]); i++) {
			if (nl[i].n_value == 0 && nl[i].n_name) {
				printf("%s not found\n", nl[i].n_name);
			}
		}

		uvm_vnodeops =	(void*)nl[NL_UVM_VNODEOPS].n_value;
		uvm_deviceops =	(void*)nl[NL_UVM_DEVICEOPS].n_value;
		aobj_pager =	(void*)nl[NL_AOBJ_PAGER].n_value;

		kernel_floor =	(void*)nl[NL_KENTER].n_value;
		nchash_addr =	nl[NL_NCHASH].n_value;

		kernel_read(kd, nl[NL_MAXSSIZ].n_value, &maxssiz, sizeof(maxssiz));
		kernel_read(kd, nl[NL_NCHASHTBL].n_value, &nchashtbl_addr, sizeof(nchashtbl_addr));
	}
	
private:
	kvm_t *kd;
	void *uvm_vnodeops;
	void *uvm_deviceops;
	void *aobj_pager;
	void *kernel_floor;
	u_long nchash_addr;
	u_long nchashtbl_addr;
	rlim_t maxssiz;
};

// global so it gets setup at program start
static KernelVM g_KernelVM;


//------------------------------------------------------------------------------
// Name: MemoryRegions()
// Desc: constructor
//------------------------------------------------------------------------------
MemoryRegions::MemoryRegions() : QAbstractItemModel(0), m_PID(0) {

}

//------------------------------------------------------------------------------
// Name: ~MemoryRegions()
// Desc: destructor
//------------------------------------------------------------------------------
MemoryRegions::~MemoryRegions() {
}

//------------------------------------------------------------------------------
// Name: setPID(edb::pid_t pid)
// Desc: 
//------------------------------------------------------------------------------
void MemoryRegions::setPID(edb::pid_t pid) {
	m_PID = pid;
	m_Regions.clear();
	sync();
}

//------------------------------------------------------------------------------
// Name: clear()
// Desc: 
//------------------------------------------------------------------------------
void MemoryRegions::clear() {
	m_PID = 0;
	m_Regions.clear();
}

//------------------------------------------------------------------------------
// Name: sync()
// Desc: reads a memory map file line by line
//------------------------------------------------------------------------------
void MemoryRegions::sync() {

	m_Regions.clear();

	if(m_PID != 0) {
		int count;
		struct kinfo_proc* kinfo = g_KernelVM.open_process(m_PID, &count);
	}
	reset();
}

//------------------------------------------------------------------------------
// Name: findRegion(edb::address_t address) const
// Desc: 
//------------------------------------------------------------------------------
bool MemoryRegions::findRegion(edb::address_t address) const {
	Q_FOREACH(const MemRegion &i, m_Regions) {
		if(i.contains(address)) {
			return true;
		}
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: findRegion(edb::address_t address, MemRegion &region) const
// Desc: 
//------------------------------------------------------------------------------
bool MemoryRegions::findRegion(edb::address_t address, MemRegion &region) const {
	Q_FOREACH(const MemRegion &i, m_Regions) {
		if(i.contains(address)) {
			region = i;
			return true;
		}
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QVariant MemoryRegions::data(const QModelIndex &index, int role) const {

	if(index.isValid() && role == Qt::DisplayRole) {
	
		const MemRegion &region = m_Regions[index.row()];
	
		switch(index.column()) {
		case 0:
			return edb::v1::formatPointer(region.start);
		case 1:
			return edb::v1::formatPointer(region.end);
		case 2:
			return QString("%1%2%3").arg(region.readable() ? 'r' : '-').arg(region.writable() ? 'w' : '-').arg(region.executable() ? 'x' : '-' );
		case 3:
			return region.name;
		}
	}
	
	return QVariant();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QModelIndex MemoryRegions::index(int row, int column, const QModelIndex &parent) const {
	Q_UNUSED(parent);

	if(row >= rowCount(parent) || column >= columnCount(parent)) {
		return QModelIndex();
	}
	
	return createIndex(row, column, const_cast<MemRegion *>(&m_Regions[row]));
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QModelIndex MemoryRegions::parent(const QModelIndex &index) const {
	Q_UNUSED(index);
	return QModelIndex(); 
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int MemoryRegions::rowCount(const QModelIndex &parent) const {
	Q_UNUSED(parent);
	return m_Regions.size();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int MemoryRegions::columnCount(const QModelIndex &parent) const {
	Q_UNUSED(parent);
	return 4;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QVariant MemoryRegions::headerData(int section, Qt::Orientation orientation, int role) const {
	if(role == Qt::DisplayRole && orientation == Qt::Horizontal) {
		switch(section) {
		case 0:
			return tr("Start Address");
		case 1:
			return tr("End Address");
		case 2:
			return tr("Permissions");
		case 3:
			return tr("Name");
		}	
	}
	
	return QVariant();
}

