public inbox for archer@sourceware.org
 help / color / mirror / Atom feed
* gdbstub initial code, v10
@ 2010-09-20  3:31 Oleg Nesterov
  0 siblings, 0 replies; only message in thread
From: Oleg Nesterov @ 2010-09-20  3:31 UTC (permalink / raw)
  To: archer, utrace-devel

[-- Attachment #1: Type: text/plain, Size: 640 bytes --]

Back to ugdb.

Changes: implement stepi, this also means breakpoints work too.

Notes:

	- almost untested, probably needs more fixes

	- syscall-stepping doesn't work correctly (should be simple)

	- don't look at handle_setregs/handle_set_one_reg, I did
	  this on purpose to be sure I really understand what gdb
	  actually does

Well. This wasn't simple because I nearly got heart attack twice
when I was writing this change ;)

However, it turns out that ptrace-utrace (old or recently changed)
is innocent.

I found 2 problems, the 1st one is /usr/bin/gdb bug, another one
(I believe) is utrace core bug. I'll report tomorrow.

Oleg.

[-- Attachment #2: ugdb.c --]
[-- Type: text/plain, Size: 54806 bytes --]

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/utrace.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/regset.h>
#include <asm/uaccess.h>

static int o_remote_debug;
module_param_named(echo, o_remote_debug, bool, 0);

#define BUFFER_SIZE		1024
#define PACKET_SIZE		1024

struct pbuf {
	char	*cur, *pkt;
	char	buf[BUFFER_SIZE];
};

static inline void pb_init(struct pbuf *pb)
{
	pb->cur = pb->buf;
	pb->pkt = NULL;
}

enum {
	U_STOP_IDLE = 0,
	U_STOP_PENDING,
	U_STOP_SENT,
};

struct ugdb {
	struct list_head	u_processes;
	struct list_head	u_stopped;

	int			u_stop_state;

	struct mutex		u_mutex;
	spinlock_t		u_slock;

	struct ugdb_thread
				*u_cur_tinfo,
				*u_cur_hg,
				*u_cur_hc;

	wait_queue_head_t	u_wait;

	int			u_err;

	struct pbuf		u_pbuf;
	char			u_cbuf[PACKET_SIZE];
	int			u_clen;

	sigset_t		u_sig_ign;

	unsigned int
				u_no_ack:1,
				u_allstop:1;
};

static inline void ugdb_ck_stopped(struct ugdb *ugdb)
{
	spin_lock(&ugdb->u_slock);
	WARN_ON(!list_empty(&ugdb->u_stopped) &&
				ugdb->u_stop_state == U_STOP_IDLE);
	WARN_ON(list_empty(&ugdb->u_stopped) &&
				ugdb->u_stop_state == U_STOP_PENDING);
	spin_unlock(&ugdb->u_slock);
}

static struct ugdb *ugdb_create(void)
{
	struct ugdb *ugdb;
	int err;

	err = -ENODEV;
	// XXX: ugly. proc_reg_open() should take care.
	if (!try_module_get(THIS_MODULE))
		goto out;

	err = -ENOMEM;
	ugdb = kzalloc(sizeof(*ugdb), GFP_KERNEL);
	if (!ugdb)
		goto put_module;

	INIT_LIST_HEAD(&ugdb->u_processes);
	INIT_LIST_HEAD(&ugdb->u_stopped);

	mutex_init(&ugdb->u_mutex);
	spin_lock_init(&ugdb->u_slock);

	init_waitqueue_head(&ugdb->u_wait);

	pb_init(&ugdb->u_pbuf);

	return ugdb;

put_module:
	module_put(THIS_MODULE);
out:
	return ERR_PTR(err);
}

#define P_DETACHING	(1 << 1)
#define P_ZOMBIE	(1 << 2)

struct ugdb_process {
	int			p_pid;
	int			p_state;

	struct list_head	p_threads;

	struct ugdb		*p_ugdb;
	struct list_head	p_processes;
};

static inline bool process_alive(struct ugdb_process *process)
{
	return !(process->p_state & P_ZOMBIE);
}

static inline void mark_process_dead(struct ugdb_process *process)
{
	process->p_state |= P_ZOMBIE;
}

static struct ugdb_process *ugdb_create_process(struct ugdb *ugdb, int pid_nr)
{
	struct ugdb_process *process;

	process = kzalloc(sizeof(*process), GFP_KERNEL);
	if (!process)
		return NULL;

	process->p_pid = pid_nr;
	process->p_ugdb = ugdb;
	INIT_LIST_HEAD(&process->p_threads);
	list_add_tail(&process->p_processes, &ugdb->u_processes);

	return process;
}

#define T_STOP_RUN	0
#define T_STOP_REQ	(1 << 0)	/* requested by gdb */
#define T_STOP_ALL	(1 << 1)	/* vCont;c:pX.-1, for report_clone */
#define T_STOP_ACK	(1 << 2)	/* visible to vStopped */
#define T_STOP_STOPPED	(1 << 3)	/* reported as stopped to gdb */
					/* TASK_TRACED + deactivated ? */

#define T_EV_NONE		0
#define T_EV_EXIT		(1 << 24)
#define T_EV_SIGN		(2 << 24)

#define T_EV_TYPE(event)	((0xff << 24) & (event))
#define T_EV_DATA(event)	(~(0xff << 24) & (event))

struct ugdb_thread {
	int			t_tid;
	int			t_stop_state;
	int			t_stop_event;
	bool			t_step; 	// XXX: move me into t_stop_event

	siginfo_t		*t_siginfo;

	struct ugdb		*t_ugdb;
	struct ugdb_process	*t_process;

	struct list_head	t_threads;
	struct list_head	t_stopped;

	struct pid		*t_spid;

	struct utrace_engine	*t_engine;
};

static inline bool thread_alive(struct ugdb_thread *thread)
{
	WARN_ON((thread->t_tid != 0) != process_alive(thread->t_process));

	return thread->t_tid != 0;
}

static inline void mark_thread_dead(struct ugdb_thread *thread)
{
	mark_process_dead(thread->t_process);
	thread->t_tid = 0;
}

/*
 * The thread should be alive, and it can't pass ugdb_report_death()
 * if the caller holds ugdb->u_mutex. However the tracee can be
 * reaped anyway, pid_task() can return NULL after detach_pid().
 */
static inline struct task_struct *thread_to_task(struct ugdb_thread *thread)
{
	BUG_ON(!thread_alive(thread));

	return pid_task(thread->t_spid, PIDTYPE_PID);
}

static struct ugdb_thread *ugdb_create_thread(struct ugdb_process *process,
						struct pid *spid)
{
	struct ugdb_thread *thread;

	thread = kzalloc(sizeof(*thread), GFP_KERNEL);
	if (!thread)
		return NULL;

	thread->t_tid = pid_vnr(spid);
	thread->t_spid = get_pid(spid);
	thread->t_process = process;
	thread->t_ugdb = process->p_ugdb;
	INIT_LIST_HEAD(&thread->t_stopped);
	list_add_tail(&thread->t_threads, &process->p_threads);

	return thread;
}

static void ugdb_del_stopped(struct ugdb *ugdb, struct ugdb_thread *thread)
{
	if (!list_empty(&thread->t_stopped)) {
		WARN_ON(!(thread->t_stop_state & T_STOP_ACK));

		spin_lock(&ugdb->u_slock);
		list_del_init(&thread->t_stopped);

		if (!(thread->t_stop_state & T_STOP_STOPPED)) {
			if (ugdb->u_stop_state == U_STOP_PENDING &&
					list_empty(&ugdb->u_stopped))
				ugdb->u_stop_state = U_STOP_IDLE;
		}
		spin_unlock(&ugdb->u_slock);
	}

	thread->t_stop_state = T_STOP_RUN;
}

static void ugdb_destroy_thread(struct ugdb_thread *thread)
{
	struct ugdb *ugdb = thread->t_ugdb;

	ugdb_ck_stopped(ugdb);

	ugdb_del_stopped(ugdb, thread);

	/* NULL if attach fails */
	if (thread->t_engine)
		utrace_engine_put(thread->t_engine);

	list_del(&thread->t_threads);
	put_pid(thread->t_spid);
	kfree(thread);
}

static int ugdb_set_events(struct ugdb_thread *thread,
				unsigned long events)
{
	WARN_ON(!thread_alive(thread));

	events |= (UTRACE_EVENT(CLONE) | UTRACE_EVENT(DEATH) |
			UTRACE_EVENT_SIGNAL_ALL);

	//XXX: I think utrace_get_signal() is buggy !!!!!!!!!!!!
	events |= UTRACE_EVENT(QUIESCE);

	return utrace_set_events_pid(thread->t_spid, thread->t_engine,
					events);
}

static int ugdb_control(struct ugdb_thread *thread,
				enum utrace_resume_action action)
{
	// XXX: temporary racy check
	WARN_ON(!thread_alive(thread) && action != UTRACE_DETACH);

	return utrace_control_pid(thread->t_spid, thread->t_engine,
					action);
}

static const struct utrace_engine_ops ugdb_utrace_ops;

static void ugdb_detach_thread(struct ugdb_thread *thread, bool running)
{
	int ret;

	ret = ugdb_control(thread, UTRACE_DETACH);

	/* engine->flags == 0, it can't run a callback */
	if (!running)
		return;

	/* callbacks are no longer possible */
	if (!ret)
		return;

	if (ret == -EINPROGRESS) {
		/*
		 * We raced with a callback, it can't be ->report_death().
		 * However, we can not use utrace_barrier_pid(), it can
		 * hang "forever" until the next utrace_resume() if it
		 * sees ->ops == &utrace_detached_ops set by us, but the
		 * tracee is no longer running.
		 *
		 * But: we have no choice.
		 */
		do {
			ret = utrace_barrier_pid(thread->t_spid,
						thread->t_engine);
		} while (ret == -ERESTARTSYS);
	} else {
		/*
		 * Nothing can help us to synchronize with ->report_death.
		 * We do not know if it was already called or not, we can't
		 * know if it is running. utrace_barrier_pid() can't help,
		 * it can return zero and then later ->report_death() will
		 * be called. Or it can return -ESRCH just because the task
		 * was alredy released and pid_task() == NULL, but this
		 * doesn't mean ->report_death() can't be called later.
		 *
		 * Fortunately, we know that the tracee is dying or dead,
		 * engine->ops should be changed after ugdb_report_death()
		 * returns UTRACE_DETACH.
		 */
		 while (thread->t_engine->ops == &ugdb_utrace_ops) {
		 	schedule_timeout_uninterruptible(1);
		 }
	}
}

/*
 * returns NULL if raced with exit(), or ERR_PTR().
 */
static struct ugdb_thread *ugdb_attach_thread(struct ugdb_process *process,
						struct pid *spid)
{
	struct ugdb_thread *thread;
	struct utrace_engine *engine;
	struct task_struct *task;

	thread = ugdb_create_thread(process, spid);
	if (!thread)
		goto err;

	engine = utrace_attach_pid(thread->t_spid, UTRACE_ATTACH_CREATE,
					&ugdb_utrace_ops, thread);
	if (IS_ERR(engine))
		goto free_thread;

	thread->t_engine = engine;

	if (ugdb_set_events(thread, 0))
		goto detach_thread;

	return thread;

detach_thread:
	ugdb_detach_thread(thread, false);
free_thread:
	ugdb_destroy_thread(thread);
err:
	rcu_read_lock();
	task = pid_task(spid, PIDTYPE_PID);
	if (task && task->exit_state)
		task = NULL;
	rcu_read_unlock();

	return task ? ERR_PTR(-ENOMEM) : NULL;
}

static inline bool is_subthread(struct ugdb_process *process,
				struct ugdb_thread *thread)
{
	return thread && thread->t_process == process;
}

static inline void ugdb_reset_tinfo(struct ugdb *ugdb)
{
	ugdb->u_cur_tinfo = NULL;
}

static void ugdb_destroy_process(struct ugdb_process *process)
{
	struct ugdb *ugdb = process->p_ugdb;
	struct ugdb_thread *thread;

	mutex_lock(&ugdb->u_mutex);
	process->p_state |= P_DETACHING;
	list_del(&process->p_processes);

	if (is_subthread(process, ugdb->u_cur_hg))
		ugdb->u_cur_hg = NULL;
	if (is_subthread(process, ugdb->u_cur_hc))
		ugdb->u_cur_hc = NULL;

	/* I hope gdb won't do detach from under qfThreadInfo */
	if (ugdb->u_cur_tinfo) {
		printk(KERN_WARNING "ugdb: detach from under qfThreadInfo\n");
		ugdb_reset_tinfo(ugdb);
	}
	mutex_unlock(&ugdb->u_mutex);

	while (!list_empty(&process->p_threads)) {
		thread = list_first_entry(&process->p_threads,
				struct ugdb_thread, t_threads);
		ugdb_detach_thread(thread, true);
		ugdb_destroy_thread(thread);
	}

	BUG_ON(!list_empty(&process->p_threads));

	kfree(process);
}

static void ugdb_destroy(struct ugdb *ugdb)
{
	struct ugdb_process *process;

	while (!list_empty(&ugdb->u_processes)) {
		process = list_first_entry(&ugdb->u_processes,
				struct ugdb_process, p_processes);
		ugdb_destroy_process(process);
	}

	BUG_ON(!list_empty(&ugdb->u_processes));
	BUG_ON(!list_empty(&ugdb->u_stopped));

	module_put(THIS_MODULE);
	kfree(ugdb);
}

static struct pid *get_next_pid(struct pid *main, struct pid *curr)
{
	struct task_struct *task;
	struct sighand_struct *sighand;
	struct pid *next = NULL;

	rcu_read_lock();
	/*
	 * If task/sighand is NULL we return NULL. This is fine if
	 * the caller is get_first_pid(), we should abort attaching.
	 *
	 * But this can also happen if curr was already attached,
	 * and this is wrong. Fortunately, this is very unlikely
	 * case. The attached sub-thread can't pass ->report_death,
	 * if it was reaped the caller of release_task() must be
	 * ptracer who re-parented this thread.
	 */
	task = pid_task(curr, PIDTYPE_PID);
	if (!task)
		goto unlock_rcu;

	// XXX: we need lock_task_sighand() but it is not exported,
	// so we ran race with de_thread().
	sighand = rcu_dereference(task->sighand);
	if (!sighand)
		goto unlock_rcu;

	spin_lock_irq(&sighand->siglock);
	for (;;) {
		task = next_thread(task);

		// XXX: if main is not leader we can race with exec.
		if (task_pid(task) == main)
			break;

		if (!task->exit_state) {
			next = get_pid(task_pid(task));
			break;
		}
	}
	spin_unlock_irq(&sighand->siglock);
unlock_rcu:
	rcu_read_unlock();

	return next;
}

static struct pid *get_first_pid(struct pid *main)
{
	struct task_struct *leader;

	rcu_read_lock();
	leader = pid_task(main, PIDTYPE_PID);
	if (leader && leader->exit_state)
		leader = NULL;
	rcu_read_unlock();

	/*
	 * The group-leader is alive, try to attach. If it exits
	 * before utrace_set_events(), get_first_pid() will be
	 * called again and it will notice ->exit_state != 0.
	 */
	if (leader)
		return get_pid(main);

	/*
	 * Try to find the live sub-thread. If the whole group
	 * is dead it returns NULL and the caller aborts.
	 */
	return get_next_pid(main, main);
}

static int ugdb_attach_all_threads(struct ugdb *ugdb,
				struct ugdb_process *process,
				struct pid *main_pid)
{
	struct ugdb_thread *thread;
	struct pid *curr_pid;

	mutex_lock(&ugdb->u_mutex);

	for (;;) {
		curr_pid = get_first_pid(main_pid);
		if (!curr_pid)
			goto abort;

		thread = ugdb_attach_thread(process, curr_pid);
		put_pid(curr_pid);

		if (IS_ERR(thread))
			goto abort;

		if (thread)
			break;
	}

	for (;;) {
		struct pid *next_pid;

		next_pid = get_next_pid(main_pid, curr_pid);
		if (!next_pid)
			break;

		thread = ugdb_attach_thread(process, next_pid);
		put_pid(next_pid);

		if (IS_ERR(thread))
			goto abort;

		if (!thread)
			continue;

		curr_pid = next_pid;
	}

	mutex_unlock(&ugdb->u_mutex);
	return 0;

abort:
	mutex_unlock(&ugdb->u_mutex);
	return -1;
}

static int ugdb_attach(struct ugdb *ugdb, int pid_nr)
{
	struct pid *main_pid;
	struct ugdb_process *process;
	int err;

	// XXX: check if exists
	// XXX: check if group leader ?

	err = -ESRCH;
	main_pid = find_get_pid(pid_nr);
	if (!main_pid)
		goto out;

	err = -ENOMEM;
	process = ugdb_create_process(ugdb, pid_nr);
	if (!process)
		goto free_pid;

	err = ugdb_attach_all_threads(ugdb, process, main_pid);
	if (err)
		ugdb_destroy_process(process);

free_pid:
	put_pid(main_pid);
out:
	return err;
}

static struct ugdb_process *ugdb_find_process(struct ugdb *ugdb, int pid)
{
	struct ugdb_process *process;

	list_for_each_entry(process, &ugdb->u_processes, p_processes) {
		if (process->p_pid == pid)
			return process;
	}

	return NULL;
}

static struct ugdb_thread *ugdb_find_thread(struct ugdb *ugdb, int pid, int tid)
{
	struct ugdb_process *process;
	struct ugdb_thread *thread;

	list_for_each_entry(process, &ugdb->u_processes, p_processes) {
		if (unlikely(!process_alive(process)))
			continue;
		if (pid && process->p_pid != pid)
			continue;

		list_for_each_entry(thread, &process->p_threads, t_threads) {
			if (WARN_ON(!thread_alive(thread)))
				continue;
			if (!tid || thread->t_tid == tid)
				return thread;
		}

		if (pid)
			break;
	}

	return NULL;
}

static int ugdb_detach(struct ugdb *ugdb, int pid)
{
	struct ugdb_process *process = ugdb_find_process(ugdb, pid);

	if (!process)
		return -1;

	ugdb_destroy_process(process);
	return 0;
}

#define CUR_TINFO_END	((struct ugdb_thread *)1)

static struct ugdb_thread *ugdb_advance_tinfo(struct ugdb *ugdb)
{
	struct ugdb_thread *cur, *nxt;
	struct ugdb_process *process;

	cur = ugdb->u_cur_tinfo;

	if (cur == CUR_TINFO_END) {
		ugdb->u_cur_tinfo = NULL;
		return NULL;
	}

	if (!cur) {
		list_for_each_entry(process, &ugdb->u_processes, p_processes) {
			if (unlikely(!process_alive(process)))
				continue;

			if (!list_empty(&process->p_threads)) {
				cur = list_first_entry(&process->p_threads,
						struct ugdb_thread, t_threads);
				break;
			}
		}

		if (!cur)
			return NULL;
	}

	process = cur->t_process;

	if (list_is_last(&cur->t_threads, &process->p_threads)) {
		nxt = CUR_TINFO_END;

		list_for_each_entry_continue(process, &ugdb->u_processes, p_processes) {
			if (unlikely(!process_alive(process)))
				continue;

			if (!list_empty(&process->p_threads)) {
				nxt = list_first_entry(&process->p_threads,
						struct ugdb_thread, t_threads);
				break;
			}
		}
	} else {
		nxt = list_first_entry(&cur->t_threads,
				struct ugdb_thread, t_threads);
	}

	ugdb->u_cur_tinfo = nxt;
	return cur;
}

// -----------------------------------------------------------------------------
static bool ugdb_add_stopped(struct ugdb_thread *thread, int stop_event)
{
	struct ugdb *ugdb = thread->t_ugdb;
	bool ret = false;

	ugdb_ck_stopped(ugdb);

	spin_lock(&ugdb->u_slock);
	if (stop_event == T_EV_NONE) {
		if (WARN_ON(thread->t_stop_state & T_STOP_ACK))
			goto unlock;
		if (WARN_ON(!list_empty(&thread->t_stopped)))
			goto unlock;

		/* raced with ugdb_cont_thread() */
		if (!(thread->t_stop_state & T_STOP_REQ))
			goto unlock;
	}

	if (thread->t_stop_state & T_STOP_ACK) {
		if (thread->t_stop_state & T_STOP_STOPPED)
			/*
			 * Alas, we can't report this event. We already
			 * reported T00 and there is no way to inform gdb
			 * the state of tracee was changed.
			 */
			goto unlock;
	} else {
		WARN_ON(thread->t_stop_state & T_STOP_STOPPED);

		thread->t_stop_state |= T_STOP_ACK;
		list_add_tail(&thread->t_stopped, &ugdb->u_stopped);

		if (ugdb->u_stop_state == U_STOP_IDLE) {
			ugdb->u_stop_state = U_STOP_PENDING;
			wake_up_all(&ugdb->u_wait);
		}
	}

	thread->t_stop_event = stop_event;
	ret = true;
unlock:
	spin_unlock(&ugdb->u_slock);

	return ret;
}

static void ugdb_process_exit(struct ugdb_thread *thread)
{
	struct ugdb *ugdb = thread->t_ugdb;
	int status;

	BUG_ON(!thread_alive(thread));

	ugdb_del_stopped(ugdb, thread);
	mark_thread_dead(thread);

	// XXX: OOPS, we can't read ->signal->group_exit_code !!!
	status = current->exit_code;
	if (ugdb_add_stopped(thread, T_EV_EXIT | status))
		return;

	WARN_ON(1);
}

static int ugdb_stop_thread(struct ugdb_thread *thread, bool all)
{
	struct ugdb *ugdb = thread->t_ugdb;
	int err;

	WARN_ON(!thread_alive(thread));

	ugdb_ck_stopped(ugdb);

	if (thread->t_stop_state != T_STOP_RUN) {
		if (!all || (thread->t_stop_state & T_STOP_ALL))
			return 0;
		/*
		 * Otherwise we should set T_STOP_ALL anyway,
		 *
		 *	(gdb) interrupt &
		 *	(gbd) interrupt -a &
		 *
		 * to ensure -a actually works if it races with clone.
		 */
	}

	err = -EALREADY;
	spin_lock(&ugdb->u_slock);
	if (thread->t_stop_state == T_STOP_RUN) {
		thread->t_stop_state = T_STOP_REQ;
		err = 0;
	}

	/*
	 * We hold ugdb->u_mutex, we can't race with ugdb_report_clone().
	 * ugdb->u_slock protects us against ugdb_add_stopped(). We can
	 * change ->t_stop_state even if we did not initiate this stop.
	 */
	if (all)
		thread->t_stop_state |= T_STOP_ALL;
	spin_unlock(&ugdb->u_slock);

	if (err)
		return 0;

	// XXX: we don't do UTRACE_STOP! this means we can't
	// stop TASK_STOPEED task. Need to discuss jctl issues.
	// if we do UTRACE_STOP we should call ugdb_add_stopped().

	ugdb_set_events(thread, UTRACE_EVENT(QUIESCE));
	err = ugdb_control(thread, UTRACE_INTERRUPT);
	if (err && err != -EINPROGRESS)
		return err;
	return 1;
}

static int ugdb_cont_thread(struct ugdb_thread *thread, bool all, bool step)
{
	struct ugdb *ugdb = thread->t_ugdb;
	int ret;

	WARN_ON(!thread_alive(thread));

	ugdb_ck_stopped(ugdb);

	// XXX: gdb shouldn't explicitly cont an unreported thread
	WARN_ON(!all && !(thread->t_stop_state & T_STOP_STOPPED));

	if (thread->t_stop_state == T_STOP_RUN)
		return 0;

	spin_lock(&ugdb->u_slock);
	/*
	 * Nothing to do except clear the pending T_STOP_REQ.
	 */
	ret = 0;
	if (!(thread->t_stop_state & T_STOP_ACK))
		goto set_run;

	/*
	 * Alas. Thanks to remote protocol, we can't cont this
	 * thread. We probably already sent the notification, we
	 * can do nothing except ack that %Stop later in response
	 * to vStopped.
	 *
	 * OTOH, gdb shouldn't send 'c' if this thread was not
	 * reported as stopped. However, this means that gdb can
	 * see the new %Stop:T00 notification after vCont;c:pX.-1,
	 * it should handle this case correctly anyway. I hope.
	 *
	 * If this stop was not initiated by gdb we should not
	 * cancel it too, this event should be reported first.
	 */
	ret = -1;
	if (!(thread->t_stop_state & T_STOP_STOPPED))
		goto unlock;

	ret = 1;
	list_del_init(&thread->t_stopped);
set_run:
	thread->t_stop_state = T_STOP_RUN;
unlock:
	spin_unlock(&ugdb->u_slock);

	if (ret >= 0) {
		unsigned long events = 0;
		enum utrace_resume_action action = UTRACE_RESUME;

		thread->t_step = step;
		if (step) {
			// events |= UTRACE_EVENT(SYSCALL_EXIT)
			action = UTRACE_SINGLESTEP;
		}

		// XXX: OK, this all is racy, and I do not see any
		// solution except: implement UTRACE_STOP_STICKY and
		// move this code up under the lock, or add
		// utrace_engine_ops->notify_stopped().

		// 1. UTRACE_RESUME is racy, this is fixeable.
		// 2. we need utrace_barrier() to close the race
		//    with the callback which is going to return
		//    UTRACE_STOP, but:
		//    	a) we can deadlock (solveable)
		//	b) in this case UTRACE_RESUME can race with
		//	   another stop initiated by tracee itself.

		ugdb_set_events(thread, events);
		ugdb_control(thread, action);
	}

	return ret;
}

static struct ugdb_thread *ugdb_next_stopped(struct ugdb *ugdb)
{
	struct ugdb_thread *thread = NULL;

	// XXX: temporary racy check
	WARN_ON(ugdb->u_stop_state == U_STOP_IDLE);

	spin_lock(&ugdb->u_slock);
	if (list_empty(&ugdb->u_stopped)) {
		ugdb->u_stop_state = U_STOP_IDLE;
	} else {
		ugdb->u_stop_state = U_STOP_SENT;

		thread = list_first_entry(&ugdb->u_stopped,
					struct ugdb_thread, t_stopped);

		thread->t_stop_state |= T_STOP_STOPPED;
		list_del_init(&thread->t_stopped);
	}
	spin_unlock(&ugdb->u_slock);

	return thread;
}

// -----------------------------------------------------------------------------
static bool ugdb_stop_pending(struct ugdb_thread *thread)
{
	if (!(thread->t_stop_state & T_STOP_REQ))
		return false;

	if (!(thread->t_stop_state & T_STOP_ACK))
		return ugdb_add_stopped(thread, T_EV_NONE);

	return true;
}

static u32 ugdb_report_quiesce(u32 action, struct utrace_engine *engine,
					unsigned long event)
{
	struct ugdb_thread *thread = engine->data;

	WARN_ON(!thread_alive(thread));

	/* ensure SIGKILL can't race with stop/cont in progress */
	if (event != UTRACE_EVENT(DEATH)) {
		if (ugdb_stop_pending(thread))
			return UTRACE_STOP;
	}

	return utrace_resume_action(action);
}

static int cont_signal(struct ugdb_thread *thread,
				struct k_sigaction *return_ka)
{
	int signr = T_EV_DATA(thread->t_stop_event);
	siginfo_t *info = thread->t_siginfo;

	thread->t_siginfo = NULL;

	if (WARN_ON(!valid_signal(signr)))
		return 0;

	if (!signr)
		return signr;
	/*
	 * Update the siginfo structure if the signal has changed.
	 */
	if (info->si_signo != signr) {
		info->si_signo = signr;
		info->si_errno = 0;
		info->si_code = SI_USER;
		info->si_pid = 0;
		info->si_uid = 0;
	}

	/* If the (new) signal is now blocked, requeue it. */
	if (sigismember(&current->blocked, signr)) {
		send_sig_info(signr, info, current);
		signr = 0;
	} else {
		spin_lock_irq(&current->sighand->siglock);
		*return_ka = current->sighand->action[signr - 1];
		spin_unlock_irq(&current->sighand->siglock);
	}

	return signr;
}

static u32 ugdb_report_signal(u32 action, struct utrace_engine *engine,
				struct pt_regs *regs,
				siginfo_t *info,
				const struct k_sigaction *orig_ka,
				struct k_sigaction *return_ka)
{
	struct ugdb_thread *thread = engine->data;
	struct ugdb *ugdb = thread->t_ugdb;
	int signr;

	WARN_ON(!thread_alive(thread));

	switch (utrace_signal_action(action)) {
	case UTRACE_SIGNAL_HANDLER:
		if (WARN_ON(thread->t_siginfo))
			thread->t_siginfo = NULL;

		if (thread->t_step) {
			// user_single_step_siginfo(current, regs, info);
			memset(info, 0, sizeof(*info));
			info->si_signo = SIGTRAP;
			break;
		}

		/* Fall through */

	default:
		if (orig_ka)
			break;

		/*
		 * It was UTRACE_SIGNAL_REPORT, but another tracer has
		 * changed utrace_report->result to deliver or stop.
		 * Fall through.
		 */

	case UTRACE_SIGNAL_REPORT:
		if (thread->t_siginfo) {
			if (WARN_ON(thread->t_siginfo != info))
				return action;
			WARN_ON(T_EV_TYPE(thread->t_stop_event) != T_EV_SIGN);

			signr = cont_signal(thread, return_ka);
			if (signr) {
				/*
				 * Consider:
				 *
				 *	(gdb) signal SIG &
				 *	(gdb) interrupt
				 *
				 * We shouldn't miss the new stop request, so
				 * we do not return from here.
				 */
				action = UTRACE_RESUME | UTRACE_SIGNAL_DELIVER;
			}
		}

		if (ugdb_stop_pending(thread))
			return UTRACE_STOP | utrace_signal_action(action);

		if (thread->t_step)
			return UTRACE_SINGLESTEP | utrace_signal_action(action);
		return action;
	}

	WARN_ON(thread->t_siginfo);

	signr = info->si_signo;
	if (WARN_ON(!signr || !valid_signal(signr)))
		return action;

	if (sigismember(&ugdb->u_sig_ign, signr))
		return action;

	if (ugdb_add_stopped(thread, T_EV_SIGN | signr)) {
		thread->t_siginfo = info;
		/*
		 * Make sure the subsequent UTRACE_SIGNAL_REPORT clears
		 * ->t_siginfo before return from get_signal_to_deliver().
		 */
		if (utrace_control(current, engine, UTRACE_INTERRUPT))
			WARN_ON(1);
		return UTRACE_STOP | UTRACE_SIGNAL_IGN;
	}

	/*
	 * We already reported T00 to gdb. We can't change our state,
	 * we are already stopped from gdb pov. Push back this signal
	 * to report it later, after "continue".
	 *
	 * Not multitrace-friendly.
	 */
	return UTRACE_STOP | UTRACE_SIGNAL_REPORT | UTRACE_SIGNAL_HOLD;
}

static bool is_already_attached(struct ugdb_process *process,
				struct task_struct *task)
{
	struct ugdb_thread *thread;

	if (likely(!task_utrace_flags(task)))
		return false;

	/*
	 * Currently there is no way to know if it was attached by us.
	 * We can't trust utrace_attach_task(UTRACE_ATTACH_MATCH_OPS),
	 * ugdb attaches without UTRACE_ATTACH_EXCLUSIVE. We have to
	 * check every attached thread.
	 *
	 * This is really bad, but without multitracing this can only
	 * happen in unlikely case right after ugdb_attach_all_threads().
	 */
	list_for_each_entry(thread, &process->p_threads, t_threads) {
		if (thread->t_spid == task_pid(task))
			return true;
	}

	return false;
}

static u32 ugdb_report_clone(u32 action, struct utrace_engine *engine,
			       unsigned long clone_flags,
			       struct task_struct *task)
{
	struct ugdb_thread *thread = engine->data;
	struct ugdb_process *process = thread->t_process;
	struct ugdb *ugdb = thread->t_ugdb;
	struct ugdb_thread *new_thread;

	WARN_ON(!thread_alive(thread));

	if (!(clone_flags & CLONE_THREAD))
		goto out;

	mutex_lock(&ugdb->u_mutex);
	if (process->p_state & P_DETACHING)
		goto unlock;
	/*
	 * This can only happen if we raced with ugdb_attach() which
	 * could attach both current and the new PF_STARTING child.
	 */
	if (unlikely(is_already_attached(process, task)))
		goto unlock;

	new_thread = ugdb_attach_thread(process, task_pid(task));
	BUG_ON(!new_thread);

	if (WARN_ON(IS_ERR(new_thread)))
		goto unlock;

	if (thread->t_stop_state & T_STOP_ALL)
		ugdb_stop_thread(new_thread, false);

unlock:
	mutex_unlock(&ugdb->u_mutex);
out:
	return utrace_resume_action(action);
}

static u32 ugdb_report_death(struct utrace_engine *engine,
				bool group_dead, int signal)
{
	struct ugdb_thread *thread = engine->data;
	struct ugdb_process *process = thread->t_process;
	struct ugdb *ugdb = thread->t_ugdb;

	WARN_ON(!thread_alive(thread));

	mutex_lock(&ugdb->u_mutex);
	if (process->p_state & P_DETACHING)
		goto unlock;

	if (ugdb->u_cur_hg == thread)
		ugdb->u_cur_hg = NULL;
	if (ugdb->u_cur_hc == thread)
		ugdb->u_cur_hc = NULL;

	if (ugdb->u_cur_tinfo == thread)
		ugdb_advance_tinfo(ugdb);

	if (list_is_singular(&process->p_threads))
		ugdb_process_exit(thread);
	else
		ugdb_destroy_thread(thread);

unlock:
	mutex_unlock(&ugdb->u_mutex);

	return UTRACE_DETACH;
}

static const struct utrace_engine_ops ugdb_utrace_ops = {
	.report_quiesce	= ugdb_report_quiesce,
	.report_signal	= ugdb_report_signal,
	.report_clone	= ugdb_report_clone,
	.report_death	= ugdb_report_death,
};

// -----------------------------------------------------------------------------
static inline int pb_size(struct pbuf *pb)
{
	return pb->cur - pb->buf;
}

static inline int pb_room(struct pbuf *pb)
{
	return pb->buf + BUFFER_SIZE - pb->cur;
}

static inline void pb_putc(struct pbuf *pb, char c)
{
	if (WARN_ON(pb->cur >= pb->buf + BUFFER_SIZE-1))
		return;
	*pb->cur++ = c;
}

static void pb_memcpy(struct pbuf *pb, const void *data, int size)
{
	if (WARN_ON(size > pb_room(pb)))
		return;
	memcpy(pb->cur, data, size);
	pb->cur += size;
}

static inline void pb_puts(struct pbuf *pb, const char *s)
{
	pb_memcpy(pb, s, strlen(s));
}

static inline void pb_putb(struct pbuf *pb, unsigned char val)
{
	static char hex[] = "0123456789abcdef";
	pb_putc(pb, hex[(val & 0xf0) >> 4]);
	pb_putc(pb, hex[(val & 0x0f) >> 0]);
}

static void pb_putbs(struct pbuf *pb, const char *data, int size)
{
	while (size--)
		pb_putb(pb, *data++);
}

static inline void __pb_start(struct pbuf *pb, char pref)
{
	WARN_ON(pb->pkt);
	pb_putc(pb, pref);
	pb->pkt = pb->cur;
}

static inline void pb_start(struct pbuf *pb)
{
	return __pb_start(pb, '$');
}

static inline void pb_cancel(struct pbuf *pb)
{
	if (WARN_ON(!pb->pkt))
		return;

	pb->cur = pb->pkt - 1;
	pb->pkt = NULL;
}

static void pb_end(struct pbuf *pb)
{
	unsigned char csm = 0;
	char *pkt = pb->pkt;

	pb->pkt = NULL;
	if (WARN_ON(!pkt))
		return;

	while (pkt < pb->cur) {
		/* pb_qfer() can write '%' */
		WARN_ON(*pkt == '$' || *pkt == '#');
		csm += (unsigned char)*pkt++;
	}

	pb_putc(pb, '#');
	pb_putb(pb, csm);
}

static inline void pb_packs(struct pbuf *pb, const char *s)
{
	pb_start(pb);
	pb_puts(pb, s);
	pb_end(pb);
}

static void __attribute__ ((format(printf, 3, 4)))
__pb_format(struct pbuf *pb, bool whole_pkt, const char *fmt, ...)
{
	int room = pb_room(pb), size;
	va_list args;

	if (whole_pkt)
		pb_start(pb);

	va_start(args, fmt);
	size = vsnprintf(pb->cur, room, fmt, args);
	va_end(args);

	if (WARN_ON(size > room))
		return;

	pb->cur += size;

	if (whole_pkt)
		pb_end(pb);
}

#define pb_printf(pb, args...)	__pb_format((pb), false, args)
#define pb_packf(pb, args...)	__pb_format((pb), true,  args)

static int pb_qfer(struct pbuf *pb, const void *_data, int len, bool more)
{
	const unsigned char *data = _data;
	int i;

	if (pb_room(pb) < 3 + len * 2) {
		WARN_ON(1);
		return -EOVERFLOW;
	}

	pb_start(pb);
	pb_putc(pb, more ? 'm' : 'l');

	for (i = 0; i < len; ++i) {
		unsigned char c = data[i];

		if (c == '$' || c == '#' || c == '}' || c == '*') {
			pb_putc(pb, '}');
			c ^= 0x20;
		}

		pb_putc(pb, c);
	}

	pb_end(pb);

	return 0;
}

static inline void *pb_alloc_bs(struct pbuf *pb, int size)
{
	if (unlikely(pb_room(pb) < 2 * size + 4))
		return NULL;
	return pb->cur + size + 1;
}

static inline void *pb_alloc_tmp(struct pbuf *pb, int size)
{
	if (unlikely(pb_room(pb) < size))
		return NULL;
	return pb->cur + BUFFER_SIZE - size;
}

static inline void pb_flush(struct pbuf *pb, int size)
{
	int keep = pb_size(pb) - size;
	if (keep)
		memmove(pb->buf, pb->buf + size, keep);
	pb->cur -= size;
}

static int pb_copy_to_user(struct pbuf *pb, char __user *ubuf, int size)
{
	int copy = min(size, pb_size(pb));

	if (!copy)
		return -EAGAIN;

	if (o_remote_debug)
		printk(KERN_INFO "<= %.*s\n", min(copy, 64), pb->buf);

	if (copy_to_user(ubuf, pb->buf, copy))
		return -EFAULT;

	pb_flush(pb, copy);
	return copy;
}

// -----------------------------------------------------------------------------
// XXX: include/gdb/signals.h:target_signal
// incomplete: 7, 29, rt?
static int to_gdb_sigmap[] = {
	[SIGHUP]	= 1,
	[SIGINT]	= 2,
	[SIGQUIT]	= 3,
	[SIGILL]	= 4,
	[SIGTRAP]	= 5,
	[SIGABRT]	= 6,
	[SIGIOT]	= 0,	/* ??? */
	[SIGBUS]	= 10,
	[SIGFPE]	= 8,
	[SIGKILL]	= 9,
	[SIGUSR1]	= 30,
	[SIGSEGV]	= 11,
	[SIGUSR2]	= 31,
	[SIGPIPE]	= 13,
	[SIGALRM]	= 14,
	[SIGTERM]	= 15,
	[SIGSTKFLT]	= 0,	/* ??? */
	[SIGCHLD]	= 20,
	[SIGCONT]	= 19,
	[SIGSTOP]	= 17,
	[SIGTSTP]	= 18,
	[SIGTTIN]	= 21,
	[SIGTTOU]	= 22,
	[SIGURG]	= 16,
	[SIGXCPU]	= 24,
	[SIGXFSZ]	= 25,
	[SIGVTALRM]	= 26,
	[SIGPROF]	= 27,
	[SIGWINCH]	= 28,
	[SIGIO]		= 23,
	[SIGPWR]	= 32,
	[SIGSYS]	= 12,
};

static int sig_to_gdb(unsigned sig)
{
	if (sig < ARRAY_SIZE(to_gdb_sigmap) && to_gdb_sigmap[sig])
		return to_gdb_sigmap[sig];
	return sig;

}

static int sig_from_gdb(unsigned sig)
{
	int i;

	// XXX: valid_signal() is wrong, gdb has its own idea
	// about signals. fix to_gdb_sigmap[].
	if (!sig || !valid_signal(sig))
		return 0;

	for (i = 0; i < ARRAY_SIZE(to_gdb_sigmap); i++) {
		if (to_gdb_sigmap[i] == sig)
			return i;
	}

	return sig;
}

static int ugdb_report_stopped(struct ugdb *ugdb, bool async)
{
	struct ugdb_thread *thread;
	int pid, tid, event, data;
	struct pbuf *pb;
	char ex_r;

	mutex_lock(&ugdb->u_mutex);
	thread = ugdb_next_stopped(ugdb);
	if (!thread)
		goto unlock;

	event = thread->t_stop_event;

	WARN_ON(thread_alive(thread) != (T_EV_TYPE(event) != T_EV_EXIT));

	pid = thread->t_process->p_pid;
	tid = thread->t_tid;
unlock:
	mutex_unlock(&ugdb->u_mutex);

	if (!thread)
		return false;

	pb = &ugdb->u_pbuf;

	// XXX: damn, cleanup me...
	if (async) {
		__pb_start(pb, '%');
		pb_puts(pb, "Stop:");
	} else {
		pb_start(pb);
	}

	data = T_EV_DATA(event);
	switch (T_EV_TYPE(event)) {
	case T_EV_EXIT:
		if (data & 0xff) {
			data = sig_to_gdb(data & 0xff);
			ex_r = 'X';
		} else {
			data >>= 8;
			ex_r = 'W';
		}

		pb_printf(pb, "%c%x;process:%x", ex_r, data, pid);
		ugdb_destroy_process(thread->t_process);
		break;

	case T_EV_SIGN:
	case T_EV_NONE:
		pb_printf(pb, "T%02xthread:p%x.%x;",
					sig_to_gdb(data), pid, tid);
		break;

	default:
		printk(KERN_INFO "ugdb: bad stop event %x\n", event);
	}

	pb_end(pb);

	return true;
}

const char *handle_vstopped(struct ugdb *ugdb)
{
	if (ugdb->u_stop_state != U_STOP_SENT)
		return "E01";

	if (ugdb_report_stopped(ugdb, false))
		return NULL;

	return "OK";
}

static const char *handle_thread_info(struct ugdb *ugdb, bool start)
{
	struct ugdb_thread *thread;
	int pid = 0, tid;

	mutex_lock(&ugdb->u_mutex);
	if (start)
		ugdb_reset_tinfo(ugdb);
	else if (!ugdb->u_cur_tinfo)
		printk(KERN_INFO "ugdb: unexpected qsThreadInfo\n");

	thread = ugdb_advance_tinfo(ugdb);
	if (thread) {
		pid = thread->t_process->p_pid;
		tid = thread->t_tid;
	}
	mutex_unlock(&ugdb->u_mutex);

	if (!pid)
		return start ? "E01" : "l";

	pb_packf(&ugdb->u_pbuf, "mp%x.%x", pid, tid);
	return NULL;
}

static char *parse_xid(char *str, int *ppid, bool multi)
{
	if (*str == '-') {
		str++;

		if (multi && *str++ == '1')
			*ppid = -1;
		else
			str = NULL;
	} else {
		char *cur = str;

		*ppid = simple_strtoul(cur, &str, 16);
		if (str == cur)
			str = NULL;
	}

	return str;
}

static char *parse_pid_tid(char *str, int *ppid, int *ptid, bool multi)
{
	if (*str++ != 'p')
		return NULL;

	str = parse_xid(str, ppid, multi);
	if (!str)
		return NULL;

	if (*str++ != '.')
		return NULL;

	str = parse_xid(str, ptid, multi);
	if (!str)
		return NULL;

	return str;
}

static const char *handle_set_cur(struct ugdb *ugdb, char *cmd)
{

	struct ugdb_thread **pthread;
	int pid, tid;

	switch (*cmd++) {
	case 'g':
		pthread = &ugdb->u_cur_hg;
		break;

	case 'c':
		pthread = &ugdb->u_cur_hc;
		break;

	default:
		goto err;
	}

	if (!parse_pid_tid(cmd, &pid, &tid, false))
		goto err;

	mutex_lock(&ugdb->u_mutex);
	*pthread = ugdb_find_thread(ugdb, pid, tid);
	mutex_unlock(&ugdb->u_mutex);

	if (*pthread)
		return "OK";

err:
	return "E01";
}

static const char *handle_ck_alive(struct ugdb *ugdb, char *cmd)
{
	struct ugdb_thread *thread;
	int pid = 0, tid;

	if (!parse_pid_tid(cmd, &pid, &tid, false))
		goto err;

	mutex_lock(&ugdb->u_mutex);
	thread = ugdb_find_thread(ugdb, pid, tid);
	mutex_unlock(&ugdb->u_mutex);

	if (thread)
		return "OK";

err:
	return "E01";
}

static int parse_pid(char *str)
{
	int pid;

	if (!parse_xid(str, &pid, false))
		return 0;

	return pid;
}

static const char *handle_vattach(struct ugdb *ugdb, char *cmd)
{
	int pid = parse_pid(cmd);

	if (pid && !ugdb_attach(ugdb, pid))
		return "OK";

	return "E01";
}

static const char *handle_detach(struct ugdb *ugdb, char *cmd)
{
	int pid;

	if (*cmd++ != ';')
		goto err;

	pid = parse_pid(cmd);
	if (pid && !ugdb_detach(ugdb, pid))
		return "OK";

err:
	return "E01";
}

typedef int (*each_func_t)(struct ugdb_thread *, void *);

static int ugdb_do_each_thread(struct ugdb *ugdb, int pid, int tid,
				each_func_t func, void *arg)
{
	struct ugdb_process *process;
	struct ugdb_thread *thread;
	int ret = -ESRCH;

	list_for_each_entry(process, &ugdb->u_processes, p_processes) {
		if (unlikely(!process_alive(process)))
			continue;
		if (pid > 0 && process->p_pid != pid)
			continue;

		list_for_each_entry(thread, &process->p_threads, t_threads) {
			if (WARN_ON(!thread_alive(thread)))
				continue;
			if (tid > 0 && thread->t_tid != tid)
				continue;

			ret = func(thread, arg);
			if (ret)
				goto out;

			if (tid >= 0)
				break;
		}

		if (pid >= 0)
			break;
	}

out:
	return ret;
}

static int do_stop_thread(struct ugdb_thread *thread, void *arg)
{
	ugdb_stop_thread(thread, arg != NULL);
	return 0;
}

static int do_cont_thread(struct ugdb_thread *thread, void *arg)
{
	ugdb_cont_thread(thread, arg != NULL, false);
	return 0;
}

static const char *handle_vcont(struct ugdb *ugdb, char *cmd)
{
	int pid, tid;
	void *arg;
	int ret;

	switch (*cmd ++) {
	default:
		return "E01";
	case '?':
		return "vCont;t";
	case ';':
		break;
	}

	// XXX: Discuss the generic case! currently trivial.

	if (*cmd++ != 't')
		return "E01";

	pid = tid = -1;
	if (*cmd++ == ':') {
		if (!parse_pid_tid(cmd, &pid, &tid, true))
			return "E01";
	}
	arg = (tid >= 0) ? NULL : (void*)1;

	mutex_lock(&ugdb->u_mutex);
	// XXX: currently we only report -ESRCH
	ret = ugdb_do_each_thread(ugdb, pid, tid, do_stop_thread, arg);
	mutex_unlock(&ugdb->u_mutex);

	return ret < 0 ? "E01" : "OK";
}

static int thread_cont_signal(struct ugdb_thread *thread, int signr)
{
	/*
	 * T_STOP_STOPPED was set under ->u_slock so we can't race
	 * with ugdb_add_stopped() and get the wrong t_stop_event.
	 * And, the tracee never changes it after T_STOP_STOPPED.
	 */

	switch (T_EV_TYPE(thread->t_stop_event)) {
	case T_EV_SIGN:
		WARN_ON(!T_EV_DATA(thread->t_stop_event));
		thread->t_stop_event = T_EV_SIGN | signr;
		break;

	default:
		if (!signr)
			break;

		// XXX: temporary hack, will be reported.
		// but perhaps this is what we want ???
		kill_pid(thread->t_spid, signr, 0);
		break;
	}

	return 0;
}

static const char *handle_c(struct ugdb *ugdb, char *cmd)
{
	struct ugdb_thread *thread;
	const char *rc = "E01";
	int gdbsig, signr = 0;
	bool step;

	step = (*cmd == 'S' || *cmd == 's');

	switch (*cmd++) {
	case 'C':
	case 'S':
		gdbsig = simple_strtoul(cmd, &cmd, 16);
		if (!gdbsig)
			return rc;

		signr = sig_from_gdb(gdbsig);
		if (!signr)
			printk(KERN_INFO "ugdb: sorry, can't map signal %d\n",
					gdbsig);

		if (*cmd == ';')
			++cmd;
		/* fall */

	case 'c':
	case 's':
		if (!*cmd)
			break;

		printk(KERN_INFO "ugdb: $c ADDR not implemented\n");
		return rc;

		break;
	}

	mutex_lock(&ugdb->u_mutex);
	thread = ugdb->u_cur_hc;
	if (!thread)
		goto unlock;

	/*
	 * Otherwise I do not know what to do if sig/step, and anyway
	 * I don't think gdb can try to cont a thread which was not
	 * reported as stopped.
	 */
	if (!(thread->t_stop_state & T_STOP_STOPPED))
		goto unlock;

	if (thread_cont_signal(thread, signr))
		goto unlock;

	if (ugdb_cont_thread(thread, false, step) <= 0)
		goto unlock;

	rc = "OK";
unlock:
	mutex_unlock(&ugdb->u_mutex);

	return rc;
}

static const char *handle_qpass_signals(struct ugdb *ugdb, char *cmd)
{
	sigset_t *set = &ugdb->u_sig_ign;

	sigemptyset(set);
	while (*cmd) {
		char *end;
		int sig = simple_strtoul(cmd, &end, 16);

		if (cmd == end || *end != ';')
			return "E01";
		cmd = end + 1;

		sig = sig_from_gdb(sig);
		if (!sig)
			// XXX: to_gdb_sigmap[] incomplete...
			// return "E01";
			continue;

		sigaddset(set, sig);
	}

	return "OK";
}

// -----------------------------------------------------------------------------
static struct task_struct *
ugdb_prepare_examine(struct ugdb *ugdb, struct utrace_examiner *exam)
{
	struct ugdb_thread *thread;
	struct task_struct *task;
	int err;

	mutex_lock(&ugdb->u_mutex);
	thread = ugdb->u_cur_hg;
	if (!thread || !(thread->t_stop_state & T_STOP_STOPPED))
		goto err;

	// XXX: u_cur_hg can't exit, we hold the mutex
	task = thread_to_task(thread);
	if (!task)
		goto err;

	for (;;) {
		if (fatal_signal_pending(current))
			goto err;

		err = utrace_prepare_examine(task, thread->t_engine, exam);
		if (!err)
			break;

		if (err == -ESRCH)
			goto err;

		schedule_timeout_interruptible(1);
	}

	return task;
err:
	mutex_unlock(&ugdb->u_mutex);
	return NULL;
}

// XXX: we hold the mutex in between, but only because we can't
// use get_task_struct/put_task_struct.

static int
ugdb_finish_examine(struct ugdb *ugdb, struct utrace_examiner *exam)
{
	// XXX: u_cur_hg can't exit, we hold the mutex
	struct ugdb_thread *thread = ugdb->u_cur_hg;
	struct task_struct *task = thread_to_task(thread);
	int ret = -ESRCH;

	if (task)
		ret = utrace_finish_examine(task, thread->t_engine, exam);

	mutex_unlock(&ugdb->u_mutex);
	return ret;
}

#define REGSET_GENERAL	0
// stolen from gdb-7.1/gdb/gdbserver/linux-x86-low.c
static int x86_64_regmap[] = {
	80, 40, 88, 96, 104, 112, 32, 152, 72, 64, 56, 48, 24, 16,
	8, 0, 128, 144, 136, 160, 184, 192, 200, 208, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 120,
};

static char *handle_getregs(struct ugdb *ugdb)
{
	struct utrace_examiner exam;
	struct task_struct *task;
	const struct user_regset_view *view;
	const struct user_regset *rset;
	struct user_regs_struct *pregs;
	int rn;

	static int pkt_size;
	if (!pkt_size) {
		int sz = 0;
		for (rn = 0; rn < ARRAY_SIZE(x86_64_regmap); ++rn) {
			int offs = x86_64_regmap[rn];
			if (offs < 0)
				continue;
			if (offs > (sizeof(*pregs) - sizeof(long))) {
				printk(KERN_INFO "XXX: x86_64_regmap is wrong!\n");
				ugdb->u_err = -EINVAL;
				goto err;
			}
			sz += sizeof(long) * 2;
		}
		pkt_size = sz;
	}

	if (pb_room(&ugdb->u_pbuf) < 4 + pkt_size + sizeof(*pregs)) {
		printk(KERN_INFO "XXX: getregs ENOMEM %d %ld\n",
					pkt_size, sizeof(*pregs));
		goto err;
	}

	pregs = pb_alloc_tmp(&ugdb->u_pbuf, sizeof(*pregs));
	BUG_ON(pregs + 1 != (void*)ugdb->u_pbuf.cur + BUFFER_SIZE);

	task = ugdb_prepare_examine(ugdb, &exam);
	if (!task)
		goto err;

	view = task_user_regset_view(task);
	rset = view->regsets + REGSET_GENERAL;

	rset->get(task, rset, 0, sizeof(*pregs), pregs, NULL);

	if (ugdb_finish_examine(ugdb, &exam))
		goto err;

	pb_start(&ugdb->u_pbuf);
	for (rn = 0; rn < ARRAY_SIZE(x86_64_regmap); ++rn) {
		int offs = x86_64_regmap[rn];
		if (offs >= 0)
			pb_putbs(&ugdb->u_pbuf, (void*)pregs + offs,
					sizeof(long));
	}

	WARN_ON(pb_room(&ugdb->u_pbuf) < sizeof(*pregs));
	pb_end(&ugdb->u_pbuf);
	return NULL;
err:
	return "E01";
}

static const char *handle_setregs(struct ugdb *ugdb, char *cmd)
{
	printk(KERN_INFO "ugdb: unexpected $G packet.\n");
	return "";
}

static const char *handle_set_one_reg(struct ugdb *ugdb, char *cmd)
{
	unsigned int reg;
	long val;
	struct utrace_examiner exam;
	struct task_struct *task;

	if (sscanf(cmd, "%x=%lx", &reg, &val) != 2)
		goto err;

	if (reg != 0x10 && reg != 0x39) {
		printk(KERN_INFO "ugdb: unknown reg %x\n", reg);
		goto err;
	}

	// XXX: Hmm.
	val = be64_to_cpu(val);

	// DO NOT LOOK AT THIS TEMPORARY CODE
	task = ugdb_prepare_examine(ugdb, &exam);
	if (!task)
		goto err;

	if (reg == 0x10)
		task_pt_regs(task)->ip = val;
	else if (reg == 0x39)
		task_pt_regs(task)->orig_ax = val;

	ugdb_finish_examine(ugdb, &exam);

	return "OK";
err:
	return "E01";
}

static typeof(access_process_vm) *u_access_process_vm;

static const char *handle_readmem(struct ugdb *ugdb, char *cmd)
{
	struct utrace_examiner exam;
	struct task_struct *task;
	unsigned long addr, size;
	unsigned char *mbuf;
	int copied;

	if (sscanf(cmd, "m%lx,%lx", &addr, &size) != 2)
		goto err;

	mbuf = pb_alloc_bs(&ugdb->u_pbuf, size);
	if (!mbuf) {
		printk(KERN_INFO "XXX: apvm(%ld) ENOMEM\n", size);
		goto err;
	}

	task = ugdb_prepare_examine(ugdb, &exam);
	if (!task)
		goto err;

	copied = u_access_process_vm(task, addr, mbuf, size, 0);

	if (ugdb_finish_examine(ugdb, &exam))
		goto err;

	if (copied > 0 ) {
		pb_start(&ugdb->u_pbuf);
		pb_putbs(&ugdb->u_pbuf, mbuf, size);
		pb_end(&ugdb->u_pbuf);

		return NULL;
	}
err:
	return "E01";
}

// XXX: hex_to_bin() after 90378889 commit
#include <linux/ctype.h>
static int cap_hex_to_bin(char ch)
{
	if ((ch >= '0') && (ch <= '9'))
		return ch - '0';
	ch = tolower(ch);
	if ((ch >= 'a') && (ch <= 'f'))
		return ch - 'a' + 10;
	return -1;
}

static int unhex(char *cmd, int size)
{
	char *bytes = cmd;

	while (size--) {
		int lo, hi;

		hi = cap_hex_to_bin(*cmd++);
		lo = cap_hex_to_bin(*cmd++);
		if (lo < 0 || hi < 0)
			return -EINVAL;

		*bytes++ = (hi << 4) | lo;
	}

	return 0;
}

static const char *handle_writemem(struct ugdb *ugdb, char *cmd, int len)
{
	unsigned long addr, size;
	unsigned int skip, written;
	struct utrace_examiner exam;
	struct task_struct *task;

	if (sscanf(cmd, "M%lx,%lx:%n", &addr, &size, &skip) != 2)
		goto err;

	cmd += skip;
	len -= skip;
	if (len != 2*size || !size)
		goto err;

	if (unhex(cmd, size))
		goto err;

	task = ugdb_prepare_examine(ugdb, &exam);
	if (!task)
		goto err;

	written = u_access_process_vm(task, addr, cmd, size, 1);

	if (ugdb_finish_examine(ugdb, &exam))
		goto err;

	if (written == size)
		return "OK";
err:
	return "E01";
}

static int ugdb_siginfo_rw(struct ugdb *ugdb, siginfo_t *info, bool write)
{
	struct task_struct *task;
	struct utrace_examiner exam;
	struct sighand_struct *sighand;
	siginfo_t *t_siginfo;
	int ret = -EINVAL;

	/*
	 * ugdb_prepare_examine() is overkill, but otherwise we can't
	 * assume task_is_traced(), and this is what ensures we can
	 * safely read/write ->t_siginfo which points to task's stack.
	 */
	task = ugdb_prepare_examine(ugdb, &exam);
	if (!task)
		goto out;

	/* OK, task_struct can't go away, but ->sighand can. */
	rcu_read_lock();
	sighand = rcu_dereference(task->sighand);
	if (!sighand)
		goto unlock_rcu;

	spin_lock_irq(&sighand->siglock);
	if (!task_is_traced(task))
		goto unlock_siglock;

	t_siginfo = ugdb->u_cur_hg->t_siginfo;
	if (!t_siginfo)
		goto unlock_siglock;

	if (write)
		*t_siginfo = *info;
	else
		*info = *t_siginfo;

	ret = 0;
unlock_siglock:
	spin_unlock_irq(&sighand->siglock);
unlock_rcu:
	rcu_read_unlock();

	ugdb_finish_examine(ugdb, &exam);
out:
	return ret;
}

static const char *handle_siginfo_read(struct ugdb *ugdb, char *cmd)
{
	unsigned int off, len;
	siginfo_t info;

	if (sscanf(cmd, "%x,%x", &off, &len) != 2)
		goto err;

	if (off >= sizeof(info))
		goto err;

	if (len > sizeof(info) || off + len > sizeof(info))
		len = sizeof(info) - off;

	if (ugdb_siginfo_rw(ugdb, &info, false))
		goto err;

	if (pb_qfer(&ugdb->u_pbuf, &info + off, len,
				(off + len < sizeof(info))))
		goto err;

	// XXX: Oh. we also need x86_siginfo_fixup(). how ugly.

	return NULL;
err:
	return "E01";
}

// -----------------------------------------------------------------------------
#define EQ(cmd, str)					\
	(strncmp((cmd), (str), sizeof(str)-1) ?	false :	\
		((cmd) += sizeof(str)-1, true))

static const char *handle_qfer(struct ugdb *ugdb, char *cmd)
{
	const char *rc = "E01";

	if (EQ(cmd, "siginfo:")) {
		if (EQ(cmd, "read::"))
			rc = handle_siginfo_read(ugdb, cmd);
	}

	return rc;
}

static void handle_command(struct ugdb *ugdb, char *cmd, int len)
{
	struct pbuf *pb = &ugdb->u_pbuf;
	const char *rc = "";

	switch (cmd[0]) {
	case '!':
	case '?':
		rc = "OK";
		break;

	case 'H':
		rc = handle_set_cur(ugdb, cmd + 1);
		break;

	case 'T':
		rc = handle_ck_alive(ugdb, cmd + 1);
		break;

	case 'D':
		rc = handle_detach(ugdb, cmd + 1);
		break;

	case 'g':
		rc = handle_getregs(ugdb);
		break;

	case 'G':
		rc = handle_setregs(ugdb, cmd + 1);
		break;

	case 'P':
		rc = handle_set_one_reg(ugdb, cmd + 1);
		break;

	case 'm':
		rc = handle_readmem(ugdb, cmd);
		break;

	case 'M':
		rc = handle_writemem(ugdb, cmd, len);
		break;

	case 'C':
	case 'c':
	case 'S':
	case 's':
		rc = handle_c(ugdb, cmd);
		break;

	case 'q':
		if (EQ(cmd, "qSupported")) {
			if (!strstr(cmd, "multiprocess+")) {
				printk(KERN_INFO "ugdb: can't work without multiprocess\n");
				ugdb->u_err = -EPROTONOSUPPORT;
			}

			pb_packf(&ugdb->u_pbuf, "PacketSize=%x;%s",
				PACKET_SIZE,
				"QStartNoAckMode+;QNonStop+;multiprocess+;"
				"QPassSignals+;qXfer:siginfo:read+");
			rc = NULL;
		}
		else if (EQ(cmd, "qfThreadInfo")) {
			rc = handle_thread_info(ugdb, true);
		}
		else if (EQ(cmd, "qsThreadInfo")) {
			rc = handle_thread_info(ugdb, false);
		}
		else if (EQ(cmd, "qXfer:")) {
			rc = handle_qfer(ugdb, cmd);
		}
		else if (EQ(cmd, "qTStatus")) {
			rc = "T0";
		}

		break;

	case 'Q':
		if (EQ(cmd, "QStartNoAckMode")) {
			ugdb->u_no_ack = true;
			rc = "OK";
		}
		else if (EQ(cmd, "QNonStop:")) {
			if (*cmd != '1') {
				printk(KERN_INFO "ugdb: all-stop is not implemented.\n");
				ugdb->u_err = -EPROTONOSUPPORT;
			}

			rc = "OK";
		}
		else if (EQ(cmd, "QPassSignals:")) {
			rc = handle_qpass_signals(ugdb, cmd);
		}

		break;

	case 'v':
		if (EQ(cmd, "vAttach;")) {
			rc = handle_vattach(ugdb, cmd);
		}
		else if (EQ(cmd, "vStopped")) {
			rc = handle_vstopped(ugdb);
		}
		else if (EQ(cmd, "vCont")) {
			rc = handle_vcont(ugdb, cmd);
		}

		break;

	default:
		;
	}

	if (rc)
		pb_packs(pb, rc);
}

static void process_commands(struct ugdb *ugdb)
{
	char *cmds = ugdb->u_cbuf;
	int todo = ugdb->u_clen;

	if (o_remote_debug)
		printk(KERN_INFO "=> %.*s\n", ugdb->u_clen, ugdb->u_cbuf);

	while (todo) {
		char first;
		char *c_cmd, *c_end;
		int c_len;

		first = *cmds++;
		todo--;

		switch (first) {
		default:
			printk(KERN_INFO "XXX: unknown chr %02x\n", first);
			pb_putc(&ugdb->u_pbuf, '-');
			break;

		case '-':
			printk(KERN_INFO "XXX: got NACK!\n");
			ugdb->u_err = -EPROTO;
		case '+':
			break;

		case 0x3:
			printk(KERN_INFO "XXX: unexpected CTRL-C\n");
			break;

		case '$':
			c_cmd = cmds;
			c_end = strnchr(c_cmd, todo, '#');
			c_len = c_end ? c_end - cmds : -1;

			if (c_len < 0 || todo < c_len + 3) {
				printk(KERN_INFO "XXX: can't find '#cs'\n");
				++todo;
				--cmds;
				goto out;
			}

			// XXX: verify checksum ?
			todo -= c_len + 3;
			cmds += c_len + 3;
			*c_end = 0;

			if (!ugdb->u_no_ack)
				pb_putc(&ugdb->u_pbuf, '+');

			handle_command(ugdb, c_cmd, c_len);
		}
	}
out:
	ugdb->u_clen = todo;
	if (todo && cmds > ugdb->u_cbuf)
		memmove(ugdb->u_cbuf, cmds, todo);
}

// -----------------------------------------------------------------------------
static int xxx_tinfo(struct ugdb *ugdb)
{
	struct ugdb_thread *thread;
	int tid = 0;

	mutex_lock(&ugdb->u_mutex);
	thread = ugdb_advance_tinfo(ugdb);
	if (thread)
		tid = thread->t_tid;
	mutex_unlock(&ugdb->u_mutex);

	return tid;
}

static int xxx_sc_threads(struct ugdb *ugdb, int tid, bool sc)
{
	void *arg = NULL;
	int pid = 0;
	int ret;

	if (tid < 0) {
		pid = -tid;
		tid = -1;
		arg = (void*)1;
	}

	mutex_lock(&ugdb->u_mutex);
	ret = ugdb_do_each_thread(ugdb, pid, tid,
				sc ? do_stop_thread : do_cont_thread,
				arg);
	mutex_unlock(&ugdb->u_mutex);

	return ret;
}

static int xxx_stop(struct ugdb *ugdb, int tid)
{
	return xxx_sc_threads(ugdb, tid, true);
}

static int xxx_cont(struct ugdb *ugdb, int tid)
{
	return xxx_sc_threads(ugdb, tid, false);
}

static int xxx_get_stopped(struct ugdb *ugdb)
{
	struct ugdb_thread *thread;
	int tid = 1;

	if (ugdb->u_stop_state == U_STOP_IDLE)
		return -1;

	if (ugdb->u_stop_state == U_STOP_PENDING)
		tid = 1000;

	thread = ugdb_next_stopped(ugdb);
	if (!thread)
		return 0;
	return tid * thread->t_tid;
}

static int xxx_show_all(struct ugdb *ugdb)
{
	struct ugdb_process *process;
	struct ugdb_thread *thread;

	printk(KERN_INFO "SHOW start ----------------------------------------\n");

	mutex_lock(&ugdb->u_mutex);
	list_for_each_entry(process, &ugdb->u_processes, p_processes) {
		printk(KERN_INFO "PROC: %x\n", process->p_pid);

		list_for_each_entry(thread, &process->p_threads, t_threads) {
			printk(KERN_INFO "    T: %x %p; %p %p\n",
					thread->t_tid, thread,
					thread->t_spid, pid_task(thread->t_spid, PIDTYPE_PID));
		}

	}
	mutex_unlock(&ugdb->u_mutex);

	printk(KERN_INFO "SHOW end ----------------------------------------\n");
	return 0;
}

static long ugdb_f_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct ugdb *ugdb = file->private_data;
	// XXX: otherwise gdb->get_tty_state(TCGETS, TCSETS, TCFLSH) complains
	int ret = 0;

	// XXX: temporary debugging hooks, ignore.
	switch (cmd) {
		case 0x666 + 0:
			ret = ugdb_attach(ugdb, arg);
			break;

		case 0x666 + 1:
			ret = ugdb_detach(ugdb, arg);
			break;

		case 0x666 + 2:
			ret = xxx_tinfo(ugdb);
			break;

		case 0x666 + 3:
			ret = xxx_stop(ugdb, arg);
			break;

		case 0x666 + 4:
			ret = xxx_cont(ugdb, arg);
			break;

		case 0x666 + 5:
			ret = xxx_get_stopped(ugdb);
			break;

		case 0x666 + 6:
			ret = xxx_show_all(ugdb);
			break;
	}

	return ret;
}

static unsigned int ugdb_f_poll(struct file *file, poll_table *wait)
{
	struct ugdb *ugdb = file->private_data;
	unsigned int mask;

	poll_wait(file, &ugdb->u_wait, wait);

	mask = (POLLOUT | POLLWRNORM);

	if (pb_size(&ugdb->u_pbuf) || ugdb->u_stop_state == U_STOP_PENDING)
		mask |= (POLLIN | POLLRDNORM);

	if (ugdb->u_err)
		mask |= POLLERR;

	return mask;
}

static ssize_t ugdb_f_read(struct file *file, char __user *ubuf,
				size_t count, loff_t *ppos)
{
	struct ugdb *ugdb = file->private_data;
	struct pbuf *pb = &ugdb->u_pbuf;

	if (ugdb->u_err)
		return ugdb->u_err;

	if (ugdb->u_stop_state == U_STOP_PENDING)
		ugdb_report_stopped(ugdb, true);

	if (pb_size(pb) > count) {
		printk(KERN_INFO "XXX: short read %d %ld\n",
					pb_size(pb), count);
	}

	return pb_copy_to_user(pb, ubuf, count);
}

static ssize_t ugdb_f_write(struct file *file, const char __user *ubuf,
				size_t count, loff_t *ppos)
{
	struct ugdb *ugdb = file->private_data;

	if (ugdb->u_err)
		return ugdb->u_err;

	if (count > PACKET_SIZE - ugdb->u_clen) {
		count = PACKET_SIZE - ugdb->u_clen;
		printk("XXX: write(%ld,%d) enospc\n", count, ugdb->u_clen);
	}

	if (copy_from_user(ugdb->u_cbuf + ugdb->u_clen, ubuf, count))
		return -EFAULT;

	ugdb->u_clen += count;
	process_commands(ugdb);

	return count;
}

static int ugdb_f_open(struct inode *inode, struct file *file)
{
	nonseekable_open(inode, file);

	file->private_data = ugdb_create();

	return	IS_ERR(file->private_data) ?
		PTR_ERR(file->private_data) : 0;
}

static int ugdb_f_release(struct inode *inode, struct file *file)
{
	ugdb_destroy(file->private_data);

	return 0;
}

static const struct file_operations ugdb_f_ops = {
	.open			= ugdb_f_open,
	.unlocked_ioctl		= ugdb_f_ioctl,
	.poll			= ugdb_f_poll,
	.read			= ugdb_f_read,
	.write			= ugdb_f_write,
	.release		= ugdb_f_release,
};

#include <linux/kallsyms.h>

struct kallsyms_sym {
	const char	*name;
	unsigned long	addr;
};

static int kallsyms_on_each_symbol_cb(void *data, const char *name,
				struct module *mod, unsigned long addr)
{
	struct kallsyms_sym *sym = data;

	if (strcmp(name, sym->name))
		return 0;

	sym->addr = addr;
	return 1;
}

// XXX: kallsyms_lookup_name() is not exported in 2.6.32
static bool lookup_unexported(void)
{
	struct kallsyms_sym sym;

	sym.name = "access_process_vm";
	if (!kallsyms_on_each_symbol(kallsyms_on_each_symbol_cb, &sym))
		goto err;
	u_access_process_vm = (void*)sym.addr;

	return true;
err:
	printk(KERN_ERR "ugdb: can't lookup %s\n", sym.name);
	return false;
}

#define PROC_NAME	"ugdb"
struct proc_dir_entry *ugdb_pde;

static int __init ugdb_init(void)
{
	if (!lookup_unexported())
		return -ESRCH;

	ugdb_pde = proc_create(PROC_NAME, S_IFREG|S_IRUGO|S_IWUGO,
				NULL, &ugdb_f_ops);
	if (!ugdb_pde)
		return -EBADF;

	return 0;
}

static void __exit ugdb_exit(void)
{
	remove_proc_entry(PROC_NAME, NULL);
}

MODULE_LICENSE("GPL");
module_init(ugdb_init);
module_exit(ugdb_exit);

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2010-09-20  3:31 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-09-20  3:31 gdbstub initial code, v10 Oleg Nesterov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).