gdbstub initial code

Oleg Nesterov oleg at redhat.com
Mon Jul 19 16:25:25 UTC 2010


Changes:

	- make it work without exporting access_process_vm(). Suprisingly,
	  kallsyms.c has useful exports.

	- Add multiprocess mode. Incomplete, in particular parse_pids()
	  can't parse the negative hex pids.

	  At first I tried to support both multiprocess and !multiprocess
	  modes, but this needs too many "unnecessary" code which I'd like
	  to avoid, at least now. So this version requires "multiprocess+"
	  in qSupported, otherwise "target extended-remote" fails.

	  Please let me know if we need both modes.

Oleg.
-------------- next part --------------
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/utrace.h>
#include <linux/regset.h>
#include <linux/mm.h>
#include <asm/uaccess.h>

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

#define PACKET_SIZE		1024
#define BUFFER_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;
}

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)
{
	WARN_ON(pb->pkt);
	pb_putc(pb, '$');
	pb->pkt = pb->cur;
}

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 == '#');
		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;
}

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 (d_echo)
		printk(KERN_INFO "<= %.*s\n", copy, pb->buf);

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

	pb_flush(pb, copy);
	return copy;
}

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

enum {
	STOP_RUN,
	STOP_REQ,
	STOP_ACK,
	STOP_FIN,
};

struct ugdb_context {
	int			c_stop;
	int			c_exit;
	const char		*fake_rc;

	int			c_pid, c_tid;
	char			c_xid[32];

	struct pid		*pid;
	struct utrace_engine	*engine;
	struct ugdb		*c_ugdb;
	struct list_head	node;
};

struct ugdb {
	char			g_ibuf[PACKET_SIZE];
	int			g_ilen;

	struct pbuf		g_pbuf;
	bool			g_no_ack;
	int			g_err;

	struct list_head	g_attached;
	struct ugdb_context	*g_current;
	wait_queue_head_t	g_wait;
};

// XXX: Of course, this all is racy ----------------------------------

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

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

	if (context->c_stop == STOP_RUN)
		return UTRACE_RESUME;

	if (context->c_stop != STOP_REQ)
		printk(KERN_INFO "XXX: %d, report_quiesce bad c_stop: %d\n",
				context->c_tid, context->c_stop);
ack:
	context->c_stop = STOP_ACK;
	wake_up_all(&context->c_ugdb->g_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_one(struct ugdb_context *context, const char *fake_rc)
{
	int err;

	if (context->c_stop != STOP_RUN)
		return 0;

	context->c_stop = STOP_REQ;
	context->fake_rc = fake_rc;

	err = utrace_control_pid(context->pid, context->engine,
						UTRACE_INTERRUPT);
	if (err == -EINPROGRESS)
		err = 0;
	return err;
}

static int ugdb_cont_one(struct ugdb_context *context)
{
	if (context->c_stop == STOP_RUN)
		return -EALREADY;

	context->c_stop = STOP_RUN;
	context->fake_rc = NULL;
	utrace_control_pid(context->pid, context->engine,
						UTRACE_RESUME);
	return 0;
}

static int ugdb_wait_for_rc(struct ugdb *ugdb)
{
	DEFINE_WAIT(wait);
	struct ugdb_context *context;
	int ret;

	for (;;) {
		prepare_to_wait(&ugdb->g_wait, &wait, TASK_INTERRUPTIBLE);

		list_for_each_entry(context, &ugdb->g_attached, node) {
			if (context->c_stop == STOP_ACK) {
				context->c_stop = STOP_FIN;

				if (context->c_exit) {
					int c = context->c_exit & 0xff;
					char r = 'X';

					if (!c) {
						c = (context->c_exit & 0xff) << 8;
						r = 'W';
					}

					pb_packf(&ugdb->g_pbuf, "%c%02x", r, c);
					continue;
				}

				if (!context->fake_rc) {
					printk(KERN_INFO "XXX: %d NULL rc\n",
							context->c_tid);
					continue;
				}
				pb_packs(&ugdb->g_pbuf, context->fake_rc);
			}
		}

		ret = 0;
		if (pb_size(&ugdb->g_pbuf))
			break;

		ret = -EINTR;
		if (signal_pending(current))
			break;

		schedule();
	}
	finish_wait(&ugdb->g_wait, &wait);

	return ret;
}

static int xxx_prepare_examine(struct task_struct *task,
				struct ugdb_context *context,
				struct utrace_examiner *exam)
{
	for (;;) {
		if (fatal_signal_pending(current))
			return -EINTR;

		if (context->c_stop == STOP_RUN) {
			printk(KERN_INFO "XXX: %d unexpected STOP_RUN\n",
						context->c_tid);
			return -EINVAL;
		}

		if (context->c_stop != STOP_REQ) {
			int err = utrace_prepare_examine(task, context->engine, exam);
			if (!err || err == -ESRCH)
				return err;
		}

		schedule_timeout_interruptible(1);
	}
}

static void ugdb_c_all(struct ugdb *ugdb, const char *fake_rc)
{
	struct ugdb_context *context;

	list_for_each_entry(context, &ugdb->g_attached, node) {
		if (fake_rc)
			ugdb_stop_one(context, fake_rc);
		else
			ugdb_cont_one(context);
	}
}

static char *handle_vattach(struct ugdb *ugdb, char *cmd)
{
	int nr = simple_strtoul(cmd, NULL, 16);
	struct pid *pid = find_get_pid(nr);
	struct ugdb_context *context;
	int err;

	if (!pid)
		goto err;

	context = kzalloc(sizeof(*context), GFP_KERNEL);
	if (!context)
		goto free_pid;

	context->c_pid = nr;
	context->c_tid = nr;
	snprintf(context->c_xid, sizeof(context->c_xid),
		"p%x.%x", context->c_pid, context->c_tid);

	context->pid = pid;
	context->c_ugdb = ugdb;
	context->engine = utrace_attach_pid(pid,
					UTRACE_ATTACH_CREATE,
					&ugdb_utrace_ops,
					context);
	if (IS_ERR(context->engine))
		goto free_ctx;

	err = utrace_set_events_pid(pid, context->engine,
				UTRACE_EVENT(QUIESCE) | UTRACE_EVENT(DEATH));
	err = ugdb_stop_one(context, "S05");
	if (err)
		goto free_engine;

	list_add_tail(&context->node, &ugdb->g_attached);

	ugdb->g_current = context;
	return NULL;

free_engine:
	utrace_control_pid(pid, context->engine, UTRACE_DETACH);
	utrace_engine_put(context->engine);
free_ctx:
	kfree(context);
free_pid:
	put_pid(pid);
err:
	return "E01";
}

static void ugdb_detach_one(struct ugdb *ugdb, struct ugdb_context *context)
{
	int ret;

	if (ugdb->g_current == context)
		ugdb->g_current = NULL;

	ret = utrace_control_pid(context->pid, context->engine, UTRACE_DETACH);
	if (ret == -EINPROGRESS)
		utrace_barrier_pid(context->pid, context->engine);
	utrace_engine_put(context->engine);

	list_del(&context->node);
	put_pid(context->pid);
	kfree(context);
}

static bool context_pids(struct ugdb_context *context, int pid, int tid)
{
	if (pid > 0 && context->c_pid != pid)
		return false;
	if (tid > 0 && context->c_tid != tid)
		return false;
	return true;
}

static void ugdb_detach_many(struct ugdb *ugdb, int pid, int tid)
{
	struct ugdb_context *context, *tmp;

	list_for_each_entry_safe(context, tmp, &ugdb->g_attached, node)
		if (context_pids(context, pid, tid))
			ugdb_detach_one(ugdb, context);
}

static struct ugdb_context *find_context(struct ugdb *ugdb, int pid, int tid)
{
	struct ugdb_context *context;

	list_for_each_entry(context, &ugdb->g_attached, node)
		if (context_pids(context, pid, tid))
			return context;

	return NULL;
}

static bool parse_pids(char *cmd, int *ppid, int *ptid)
{
	int r = sscanf(cmd, "p%x.%x", ppid, ptid);
	if (r == 2)
		return true;
	WARN("ERR!! parse_pids(%s) failed\n", cmd);
	return false;
}

static char *handle_hg(struct ugdb *ugdb, char *cmd)
{
	int pid, tid;

	if (parse_pids(cmd, &pid, &tid)) {
		ugdb->g_current = find_context(ugdb, pid, tid);
		if (ugdb->g_current)
			return "OK";
	}

	return "E01";
}

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

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

	if (pid <= 0 || tid <= 0)
		printk(KERN_INFO "XXX: T with multipid? %s\n", cmd);

	if (find_context(ugdb, pid, tid))
		return "OK";

err:
	return "E01";
}

#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_g(struct ugdb *ugdb)
{
	struct ugdb_context *context = ugdb->g_current;
	const struct user_regset_view *view;
	const struct user_regset *rset;
	struct utrace_examiner exam;
	struct user_regs_struct *pregs;
	struct task_struct *task;
	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");
				goto err;
			}
			sz += sizeof(long) * 2;
		}
		pkt_size = sz;
	}

	if (!context)
		goto err;

	if (pb_room(&ugdb->g_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->g_pbuf, sizeof(*pregs));
	BUG_ON(pregs + 1 != (void*)ugdb->g_pbuf.cur + BUFFER_SIZE);

	task = pid_task(context->pid, PIDTYPE_PID);
	if (!task)
		goto err;

	if (xxx_prepare_examine(task, context, &exam))
		goto err;

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

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

	if (utrace_finish_examine(task, context->engine, &exam))
		goto err;

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

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

static typeof(access_process_vm) *u_access_process_vm;

static void apvm(struct ugdb *ugdb, struct task_struct *task,
			unsigned long addr, int size)
{
	unsigned char *mbuf;
	char *err = "E01";

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

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

	pb_putbs(&ugdb->g_pbuf, mbuf, size);
	err = "";
end:
	pb_puts(&ugdb->g_pbuf, err);
	pb_end(&ugdb->g_pbuf);
}

static const char *handle_m(struct ugdb *ugdb, char *cmd)
{
	struct ugdb_context *context = ugdb->g_current;
	struct utrace_examiner exam;
	struct task_struct *task;
	unsigned long addr, size;

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

	if (!context)
		goto err;

	task = pid_task(context->pid, PIDTYPE_PID);
	if (!task)
		goto err;

	if (xxx_prepare_examine(task, context, &exam))
		goto err;

	apvm(ugdb, task, addr, size);

	/* Too late to report the error*/
	if (utrace_finish_examine(task, context->engine, &exam))
		;

	return NULL;
err:
	return "E01";
}

static const char *handle_d(struct ugdb *ugdb, char *cmd)
{
	int nr = simple_strtol(cmd, NULL, 16);

	ugdb_detach_many(ugdb, nr, -1);

	return "OK";
}

#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)
{
	const char *rc = "";

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

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

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

	case 'c':
		ugdb_c_all(ugdb, NULL);
		rc = NULL;
		break;

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

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

			pb_packf(&ugdb->g_pbuf,
				"PacketSize=%x;QStartNoAckMode+;multiprocess+",
				PACKET_SIZE);
			rc = NULL;
		} else if (EQ(cmd, "qC")) {
			if (ugdb->g_current) {
				pb_packf(&ugdb->g_pbuf, "QC%s",
						ugdb->g_current->c_xid);
				rc = NULL;
			}
		} else if (EQ(cmd, "qfThreadInfo")) {
			if (ugdb->g_current) {
				pb_packf(&ugdb->g_pbuf, "m%s",
						ugdb->g_current->c_xid);
				rc = NULL;
			}
		} else if (EQ(cmd, "qsThreadInfo")) {
			rc = "l";
		} else if (EQ(cmd, "qTStatus")) {
			rc = "T0";
		}
		break;

	default:
		if (EQ(cmd, "QStartNoAckMode")) {
			ugdb->g_no_ack = true;
			rc = "OK";
		} else if (EQ(cmd, "vAttach;")) {
			rc = handle_vattach(ugdb, cmd);
		} else if (EQ(cmd, "D;")) {
			rc = handle_d(ugdb, cmd);
		} else if (EQ(cmd, "Hg")) {
			rc = handle_hg(ugdb, cmd);
		}
	}

	if (rc)
		pb_packs(&ugdb->g_pbuf, rc);
}

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

	if (d_echo)
		printk(KERN_INFO "=> %.*s\n", ugdb->g_ilen, ugdb->g_ibuf);

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

		first = *cmds++;
		todo--;

		switch (first) {
		default:
			// XXX: Ctrl-C sends chr = 3, not implemented.
			printk(KERN_INFO "XXX: unknown chr %02x\n", first);
			pb_putc(&ugdb->g_pbuf, '-');
			break;

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

		case 0x3:
			ugdb_c_all(ugdb, "S02");
			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->g_no_ack)
				pb_putc(&ugdb->g_pbuf, '+');

			handle_command(ugdb, c_cmd, c_len);
		}
	}
out:
	ugdb->g_ilen = todo;
	if (todo && cmds > ugdb->g_ibuf)
		memcpy(ugdb->g_ibuf, cmds, todo);
}

static struct ugdb *ugdb_inifin(struct ugdb *ugdb)
{
	int err = 0;

	if (ugdb)
		goto dtor;

	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;

	pb_init(&ugdb->g_pbuf);
	INIT_LIST_HEAD(&ugdb->g_attached);
	init_waitqueue_head(&ugdb->g_wait);

	return ugdb;

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

static int ugdb_f_open(struct inode *inode, struct file *file)
{
	nonseekable_open(inode, file);
	file->private_data = ugdb_inifin(NULL);
	return	IS_ERR(file->private_data) ?
		PTR_ERR(file->private_data) : 0;
}

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

	ugdb_detach_many(ugdb, -1, -1);
	ugdb_inifin(ugdb);

	return 0;
}

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->g_err)
		return ugdb->g_err;

	if (count > PACKET_SIZE - ugdb->g_ilen) {
		count = PACKET_SIZE - ugdb->g_ilen;
		printk("XXX: write(%ld,%d) enospc\n", count, ugdb->g_ilen);
	}
	if (copy_from_user(ugdb->g_ibuf + ugdb->g_ilen, ubuf, count))
		return -EFAULT;

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

	*ppos += count;
	return count;
}

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

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

	if (!pb_size(&ugdb->g_pbuf)) {
		int err = ugdb_wait_for_rc(ugdb);
		if (err)
			return err;
	}

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

	count = pb_copy_to_user(&ugdb->g_pbuf, ubuf, count);
	if (count > 0)
		*ppos += count;
	return count;
}

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

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

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


More information about the utrace-devel mailing list