/*
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 <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <signal.h>
#include <unistd.h>
#include <mach/mach.h>

//------------------------------------------------------------------------------
// Name: DebuggerCore()
// Desc: constructor
//------------------------------------------------------------------------------
DebuggerCore::DebuggerCore() : m_ActiveThread(0), m_PID(0) {
	m_PageSize = 4096;
}

//------------------------------------------------------------------------------
// Name: getFaultAddress()
// Desc: gets the address of a segfault (Linux >= 2.3.99-pre6)
//------------------------------------------------------------------------------
edb::address_t DebuggerCore::getFaultAddress(const DebugEvent &event) {
	edb::address_t ret = static_cast<edb::address_t>(-1);
	if(attached()) {
		// TODO: implement
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: waitForDebugEvent(DebugEvent &event, bool &ok, int secs)
// Desc: waits for a debug event, secs is a timeout (but is not yet respected)
//       ok will be set to false if the timeout expires
//------------------------------------------------------------------------------
void DebuggerCore::waitForDebugEvent(DebugEvent &event, bool &ok, int msecs) {
	ok = false;
	if(attached()) {
		// TODO: implement
	}
}

//------------------------------------------------------------------------------
// Name: readData(edb::address_t address, bool &ok)
// Desc: 
//------------------------------------------------------------------------------
long DebuggerCore::readData(edb::address_t address, bool &ok) {
	errno = 0;
	const long v = ptrace(PTRACE_PEEKTEXT, pid(), (char *)address, 0);
	SET_OK(ok, v);
	return v;
}

//------------------------------------------------------------------------------
// Name: writeData(edb::address_t address, long value, bool &ok)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::writeData(edb::address_t address, long value, bool &ok) {
	const long v = ptrace(PT_WRITE_D, pid(), (char *)address, value);
	ok = (v != -1);
}

//------------------------------------------------------------------------------
// Name: attach(edb::pid_t pid)
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerCore::attach(edb::pid_t pid) {
	detach();
	
	const long ret = ptrace(PT_ATTACH, pid, 0, 0);
	if(ret == 0) {
		m_PID			= pid;
		m_ActiveThread	= pid;
	}
	
	return ret == 0;
}

//------------------------------------------------------------------------------
// Name: detach()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::detach() {
	if(attached()) {
		clearBreakpoints();
		ptrace(PT_DETACH, pid(), 0, 0);
		m_PID = 0;
	}
}

//------------------------------------------------------------------------------
// Name: kill()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::kill() {
	if(attached()) {
		clearBreakpoints();
		edb::pid_t p = pid();
        ptrace(PT_KILL, pid(), 0, 0);
		waitpid(p, 0, 0);
		m_PID = 0;
	}
}

//------------------------------------------------------------------------------
// Name: pause()
// Desc: stops *all* threads of a process
//------------------------------------------------------------------------------
void DebuggerCore::pause() {

	if(attached()) {
		::kill(pid(), SIGSTOP);
	}
}

//------------------------------------------------------------------------------
// Name: resume(const DebugEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::resume(const DebugEvent *event) {
	// TOOD: assert that we are paused
	
	if(attached()) {
		if(event != 0) {
			ptrace(PT_CONTINUE, pid(), 0, reinterpret_cast<void *>(event->stopCode()));
		} else {
			ptrace(PT_CONTINUE, pid(), 0, 0);
		}
	}
}

//------------------------------------------------------------------------------
// Name: step(const DebugEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::step(const DebugEvent *event) {
	// TOOD: assert that we are paused
	
	if(attached()) {
		if(event != 0) {
			ptrace(PT_STEP, pid(), 0, reinterpret_cast<void *>(event->stopCode()));
		} else {
			ptrace(PT_STEP, pid(), 0, 0);
		}
	}
}

//------------------------------------------------------------------------------
// Name: getState(State &state)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::getState(State &state) {
	// TOOD: assert that we are paused
	
	if(attached()) {
      	mach_port_t task;
	  	kern_return_t err;

	  	/* Get the mach task for the target process */
	  	err = task_for_pid(mach_task_self(), pid(), &task);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "task_for_pid() failed with %x\n", err);
	  	}

	  	/* Suspend the target process */

	 	err = task_suspend(task);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "task_suspend() failed\n");
	  	}

	  	/* Get all threads in the specified task */
	  	thread_act_port_array_t threadList;
	  	mach_msg_type_number_t threadCount;
	  	err = task_threads(task, &threadList, &threadCount);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "task_threads() failed\n");
			err = task_resume(task);
			if (err != KERN_SUCCESS) {
			    fprintf(stderr, "task_resume() failed\n");
			}	

	  	}

	  	assert(threadCount > 0);
	  	printf("Thread Count = %d\n", threadCount);

	  	i386_thread_state_t thread_state;
	  	mach_msg_type_number_t stateCount;
	  	stateCount = x86_THREAD_STATE32_COUNT;
	
	 	// TODO Get all threads, nost just the first one.
		err = thread_get_state(threadList[0],
	                         x86_THREAD_STATE32,
	                         (thread_state_t)&thread_state,
	                         &stateCount);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "thread_get_state() failed with %.08x\n", err);
			err = task_resume(task);
			if (err != KERN_SUCCESS) {
			    fprintf(stderr, "task_resume() failed\n");
			}	
			
	  	}

		state.eax = thread_state.eax;
		state.ebx = thread_state.ebx;
		state.ecx = thread_state.ecx;
		state.edx = thread_state.edx;
		state.esp = thread_state.esp;
		state.ebp = thread_state.ebp;
		state.edi = thread_state.edi;
		state.esi = thread_state.esi;
		state.eip = thread_state.eip;
		state.eflags = thread_state.eflags;
		state._cs = thread_state.cs;
		state._ds = thread_state.ds;
		state._es = thread_state.es;
		state._fs = thread_state.fs;
		state._gs = thread_state.gs;
		state._ss = thread_state.ss;
	} else {
		state.clear();
	}
}

//------------------------------------------------------------------------------
// Name: setState(const State &state)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::setState(const State &state) {
	
	// TOOD: assert that we are paused
	
	if(attached()) {
      	mach_port_t task;
	  	kern_return_t err;

	  	/* Get the mach task for the target process */
	  	err = task_for_pid(mach_task_self(), pid(), &task);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "task_for_pid() failed with %x\n", err);
	  	}

	  	/* Suspend the target process */

	 	err = task_suspend(task);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "task_suspend() failed\n");
	  	}

	  	/* Get all threads in the specified task */
	  	thread_act_port_array_t threadList;
	  	mach_msg_type_number_t threadCount;
	  	err = task_threads(task, &threadList, &threadCount);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "task_threads() failed\n");
			err = task_resume(task);
			if (err != KERN_SUCCESS) {
			    fprintf(stderr, "task_resume() failed\n");
			}	

	  	}

	  	assert(threadCount > 0);
	  	printf("Thread Count = %d\n", threadCount);

	  	i386_thread_state_t thread_state;
	  	mach_msg_type_number_t stateCount;
	  	stateCount = x86_THREAD_STATE32_COUNT;
	
	 	// TODO Set for specific thread, not first one

		thread_state.eax = state.eax;
		thread_state.ebx = state.ebx;
		thread_state.ecx = state.ecx;
		thread_state.edx = state.edx;
		thread_state.esp = state.esp;
		thread_state.ebp = state.ebp;
		thread_state.edi = state.edi;
		thread_state.esi = state.esi;
		thread_state.eip = state.eip;
		thread_state.eflags = state.eflags;
		thread_state.cs = state.cs;
		thread_state.ds = state.ds;
		thread_state.es = state.es;
		thread_state.fs = state.fs;
		thread_state.gs = state.gs;
		thread_state.ss = state.ss;

		err = thread_set_state(threadList[0],
	                         x86_THREAD_STATE32,
	                         (thread_state_t)&thread_state,
	                         stateCount);
	  	if (err != KERN_SUCCESS) {
	    	fprintf(stderr, "thread_get_state() failed with %.08x\n", err);
			err = task_resume(task);
			if (err != KERN_SUCCESS) {
			    fprintf(stderr, "task_resume() failed\n");
			}	
			
	  	}
	}
}

//------------------------------------------------------------------------------
// Name: open(const QString &path, const QString &cwd, const QStringList &args, const QString &tty)
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerCore::open(const QString &path, const QString &cwd, const QStringList &args, const QString &tty) {
	detach();
	bool ret = false;
	pid_t pid;
	
	switch(pid = fork()) {
	case 0:
		// we are in the child now...
	
		// set ourselves (the child proc) up to be traced
		ptrace(PT_TRACEME, 0, 0, 0);
		
		// redirect it's I/O
		if(!tty.isEmpty()) {
			freopen(qPrintable(tty), "r+b", stdout);
			freopen(qPrintable(tty), "r+b", stdin);
			freopen(qPrintable(tty), "r+b", stderr);
		}
		
		// do the actual exec
		doExecProcess(path, cwd, args);
		
		// we should never get here!
		abort();
		break;
	case -1:
		// error!
		m_PID = 0;
		break;
	default:
		// parent
		do {
			m_PID = pid;
			ret = true;

			bool ok;
			DebugEvent event;
			waitForDebugEvent(event, ok, 0);
			
			if(!event.stopped() || event.stopCode() != DebugEvent::sigtrap) {
				m_PID = 0;
				ret = false;
			}
			
		} while(0);
		break;
	}
	return ret;
}
