public inbox for archer@sourceware.org
 help / color / mirror / Atom feed
* gdbstub initial code, v9
@ 2010-09-08 19:22 Oleg Nesterov
  2010-09-09 10:29 ` Jan Kratochvil
  2010-09-09 12:39 ` Frank Ch. Eigler
  0 siblings, 2 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-08 19:22 UTC (permalink / raw)
  To: archer, utrace-devel

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

Changes:

	- partly fix the multitracing problems.

	  ugdb still can't work with ptrace, ptrace-utrace.c needs
	  changes. But at least multiple udgb tracers can coexist,
	  more or less.

	  But of course they can confuse each other anyway, no matter
	  what ugdb does.

	- implement memory writes ($M).

	- refactor memory reads to avoid the "Too late to report the
	  error" case.

But, Jan. Implementing the memory writes does not mean breakpoints
automatically start to work!

Yes, gdb writes cc, and yes the tracee reports SIGTRAP. But after
that "continue" does nothing except "$c", and the tracee naturally
gets SIGILL. I expected that, since ugdb doesn't even know the code
was changed, gdb should write the original byte back before continue,
but this doesn't happen.

Tried to understand how this works with gdbserver, but failed so far.
Will continue, but any hint is very much appreciated ;)

Oleg.

[-- Attachment #2: ugdb.c --]
[-- Type: text/plain, Size: 53031 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;

	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)
{
	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) {
		// 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, 0);
		ugdb_control(thread, UTRACE_RESUME);
	}

	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;

		/* 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);

		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);
	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)
{
	/*
	 * Otherwise I do not know what to do, 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))
		return -EINVAL;

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

	switch (*cmd++) {
	case 'C':
		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':
		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;

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

	if (ugdb_cont_thread(thread, false) <= 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 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";
}

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

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

		lo = hex_to_bin(*cmd++);
		hi = 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 'm':
		rc = handle_readmem(ugdb, cmd);
		break;

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

	case 'C':
	case 'c':
		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] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-08 19:22 gdbstub initial code, v9 Oleg Nesterov
@ 2010-09-09 10:29 ` Jan Kratochvil
  2010-09-09 15:06   ` Oleg Nesterov
  2010-09-09 12:39 ` Frank Ch. Eigler
  1 sibling, 1 reply; 28+ messages in thread
From: Jan Kratochvil @ 2010-09-09 10:29 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: archer, utrace-devel

Hi Oleg,

kernel-devel-2.6.34.6-54.fc13.x86_64 (real F13) says:

ugdb.c:1988: error: implicit declaration of function ‘hex_to_bin’


Jan

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-08 19:22 gdbstub initial code, v9 Oleg Nesterov
  2010-09-09 10:29 ` Jan Kratochvil
@ 2010-09-09 12:39 ` Frank Ch. Eigler
  2010-09-09 15:33   ` Oleg Nesterov
  2010-09-10 22:03   ` ugdb && breakpoints Oleg Nesterov
  1 sibling, 2 replies; 28+ messages in thread
From: Frank Ch. Eigler @ 2010-09-09 12:39 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: archer, utrace-devel

Oleg Nesterov <oleg@redhat.com> writes:

> [...]
> But, Jan. Implementing the memory writes does not mean breakpoints
> automatically start to work!

It approximately should though.

> Yes, gdb writes cc, and yes the tracee reports SIGTRAP. But after
> that "continue" does nothing except "$c", and the tracee naturally
> gets SIGILL. I expected that, since ugdb doesn't even know the code
> was changed, gdb should write the original byte back before continue,
> but this doesn't happen.

In normal all-stop mode, gdb does normally replace the old
instruction, in order to single-step over it with the 's' packet.
Perhaps you're testing some buggy non-stop aspect that only works
with 'Z' breakpoint management packets?  A fuller packet trace
would help explain.

- FChE

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 10:29 ` Jan Kratochvil
@ 2010-09-09 15:06   ` Oleg Nesterov
  0 siblings, 0 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-09 15:06 UTC (permalink / raw)
  To: Jan Kratochvil; +Cc: archer, utrace-devel

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

On 09/09, Jan Kratochvil wrote:
>
> Hi Oleg,
>
> kernel-devel-2.6.34.6-54.fc13.x86_64 (real F13) says:
>
> ugdb.c:1988: error: implicit declaration of function ‘hex_to_bin’

OOPS. It was added by 903788892ea0fc7fcaf7e8e5fac9a77379fc215b

Please see the attachment with the copy-and-pastes hex_to_bin().

Oleg.

[-- Attachment #2: ugdb.c --]
[-- Type: text/plain, Size: 53287 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;

	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)
{
	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) {
		// 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, 0);
		ugdb_control(thread, UTRACE_RESUME);
	}

	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;

		/* 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);

		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);
	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)
{
	/*
	 * Otherwise I do not know what to do, 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))
		return -EINVAL;

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

	switch (*cmd++) {
	case 'C':
		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':
		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;

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

	if (ugdb_cont_thread(thread, false) <= 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 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;

		lo = cap_hex_to_bin(*cmd++);
		hi = 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 'm':
		rc = handle_readmem(ugdb, cmd);
		break;

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

	case 'C':
	case 'c':
		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] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 12:39 ` Frank Ch. Eigler
@ 2010-09-09 15:33   ` Oleg Nesterov
  2010-09-09 15:54     ` Oleg Nesterov
  2010-09-13  5:54     ` Kevin Buettner
  2010-09-10 22:03   ` ugdb && breakpoints Oleg Nesterov
  1 sibling, 2 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-09 15:33 UTC (permalink / raw)
  To: Frank Ch. Eigler; +Cc: archer, utrace-devel

On 09/09, Frank Ch. Eigler wrote:
>
> Oleg Nesterov <oleg@redhat.com> writes:
>
> > [...]
> > But, Jan. Implementing the memory writes does not mean breakpoints
> > automatically start to work!
>
> It approximately should though.
>
> > Yes, gdb writes cc, and yes the tracee reports SIGTRAP. But after
> > that "continue" does nothing except "$c", and the tracee naturally
> > gets SIGILL. I expected that, since ugdb doesn't even know the code
> > was changed, gdb should write the original byte back before continue,
> > but this doesn't happen.
>
> In normal all-stop mode,

Currently ugdb only supports non-stop

> gdb does normally replace the old
> instruction, in order to single-step over it with the 's' packet.

Yes, probably single-stepping is needed... I am still trying to
understand how this works with gdbserver, but I see vCont:s packets.

> Perhaps you're testing some buggy non-stop aspect that only works
> with 'Z' breakpoint management packets?

No. Just a trivial test-case which printfs in a loop.

> A fuller packet trace
> would help explain.

Please see below. But the only important part is:

	$M4005ba,1:cc			<------- set bp
	$c				<------- resume

of course, this can't work.

Full trace:

	=> qSupported:multiprocess+
	<= PacketSize=400;QStartNoAckMode+;QNonStop+;multiprocess+;QPassS...
	=> QStartNoAckMode
	<= OK
	=> !
	<= OK
	=> Hgp0.0
	<= E01
	=> QNonStop:1
	<= OK
	=> qfThreadInfo
	<= E01
	=> ?
	<= OK
	=> qSymbol::
	<=
	=> vAttach;95b
	<= OK
	=> qfThreadInfo
	<= mp95b.95b
	=> qsThreadInfo
	<= l
	=> Hgp95b.95b
	<= OK
	=> vCont?
	<= vCont;t
	=> vCont;t:p95b.-1
	<= OK
	<= %Stop:T00thread:p95b.95b;
	=> vStopped
	<= OK
	=> g
	<= fcfdffffffffffff90ad5329ff7f0000ffffffffffffffff00000000000000...
	=> m600880,8
	<= 403c6d7d007f0000
	=> m7f007d6d3c48,8
	<= 00106d7d007f0000
	=> m7f007d6d1000,28
	<= 0000000000000000f6e04c7d007f0000e80760000000000080156d7d007f00...
	=> m7f007d6d1580,28
	<= 00f0ef29ff7f0000f6e04c7d007f000050f45f29ff7f000000c06c7d007f00...
	=> m7f007d4ce0f4,4
	<= 090a0069
	=> m7f007d6cc000,28
	<= 0030167d007f0000781f6d7d007f0000400b4b7d007f0000e8346d7d007f00...
	=> m7f007d6d1f78,4
	<= 2f6c6962
	=> m7f007d6d1f7c,4
	<= 2f6c6962
	=> m7f007d6d1f80,4
	<= 632e736f
	=> m7f007d6d1f84,4
	<= 2e360000
	=> m7f007d6d34e8,28
	<= 00704b7d007f00000002400000000000082e6d7d007f000000000000000000...
	=> m400200,4
	<= 2f6c6962
	=> m400204,4
	<= 2f6c642d
	=> m400208,4
	<= 6c696e75
	=> m40020c,4
	<= 782d7838
	=> m400210,4
	<= 362d3634
	=> m400214,4
	<= 2e736f2e
	=> m400218,4
	<= 32000000
	=> m7f007d6d3c40,4
	<= 01000000
	=> m7f007d6d3c48,8
	<= 00106d7d007f0000
	=> m7f007d6d3c50,8
	<= c04e4c7d007f0000
	=> Z0,7f007d4c4ec0,1
	<=
	=> m7f007d4c4ec0,1
	<= f3
	=> X7f007d4c4ec0,0:
	<=
	=> M7f007d4c4ec0,1:cc
	<= OK
	=> m600880,8
	<= 403c6d7d007f0000
	=> m7f007d6d3c48,8
	<= 00106d7d007f0000
	=> m7f007d6d1000,28
	<= 0000000000000000f6e04c7d007f0000e80760000000000080156d7d007f00...
	=> m7f007d6d1580,28
	<= 00f0ef29ff7f0000f6e04c7d007f000050f45f29ff7f000000c06c7d007f00...
	=> m7f007d4ce0f4,4
	<= 090a0069
	=> m7f007d6cc000,28
	<= 0030167d007f0000781f6d7d007f0000400b4b7d007f0000e8346d7d007f00...
	=> m7f007d6d1f78,4
	<= 2f6c6962
	=> m7f007d6d1f7c,4
	<= 2f6c6962
	=> m7f007d6d1f80,4
	<= 632e736f
	=> m7f007d6d1f84,4
	<= 2e360000
	=> m7f007d6d34e8,28
	<= 00704b7d007f00000002400000000000082e6d7d007f000000000000000000...
	=> m400200,4
	<= 2f6c6962
	=> m400204,4
	<= 2f6c642d
	=> m400208,4
	<= 6c696e75
	=> m40020c,4
	<= 782d7838
	=> m400210,4
	<= 362d3634
	=> m400214,4
	<= 2e736f2e
	=> m400218,4
	<= 32000000
	=> m7f007d6d3c40,4
	<= 01000000
	=> vCont;t:p95b.-1
	<= OK
	=> m7f007d201f40,1
	<= 48
	=> m7f007d201f40,1
	<= 48
	=> g
	<= fcfdffffffffffff90ad5329ff7f0000ffffffffffffffff00000000000000...
	=> m7f007d201f40,1
	<= 48
	=> m7f007d201f40,1
	<= 48
	=> m40056c,12
	<= 554889e5e8e3feffff89c6ba07000000bfdc
	=> m40056c,1
	<= 55
	=> m40056d,3
	<= 4889e5
	=> m40056c,12
	<= 554889e5e8e3feffff89c6ba07000000bfdc
	=> m40056c,1
	<= 55
	=> m40056d,3
	<= 4889e5
	=> m4005ba,1
	<= e8
	=> m4005ba,1
	<= e8

(gdb) b BP.c:13
Breakpoint 1 at 0x4005ba: file BP.c, line 13.

	=> M4005ba,1:cc
	<= OK

gdb writes "int 3".

(gdb) c
Continuing.

	=> QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;4c;
	<= OK
	=> Hcp95b.95b
	<= OK
	=> c
	<= OK
	<= %Stop:T05thread:p95b.95b;

the tracee hits this bp and reports SIGTRAP

	=> vStopped
	<= OK
	=> g
	<= 00000000000000000006400000000000401f207d007f000000000000000000...
	=> P10=ba05400000000000
	<=
	=> G00000000000000000006400000000000401f207d007f00000000000000000...
	<=
	=> m4005ba,1
	<= cc
	=> m4005ba,1
	<= cc
	=> g
	<= 00000000000000000006400000000000401f207d007f000000000000000000...
	=> m4005bb,1
	<= 99
	=> m4005bb,1
	<= 99

Breakpoint 1, main () at BP.c:13
13                      printf("THREE %d %d\n\n", getpid(), __LINE__);
(gdb) c
Continuing.

	=> c
	<= OK

gdb just resumes the tracee,

	<= %Stop:T04thread:p95b.95b;

and of course it gets SIGILL after "int 3"

	=> vStopped
	<= OK
	=> g
	<= 00000000000000000006400000000000401f207d007f000000000000000000...
	=> m4005bc,1
	<= fe
	=> m4005bc,1
	<= fe
	=> g
	<= 00000000000000000006400000000000401f207d007f000000000000000000...
	=> m4005bc,1
	<= fe
	=> m4005bc,1
	<= fe
	=> qTStatus
	<= T0
	=> M4005ba,1:e8
	<= OK
	=> M7f007d4c4ec0,1:f3
	<= OK
	=> D;95b
	<= OK
	=> qTStatus
	<= T0

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 15:33   ` Oleg Nesterov
@ 2010-09-09 15:54     ` Oleg Nesterov
  2010-09-09 16:07       ` Frank Ch. Eigler
  2010-09-09 16:11       ` Jan Kratochvil
  2010-09-13  5:54     ` Kevin Buettner
  1 sibling, 2 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-09 15:54 UTC (permalink / raw)
  To: Frank Ch. Eigler; +Cc: archer, utrace-devel

On 09/09, Oleg Nesterov wrote:
>
> the tracee hits this bp and reports SIGTRAP
>
> 	=> vStopped
> 	<= OK
> 	=> g
> 	<= 00000000000000000006400000000000401f207d007f000000000000000000...
> 	=> P10=ba05400000000000
> 	<=
> 	=> G00000000000000000006400000000000401f207d007f00000000000000000...
> 	<=

May be this can explain...

Probably I need to implement G/P first, otherwise gdb can't change ip.

Still, I'd appreciate if someone can explain me what gdb needs/expects
to handle breakpoints before I start to read the sources.

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 15:54     ` Oleg Nesterov
@ 2010-09-09 16:07       ` Frank Ch. Eigler
  2010-09-09 16:37         ` Oleg Nesterov
  2010-09-09 16:11       ` Jan Kratochvil
  1 sibling, 1 reply; 28+ messages in thread
From: Frank Ch. Eigler @ 2010-09-09 16:07 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: archer, utrace-devel

Hi -

On Thu, Sep 09, 2010 at 05:50:47PM +0200, Oleg Nesterov wrote:

> Probably I need to implement G/P first, otherwise gdb can't change ip.

Perhaps.

> Still, I'd appreciate if someone can explain me what gdb needs/expects
> to handle breakpoints before I start to read the sources.

It'd be simpler if the normal all-stop mode was what you first focused
on.  That mode works fine with Z/z and with M/m based breakpoint
insertion/removal and c/s continue/singlestep.  (This stuff was all
working in the earlier gdbstub code.)

Re. non-stop mode, see
http://www.codesourcery.com/publications/non_stop_multi_threaded_debugging_in_gdb.pdf

- FChE

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 15:54     ` Oleg Nesterov
  2010-09-09 16:07       ` Frank Ch. Eigler
@ 2010-09-09 16:11       ` Jan Kratochvil
  2010-09-09 16:34         ` Oleg Nesterov
  1 sibling, 1 reply; 28+ messages in thread
From: Jan Kratochvil @ 2010-09-09 16:11 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: Frank Ch. Eigler, archer, utrace-devel

On Thu, 09 Sep 2010 17:50:47 +0200, Oleg Nesterov wrote:
> Probably I need to implement G/P first, otherwise gdb can't change ip.
> 
> Still, I'd appreciate if someone can explain me what gdb needs/expects
> to handle breakpoints before I start to read the sources.

Yes, GDB tries to set PC to PC-1 to skip back after the hit of 0xCC. 
As it fails to fix-up PC then PC is wrong and trap_expected is then 0 (it
should be 1) and everything fails.

BTW ugdb needs one simple fix. :-)
(gdb) p/x done=0x5a
$1 = 0xa5


Thanks,
Jan

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 16:11       ` Jan Kratochvil
@ 2010-09-09 16:34         ` Oleg Nesterov
  2010-09-09 16:36           ` Jan Kratochvil
  0 siblings, 1 reply; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-09 16:34 UTC (permalink / raw)
  To: Jan Kratochvil; +Cc: Frank Ch. Eigler, archer, utrace-devel

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

On 09/09, Jan Kratochvil wrote:
>
> On Thu, 09 Sep 2010 17:50:47 +0200, Oleg Nesterov wrote:
> > Probably I need to implement G/P first, otherwise gdb can't change ip.
> >
> > Still, I'd appreciate if someone can explain me what gdb needs/expects
> > to handle breakpoints before I start to read the sources.
>
> Yes, GDB tries to set PC to PC-1 to skip back after the hit of 0xCC.
> As it fails to fix-up PC then PC is wrong and trap_expected is then 0 (it
> should be 1) and everything fails.

OK. I'll implement G or P, then we will see.

But probably not right now, I have to switch to bug-report I have.

> BTW ugdb needs one simple fix. :-)
> (gdb) p/x done=0x5a
> $1 = 0xa5

OOPS! indeed, unhex() confuses lo and hi. I am attaching ugdb.c with
the trivial fix.

Thanks!

Cough... could you tell me how can I change the variable "done"
without printing it?

Oleg.


[-- Attachment #2: ugdb.c --]
[-- Type: text/plain, Size: 53287 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;

	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)
{
	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) {
		// 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, 0);
		ugdb_control(thread, UTRACE_RESUME);
	}

	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;

		/* 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);

		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);
	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)
{
	/*
	 * Otherwise I do not know what to do, 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))
		return -EINVAL;

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

	switch (*cmd++) {
	case 'C':
		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':
		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;

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

	if (ugdb_cont_thread(thread, false) <= 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 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 'm':
		rc = handle_readmem(ugdb, cmd);
		break;

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

	case 'C':
	case 'c':
		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] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 16:34         ` Oleg Nesterov
@ 2010-09-09 16:36           ` Jan Kratochvil
  2010-09-09 16:48             ` Oleg Nesterov
  0 siblings, 1 reply; 28+ messages in thread
From: Jan Kratochvil @ 2010-09-09 16:36 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: Frank Ch. Eigler, archer, utrace-devel

On Thu, 09 Sep 2010 18:30:31 +0200, Oleg Nesterov wrote:
> OOPS! indeed, unhex() confuses lo and hi.

It works for 0xcc, though.

> Cough... could you tell me how can I change the variable "done"
> without printing it?

(gdb) help set variable 
Evaluate expression EXP and assign result to variable VAR, using assignment
syntax appropriate for the current language (VAR = EXP or VAR := EXP for
example).  VAR may be a debugger "convenience" variable (names starting
with $), a register (a few standard names starting with $), or an actual
variable in the program being debugged.  EXP is any valid expression.
This may usually be abbreviated to simply "set".


Regards,
Jan

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 16:07       ` Frank Ch. Eigler
@ 2010-09-09 16:37         ` Oleg Nesterov
  0 siblings, 0 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-09 16:37 UTC (permalink / raw)
  To: Frank Ch. Eigler; +Cc: archer, utrace-devel

On 09/09, Frank Ch. Eigler wrote:
> Hi -
>
> On Thu, Sep 09, 2010 at 05:50:47PM +0200, Oleg Nesterov wrote:
>
> > Probably I need to implement G/P first, otherwise gdb can't change ip.
>
> Perhaps.
>
> > Still, I'd appreciate if someone can explain me what gdb needs/expects
> > to handle breakpoints before I start to read the sources.
>
> It'd be simpler if the normal all-stop mode was what you first focused
> on.  That mode works fine with Z/z and with M/m based breakpoint
> insertion/removal and c/s continue/singlestep.  (This stuff was all
> working in the earlier gdbstub code.)
>
> Re. non-stop mode, see
> http://www.codesourcery.com/publications/non_stop_multi_threaded_debugging_in_gdb.pdf

Thanks a lot, downloaded.

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 16:36           ` Jan Kratochvil
@ 2010-09-09 16:48             ` Oleg Nesterov
  2010-09-09 16:50               ` Oleg Nesterov
  2010-09-09 16:51               ` Tom Tromey
  0 siblings, 2 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-09 16:48 UTC (permalink / raw)
  To: Jan Kratochvil; +Cc: Frank Ch. Eigler, archer, utrace-devel

On 09/09, Jan Kratochvil wrote:
>
> > Cough... could you tell me how can I change the variable "done"
> > without printing it?
>
> (gdb) help set variable
> Evaluate expression EXP and assign result to variable VAR, using assignment
> syntax appropriate for the current language (VAR = EXP or VAR := EXP for
> example).  VAR may be a debugger "convenience" variable (names starting
> with $), a register (a few standard names starting with $), or an actual
> variable in the program being debugged.  EXP is any valid expression.
> This may usually be abbreviated to simply "set".

Thanks... but I tried this when I tested the fix.

	(gdb) p/x var
	$1 = 0x1234
	(gdb) set var
	Argument required (expression to compute).
	(gdb) set var 0
	(gdb) p/x var
	$2 = 0x1234

strange ;)

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 16:48             ` Oleg Nesterov
@ 2010-09-09 16:50               ` Oleg Nesterov
  2010-09-09 16:51               ` Tom Tromey
  1 sibling, 0 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-09 16:50 UTC (permalink / raw)
  To: Jan Kratochvil; +Cc: Frank Ch. Eigler, archer, utrace-devel

On 09/09, Oleg Nesterov wrote:
>
> Thanks... but I tried this when I tested the fix.
>
> 	(gdb) p/x var
> 	$1 = 0x1234
> 	(gdb) set var
> 	Argument required (expression to compute).
> 	(gdb) set var 0
> 	(gdb) p/x var
> 	$2 = 0x1234
>
> strange ;)

Aah.

	(gdb) set variable var=0x4321
	(gdb) p/x var
	$7 = 0x4321

Thanks ;)

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 16:48             ` Oleg Nesterov
  2010-09-09 16:50               ` Oleg Nesterov
@ 2010-09-09 16:51               ` Tom Tromey
  2010-09-10 10:12                 ` Roland McGrath
  1 sibling, 1 reply; 28+ messages in thread
From: Tom Tromey @ 2010-09-09 16:51 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: Jan Kratochvil, Frank Ch. Eigler, archer, utrace-devel

>>>>> "Oleg" == Oleg Nesterov <oleg@redhat.com> writes:

Oleg> 	(gdb) set var 0

You need:  set variable var = 0
The "variable" can be abbreviated.

FWIW, "print", "set variable", and "call" are basically aliases.
"print" just happens to print the result of the expression.

Tom

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 16:51               ` Tom Tromey
@ 2010-09-10 10:12                 ` Roland McGrath
  2010-09-10 18:14                   ` Oleg Nesterov
  0 siblings, 1 reply; 28+ messages in thread
From: Roland McGrath @ 2010-09-10 10:12 UTC (permalink / raw)
  To: Tom Tromey
  Cc: Oleg Nesterov, utrace-devel, Frank Ch. Eigler, Jan Kratochvil, archer

> >>>>> "Oleg" == Oleg Nesterov <oleg@redhat.com> writes:
> 
> Oleg> 	(gdb) set var 0
> 
> You need:  set variable var = 0
> The "variable" can be abbreviated.

I've always just used:

	(gdb) set var=0

Thanks,
Roland

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-10 10:12                 ` Roland McGrath
@ 2010-09-10 18:14                   ` Oleg Nesterov
  2010-09-10 19:43                     ` Tom Tromey
  0 siblings, 1 reply; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-10 18:14 UTC (permalink / raw)
  To: Roland McGrath
  Cc: Tom Tromey, utrace-devel, Frank Ch. Eigler, Jan Kratochvil, archer

On 09/10, Roland McGrath wrote:
>
> > >>>>> "Oleg" == Oleg Nesterov <oleg@redhat.com> writes:
> >
> > Oleg> 	(gdb) set var 0
> >
> > You need:  set variable var = 0
> > The "variable" can be abbreviated.
>
> I've always just used:
>
> 	(gdb) set var=0

No, I tried this too, doesn't work.

	(gdb) set var=0
	A syntax error in expression, near `=0'.

But, it turns out I choosed a bad name for the variable when
I tested the fix in unxex().

	(gdb) set xxx=0

This works.

	(gdb) set var var=0

This works too. I guess, when gdb sees "set var" it expects the
full "set variable ..." command.

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-10 18:14                   ` Oleg Nesterov
@ 2010-09-10 19:43                     ` Tom Tromey
  2010-09-10 19:49                       ` Oleg Nesterov
  0 siblings, 1 reply; 28+ messages in thread
From: Tom Tromey @ 2010-09-10 19:43 UTC (permalink / raw)
  To: Oleg Nesterov
  Cc: Roland McGrath, utrace-devel, Frank Ch. Eigler, Jan Kratochvil, archer

>>>>> "Oleg" == Oleg Nesterov <oleg@redhat.com> writes:

>> I've always just used:
>> (gdb) set var=0

Oleg> No, I tried this too, doesn't work.
Oleg> 	(gdb) set var=0
Oleg> 	A syntax error in expression, near `=0'.

Yeah, it is ambiguous if the actual variable name conflicts with any gdb
"set" subcommand.

I typically just use call or print.

Tom

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-10 19:43                     ` Tom Tromey
@ 2010-09-10 19:49                       ` Oleg Nesterov
  0 siblings, 0 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-10 19:49 UTC (permalink / raw)
  To: Tom Tromey
  Cc: Roland McGrath, utrace-devel, Frank Ch. Eigler, Jan Kratochvil, archer

On 09/10, Tom Tromey wrote:
>
> >>>>> "Oleg" == Oleg Nesterov <oleg@redhat.com> writes:
>
> >> I've always just used:
> >> (gdb) set var=0
>
> Oleg> No, I tried this too, doesn't work.
> Oleg> 	(gdb) set var=0
> Oleg> 	A syntax error in expression, near `=0'.
>
> Yeah, it is ambiguous if the actual variable name conflicts with any gdb
> "set" subcommand.

Ah, good to know. gdb has a lot of them ;)

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* ugdb && breakpoints
  2010-09-09 12:39 ` Frank Ch. Eigler
  2010-09-09 15:33   ` Oleg Nesterov
@ 2010-09-10 22:03   ` Oleg Nesterov
  2010-09-10 22:12     ` Tom Tromey
  1 sibling, 1 reply; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-10 22:03 UTC (permalink / raw)
  To: Frank Ch. Eigler; +Cc: archer, utrace-devel

On 09/09, Frank Ch. Eigler wrote:
>
> Oleg Nesterov <oleg@redhat.com> writes:
>
> > [...]
> > But, Jan. Implementing the memory writes does not mean breakpoints
> > automatically start to work!
>
> It approximately should though.

No.

Frank, I guess I did a mistake, I should have read the pdf you sent
me first. I'll read it anyway later, but I think that I already
understand how this work.

	gdb replaces the original insn with "int 3".

	the tracee reports SIGTRAP.

	Now, to continue the tracee, gdb does not restore the
	original instruction. Instead, it

		- writes this insn into _start code

		- changes regs->ip to point to this insn

		- does single-step to execute this insn

		- changes regs->ip again

So. Let's forget about breakpoints temporary. ugdb needs the single
stepping first.

Damn. I spent much more time than I'd wish trying to understand this.
I misunderstood the "target byte order" part in the documentation of
P packet.

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: ugdb && breakpoints
  2010-09-10 22:03   ` ugdb && breakpoints Oleg Nesterov
@ 2010-09-10 22:12     ` Tom Tromey
  2010-09-14 19:38       ` Roland McGrath
  0 siblings, 1 reply; 28+ messages in thread
From: Tom Tromey @ 2010-09-10 22:12 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: Frank Ch. Eigler, archer, utrace-devel

Oleg> 	Now, to continue the tracee, gdb does not restore the
Oleg> 	original instruction. Instead, it
Oleg> 		- writes this insn into _start code
Oleg> 		- changes regs->ip to point to this insn
Oleg> 		- does single-step to execute this insn
Oleg> 		- changes regs->ip again

This is what is done for non-stop.
I believe it is called "displaced stepping" in gdb.

I think eventually we would like it if uprobes did this work, instead of
gdb doing it.  Presumably that would yield better performance.  E.g., if
we have a thread-specific breakpoint, then other threads hitting that
breakpoint could simply do the displaced stepping via uprobes, and not
report a breakpoint hit to gdb at all.

For all-stop, breakpoints are handled differently, though I don't
remember how offhand.

Tom

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-09 15:33   ` Oleg Nesterov
  2010-09-09 15:54     ` Oleg Nesterov
@ 2010-09-13  5:54     ` Kevin Buettner
  2010-09-14  2:20       ` Roland McGrath
  1 sibling, 1 reply; 28+ messages in thread
From: Kevin Buettner @ 2010-09-13  5:54 UTC (permalink / raw)
  To: archer

On Thu, 9 Sep 2010 17:29:37 +0200
Oleg Nesterov <oleg@redhat.com> wrote:

> Currently ugdb only supports non-stop

Could someone explain to me why non-stop (rather than all-stop) is
being focused upon first?


I skimmed the v9 code.  It's good to see that support has been added
for the 'm', 'M', and 'g' commands.  It appears to me that 'G' and 's'
are still missing though.  As discussed by Jan, x86 will need G (or P,
but G is required by the spec whereas P is not), due to the need to
adjust the PC backwards after a break.  (In gdb sources, this is
known as decr_pc_after_break.)

Something else that came to mind while reading this thread is the
issue of cache synchronization.  When writing either breakpoints or
original instructions to memory, it may be necessary to flush the data
cache to memory and invalidate some lines in the instruction cache so
as to not have the CPU execute the instruction that was there prior to
the modification - i.e. you want the CPU to execute the instruction
that was just written to memory, not necessarily whatever it has lying
about it it's cache.  It occurred to me that this could be one reason
for breakpoints not working after implementation of the 'M' command.
(If the kernel calls that you're using already automagically do this, then
ignore this comment...)

Kevin

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-13  5:54     ` Kevin Buettner
@ 2010-09-14  2:20       ` Roland McGrath
  2010-09-14 16:10         ` Oleg Nesterov
  0 siblings, 1 reply; 28+ messages in thread
From: Roland McGrath @ 2010-09-14  2:20 UTC (permalink / raw)
  To: Kevin Buettner; +Cc: archer, Oleg Nesterov

> Could someone explain to me why non-stop (rather than all-stop) is
> being focused upon first?

We've tried to encourage Oleg to do whatever is easiest first.
It's not clear to me why all-stop isn't what's easiest, all in all.

> Something else that came to mind while reading this thread is the
> issue of cache synchronization.  [...]
> (If the kernel calls that you're using already automagically do this, then
> ignore this comment...)

They do.  Not to worry.  (It's the same internal path that you get to when
gdb or gdbserver uses a ptrace syscall or a write syscall on /proc/pid/mem.)


Thanks,
Roland

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-14  2:20       ` Roland McGrath
@ 2010-09-14 16:10         ` Oleg Nesterov
  2010-09-14 16:30           ` Jan Kratochvil
  2010-09-15 19:43           ` Kevin Buettner
  0 siblings, 2 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-14 16:10 UTC (permalink / raw)
  To: Roland McGrath; +Cc: Kevin Buettner, archer

On 09/13, Roland McGrath wrote:
>
> > Could someone explain to me why non-stop (rather than all-stop) is
> > being focused upon first?
>
> We've tried to encourage Oleg to do whatever is easiest first.
> It's not clear to me why all-stop isn't what's easiest, all in all.

Hmm. I thought that all-stop is more simple, and the very first version
worked in this mode.

I guess, I misunderstood the subsequent discussion as if non-stop is
more important or preferred (I do not know how people use gdb). That
is why I switched to non-stop.

So, in the long term, which mode is more useful? And, I suppose that
(unfortunately ;) ugdb should support both ?

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-14 16:10         ` Oleg Nesterov
@ 2010-09-14 16:30           ` Jan Kratochvil
  2010-09-14 21:30             ` Tom Tromey
  2010-09-15 19:43           ` Kevin Buettner
  1 sibling, 1 reply; 28+ messages in thread
From: Jan Kratochvil @ 2010-09-14 16:30 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: Roland McGrath, Kevin Buettner, archer

On Tue, 14 Sep 2010 18:07:00 +0200, Oleg Nesterov wrote:
> I guess, I misunderstood the subsequent discussion as if non-stop is
> more important or preferred (I do not know how people use gdb). That
> is why I switched to non-stop.

I would find non-stop as a more general solution.  In non-stop mode one still
can stop each and all the threads manually.  But in all-stop mode one cannot
keep the (other) threads running.


> So, in the long term, which mode is more useful? And, I suppose that
> (unfortunately ;) ugdb should support both ?

Just people are more used to the all-stop mode and also the testsuite cases do
not expect non-stop so for the compatibility reasons (both with humans and
with the testsuite) the all-stop mode at ugdb probably makes sense.

all-stop mode could be also emulated probably from the GDB client side.
Still not suitable for some specially crafted testsuite cases but those may
not be applicable for various reasons to ugdb anyway.



Thanks,
Jan

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: ugdb && breakpoints
  2010-09-10 22:12     ` Tom Tromey
@ 2010-09-14 19:38       ` Roland McGrath
  2010-09-14 19:47         ` Oleg Nesterov
  0 siblings, 1 reply; 28+ messages in thread
From: Roland McGrath @ 2010-09-14 19:38 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Oleg Nesterov, utrace-devel, Frank Ch. Eigler, archer

The traditional method is to restore the original instruction replaced by
the breakpoint in text, single-step over that instruction, then restore the
breakpoint in text, then continue.  That method requires all-stop so that
while you are stepping the thread that just hit the breakpoint, you can't
have another thread run past that instruction and miss the breakpoint.

Both this traditional in-place method, and the instruction-copying method,
depend on using single-step.  So "stepi" has to work before "break" can work.


Thanks,
Roland

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: ugdb && breakpoints
  2010-09-14 19:38       ` Roland McGrath
@ 2010-09-14 19:47         ` Oleg Nesterov
  0 siblings, 0 replies; 28+ messages in thread
From: Oleg Nesterov @ 2010-09-14 19:47 UTC (permalink / raw)
  To: Roland McGrath; +Cc: Tom Tromey, utrace-devel, Frank Ch. Eigler, archer

On 09/14, Roland McGrath wrote:
>
> Both this traditional in-place method, and the instruction-copying method,
> depend on using single-step.  So "stepi" has to work before "break" can work.

Yes, thanks, I already got it.

Oleg.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-14 16:30           ` Jan Kratochvil
@ 2010-09-14 21:30             ` Tom Tromey
  0 siblings, 0 replies; 28+ messages in thread
From: Tom Tromey @ 2010-09-14 21:30 UTC (permalink / raw)
  To: Jan Kratochvil; +Cc: Oleg Nesterov, Roland McGrath, Kevin Buettner, archer

>>>>> "Jan" == Jan Kratochvil <jan.kratochvil@redhat.com> writes:

Jan> I would find non-stop as a more general solution.  In non-stop mode
Jan> one still can stop each and all the threads manually.  But in
Jan> all-stop mode one cannot keep the (other) threads running.
[...]
Jan> all-stop mode could be also emulated probably from the GDB client side.

I thought Pedro talked about this once, but I can't find the message.

This seems like a reasonable future project for us -- it seems to me
that if ugdb does the thread-specific stop filtering, then it would make
sense to notify it of thread-specific breakpoints, and then always use
non-stop.

Tom

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: gdbstub initial code, v9
  2010-09-14 16:10         ` Oleg Nesterov
  2010-09-14 16:30           ` Jan Kratochvil
@ 2010-09-15 19:43           ` Kevin Buettner
  1 sibling, 0 replies; 28+ messages in thread
From: Kevin Buettner @ 2010-09-15 19:43 UTC (permalink / raw)
  To: archer

On Tue, 14 Sep 2010 18:07:00 +0200
Oleg Nesterov <oleg@redhat.com> wrote:

> So, in the long term, which mode is more useful?

I don't know about long term, but the most common use case is
currently all-stop.  Certainly for the short term, I think it
makes more sense to implement all-stop first.  (I think you'll
run into fewer GDB bugs with all-stop...)

> And, I suppose that (unfortunately ;) ugdb should support both ?

It depends upon the goals of the ugdb project.

If the goal is to demonstrate utrace, then support for only one mode
might be sufficient.  (I'll let someone else make that determination.)

If the goal is to become a full-fledged debug agent ala gdbserver,
then ugdb will need to support both.

Kevin

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2010-09-15 19:43 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-09-08 19:22 gdbstub initial code, v9 Oleg Nesterov
2010-09-09 10:29 ` Jan Kratochvil
2010-09-09 15:06   ` Oleg Nesterov
2010-09-09 12:39 ` Frank Ch. Eigler
2010-09-09 15:33   ` Oleg Nesterov
2010-09-09 15:54     ` Oleg Nesterov
2010-09-09 16:07       ` Frank Ch. Eigler
2010-09-09 16:37         ` Oleg Nesterov
2010-09-09 16:11       ` Jan Kratochvil
2010-09-09 16:34         ` Oleg Nesterov
2010-09-09 16:36           ` Jan Kratochvil
2010-09-09 16:48             ` Oleg Nesterov
2010-09-09 16:50               ` Oleg Nesterov
2010-09-09 16:51               ` Tom Tromey
2010-09-10 10:12                 ` Roland McGrath
2010-09-10 18:14                   ` Oleg Nesterov
2010-09-10 19:43                     ` Tom Tromey
2010-09-10 19:49                       ` Oleg Nesterov
2010-09-13  5:54     ` Kevin Buettner
2010-09-14  2:20       ` Roland McGrath
2010-09-14 16:10         ` Oleg Nesterov
2010-09-14 16:30           ` Jan Kratochvil
2010-09-14 21:30             ` Tom Tromey
2010-09-15 19:43           ` Kevin Buettner
2010-09-10 22:03   ` ugdb && breakpoints Oleg Nesterov
2010-09-10 22:12     ` Tom Tromey
2010-09-14 19:38       ` Roland McGrath
2010-09-14 19:47         ` 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).