public inbox for archer@sourceware.org
 help / color / mirror / Atom feed
From: Oleg Nesterov <oleg@redhat.com>
To: Roland McGrath <roland@redhat.com>
Cc: archer@sourceware.org, utrace-devel@redhat.com
Subject: gdbstub initial code, another approach
Date: Wed, 28 Jul 2010 18:19:00 -0000	[thread overview]
Message-ID: <20100728181702.GA26678@redhat.com> (raw)
In-Reply-To: <20100726142759.GA17171@redhat.com>

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

On 07/26, Oleg Nesterov wrote:
>
> I decided to take a bit different approach, we will see if it
> makes sense in the longer term.

Please see the attached files,

	- ugdb.c

		The kernel module which implements the basic
		user-space API on top of utrace. Of course,
		this API should be discussed.

	- gdbstub

		The simple user-space gdbserver written in
		perl which works with ugdb API.

Limitations:

	- this is just initial code, again.

	- doesn't work in all-stop mode (should be simple to
	  implement).

	- currently it only supports attach, stop, cont, detach
	  and exit.

	- the testing was very limited. I played with it about
	  an hour and didn't find any problems, vut that is all.

However, please note that this time the code is clearly opened
for improvements.

I stronly believe this is the only sane approach. Even for
prototyping. No, _especially_ for prototyping!

Btw, gdb crashes very often right after

	(gdb) set target-async on
	(gdb) set non-stop
	(gdb) file mt-program
	(gdb) target extended-remote :port
	(gdb) attach its_pid

I didn't even try to investigate (this doesn't happen when
it works with the real gdbserver). Just retry, gdb is buggy.

What do you think?

Oleg.

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

struct ugdb_thread {
	int			t_tid;

	int			t_stop;
	int			t_exit;

	struct pid		*t_spid;
	struct ugdb		*t_ugdb;
	struct utrace_engine	*t_engine;

	struct list_head	t_threads;
	struct list_head	t_stopped;
};

struct ugdb {
	spinlock_t		u_llock;
	struct list_head	u_threads;
	struct list_head	u_stopped;

	wait_queue_head_t	u_wait;
};

// XXX: gdb is single-thread, no locking currently.
#define T printk(KERN_INFO "TRACE: %s:%d\n", __FUNCTION__, __LINE__)

static struct ugdb_thread *ugdb_create_thread(struct ugdb *ugdb, int tid)
{
	struct pid *spid;
	struct ugdb_thread *thread;
	int err;

	err = -ESRCH;
	spid = find_get_pid(tid);
	if (!spid)
		goto err;

	err = -ENOMEM;
	thread = kzalloc(sizeof(*thread), GFP_KERNEL);
	if (!thread)
		goto free_pid;

	thread->t_tid = tid;

	thread->t_spid = spid;
	thread->t_ugdb = ugdb;

	INIT_LIST_HEAD(&thread->t_stopped);
	spin_lock(&ugdb->u_llock);
	list_add_tail(&thread->t_threads, &ugdb->u_threads);
	spin_unlock(&ugdb->u_llock);

	return thread;

free_pid:
	put_pid(spid);
err:
	return ERR_PTR(err);
}

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

	spin_lock(&ugdb->u_llock);
	list_del(&thread->t_stopped);
	list_del(&thread->t_threads);
	spin_unlock(&ugdb->u_llock);

	put_pid(thread->t_spid);
	kfree(thread);
}

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

	spin_lock(&ugdb->u_llock);
	list_for_each_entry(thread, &ugdb->u_threads, t_threads) {
		if (thread->t_tid == tid)
			goto found;
	}
	thread = NULL;
found:
	spin_unlock(&ugdb->u_llock);

	return thread;
}

static int ugdb_set_events(struct ugdb_thread *thread,
				unsigned long events)
{
	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 const struct utrace_engine_ops ugdb_utrace_ops;

static struct ugdb_thread *ugdb_attach_thread(struct ugdb *ugdb, int tid)
{
	struct ugdb_thread *thread;
	void *errp;
	int err;

	thread = ugdb_create_thread(ugdb, tid);
	if (IS_ERR(thread)) {
		errp = 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)) {
		errp = thread->t_engine;
		goto free_thread;
	}

	err = ugdb_set_events(thread,
			UTRACE_EVENT(QUIESCE) | UTRACE_EVENT(DEATH));
	if (err) {
		errp = ERR_PTR(-ESRCH);
		goto free_engine;
	}

	return thread;

free_engine:
	ugdb_control(thread, UTRACE_DETACH);
	utrace_engine_put(thread->t_engine);
free_thread:
	ugdb_destroy_thread(thread);
err:
	return errp;
}

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

	ret = ugdb_control(thread, UTRACE_DETACH);
	if (ret == -EINPROGRESS)
		utrace_barrier_pid(thread->t_spid, thread->t_engine);
	utrace_engine_put(thread->t_engine);

	ugdb_destroy_thread(thread);
}

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;

	spin_lock_init(&ugdb->u_llock);
	INIT_LIST_HEAD(&ugdb->u_threads);
	INIT_LIST_HEAD(&ugdb->u_stopped);
	init_waitqueue_head(&ugdb->u_wait);

	return ugdb;

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

static void ugdb_destroy(struct ugdb *ugdb)
{
	struct ugdb_thread *thread;

	while (!list_empty(&ugdb->u_threads)) {
		thread = list_first_entry(&ugdb->u_threads,
				struct ugdb_thread, t_threads);

		ugdb_detach_thread(thread);
	}

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

	module_put(THIS_MODULE);
	kfree(ugdb);
}

// XXX: Of course, this all is racy --------------------------------------------
enum {
	STOP_RUN,
	STOP_REQ,
	STOP_ACK,
	STOP_FIN,
};

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

	if (event == UTRACE_EVENT(DEATH)) {
		thread->t_exit = current->exit_code | INT_MIN;
		goto ack;
	}

	if (thread->t_stop == STOP_RUN)
		return UTRACE_RESUME;

	if (thread->t_stop != STOP_REQ)
		printk(KERN_INFO "XXX: %d, report_quiesce bad c_stop: %d\n",
				thread->t_tid, thread->t_stop);

ack:
	thread->t_stop = STOP_ACK;

	// SIGKILL can re-add to stopped
	if (list_empty(&thread->t_stopped)) {
		spin_lock(&ugdb->u_llock);
		list_add_tail(&thread->t_stopped, &ugdb->u_stopped);
		spin_unlock(&ugdb->u_llock);
	}
	wake_up_all(&ugdb->u_wait);

	return UTRACE_STOP;
}

static u32 ugdb_report_death(struct utrace_engine *engine,
				bool group_dead, int signal)
{
	return UTRACE_RESUME;
}

static const struct utrace_engine_ops ugdb_utrace_ops = {
	.report_quiesce = ugdb_report_quiesce,
	.report_death	= ugdb_report_death,
};

static int ugdb_stop_thread(struct ugdb_thread *thread)
{
	int err;

	if (thread->t_stop != STOP_RUN)
		return 0;

	thread->t_stop = STOP_REQ;

	// XXX: we don't do UTRACE_STOP! this means we can't
	// stop TASK_STOPEED task. temporarily.

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

static int ugdb_cont_thread(struct ugdb_thread *thread)
{
	struct ugdb *ugdb = thread->t_ugdb;

	if (thread->t_stop == STOP_RUN)
		return 0;

	if (!list_empty(&thread->t_stopped)) {
		spin_lock(&ugdb->u_llock);
		list_del_init(&thread->t_stopped);
		spin_unlock(&ugdb->u_llock);
	}

	thread->t_stop = STOP_RUN;
	ugdb_control(thread, UTRACE_RESUME);
	return 1;
}

static struct task_struct *
ugdb_prepare_examine(struct ugdb_thread *thread, struct utrace_examiner *exam)
{
	struct task_struct *task;

	if (!thread)
		return ERR_PTR(-ESRCH);

	task = pid_task(thread->t_spid, PIDTYPE_PID);
	if (!task)
		return ERR_PTR(-ESRCH);

	for (;;) {
		if (fatal_signal_pending(current))
			return ERR_PTR(-EINTR);

		if (thread->t_stop == STOP_RUN) {
			printk(KERN_INFO "XXX: %d unexpected STOP_RUN\n",
						thread->t_tid);
			return ERR_PTR(-EBUSY);
		}

		if (thread->t_stop != STOP_REQ) {
			int err = utrace_prepare_examine(task,
					thread->t_engine, exam);

			if (err == 0)
				return task;

			if (err == -ESRCH)
				return ERR_PTR(err);
		}

		schedule_timeout_interruptible(1);
	}
}

// -----------------------------------------------------------------------------
#define UGDB_ATTACH	(0x666 + 1)
#define UGDB_DETACH	(0x666 + 2)
#define UGDB_STOP	(0x666 + 3)
#define UGDB_CONT	(0x666 + 4)
#define	UGDB_GETEV	(0x666 + 5)

#define	UGDB_PEEKMEM	(0x666 + 6)
#define	UGDB_POKEMEM	(0x666 + 7)

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

	if (thread)
		return -EALREADY;

	thread = ugdb_attach_thread(ugdb, tid);
	if (IS_ERR(thread))
		return PTR_ERR(thread);

	return 0;
}

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

	if (!thread)
		return -ESRCH;

	ugdb_detach_thread(thread);

	return 0;
}

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

	if (!thread)
		return -ESRCH;

	return ugdb_stop_thread(thread);
}

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

	if (!thread)
		return -ESRCH;

	return ugdb_cont_thread(thread);
}

enum {
	UGDB_EV_STOP,
	UGDB_EV_EXIT,
};

struct ugdb_event {
	int			ev_tid;
	int			ev_type;

	union {
		unsigned	ev_data;
	};
};

static int ugdb_getev(struct ugdb *ugdb, struct ugdb_event __user *uev)
{
	struct ugdb_thread *thread;
	struct ugdb_event ev;

	if (list_empty(&ugdb->u_threads))
		return -ECHILD;

	if (list_empty(&ugdb->u_stopped))
		return -EWOULDBLOCK;

	spin_lock(&ugdb->u_llock);
	thread = list_first_entry(&ugdb->u_stopped,
					struct ugdb_thread, t_stopped);
	list_del_init(&thread->t_stopped);
	spin_unlock(&ugdb->u_llock);

	ev.ev_tid = thread->t_tid;
	ev.ev_type = UGDB_EV_STOP;

	if (thread->t_exit) {
		ev.ev_type = UGDB_EV_EXIT;
		ev.ev_data = thread->t_exit & 0xffff;
	}

	if (copy_to_user(uev, &ev, sizeof(ev)))
		return -EFAULT;

	return 0;
}

struct ugdb_xmem {
	long		tid;

	void __user	*src, *dst;
	unsigned long	len;
};

static typeof(access_process_vm) *u_access_process_vm;

static int ugdb_peekmem(struct ugdb *ugdb, struct ugdb_xmem __user *uxmem)
{
	struct ugdb_xmem xmem;
	struct utrace_examiner exam;
	struct ugdb_thread *thread;
	struct task_struct *task;
	char mbuf[256];
	int size;

	if (copy_from_user(&xmem, uxmem, sizeof(xmem)))
		return -EFAULT;

	thread = ugdb_find_thread(ugdb, xmem.tid);
	task = ugdb_prepare_examine(thread, &exam);
	if (IS_ERR(task))
		return PTR_ERR(task);

	size = 0;
	while (xmem.len) {
		int chunk = min(xmem.len, sizeof (mbuf));

		chunk = u_access_process_vm(task, (unsigned long)xmem.src,
						mbuf, chunk, 0);
		if (chunk <= 0)
			break;

		if (copy_to_user(xmem.dst, mbuf, chunk)) {
			size = -EFAULT;
			break;
		}

		xmem.src += chunk;
		xmem.dst += chunk;
		xmem.len -= chunk;

		size += chunk;
	}

	if (utrace_finish_examine(task, thread->t_engine, &exam))
		size = -ESRCH;

	return size;
}

// XXX: temporarily hack !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#define UGDB_GETREGS	(0x666 + 100)
#define REGSET_GENERAL	0

struct ugdb_getregs {
	long		tid;
	void __user	*addr;
};

static int get_regset(struct task_struct *task, void __user *uaddr)
{
	return copy_regset_to_user(task, task_user_regset_view(current),
					REGSET_GENERAL,
					0, sizeof(struct user_regs_struct),
					uaddr);
}

static int ugdb_getregs(struct ugdb *ugdb, struct ugdb_getregs __user *uregs)
{
	struct ugdb_getregs regs;
	struct utrace_examiner exam;
	struct ugdb_thread *thread;
	struct task_struct *task;
	int err;

	if (WARN_ON(sizeof(struct user_regs_struct) != 216))
		return -EFAULT;

	if (copy_from_user(&regs, uregs, sizeof(regs)))
		return -EFAULT;

	thread = ugdb_find_thread(ugdb, regs.tid);
	task = ugdb_prepare_examine(thread, &exam);
	if (IS_ERR(task))
		return PTR_ERR(task);

	err = get_regset(task, regs.addr);

	if (utrace_finish_examine(task, thread->t_engine, &exam))
		return -ESRCH;

	return err;
}

static long ugdb_f_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct ugdb *ugdb = file->private_data;
	int ret = -EINVAL;

	switch (cmd) {
	case UGDB_ATTACH:
		ret = ugdb_attach(ugdb, arg);
		break;

	case UGDB_DETACH:
		ret = ugdb_detach(ugdb, arg);
		break;

	case UGDB_STOP:
		ret = ugdb_stop(ugdb, arg);
		break;

	case UGDB_CONT:
		ret = ugdb_cont(ugdb, arg);
		break;

	case UGDB_GETEV:
		ret = ugdb_getev(ugdb, (void __user*)arg);
		break;

	case UGDB_PEEKMEM:
		ret = ugdb_peekmem(ugdb, (void __user*)arg);
		break;

	case UGDB_GETREGS:
		ret = ugdb_getregs(ugdb, (void __user*)arg);
		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 = 0;
	if (!list_empty(&ugdb->u_stopped))
		mask |= POLLIN;

	return mask;
}

// -----------------------------------------------------------------------------
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,
	.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);

[-- Attachment #3: gdbstub --]
[-- Type: text/plain, Size: 12781 bytes --]

#!/usr/bin/perl -w

#-----------------------------------------------------------------------------
package utrace;
use strict;
use warnings FATAL => qw(all);
use feature qw(switch);

sub pr { main::pr(@_) }

my $f_ugdb;

use constant {
	# not really needed
	ECHILD		=> 10,
	EAGAIN		=> 11,

	UGDB_ATTACH	=> (0x666 + 1),
	UGDB_DETACH	=> (0x666 + 2),

	UGDB_STOP	=> (0x666 + 3),
	UGDB_CONT	=> (0x666 + 4),

	UGDB_GETEV	=> (0x666 + 5),

	UGDB_PEEKMEM	=> (0x666 + 6),
	UGDB_POKEMEM	=> (0x666 + 7),

# XXX: temporarily hack !!!!!
UGDB_GETREGS => (0x666 + 100),

	UGDB_EV_STOP	=> 0,
	UGDB_EV_EXIT	=> 1,
};

sub create
{
	sysopen $f_ugdb, '/proc/ugdb', 0
		or return;
	return $f_ugdb;
}

sub attach_thread
{
	my ($pid, $tid) = @_;

	defined ioctl $f_ugdb, UGDB_ATTACH, 0+$tid;
}

sub detach_thread
{
	my ($tid) = @_;

	defined ioctl $f_ugdb, UGDB_DETACH, 0+$tid;
}

sub ck_true
{
	defined (my $r = shift) or return;
	$r == 1 or pr "WARN! should be true";
	1;
}

sub stop_thread
{
	my ($tid) = @_;

	ck_true ioctl $f_ugdb, UGDB_STOP, 0+$tid;
}

sub cont_thread
{
	my ($tid) = @_;

	ck_true ioctl $f_ugdb, UGDB_CONT, 0+$tid;
}

sub get_event
{
	defined ioctl $f_ugdb, UGDB_GETEV, my $event = 'x' x 64
		or do {
			# just a sanity check
			return if $! == EAGAIN || $! == ECHILD;
			return (0, 'EV_ERROR', "[errno: $!]");
		};

	my ($tid, $type, $data) = unpack 'i!i!a*', $event;

	my @event;
	given ($type) {
		when (UGDB_EV_STOP)
			{ @event = 'EV_STOP'; }

		when (UGDB_EV_EXIT)
			{ @event = ('EV_EXIT', unpack 'I', $data); }

		@event = ('EV_UNKNOWN', $type);
	}

	$tid, @event;
}

sub read_mem
{
	my ($tid, $addr, $size) = @_;

	my $mbuf = 'x' x $size;
	my $pbuf = unpack 'L!', pack 'P', $mbuf;
	my $xmem = pack 'L!4', 0+$tid, +$addr, 0+$pbuf, $size;

	my $r = ioctl $f_ugdb, UGDB_PEEKMEM, $xmem
		or return;
	substr $mbuf, 0, $r;
}

sub get_regs
{
	my $tid = 0+shift;
	my $regs = 'x' x 216; # struct user_regs_struct

	my $pregs = unpack 'L!', pack 'P', $regs;
	my $xregs = pack 'L!2', 0+$tid, 0+$pregs;
	defined ioctl $f_ugdb, UGDB_GETREGS, $xregs
		or return;
	$regs;
}

#-----------------------------------------------------------------------------
package main;
use strict;
use feature qw(state switch);
use warnings FATAL => qw(all);
no warnings 'portable'; #hex

sub pr { print STDERR "@_\n" }

=pod
	tread:
		T_PID T_TID	pid, tid
		T_XID		pPID.TID

		T_STP		undef, false==pending, or STOP_REPLY

	process:
		P_PID		pid
		P_TID		list of sub-threads

	both:
		S_KEY		= sorting key

=cut
#-----------------------------------------------------------------------------
sub err
{
	pr "ERR!! @_"; return;
}

sub hv($)
{
	sprintf '%02x', 0+shift;
}
sub hs($)
{
	unpack 'H*', shift // return undef;
}

sub shex($)
{
	my $h = shift; ($h =~ s/^-//) ? -hex $h : +hex $h;
}

sub __s_key
{
	sort { $a->{S_KEY} <=> $b->{S_KEY} } values %{+shift}
}

my ($O_NOACK, $O_NOSTOP);
my ($S_KEY, $P_NUM, %P_ALL, %T_ALL) = (0, 0);
my ($G_CURR, $C_CURR);

sub select_threads
{
	my ($pid, $tid) = @_;

	$pid < 0 || $tid < 0 and
		return err "unexpected multi-THREAD-ID"
		unless wantarray;

	return unless %T_ALL;

	if ($tid > 0) {
		my $t = $T_ALL{$tid} || return;
		$t->{T_PID} == $pid || return if $pid > 0;
		return $t;
	}

	my @p;
	if ($pid > 0) {
		@p = $P_ALL{$pid} || return;
	} else {
		@p = __s_key \%P_ALL;
		splice @p, 1 unless $pid;
	}

	my @t = map {
		my @t = __s_key $_->{P_TID} or die;
		splice @t, 1 unless $tid;
		@t;
	} @p;

	die unless @t;

	wantarray ? @t : $t[0];
}

sub select_one_thread
{
	scalar select_threads @_;
}

sub attach_thread
{
	my ($p, $tid) = @_;
	my $pid = $p->{P_PID};

	die if $T_ALL{$tid} || $p->{P_TID}{$tid};

	utrace::attach_thread $pid, $tid
		or return err "attach $tid failed: $!";

	$T_ALL{$tid} = $p->{P_TID}{$tid} = {
		S_KEY => ++$S_KEY,

		T_PID => $pid,
		T_TID => $tid,
		T_XID => sprintf('p%x.%x', $pid, $tid),

		T_STP => undef,
	};
}

sub detach_thread
{
	my ($p, $t) = @_;
	my $tid = $t->{T_TID};

	utrace::detach_thread $tid, $t->{T_STP}
		or err "detach $tid: $!";

	$_ && $_ == $t and undef $_ for $G_CURR, $C_CURR;

	$t == delete $T_ALL{$tid} or die;
	$t == delete $p->{P_TID}{$tid} or die;

	return $t;
}

sub detach_process
{
	my $p = shift;

	detach_thread $p, $_ for values %{$p->{P_TID}};
	$p == delete $P_ALL{$p->{P_PID}} or die;

	if (--$P_NUM <= 0) {
		die if $P_NUM;
		die if %T_ALL;
		die if %P_ALL;
		die if $G_CURR || $C_CURR;
	}

	die if keys %{$p->{P_TID}};
	return $p;
}

sub proc_list_pid
{
	my $pid = shift;
	my @tid = map /(\d+)\z/, glob "/proc/$pid/task/*";
	# do not return an empty list!
	@tid ? @tid : $pid;
}

sub c_attach
{
	# XXX: todo !!!!!!
	$O_NOSTOP || return err "Sorry, all-stop mode is not implemented yet.";

	my $pid = shex shift;

	$P_ALL{$pid} and
		return err "$pid already attached";

	$P_NUM++;
	$P_ALL{$pid} = my $p = {
		S_KEY => ++$S_KEY,

		P_PID => $pid,
	};

	for my $tid (proc_list_pid $pid) {
		attach_thread $p, $tid or do {
			detach_process $p;
			return;
		};
	}

	# seems not strictly necessary ?
	$G_CURR = $p->{P_TID}{$pid} if !$O_NOSTOP;

	$p;
}

sub c_detach
{
	detach_process $P_ALL{shex shift} || return;
}

# for ptrace plugin
sub utrace::stop_pending
{
	my $t = $T_ALL{+shift} || return;
	defined $t->{T_STP} && !$t->{T_STP};
}

sub stop_one_thread
{
	my $t = shift;

	unless (defined $t->{T_STP}) {
		utrace::stop_thread $t->{T_TID}
			or return err "stop $t->{T_TID}: $!";

		$t->{T_STP} = '';
	}

	$t;
}

sub stop_threads
{
	stop_one_thread $_ for @{+shift};
}

sub cont_one_thread
{
	my $t = shift;

	if (defined $t->{T_STP}) {
		utrace::cont_thread $t->{T_TID}, $t->{T_STP}
			or return err "cont $t->{T_TID}: $!";

		undef $t->{T_STP};
	}

	$t;
}

sub cont_threads
{
	cont_one_thread $_ for @{+shift};
}

sub c_thread_info
{
	'm' . join ',', map $_->{T_XID}, select_threads -1, -1;
}

sub c_setcurr
{
	my ($w, $pid, $tid) = @_;

	my $t = select_one_thread shex $pid, shex $tid
		or return;

	$w eq 'g' ? ($G_CURR = $t) :
	$w eq 'c' ? ($C_CURR = $t) :
	err "H$w not implemented";
}

sub c_ck_alive
{
	my ($pid, $tid) = @_;

	my $t = select_one_thread shex $pid, shex $tid
		or return;

	$t;
}

# include/gdb/signals.h:target_signal (incomplete)
my @to_gdb_sigmap = (
	(7, 10),  (10,30),  (12,31),  (17,20),  (18,19),  (19,17),
	(20,18),  (23,16),  (29,23),  (30,32),  (31,12));

sub sig_to_gdb($)
{
	my $sig = shift;
	state $map = {@to_gdb_sigmap};
	$map->{$sig} || $sig;
}

sub sig_from_gdb($)
{
	my $sig = shift;
	state $map = {reverse @to_gdb_sigmap};
	$map->{$sig} || $sig;
}

# gdb-7.1/gdb/gdbserver/linux-x86-low.c
my @x86_64_regmap = (
	10,  5, 11, 12, 13, 14,  4, 19,  9,  8,  7,  6,  3,  2,  1,  0,
	16, 18, 17, 20, 23, 24, 25, 26, -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, 15);

sub c_get_regs
{
	my $regs = utrace::get_regs $G_CURR->{T_TID}
		or return undef;

	my @regs = unpack 'L!*', $regs;

	hs pack 'L!*', map {
		$_ >= 0
			? $regs[$_] // die
			: 0;
	} @x86_64_regmap;
}

sub c_read_mem
{
	my ($addr, $size) = @_;

	hs utrace::read_mem $G_CURR->{T_TID},
			hex $addr, hex $size;
}

my $RE_HEX = qr/([a-f\d]+)/;
my $RE_PID = qr/(-?[a-f\d]+)/;
my $RE_XID = qr/p$RE_PID.$RE_PID/;

sub c_vcont
{
	my %seen;

	for (split ';', shift) {
		my ($cmd, $pid, $tid) = /^ ([^:]+) (?: : $RE_XID )? \z/x
			or return err "vCont: can't parse '$_'";

		($pid, $tid) = defined $pid
			? (shex $pid, shex $tid)
			: (-1, -1);

		my $handler;
		given ($cmd) {
			when ('t')
				{ $handler = \&stop_threads }
			when ('c')
				{ $handler = \&cont_threads }

			return err "vCont;$cmd is not implemented!";
		}

		my @threads = grep !$seen{$_->{T_XID}}++,
				select_threads $pid, $tid
			or err "vCont: no threads in '$_'";

		$handler->(\@threads);
	}

	scalar %seen;
}

# XXX: this all is wrong. blame gdb!!!
sub hack_exit
{
	my ($tid, $code) = @_;
	my $t = $T_ALL{$tid} || die;
	my $p = $P_ALL{$t->{T_PID}} or die;

	detach_thread $p, $t;

	return unless $p->{P_PID} == $tid;

	# main thread dies. report the group exit.
	detach_process $p;

	my $stp;
	if ($code & 0xff) {
		$stp = 'X' . hv sig_to_gdb($code & 0xff);
	} else {
		$stp = 'W' . hv(($code >> 8) & 0xff);
	}

	my $xid = hv $p->{P_PID};
	$stp .= ";process:$xid";

	return $stp;
}

my ($V_CUR, %V_STP, @V_STP);

sub handle_event
{
	my ($tid, $ev_name, @ev_data) = @_;

	my ($t, $stp);

	given ($ev_name) {
		when ('EV_STOP')
			{ $stp = 'T00' }
		when ('EV_SIGNAL')
			{ $stp = 'T' . hv sig_to_gdb $ev_data[0] }

		when ('EV_EXIT') {
			# XXX!!!!!! I do not know what to do with
			# the current limitations. The dirty hack
			# for now

			$stp = hack_exit $tid, $ev_data[0] or return;
			push @V_STP, +{ T_STP => $stp, };
			return;
		}

		die "unimplemented event: tid=$tid $ev_name, @ev_data";
	}

	$t = $T_ALL{$tid} || die;

	$t->{T_STP} = $stp . "thread:$t->{T_XID};";

	push @V_STP, $t unless $V_STP{$t->{T_XID}}++;
}

sub get_notification
{
	@V_STP && !$V_CUR && $V_STP[0]{T_STP};
}

sub c_vstopped
{
	@V_STP || err 'unexpected vStopped';

	++$V_CUR < @V_STP and
		return $V_STP[$V_CUR]{T_STP} || die;

	($V_CUR, %V_STP, @V_STP) = ();
	return 'OK';
}

sub handle_cmd
{
	given ($_) {
		when (/^qSupported (.*multiprocess\+)?/x) {
			$1 || die "ERR!! need multiprocess\n";
			@_ = join ';', qw{
				PacketSize=400 QStartNoAckMode+
				QNonStop+ multiprocess+};
		}

		when ('vCont?')
			{ @_ = 'vCont;t;c;C;s;S' }

		@_ = 'OK';

		when ('QStartNoAckMode')
			{ $O_NOACK = 1 }
		when (/^QNonStop:([01])/)
			{ $O_NOSTOP = !!$1 }
		when ([qw'!']) {}


		@_ = undef;

		when (/^vAttach; $RE_PID \z/x)
			{ @_ = $O_NOSTOP ? 'OK':'S05' if c_attach $1 }
		when (/^D; $RE_PID \z/x)
			{ @_ = 'OK' if c_detach $1 }

		when (/^H (.) $RE_XID \z/x)
			{ @_ = 'OK' if c_setcurr $1, $2, $3 }
		when ('qC')
			{ @_ = 'QC' . $G_CURR->{T_XID} if $G_CURR }
		when (/^T $RE_XID \z/x)
			{ @_ = 'OK' if c_ck_alive $1, $2 }

		when ('qfThreadInfo')
			{ @_ = c_thread_info if %T_ALL }
		when ('qsThreadInfo')
			{ @_ = 'l' }

		when (/^vCont;(.*)/)
			{ @_ = 'OK' if c_vcont $1 }
		when ('vStopped')
			{ @_ = c_vstopped }

		when ('g')
			{ @_ = c_get_regs if $G_CURR }
		when (/^m $RE_HEX , $RE_HEX \z/x)
			{ @_ = c_read_mem $1, $2 if $G_CURR }

		when (/^[GM]/)
			{ } # uninplemented ...

		@_ = '';

		when ('qTStatus')
			{ @_ = 'T0' }
		when ('?')
			{ @_ = $O_NOSTOP ? 'OK' : 'W00' }
	}

	return @_;
}

#-----------------------------------------------------------------------------
my ($F_UGDB, $F_CONN, $F_OCMD);

use constant {
	# ARCH DEPENDANT
	FIONBIO		=> 0x5421,
	EAGAIN		=> 11,
};

sub echo
{
	my $str = "@_";
	substr($str, 62) = '...' if length $str > 64;
	pr $str;
};

sub __put
{
	# XXX: doesn't support NONBLOCK or short writes
	my $w = syswrite $F_OCMD, my $pkt = join '', @_;
	($w ||= 0) == length $pkt or
		die 'ERR!! conn put(', length $pkt, ')',
			"failed: $w $!\n";
}

sub __put_pkt
{
	my $sym = shift;
	my $pkt = join '', @_;
	my $csm = 0; $csm += ord $1 while $pkt =~ /(.)/sg;
	__put $sym, $pkt, '#', hv $csm % 256;
	echo '<=', $pkt;
}

sub put_p
{
	__put_pkt '$', @_;
}
sub put_n
{
	__put_pkt '%', @_;
}

sub get_p
{
	state $buf = '';

	for (;;) {
		$buf =~ s/^\+*//;
		$buf =~ s/^\$ ([^#]*) \#..//x and $_ = $1, last;

		if ($buf =~ s/^([^\$]+)//s) {
			err "bad cmd or nack: $1";
		} elsif (!sysread $F_CONN, $buf, 4096, length $buf) {
			return if $! == EAGAIN;
			pr 'conn closed:', $! || 'EOF';
			exit;
		}
	}

	__put '+' unless $O_NOACK;
	echo '=>', $_;
	1;
}

sub process_cmds
{
	while (get_p) {
		my ($r, @r) = handle_cmd or next;
		put_p $r // 'E01', @r;
	}
}

sub process_ugdb
{
	while (my @ev = utrace::get_event)
	{
		handle_event @ev;
	}

	my $n = get_notification;
	put_n 'Stop:' . $n if $n;
}

sub main_loop
{
	($F_CONN, $F_OCMD) = @_;

	$F_UGDB = utrace::create
		or die "ERR!! can't create utrace fd: $!\n";

	ioctl $F_CONN, FIONBIO, pack 'i!', 1 or die $!;
	ioctl $F_UGDB, FIONBIO, pack 'i!', 1 or die $!;

	for (my $rfd = '';;) {
		vec($rfd, fileno $F_CONN, 1) = 1;
		vec($rfd, fileno $F_UGDB, 1) = 1;

		0 < select $rfd, undef, undef, undef
			or next; # EINTR

		process_cmds if vec $rfd, fileno $F_CONN, 1;
		process_ugdb if vec $rfd, fileno $F_UGDB, 1;
	}
}

sub wait_for_connect
{
	my $port = 0+shift;

	socket my $sk, 2,1,0
		or return err "sock create: $!";
	defined setsockopt $sk, 1,2,1
		or return err "sock reuseaddr: $!";
	bind $sk, pack 'Sna12', 2, $port, ''
		or return err "sock bind $port port: $!";
	listen $sk, 2
		or return err "sock listen: $!";

	pr "wait for connection on $port port ...";
	accept my $conn, $sk
		or return err "sock accept: $!";

	return $conn;
}

sub main
{
	my $port = 2000;

	if (@_) {
		$port = shift;

		die "Usage: $0 [port]\n"
			if @_ || $port =~ /\D/;
	}

	my $conn = wait_for_connect $port or exit;
	main_loop $conn, $conn;
}

main @ARGV;

  parent reply	other threads:[~2010-07-28 18:19 UTC|newest]

Thread overview: 59+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-07-16 20:54 Q: mutlithreaded tracees && clone/exit Oleg Nesterov
2010-07-16 21:40 ` Roland McGrath
2010-07-18 17:51   ` Oleg Nesterov
2010-07-18 18:04     ` Oleg Nesterov
2010-07-18 20:22     ` Roland McGrath
2010-07-19 13:44 ` BUG: add_thread_silent()->switch_to_thread(minus_one_ptid) is wrong Oleg Nesterov
2010-07-19 15:36   ` Oleg Nesterov
2010-07-19 16:01 ` Q: mutlithreaded tracees && clone/exit Jan Kratochvil
2010-07-19 22:57   ` Roland McGrath
2010-07-20 13:18   ` Oleg Nesterov
2010-07-20 14:04     ` BUG? gdb, non-stop && c -a Oleg Nesterov
2010-07-20 14:12       ` Jan Kratochvil
2010-07-20 14:49         ` Oleg Nesterov
2010-07-20 15:08           ` Jan Kratochvil
2010-07-20 15:28             ` Oleg Nesterov
2010-07-20 19:43         ` Roland McGrath
2010-07-21  7:59           ` Oleg Nesterov
2010-07-21  8:10             ` Jan Kratochvil
2010-07-21 11:12               ` Oleg Nesterov
2010-07-20 14:21     ` Q: mutlithreaded tracees && clone/exit Jan Kratochvil
2010-07-20 15:09       ` Oleg Nesterov
2010-07-20 19:41     ` Roland McGrath
2010-07-21  8:32       ` Oleg Nesterov
2010-07-20 14:43 ` Q: who maintains the STOPPED/RUNNING state? Oleg Nesterov
     [not found]   ` <y0mk4ophmvn.fsf@fche.csb>
2010-07-21 10:20     ` Oleg Nesterov
2010-07-21 10:51       ` Oleg Nesterov
2010-07-21 17:06 ` Q: multiple inferiors, all-stop && vCont Oleg Nesterov
2010-07-21 20:42   ` Roland McGrath
2010-07-23 17:33     ` Oleg Nesterov
2010-07-26 14:30       ` Oleg Nesterov
2010-07-26 16:06         ` Oleg Nesterov
2010-07-28 18:19         ` Oleg Nesterov [this message]
2010-07-29 21:38           ` gdbstub initial code, another approach Frank Ch. Eigler
2010-07-30 13:00             ` Oleg Nesterov
2010-07-30 13:16               ` Frank Ch. Eigler
2010-07-30 15:01                 ` Oleg Nesterov
2010-07-30 13:25               ` Jan Kratochvil
2010-07-30 14:44                 ` Oleg Nesterov
2010-07-30 15:20                   ` Jan Kratochvil
2010-08-02 12:54                     ` Oleg Nesterov
2010-08-03 13:55                       ` Jan Kratochvil
2010-07-30 17:59                 ` Tom Tromey
2010-08-02 18:25           ` Oleg Nesterov
2010-08-02 23:54           ` Jan Kratochvil
2010-08-03 12:27             ` Q: %Stop && gdb crash Oleg Nesterov
2010-08-03 13:17               ` Oleg Nesterov
2010-08-03 19:57                 ` Kevin Buettner
2010-08-04 19:42                   ` Oleg Nesterov
2010-08-04 23:32                     ` Kevin Buettner
2010-08-05 18:24                       ` Oleg Nesterov
2010-08-03 13:36               ` Jan Kratochvil
2010-08-03 15:09                 ` Oleg Nesterov
2010-08-03 12:39         ` Q: multiple inferiors, all-stop && vCont Jan Kratochvil
2010-08-03 14:32           ` Oleg Nesterov
2010-08-03 15:55             ` Jan Kratochvil
2010-08-03 16:56               ` Oleg Nesterov
2010-08-03 18:37                 ` Jan Kratochvil
2010-08-18 17:07           ` Jan Kratochvil
2010-08-18 19:22             ` Roland McGrath

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20100728181702.GA26678@redhat.com \
    --to=oleg@redhat.com \
    --cc=archer@sourceware.org \
    --cc=roland@redhat.com \
    --cc=utrace-devel@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).