/*
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 <asm/ldt.h>

// doesn't always seem to be defined in the headers
#ifndef PTRACE_GETSIGINFO
#define PTRACE_GETSIGINFO		static_cast<__ptrace_request>(0x4202)
#endif

#ifndef PTRACE_GET_THREAD_AREA
#define PTRACE_GET_THREAD_AREA	static_cast<__ptrace_request>(25)
#endif

#ifndef PTRACE_SET_THREAD_AREA
#define PTRACE_SET_THREAD_AREA	static_cast<__ptrace_request>(26)
#endif

//------------------------------------------------------------------------------
// Name: DebuggerCore()
// Desc: constructor
//------------------------------------------------------------------------------
DebuggerCore::DebuggerCore() : m_ActiveThread(0), m_PID(0) {
#if defined(_SC_PAGESIZE)
	m_PageSize = sysconf(_SC_PAGESIZE);
#elif defined(_SC_PAGE_SIZE)
	m_PageSize = sysconf(_SC_PAGE_SIZE);
#else
	m_PageSize = PAGE_SIZE;
#endif
}

//------------------------------------------------------------------------------
// 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()) {
		siginfo_t info;
		const int r = ptrace(PTRACE_GETSIGINFO, event.thread(), 0, &info);
		if(r != -1) {
			ret = reinterpret_cast<edb::address_t>(info.si_addr);
		}
	}
	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()) {
		Q_UNUSED(msecs);
		// TODO: implement the timeout here...somehow
		// TODO: wait on all child pids
		int status;
		pid_t tid;
		do {
			tid = waitpid(pid(), &status, 0);
		} while(tid == -1 && errno == EINTR);

		ok = (tid != static_cast<pid_t>(-1));
		if(ok) {
			event = DebugEvent(status, pid(), tid);
			m_ActiveThread = event.thread();
		}
	}
}

//------------------------------------------------------------------------------
// 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(), 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(PTRACE_POKETEXT, pid(), address, value);
	ok = (v != -1);
}

//------------------------------------------------------------------------------
// Name: attach(edb::pid_t pid)
// Desc: 
//------------------------------------------------------------------------------
bool DebuggerCore::attach(edb::pid_t pid) {
	detach();
	
	const long ret = ptrace(PTRACE_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(PTRACE_DETACH, pid(), 0, 0);
		m_PID = 0;
	}
}

//------------------------------------------------------------------------------
// Name: kill()
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::kill() {
	if(attached()) {
		clearBreakpoints();
		edb::pid_t p = pid();
		ptrace(PTRACE_KILL, p, 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(PTRACE_CONT, pid(), 0, reinterpret_cast<void *>(event->stopCode()));
		} else {
			ptrace(PTRACE_CONT, 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(PTRACE_SINGLESTEP, pid(), 0, reinterpret_cast<void *>(event->stopCode()));
		} else {
			ptrace(PTRACE_SINGLESTEP, pid(), 0, 0);
		}
	}
}

//------------------------------------------------------------------------------
// Name: getState(State &state)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::getState(State &state) {
	// TOOD: assert that we are paused
	
	if(attached()) {
	#if defined(EDB_X86)
		user_regs_struct	regs;
		user_fpregs_struct	i387_regs;
		
		if(ptrace(PTRACE_GETREGS, pid(), 0, &regs) != -1) {
			state.eax = regs.eax;
			state.ebx = regs.ebx;
			state.ecx = regs.ecx;
			state.edx = regs.edx;
			state.esp = regs.esp;
			state.ebp = regs.ebp;
			state.edi = regs.edi;
			state.esi = regs.esi;
			state.eip = regs.eip;
			state.eflags = regs.eflags;
			state._cs = regs.xcs;
			state._ds = regs.xds;
			state._es = regs.xes;
			state._fs = regs.xfs;
			state._gs = regs.xgs;
			state._ss = regs.xss;
			state.orig_eax = regs.orig_eax;
			
			struct user_desc desc;
			std::memset(&desc, 0, sizeof(desc));
			if(ptrace(PTRACE_GET_THREAD_AREA, pid(), (state._gs / LDT_ENTRY_SIZE), &desc) != -1) {
				state.gs_base = desc.base_addr;
			} else {
				state.gs_base = 0;
			}

			if(ptrace(PTRACE_GET_THREAD_AREA, pid(), (state._fs / LDT_ENTRY_SIZE), &desc) != -1) {
				state.fs_base = desc.base_addr;
			} else {
				state.fs_base = 0;
			}
		}
		
		if(ptrace(PTRACE_GETFPREGS, pid(), 0, &i387_regs) != -1) {
			State::i387Register *const p = reinterpret_cast<State::i387Register *>(i387_regs.st_space);
			qCopy(&p[0], &p[8], state.st);
		}
		
		state.dr[0] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[0]), 0);
		state.dr[1] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[1]), 0);
		state.dr[2] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[2]), 0);
		state.dr[3] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[3]), 0);
		state.dr[4] = 0;
		state.dr[5] = 0;
		state.dr[6] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[6]), 0);
		state.dr[7] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[7]), 0);
	#elif defined(EDB_X86_64)
		user_regs_struct	regs;
		user_fpregs_struct	i387_regs;
		
		if(ptrace(PTRACE_GETREGS, pid(), 0, &regs) != -1) {
			state.rax = regs.rax;
			state.rbx = regs.rbx;
			state.rcx = regs.rcx;
			state.rdx = regs.rdx;
			state.rsp = regs.rsp;
			state.rbp = regs.rbp;
			state.rdi = regs.rdi;
			state.rsi = regs.rsi;
			state.rip = regs.rip;
			state.r8 = regs.r8;
			state.r9 = regs.r9;
			state.r10 = regs.r10;
			state.r11 = regs.r11;
			state.r12 = regs.r12;
			state.r13 = regs.r13;
			state.r14 = regs.r14;
			state.r15 = regs.r15;
			state.rflags = regs.eflags;
			state.cs = regs.cs;
			state.ds = regs.ds;
			state.es = regs.es;
			state.fs = regs.fs;
			state.gs = regs.gs;
			state.ss = regs.ss;
			state.orig_rax = regs.orig_rax;
			state.fs_base = regs.fs_base;
			state.gs_base = regs.gs_base;
		}
		
		if(ptrace(PTRACE_GETFPREGS, pid(), 0, &i387_regs) != -1) {
			State::i387Register *const p = reinterpret_cast<State::i387Register *>(i387_regs.st_space);
			qCopy(&p[0], &p[8], state.st);
		}
		
		state.dr[0] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[0]), 0);
		state.dr[1] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[1]), 0);
		state.dr[2] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[2]), 0);
		state.dr[3] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[3]), 0);
		state.dr[4] = 0;
		state.dr[5] = 0;
		state.dr[6] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[6]), 0);
		state.dr[7] = ptrace(PTRACE_PEEKUSER, pid(), offsetof(user, u_debugreg[7]), 0);
#endif
	} else {
		state.clear();
	}
}

//------------------------------------------------------------------------------
// Name: setState(const State &state)
// Desc: 
//------------------------------------------------------------------------------
void DebuggerCore::setState(const State &state) {
	
	// TOOD: assert that we are paused
	
	if(attached()) {
#if defined(EDB_X86)
		user_regs_struct regs;

		regs.eax = state.eax;
		regs.ebx = state.ebx;
		regs.ecx = state.ecx;
		regs.edx = state.edx;
		regs.esp = state.esp;
		regs.ebp = state.ebp;
		regs.edi = state.edi;
		regs.esi = state.esi;
		regs.eip = state.eip;
		regs.eflags = state.eflags;
		regs.xcs = state._cs;
		regs.xds = state._ds;
		regs.xes = state._es;
		regs.xfs = state._fs;
		regs.xgs = state._gs;
		regs.xss = state._ss;
		regs.orig_eax = state.orig_eax;
		
		ptrace(PTRACE_SETREGS, pid(), 0, &regs);
		
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[0]), state.dr[0]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[1]), state.dr[1]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[2]), state.dr[2]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[3]), state.dr[3]);
//		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[4]), state.dr[4]);
//		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[5]), state.dr[5]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[6]), state.dr[6]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[7]), state.dr[7]);
#elif defined(EDB_X86_64)
		user_regs_struct regs;

		regs.rax = state.rax;
		regs.rbx = state.rbx;
		regs.rcx = state.rcx;
		regs.rdx = state.rdx;
		regs.rsp = state.rsp;
		regs.rbp = state.rbp;
		regs.rdi = state.rdi;
		regs.rsi = state.rsi;
		regs.r8 = state.r8;
		regs.r9 = state.r9;
		regs.r10 = state.r10;
		regs.r11 = state.r11;
		regs.r12 = state.r12;
		regs.r13 = state.r13;
		regs.r14 = state.r14;
		regs.r15 = state.r15;
		regs.rip = state.rip;
		regs.eflags = state.rflags;
		regs.cs = state.cs;
		regs.ds = state.ds;
		regs.es = state.es;
		regs.fs = state.fs;
		regs.gs = state.gs;
		regs.ss = state.ss;
		regs.orig_rax = state.orig_rax;
		regs.fs_base = state.fs_base;
		regs.gs_base = state.gs_base;
		
		ptrace(PTRACE_SETREGS, pid(), 0, &regs);
		
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[0]), state.dr[0]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[1]), state.dr[1]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[2]), state.dr[2]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[3]), state.dr[3]);
//		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[4]), state.dr[4]);
//		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[5]), state.dr[5]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[6]), state.dr[6]);
		ptrace(PTRACE_POKEUSER, pid(), offsetof(user, u_debugreg[7]), state.dr[7]);
#endif
	}
}

//------------------------------------------------------------------------------
// 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(PTRACE_TRACEME, 0, 0, 0);
		
		// redirect it's I/O
		if(!tty.isEmpty()) {
			FILE *const std_out = freopen(qPrintable(tty), "r+b", stdout);
			FILE *const std_in = freopen(qPrintable(tty), "r+b", stdin);
			FILE *const std_err = freopen(qPrintable(tty), "r+b", stderr);
			
			Q_UNUSED(std_out);
			Q_UNUSED(std_in);
			Q_UNUSED(std_err);
		}
		
		// 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;
}
