public inbox for archer@sourceware.org
 help / color / mirror / Atom feed
* gdbstub initial code, v3
@ 2010-08-12  0:01 Oleg Nesterov
  2010-08-12  1:14 ` Oleg Nesterov
  0 siblings, 1 reply; 20+ messages in thread
From: Oleg Nesterov @ 2010-08-12  0:01 UTC (permalink / raw)
  To: archer, utrace-devel

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

Please see the attached ugdb.c.

It supports multiple inferiors/threads, stop/cont, clone/exit.
It doesn't report W/X when the process exits yet, but this is
only because I'd like to discuss the current problems with the
exited leader first, then implement this as a separate change.

Any code review is very much appreciated.

Problems:

	- It doesn't support all-stop mode.

	  Please tell me if this is needed. I hope not, this needs
	  a lot of nasty complications :/

	- We should discuss utrace limitations: UTRACE_RESUME
	  races/problems, utrace_prepare_examine() issues.

	- I just finished this code, and it doesn't work with gdb ;)

	  I will investigate tomorrow, but I am almost sure gdb is
	  wrong.

Oleg.

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

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

static inline void ugdb_ck_stopped(struct ugdb *ugdb)
{
	// XXX: temporary racy check
	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);
}

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)

struct ugdb_process {
	int			p_pid;
	int			p_state;

	struct list_head	p_threads;

	struct ugdb		*p_ugdb;
	struct list_head	p_processes;
};

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

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

	process->p_pid = pid;
	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 ? */

struct ugdb_thread {
	int			t_tid;
	int			t_stop_state;

	struct ugdb		*t_ugdb;
	struct ugdb_process	*t_process;

	struct list_head	t_threads;
	struct list_head	t_stopped;

	struct pid		*t_spid;

	// create/attach border
	struct utrace_engine	*t_engine;
};

static inline struct task_struct *thread_to_task(struct ugdb_thread *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 inline void ugdb_del_stopped(struct ugdb *ugdb,
					struct ugdb_thread *thread)
{
	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;
	}
}

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

	ugdb_ck_stopped(ugdb);

	if (!list_empty(&thread->t_stopped)) {
		WARN_ON(!(thread->t_stop_state & T_STOP_ACK));
		spin_lock(&ugdb->u_slock);
		ugdb_del_stopped(ugdb, thread);
		spin_unlock(&ugdb->u_slock);
	}

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

static int ugdb_set_events(struct ugdb_thread *thread,
				unsigned long events)
{
	events |= (UTRACE_EVENT(CLONE) | UTRACE_EVENT(DEATH));

	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)
{
	return utrace_control_pid(thread->t_spid, thread->t_engine,
					action);
}

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

	ret = ugdb_control(thread, UTRACE_DETACH);
	if (ret == -EINPROGRESS) {
		if (WARN_ON(!can_wait))
			goto put;
		utrace_barrier_pid(thread->t_spid, thread->t_engine);
	}
put:
	utrace_engine_put(thread->t_engine);
	/* to catch use-after-free */
	thread->t_engine = NULL;
}

static const struct utrace_engine_ops ugdb_utrace_ops;

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

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

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

	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 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->u_cur_tinfo = NULL;
	}
	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 ugdb_thread *ugdb_attach_main(struct ugdb *ugdb,
						struct ugdb_process *process)
{
	struct ugdb_thread *thread;
	struct pid *spid;

	spid = find_get_pid(process->p_pid);
	if (!spid)
		return NULL;

	thread = ugdb_attach_thread(process, spid);
	if (IS_ERR(thread))
		thread = NULL;

	put_pid(spid);

	return thread;
}

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

	task = pid_task(curr, PIDTYPE_PID);
	BUG_ON(!task);

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

		// XXX: BUG: if main is not group 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(&task->sighand->siglock);

	return next;
}

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

	// XXX: check if exists
	process = ugdb_create_process(ugdb, pid);
	if (!process)
		goto err;

	mutex_lock(&ugdb->u_mutex);

	// XXX: check if group leader ?
	thread = ugdb_attach_main(ugdb, process);
	if (!thread)
		goto abort;

	main_pid = thread->t_spid;
	curr_pid = main_pid;
	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;
	}

	// XXX mark it just attached

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

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

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 (pid && process->p_pid != pid)
			continue;

		list_for_each_entry(thread, &process->p_threads, t_threads) {
			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_cur_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 (!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 (!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)
{
	struct ugdb *ugdb = thread->t_ugdb;
	bool ret = false;

	ugdb_ck_stopped(ugdb);

	spin_lock(&ugdb->u_slock);

	WARN_ON(thread->t_stop_state & T_STOP_ACK);
	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;

	ret = true;
	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);
	}
unlock:
	spin_unlock(&ugdb->u_slock);

	return ret;
}

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

	ugdb_ck_stopped(ugdb);

	if (thread->t_stop_state != T_STOP_RUN) {
		/*
		 * (gdb) interrupt &
		 * (gbd) interrupt -a &
		 *
		 * make sure -a actually works if it races with clone.
		 */
		if (all && !(thread->t_stop_state & T_STOP_ALL)) {
			/*
			 * We hold ugdb->u_mutex, so we can't race with
			 * ugdb_report_clone(). But we need spinlock to
			 * avoid the race with ugdb_add_stopped() which
			 * can change ->t_stop_state in parallel.
			 */
			spin_lock(&ugdb->u_slock);
			thread->t_stop_state |= T_STOP_ALL;
			spin_unlock(&ugdb->u_slock);
		}

		return 0;
	}

	// XXX: currently we can do this lockless ...
	thread->t_stop_state = all ? (T_STOP_REQ | T_STOP_ALL) : T_STOP_REQ;

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

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

	return true;
}

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

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

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

	mutex_lock(&ugdb->u_mutex);
	if (process->p_state & P_DETACHING)
		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;
	bool is_main;

	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_cur_tinfo(ugdb);

	is_main = (process->p_pid == thread->t_tid);
	ugdb_destroy_thread(thread);

	if (is_main)
		; // XXX: DAMN!!!

unlock:
	mutex_unlock(&ugdb->u_mutex);

	return UTRACE_DETACH;
}

static const struct utrace_engine_ops ugdb_utrace_ops = {
	.report_quiesce	= ugdb_report_quiesce,
	.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) {
		WARN_ON(*pkt == '$' || *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 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;
}

// -----------------------------------------------------------------------------
static int ugdb_report_stopped(struct ugdb *ugdb, bool async)
{
	struct ugdb_thread *thread;
	const char *stop = "T00";
	int pid, tid;
	int ret = 0;

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

	pid = thread->t_process->p_pid;
	tid = thread->t_tid;

	ret = 1;
unlock:
	mutex_unlock(&ugdb->u_mutex);

	if (ret) {
		struct pbuf *pb = &ugdb->u_pbuf;

		if (async) {
			__pb_start(pb, '%');
			pb_puts(pb, "Stop:");
		} else {
			pb_start(pb);
		}

		pb_printf(pb, "%sthread:p%x.%x;", stop, pid, tid);
		pb_end(pb);
	}

	return ret;
}

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);
	thread = ugdb_advance_cur_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 (pid > 0 && process->p_pid != pid)
			continue;

		list_for_each_entry(thread, &process->p_threads, t_threads) {
			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 const char *handle_vcont(struct ugdb *ugdb, char *cmd)
{
	int pid, tid;
	void *arg;

	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);
	ugdb_do_each_thread(ugdb, pid, tid, do_stop_thread, arg);
	mutex_unlock(&ugdb->u_mutex);

	return "OK";
}

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

	mutex_lock(&ugdb->u_mutex);
	if (ugdb->u_cur_hc)
		if (ugdb_cont_thread(ugdb->u_cur_hc, false) > 0)
			rc = "OK";
	mutex_unlock(&ugdb->u_mutex);

	return rc;
}

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

	task = thread_to_task(thread);

	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)
{
	struct ugdb_thread *thread = ugdb->u_cur_hg;
	struct task_struct *task = thread_to_task(thread);

	int 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 *apvm(struct ugdb *ugdb, struct task_struct *task,
			unsigned long addr, int size)
{
	unsigned char *mbuf;

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

	size = u_access_process_vm(task, addr, mbuf, size, 0);
	if (size <= 0)
		goto err;

	pb_start(&ugdb->u_pbuf);
	pb_putbs(&ugdb->u_pbuf, mbuf, size);
	pb_end(&ugdb->u_pbuf);
	return NULL;
err:
	return "E01";
}

static const char *handle_readmem(struct ugdb *ugdb, char *cmd)
{
	struct utrace_examiner exam;
	struct task_struct *task;
	unsigned long addr, size;
	const char *ret = "E01";

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

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

	ret = apvm(ugdb, task, addr, size);

	/* Too late to report the error*/
	if (ugdb_finish_examine(ugdb, &exam))
		;
out:
	return ret;
}

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

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 '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+");
			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, "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";
		}

		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_cur_tinfo(ugdb);
	if (thread)
		tid = thread->t_tid;
	mutex_unlock(&ugdb->u_mutex);

	return tid;
}

static int xxx_stop(struct ugdb *ugdb, int tid)
{
	struct ugdb_thread *thread = ugdb_find_thread(ugdb, 0, tid);

	if (!thread)
		return -ESRCH;

	return ugdb_stop_thread(thread, false);

}
static int xxx_cont(struct ugdb *ugdb, int tid)
{
	struct ugdb_thread *thread = ugdb_find_thread(ugdb, 0, tid);

	if (!thread)
		return -ESRCH;

	return ugdb_cont_thread(thread, true);

}

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 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;
	}

	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);
	}

	count = pb_copy_to_user(pb, ubuf, count);
	if (count > 0)
		*ppos += count;
	return 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);

	*ppos += count;
	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] 20+ messages in thread

* Re: gdbstub initial code, v3
  2010-08-12  0:01 gdbstub initial code, v3 Oleg Nesterov
@ 2010-08-12  1:14 ` Oleg Nesterov
  2010-08-12  2:40   ` Oleg Nesterov
                     ` (2 more replies)
  0 siblings, 3 replies; 20+ messages in thread
From: Oleg Nesterov @ 2010-08-12  1:14 UTC (permalink / raw)
  To: archer, utrace-devel

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

On 08/12, Oleg Nesterov wrote:
>
> Problems:
>
> 	- It doesn't support all-stop mode.
>
> 	  Please tell me if this is needed. I hope not, this needs
> 	  a lot of nasty complications :/
>
> 	- We should discuss utrace limitations: UTRACE_RESUME
> 	  races/problems, utrace_prepare_examine() issues.
>
> 	- I just finished this code, and it doesn't work with gdb ;)

Noticed the typo in ugdb.c, it needs this one-liner fix:

	--- x/ugdb.c
	+++ x/ugdb.c
	@@ -603,7 +603,7 @@ static int ugdb_cont_thread(struct ugdb_
		ugdb_ck_stopped(ugdb);
	 
		// XXX: gdb shouldn't explicitly cont an unreported thread
	-	WARN_ON(!all && !(thread->t_stop_state == T_STOP_STOPPED));
	+	WARN_ON(!all && !(thread->t_stop_state & T_STOP_STOPPED));
	 
		if (thread->t_stop_state == T_STOP_RUN)
			return 0;

I attached the patched ugdb.c.

> 	  I will investigate tomorrow, but I am almost sure gdb is
> 	  wrong.

Yes, this seems to be true... Tomorrow.

Oleg.

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

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

static inline void ugdb_ck_stopped(struct ugdb *ugdb)
{
	// XXX: temporary racy check
	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);
}

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)

struct ugdb_process {
	int			p_pid;
	int			p_state;

	struct list_head	p_threads;

	struct ugdb		*p_ugdb;
	struct list_head	p_processes;
};

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

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

	process->p_pid = pid;
	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 ? */

struct ugdb_thread {
	int			t_tid;
	int			t_stop_state;

	struct ugdb		*t_ugdb;
	struct ugdb_process	*t_process;

	struct list_head	t_threads;
	struct list_head	t_stopped;

	struct pid		*t_spid;

	// create/attach border
	struct utrace_engine	*t_engine;
};

static inline struct task_struct *thread_to_task(struct ugdb_thread *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 inline void ugdb_del_stopped(struct ugdb *ugdb,
					struct ugdb_thread *thread)
{
	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;
	}
}

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

	ugdb_ck_stopped(ugdb);

	if (!list_empty(&thread->t_stopped)) {
		WARN_ON(!(thread->t_stop_state & T_STOP_ACK));
		spin_lock(&ugdb->u_slock);
		ugdb_del_stopped(ugdb, thread);
		spin_unlock(&ugdb->u_slock);
	}

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

static int ugdb_set_events(struct ugdb_thread *thread,
				unsigned long events)
{
	events |= (UTRACE_EVENT(CLONE) | UTRACE_EVENT(DEATH));

	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)
{
	return utrace_control_pid(thread->t_spid, thread->t_engine,
					action);
}

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

	ret = ugdb_control(thread, UTRACE_DETACH);
	if (ret == -EINPROGRESS) {
		if (WARN_ON(!can_wait))
			goto put;
		utrace_barrier_pid(thread->t_spid, thread->t_engine);
	}
put:
	utrace_engine_put(thread->t_engine);
	/* to catch use-after-free */
	thread->t_engine = NULL;
}

static const struct utrace_engine_ops ugdb_utrace_ops;

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

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

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

	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 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->u_cur_tinfo = NULL;
	}
	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 ugdb_thread *ugdb_attach_main(struct ugdb *ugdb,
						struct ugdb_process *process)
{
	struct ugdb_thread *thread;
	struct pid *spid;

	spid = find_get_pid(process->p_pid);
	if (!spid)
		return NULL;

	thread = ugdb_attach_thread(process, spid);
	if (IS_ERR(thread))
		thread = NULL;

	put_pid(spid);

	return thread;
}

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

	task = pid_task(curr, PIDTYPE_PID);
	BUG_ON(!task);

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

		// XXX: BUG: if main is not group 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(&task->sighand->siglock);

	return next;
}

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

	// XXX: check if exists
	process = ugdb_create_process(ugdb, pid);
	if (!process)
		goto err;

	mutex_lock(&ugdb->u_mutex);

	// XXX: check if group leader ?
	thread = ugdb_attach_main(ugdb, process);
	if (!thread)
		goto abort;

	main_pid = thread->t_spid;
	curr_pid = main_pid;
	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;
	}

	// XXX mark it just attached

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

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

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 (pid && process->p_pid != pid)
			continue;

		list_for_each_entry(thread, &process->p_threads, t_threads) {
			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_cur_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 (!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 (!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)
{
	struct ugdb *ugdb = thread->t_ugdb;
	bool ret = false;

	ugdb_ck_stopped(ugdb);

	spin_lock(&ugdb->u_slock);

	WARN_ON(thread->t_stop_state & T_STOP_ACK);
	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;

	ret = true;
	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);
	}
unlock:
	spin_unlock(&ugdb->u_slock);

	return ret;
}

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

	ugdb_ck_stopped(ugdb);

	if (thread->t_stop_state != T_STOP_RUN) {
		/*
		 * (gdb) interrupt &
		 * (gbd) interrupt -a &
		 *
		 * make sure -a actually works if it races with clone.
		 */
		if (all && !(thread->t_stop_state & T_STOP_ALL)) {
			/*
			 * We hold ugdb->u_mutex, so we can't race with
			 * ugdb_report_clone(). But we need spinlock to
			 * avoid the race with ugdb_add_stopped() which
			 * can change ->t_stop_state in parallel.
			 */
			spin_lock(&ugdb->u_slock);
			thread->t_stop_state |= T_STOP_ALL;
			spin_unlock(&ugdb->u_slock);
		}

		return 0;
	}

	// XXX: currently we can do this lockless ...
	thread->t_stop_state = all ? (T_STOP_REQ | T_STOP_ALL) : T_STOP_REQ;

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

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

	return true;
}

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

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

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

	mutex_lock(&ugdb->u_mutex);
	if (process->p_state & P_DETACHING)
		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;
	bool is_main;

	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_cur_tinfo(ugdb);

	is_main = (process->p_pid == thread->t_tid);
	ugdb_destroy_thread(thread);

	if (is_main)
		; // XXX: DAMN!!!

unlock:
	mutex_unlock(&ugdb->u_mutex);

	return UTRACE_DETACH;
}

static const struct utrace_engine_ops ugdb_utrace_ops = {
	.report_quiesce	= ugdb_report_quiesce,
	.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) {
		WARN_ON(*pkt == '$' || *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 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;
}

// -----------------------------------------------------------------------------
static int ugdb_report_stopped(struct ugdb *ugdb, bool async)
{
	struct ugdb_thread *thread;
	const char *stop = "T00";
	int pid, tid;
	int ret = 0;

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

	pid = thread->t_process->p_pid;
	tid = thread->t_tid;

	ret = 1;
unlock:
	mutex_unlock(&ugdb->u_mutex);

	if (ret) {
		struct pbuf *pb = &ugdb->u_pbuf;

		if (async) {
			__pb_start(pb, '%');
			pb_puts(pb, "Stop:");
		} else {
			pb_start(pb);
		}

		pb_printf(pb, "%sthread:p%x.%x;", stop, pid, tid);
		pb_end(pb);
	}

	return ret;
}

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);
	thread = ugdb_advance_cur_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 (pid > 0 && process->p_pid != pid)
			continue;

		list_for_each_entry(thread, &process->p_threads, t_threads) {
			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 const char *handle_vcont(struct ugdb *ugdb, char *cmd)
{
	int pid, tid;
	void *arg;

	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);
	ugdb_do_each_thread(ugdb, pid, tid, do_stop_thread, arg);
	mutex_unlock(&ugdb->u_mutex);

	return "OK";
}

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

	mutex_lock(&ugdb->u_mutex);
	if (ugdb->u_cur_hc)
		if (ugdb_cont_thread(ugdb->u_cur_hc, false) > 0)
			rc = "OK";
	mutex_unlock(&ugdb->u_mutex);

	return rc;
}

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

	task = thread_to_task(thread);

	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)
{
	struct ugdb_thread *thread = ugdb->u_cur_hg;
	struct task_struct *task = thread_to_task(thread);

	int 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 *apvm(struct ugdb *ugdb, struct task_struct *task,
			unsigned long addr, int size)
{
	unsigned char *mbuf;

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

	size = u_access_process_vm(task, addr, mbuf, size, 0);
	if (size <= 0)
		goto err;

	pb_start(&ugdb->u_pbuf);
	pb_putbs(&ugdb->u_pbuf, mbuf, size);
	pb_end(&ugdb->u_pbuf);
	return NULL;
err:
	return "E01";
}

static const char *handle_readmem(struct ugdb *ugdb, char *cmd)
{
	struct utrace_examiner exam;
	struct task_struct *task;
	unsigned long addr, size;
	const char *ret = "E01";

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

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

	ret = apvm(ugdb, task, addr, size);

	/* Too late to report the error*/
	if (ugdb_finish_examine(ugdb, &exam))
		;
out:
	return ret;
}

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

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 '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+");
			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, "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";
		}

		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_cur_tinfo(ugdb);
	if (thread)
		tid = thread->t_tid;
	mutex_unlock(&ugdb->u_mutex);

	return tid;
}

static int xxx_stop(struct ugdb *ugdb, int tid)
{
	struct ugdb_thread *thread = ugdb_find_thread(ugdb, 0, tid);

	if (!thread)
		return -ESRCH;

	return ugdb_stop_thread(thread, false);

}
static int xxx_cont(struct ugdb *ugdb, int tid)
{
	struct ugdb_thread *thread = ugdb_find_thread(ugdb, 0, tid);

	if (!thread)
		return -ESRCH;

	return ugdb_cont_thread(thread, true);

}

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 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;
	}

	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);
	}

	count = pb_copy_to_user(pb, ubuf, count);
	if (count > 0)
		*ppos += count;
	return 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);

	*ppos += count;
	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] 20+ messages in thread

* Re: gdbstub initial code, v3
  2010-08-12  1:14 ` Oleg Nesterov
@ 2010-08-12  2:40   ` Oleg Nesterov
  2010-08-12  4:37     ` Tom Tromey
  2010-08-13  1:02     ` gdbstub initial code, v3 Roland McGrath
  2010-08-12 14:52   ` Kevin Buettner
  2010-08-12 15:06   ` Frank Ch. Eigler
  2 siblings, 2 replies; 20+ messages in thread
From: Oleg Nesterov @ 2010-08-12  2:40 UTC (permalink / raw)
  To: archer, utrace-devel

On 08/12, Oleg Nesterov wrote:
>
> > 	  I will investigate tomorrow, but I am almost sure gdb is
> > 	  wrong.
>
> Yes, this seems to be true... Tomorrow.

Can't sleep because of this problem ;)

So, the patch below fixes the problem, and gdb + /proc/ugdb seems
to work.

Indeed, gdb sees that this fd is not pipe/tcp and uses the "hardwire"
serial_ops, but hardwire_readchar() doesn't play well with select().

Please teach gdb to use poll/select ?

Oleg.

--- gdb-7.1/gdb/ser-unix.c
+++ gdb-7.1/gdb/ser-unix.c
@@ -452,7 +452,7 @@ hardwire_raw (struct serial *scb)
 static int
 wait_for (struct serial *scb, int timeout)
 {
-#ifdef HAVE_SGTTY
+#if 1
   while (1)
     {
       struct timeval tv;
@@ -474,13 +474,14 @@ wait_for (struct serial *scb, int timeou
       else
 	numfds = gdb_select (scb->fd + 1, &readfds, 0, 0, 0);
 
-      if (numfds <= 0)
+      if (numfds <= 0) {
 	if (numfds == 0)
 	  return SERIAL_TIMEOUT;
 	else if (errno == EINTR)
 	  continue;
 	else
 	  return SERIAL_ERROR;	/* Got an error from select or poll */
+      }
 
       return 0;
     }

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

* Re: gdbstub initial code, v3
  2010-08-12  2:40   ` Oleg Nesterov
@ 2010-08-12  4:37     ` Tom Tromey
  2010-08-12 23:55       ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
  2010-08-13  1:02     ` gdbstub initial code, v3 Roland McGrath
  1 sibling, 1 reply; 20+ messages in thread
From: Tom Tromey @ 2010-08-12  4:37 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: archer, utrace-devel

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

Oleg> So, the patch below fixes the problem, and gdb + /proc/ugdb seems
Oleg> to work.

Oleg> Indeed, gdb sees that this fd is not pipe/tcp and uses the "hardwire"
Oleg> serial_ops, but hardwire_readchar() doesn't play well with select().

Oleg> Please teach gdb to use poll/select ?

I looked at this a little bit.  It seems to me that the "hardwire" stuff
is for talking to ttys, and we instead want gdb to be using the pipe code.
Maybe a little hack to stat the open_name in the hardwire case (in
serial_open) would be appropriate.

Tom

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

* Re: gdbstub initial code, v3
  2010-08-12  1:14 ` Oleg Nesterov
  2010-08-12  2:40   ` Oleg Nesterov
@ 2010-08-12 14:52   ` Kevin Buettner
  2010-08-13  0:53     ` Roland McGrath
  2010-08-12 15:06   ` Frank Ch. Eigler
  2 siblings, 1 reply; 20+ messages in thread
From: Kevin Buettner @ 2010-08-12 14:52 UTC (permalink / raw)
  To: archer

On Thu, 12 Aug 2010 03:11:13 +0200
Oleg Nesterov <oleg@redhat.com> wrote:

> > 	- It doesn't support all-stop mode.
> >
> > 	  Please tell me if this is needed. I hope not, this needs
> > 	  a lot of nasty complications :/

I'm pretty sure this will be needed since it's GDB's default mode
of operation.

> I attached the patched ugdb.c.

I've skimmed the code.  If I'm not mistaken, support for the 's', 'G',
and 'M' commands isn't implemented yet?  (That's single-step, write
registers, and write memory, all of which must be supported by a stub.)

Kevin

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

* Re: gdbstub initial code, v3
  2010-08-12  1:14 ` Oleg Nesterov
  2010-08-12  2:40   ` Oleg Nesterov
  2010-08-12 14:52   ` Kevin Buettner
@ 2010-08-12 15:06   ` Frank Ch. Eigler
  2 siblings, 0 replies; 20+ messages in thread
From: Frank Ch. Eigler @ 2010-08-12 15:06 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: archer, utrace-devel


oleg wrote:

> [...]
> 	- It doesn't support all-stop mode.
> 	  Please tell me if this is needed. I hope not, this needs
> 	  a lot of nasty complications :/
> [...]

As opposed to non-stop mode?  I'm pretty sure all-stop will be needed
as that is the common & default gdb usage model.


- FChE

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

* problems with v3 (Was: gdbstub initial code, v3)
  2010-08-12  4:37     ` Tom Tromey
@ 2010-08-12 23:55       ` Oleg Nesterov
  2010-08-13  1:45         ` problems with v3 Tom Tromey
  2010-08-13 20:56         ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
  0 siblings, 2 replies; 20+ messages in thread
From: Oleg Nesterov @ 2010-08-12 23:55 UTC (permalink / raw)
  To: Tom Tromey; +Cc: archer, utrace-devel

On 08/11, Tom Tromey wrote:
>
> >>>>> "Oleg" == Oleg Nesterov <oleg@redhat.com> writes:
>
> Oleg> So, the patch below fixes the problem, and gdb + /proc/ugdb seems
> Oleg> to work.
>
> Oleg> Indeed, gdb sees that this fd is not pipe/tcp and uses the "hardwire"
> Oleg> serial_ops, but hardwire_readchar() doesn't play well with select().
>
> Oleg> Please teach gdb to use poll/select ?
>
> I looked at this a little bit.  It seems to me that the "hardwire" stuff
> is for talking to ttys, and we instead want gdb to be using the pipe code.

I didn't verify this, but I don't think so. Please look at pipe_open().
Perhaps it makes sense to serial_add_interface("ugdb"), I dunno.


OK. I was going to add some cleanups and send the new version today.
But after some testing (without gdb) I hit the kernel crashes. This
makes me think that probably something is wrong ;)

As usual, I can't blame my code. I am still investigating, the crash
is not easy to reproduce, but so far I _suspect_ the problems in utrace
code. At least utrace_barrier()->signal_pending() is definitely not
right.

Will continue tomorrow.

Oleg.

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

* Re: gdbstub initial code, v3
  2010-08-12 14:52   ` Kevin Buettner
@ 2010-08-13  0:53     ` Roland McGrath
  2010-08-13  3:31       ` Kevin Buettner
  0 siblings, 1 reply; 20+ messages in thread
From: Roland McGrath @ 2010-08-13  0:53 UTC (permalink / raw)
  To: Kevin Buettner; +Cc: archer

> > > 	- It doesn't support all-stop mode.
> > >
> > > 	  Please tell me if this is needed. I hope not, this needs
> > > 	  a lot of nasty complications :/
> 
> I'm pretty sure this will be needed since it's GDB's default mode
> of operation.

That is indeed most likely the mode in which the gdb side of things has had
the most testing.  So starting with non-stop may open more gdb pain while
avoiding some pain in the kernel module.

> I've skimmed the code.  If I'm not mistaken, support for the 's', 'G',
> and 'M' commands isn't implemented yet?  (That's single-step, write
> registers, and write memory, all of which must be supported by a stub.)

Yes, that's fine.  For a starting place, having only reading support is OK.
These bits are relatively trivial to fill in later as needed, so I think
made plenty of sense for Oleg not to include them yet.


Thanks,
Roland

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

* Re: gdbstub initial code, v3
  2010-08-12  2:40   ` Oleg Nesterov
  2010-08-12  4:37     ` Tom Tromey
@ 2010-08-13  1:02     ` Roland McGrath
  1 sibling, 0 replies; 20+ messages in thread
From: Roland McGrath @ 2010-08-13  1:02 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: archer, utrace-devel

> Indeed, gdb sees that this fd is not pipe/tcp and uses the "hardwire"
> serial_ops, but hardwire_readchar() doesn't play well with select().
> 
> Please teach gdb to use poll/select ?

If it makes it easier, could use:

	bash$ nc -l -U /tmp/socket <> /proc/ugdb &
	(gdb) target remote |nc -U /tmp/socket

for the moment.  Silly of course, but just not to be blocked on cleaning up
gdb's serial-device handling to be more nicely nonblocking.


Thanks,
Roland

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

* Re: problems with v3
  2010-08-12 23:55       ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
@ 2010-08-13  1:45         ` Tom Tromey
  2010-08-13  1:59           ` Roland McGrath
  2010-08-13 20:56         ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
  1 sibling, 1 reply; 20+ messages in thread
From: Tom Tromey @ 2010-08-13  1:45 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: archer, utrace-devel

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

Tom> I looked at this a little bit.  It seems to me that the "hardwire" stuff
Tom> is for talking to ttys, and we instead want gdb to be using the pipe code.

Oleg> I didn't verify this, but I don't think so. Please look at pipe_open().

Yeah, I wasn't clear enough.  I meant a hybrid with most code coming
from ser-pipe.c.

Oleg> Perhaps it makes sense to serial_add_interface("ugdb"), I dunno.

I started a quick implementation of my idea, I'll finish it tomorrow.
I think we should start an archer branch to hold whatever gdb hacks we
need... I will do that tomorrow as well.

Tom

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

* Re: problems with v3
  2010-08-13  1:45         ` problems with v3 Tom Tromey
@ 2010-08-13  1:59           ` Roland McGrath
  2010-08-13  4:37             ` Tom Tromey
  0 siblings, 1 reply; 20+ messages in thread
From: Roland McGrath @ 2010-08-13  1:59 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Oleg Nesterov, archer, utrace-devel

I don't really know the gdb code, but I'm surprised it really has multiple
different "serial" backends.  I would have thought that after opening the
fd, you would treat them all about the same.  If tcsetattr et al work on
it, then you set the baud rate and whatever, if they say ENOTTY then fine.
It might make sense to default to a short read timeout for an actual serial
port (or UDP port), which might be unplugged or the other end dead, or
whatever; and to an infinite timeout for a non-tty, which should more
presumably have its fd shut down and read EOF or EPIPE or whatever when the
stub goes away, and otherwise perhaps the stub is just taking that long.
But aside from that, I don't know why you wouldn't treat all kinds of
remote protocol fd's the same wrt select/poll and blocking/nonblocking
reads/writes, be they serial-port fds, "pipe" socketpair sockets, network
sockets, or fds to a new magic file that pretends to be a tty or a socket
or whatever (or even a disk file of canned response playback!).


Thanks,
Roland

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

* Re: gdbstub initial code, v3
  2010-08-13  0:53     ` Roland McGrath
@ 2010-08-13  3:31       ` Kevin Buettner
  0 siblings, 0 replies; 20+ messages in thread
From: Kevin Buettner @ 2010-08-13  3:31 UTC (permalink / raw)
  To: archer

On Thu, 12 Aug 2010 17:53:38 -0700 (PDT)
Roland McGrath <roland@redhat.com> wrote:

> > I've skimmed the code.  If I'm not mistaken, support for the 's', 'G',
> > and 'M' commands isn't implemented yet?  (That's single-step, write
> > registers, and write memory, all of which must be supported by a stub.)
> 
> Yes, that's fine.  For a starting place, having only reading support is OK.
> These bits are relatively trivial to fill in later as needed, so I think
> made plenty of sense for Oleg not to include them yet.

It certainly makes sense for Oleg to get communication between the
stub and gdb sorted out first.  As another early step, it also makes
sense to attempt to stop the process and determine its status.

Once that's sorted though, I should think that memory write and
single-step will become important to implement.  The 'M' (memory
write) packet may be used by GDB for inserting and removing
breakpoints.  (Or, alternately, Z0/z0 packets may be used.  I'd
rather see 'M' implemented first though.)  Single-step, whether
implemented as an 's' packet or 'vCont;s' packet, will be needed in
order to continue from a breakpoint as well as for single-stepping.

Implementation of the 'G' command (register write) is probably the
least important of the three that I mentioned earlier.

Kevin

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

* Re: problems with v3
  2010-08-13  1:59           ` Roland McGrath
@ 2010-08-13  4:37             ` Tom Tromey
  2010-08-13  8:30               ` Roland McGrath
  2010-08-13 14:08               ` Daniel Jacobowitz
  0 siblings, 2 replies; 20+ messages in thread
From: Tom Tromey @ 2010-08-13  4:37 UTC (permalink / raw)
  To: Roland McGrath; +Cc: Oleg Nesterov, archer, utrace-devel

Roland> I don't really know the gdb code, but I'm surprised it really
Roland> has multiple different "serial" backends.

I don't know this area well, but considering that ser-unix.c is just
chock full of tty-related goo, I think it is probably important for
something.  My impression is that this API is not just used for target
communication but also for manipulating gdb's own terminal.

I looked closer and aside from open/close, ser-pipe is just falling back
to some generic code.

I've appended the patch I came up with.  I have not tried it at all, but
it should all be pretty obvious.

Maybe I'd do it a little differently if this were going upstream.
That doesn't seem necessary though.

Tom

diff --git a/gdb/ser-unix.c b/gdb/ser-unix.c
index 266453c..672b2a8 100644
--- a/gdb/ser-unix.c
+++ b/gdb/ser-unix.c
@@ -32,6 +32,7 @@
 #include "gdb_select.h"
 #include "gdb_string.h"
 #include "gdbcmd.h"
+#include "gdb_stat.h"
 
 #ifdef HAVE_TERMIOS
 
@@ -103,15 +104,56 @@ static int hardwire_setstopbits (struct serial *, int);
 
 void _initialize_ser_hardwire (void);
 
+static struct serial_ops *
+get_pipe_like_ops (void)
+{
+  static struct serial_ops *ops;
+  
+  if (ops == NULL)
+    {
+      ops = XMALLOC (struct serial_ops);
+      memset (ops, 0, sizeof (struct serial_ops));
+      ops->name = NULL;
+      ops->open = NULL;
+      ops->close = hardwire_close;
+      ops->readchar = ser_base_readchar;
+      ops->write = ser_base_write;
+      ops->flush_output = ser_base_flush_output;
+      ops->flush_input = ser_base_flush_input;
+      ops->send_break = ser_base_send_break;
+      ops->go_raw = ser_base_raw;
+      ops->get_tty_state = ser_base_get_tty_state;
+      ops->set_tty_state = ser_base_set_tty_state;
+      ops->print_tty_state = ser_base_print_tty_state;
+      ops->noflush_set_tty_state = ser_base_noflush_set_tty_state;
+      ops->setbaudrate = ser_base_setbaudrate;
+      ops->setstopbits = ser_base_setstopbits;
+      ops->drain_output = ser_base_drain_output;
+      ops->async = ser_base_async;
+      ops->read_prim = ser_unix_read_prim;
+      ops->write_prim = ser_unix_write_prim;
+    }
+
+  return ops;
+}
+
 /* Open up a real live device for serial I/O */
 
 static int
 hardwire_open (struct serial *scb, const char *name)
 {
+  struct stat buf;
+
   scb->fd = open (name, O_RDWR);
   if (scb->fd < 0)
     return -1;
 
+  if (fstat (scb->fd, &buf) == 0 && S_ISFIFO (buf.st_mode))
+    {
+      /* Super hack!  */
+      scb->ops = get_pipe_like_ops ();
+    }
+
   return 0;
 }
 

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

* Re: problems with v3
  2010-08-13  4:37             ` Tom Tromey
@ 2010-08-13  8:30               ` Roland McGrath
  2010-08-13 16:31                 ` Tom Tromey
  2010-08-13 14:08               ` Daniel Jacobowitz
  1 sibling, 1 reply; 20+ messages in thread
From: Roland McGrath @ 2010-08-13  8:30 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Oleg Nesterov, archer, utrace-devel

> I don't know this area well, but considering that ser-unix.c is just
> chock full of tty-related goo, I think it is probably important for
> something.  My impression is that this API is not just used for target
> communication but also for manipulating gdb's own terminal.

Ah, I see.

> I've appended the patch I came up with.  I have not tried it at all, but
> it should all be pretty obvious.

Yeah, that seems like it should be reasonable, i.e. more or less like what
I'd figured it would do if it didn't have all these different backends.

However, I think the test you probably want is !S_ISCHR.
I believe that /proc/ugdb as is stats as S_ISREG, not S_ISFIFO.
Actually, better yet, just make it !isatty (fd).
Another pseudodevice that also behaves more like a socket than
like a tty might be S_ISCHR, but only a tty isatty.


Thanks,
Roland

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

* Re: problems with v3
  2010-08-13  4:37             ` Tom Tromey
  2010-08-13  8:30               ` Roland McGrath
@ 2010-08-13 14:08               ` Daniel Jacobowitz
  1 sibling, 0 replies; 20+ messages in thread
From: Daniel Jacobowitz @ 2010-08-13 14:08 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Roland McGrath, Oleg Nesterov, archer, utrace-devel

On Thu, Aug 12, 2010 at 10:37:18PM -0600, Tom Tromey wrote:
> Roland> I don't really know the gdb code, but I'm surprised it really
> Roland> has multiple different "serial" backends.
> 
> I don't know this area well, but considering that ser-unix.c is just
> chock full of tty-related goo, I think it is probably important for
> something.  My impression is that this API is not just used for target
> communication but also for manipulating gdb's own terminal.

Correct.  Also, the abstraction layer is important on non-Unix
systems; "everything is a file descriptor" works poorly on Windows :-)

-- 
Daniel Jacobowitz
CodeSourcery

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

* Re: problems with v3
  2010-08-13  8:30               ` Roland McGrath
@ 2010-08-13 16:31                 ` Tom Tromey
  2010-08-13 21:15                   ` Oleg Nesterov
  0 siblings, 1 reply; 20+ messages in thread
From: Tom Tromey @ 2010-08-13 16:31 UTC (permalink / raw)
  To: Roland McGrath; +Cc: Oleg Nesterov, archer, utrace-devel

Roland> However, I think the test you probably want is !S_ISCHR.

Thanks.  I wasn't sure.

Roland> Actually, better yet, just make it !isatty (fd).

Ok.

I made a new branch, 'archer-ugdb'.
This patch is there.  (Actually 2 patches due to me not reading
carefully enough the first time -- but whatever.)

Oleg, use this branch and give the feature a try.
We can push whatever fixes or hacks you need to this branch.
I can also merge from gdb's master as needed.

Tom

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

* Re: problems with v3 (Was: gdbstub initial code, v3)
  2010-08-12 23:55       ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
  2010-08-13  1:45         ` problems with v3 Tom Tromey
@ 2010-08-13 20:56         ` Oleg Nesterov
  2010-08-13 21:31           ` Roland McGrath
  1 sibling, 1 reply; 20+ messages in thread
From: Oleg Nesterov @ 2010-08-13 20:56 UTC (permalink / raw)
  To: Tom Tromey; +Cc: archer, utrace-devel

On 08/13, Oleg Nesterov wrote:
>
> As usual, I can't blame my code. I am still investigating, the crash
> is not easy to reproduce, but so far I _suspect_ the problems in utrace
> code. At least utrace_barrier()->signal_pending() is definitely not
> right.

I seem to understand the problem(s). I am a bit surprized. Basically
I have the questions about almost every utrace_ function. I'll try
to recheck and summarize my concerns tomorrow with a fresh head, I
hope I missed something.

And I think at least we should cleanup utrace_set_events(), I'll try
to send the patch a bit later.

Oleg.

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

* Re: problems with v3
  2010-08-13 16:31                 ` Tom Tromey
@ 2010-08-13 21:15                   ` Oleg Nesterov
  2010-08-13 21:18                     ` Tom Tromey
  0 siblings, 1 reply; 20+ messages in thread
From: Oleg Nesterov @ 2010-08-13 21:15 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Roland McGrath, archer, utrace-devel

On 08/13, Tom Tromey wrote:
>
> Roland> However, I think the test you probably want is !S_ISCHR.
>
> Thanks.  I wasn't sure.
>
> Roland> Actually, better yet, just make it !isatty (fd).
>
> Ok.

Good. IIUC, this also allows to remove the ugly "return 0
to pretend TCGETS/TCSETS/TCFLSH works" from ->ioctl().

> I made a new branch, 'archer-ugdb'.

I assume, you mean git://sourceware.org/git/gdb.git ?

> This patch is there.  (Actually 2 patches due to me not reading
> carefully enough the first time -- but whatever.)
>
> Oleg, use this branch and give the feature a try.

Yes, once I resolve the current problems I'll test it.

Thanks!

Oleg.

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

* Re: problems with v3
  2010-08-13 21:15                   ` Oleg Nesterov
@ 2010-08-13 21:18                     ` Tom Tromey
  0 siblings, 0 replies; 20+ messages in thread
From: Tom Tromey @ 2010-08-13 21:18 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: Roland McGrath, archer, utrace-devel

Tom> I made a new branch, 'archer-ugdb'.

Oleg> I assume, you mean git://sourceware.org/git/gdb.git ?

git://sourceware.org/git/archer.git

I can give you write access to this repository if you want.
Just let me know.

Tom

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

* Re: problems with v3 (Was: gdbstub initial code, v3)
  2010-08-13 20:56         ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
@ 2010-08-13 21:31           ` Roland McGrath
  0 siblings, 0 replies; 20+ messages in thread
From: Roland McGrath @ 2010-08-13 21:31 UTC (permalink / raw)
  To: Oleg Nesterov; +Cc: Tom Tromey, utrace-devel, archer

> I seem to understand the problem(s). I am a bit surprized. Basically
> I have the questions about almost every utrace_ function. I'll try
> to recheck and summarize my concerns tomorrow with a fresh head, I
> hope I missed something.

Ok.  That stuff about pure kernel implementation issues doesn't need
to go to the archer list and Tom, only to utrace-devel.


Thanks,
Roland

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

end of thread, other threads:[~2010-08-13 21:31 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-08-12  0:01 gdbstub initial code, v3 Oleg Nesterov
2010-08-12  1:14 ` Oleg Nesterov
2010-08-12  2:40   ` Oleg Nesterov
2010-08-12  4:37     ` Tom Tromey
2010-08-12 23:55       ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
2010-08-13  1:45         ` problems with v3 Tom Tromey
2010-08-13  1:59           ` Roland McGrath
2010-08-13  4:37             ` Tom Tromey
2010-08-13  8:30               ` Roland McGrath
2010-08-13 16:31                 ` Tom Tromey
2010-08-13 21:15                   ` Oleg Nesterov
2010-08-13 21:18                     ` Tom Tromey
2010-08-13 14:08               ` Daniel Jacobowitz
2010-08-13 20:56         ` problems with v3 (Was: gdbstub initial code, v3) Oleg Nesterov
2010-08-13 21:31           ` Roland McGrath
2010-08-13  1:02     ` gdbstub initial code, v3 Roland McGrath
2010-08-12 14:52   ` Kevin Buettner
2010-08-13  0:53     ` Roland McGrath
2010-08-13  3:31       ` Kevin Buettner
2010-08-12 15:06   ` Frank Ch. Eigler

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).