Hello.
Please see the attachment. Don't take this code seriously, this is
the early prototype and everything should be rewritten. It barely
uses utrace, only to stop the target.
(gdb) file /path/to/binary
(gdb) target extended-remote /proc/ugdb
(gdb) attach PID
(gdb) disassemble _start
(gdb) bt
(gdb) info registers
(gdb) info threads
(gdb) detach
This seems to work, but I had to export access_process_vm().
Currently it only attaches to the single thread. vCont or ^C doesn't
work.
I still can't understand what utrace_xxx_pid() buys us, and I still
think that utrace_prepare_examine() can't protect the task even for
regset calls.
Oleg.
#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>
#define static
#define PACKET_SIZE 1024
#define BUFFER_SIZE 1024
struct cbuf {
char *w_ptr;
char *r_ptr;
char buf[BUFFER_SIZE];
};
static inline void cb_init(struct cbuf *cb)
{
cb->w_ptr = cb->r_ptr = cb->buf;
}
static int cb_rsz(struct cbuf *cb)
{
int rsz = cb->w_ptr - cb->r_ptr;
if (rsz < 0)
rsz += BUFFER_SIZE;
return rsz;
}
static int cb_wsz(struct cbuf *cb)
{
int wsz = cb->r_ptr - cb->w_ptr - 1;
if (wsz < 0)
wsz += BUFFER_SIZE;
return wsz;
}
static int cb_l_wsz(struct cbuf *cb)
{
char *lim = cb->r_ptr - 1;
if (cb->w_ptr >= cb->r_ptr) {
lim = cb->buf + BUFFER_SIZE;
if (cb->r_ptr == cb->buf)
lim--;
}
return lim - cb->w_ptr;
}
static void __cb_padd(struct cbuf *cb, char **pptr, int cnt)
{
char *p = *pptr + cnt;
if (p >= cb->buf + BUFFER_SIZE)
p -= BUFFER_SIZE;
*pptr = p;
}
#define cb_pinc(cb, pptr) __cb_padd((cb), &(pptr), 1)
#define cb_padd(cb, pptr, cnt) __cb_padd((cb), &(pptr), (cnt))
static inline void put_hex(unsigned char val, char to[2])
{
static char hex[] = "0123456789abcdef";
to[0] = hex[(val & 0xf0) >> 4];
to[1] = hex[(val & 0x0f) >> 0];
}
static int cb_copy_to_user(struct cbuf *cb, unsigned char *pcsum,
char __user *uptr, int size)
{
unsigned char csum = *pcsum;
char *kptr = cb->r_ptr;
int total, copied = 0;
if (!access_ok(VERIFY_WRITE, uptr, size))
goto efault;
for (total = cb_rsz(cb); total; --total) {
unsigned char c = *kptr;
int sz = (c == '#') ? 3 : 1;
char csh[2];
size -= sz;
if (size < 0)
break;
if (__put_user(c, uptr))
goto efault;
switch (c) {
case '#':
put_hex(csum, csh);
if (__copy_to_user(uptr + 1, csh, sizeof csh))
goto efault;
case '$':
case '%':
csum = 0;
break;
default:
csum += (unsigned char)c;
}
cb_pinc(cb, kptr);
copied += sz;
uptr += sz;
}
ret:
*pcsum = csum;
cb->r_ptr = kptr;
return copied ?: -EPIPE;
efault:
copied = copied ?: -EFAULT;
goto ret;
}
static int cb_copy_from(struct cbuf *cb, const char *data, int size)
{
int flat, i;
if (size > cb_wsz(cb))
return -EOVERFLOW;
for (i = 0; size && i < 2; ++i) {
flat = cb_l_wsz(cb);
if (flat > size)
flat = size;
memcpy(cb->w_ptr, data, flat);
cb_padd(cb, cb->w_ptr, flat);
data += flat;
size -= flat;
}
BUG_ON(size);
return 0;
}
static int __attribute__ ((format(printf, 2, 3)))
cb_printf(struct cbuf *cb, const char *fmt, ...)
{
char sbuf[64];
va_list args;
int size;
va_start(args, fmt);
size = vsnprintf(sbuf, sizeof(sbuf), fmt, args);
va_end(args);
if (size >= sizeof(sbuf))
return -EINVAL;
return cb_copy_from(cb, sbuf, size);
}
static void cb_hexcopy_from(struct cbuf *cb, const char *data, int size)
{
if (WARN_ON(2 * size > cb_wsz(cb)))
return;
while (size--) {
char byte[2];
put_hex(*data++, byte);
*cb->w_ptr = byte[0];
cb_pinc(cb, cb->w_ptr);
*cb->w_ptr = byte[1];
cb_pinc(cb, cb->w_ptr);
}
}
#define cb_puthex(cb, val) \
cb_hexcopy_from((cb), &(val), sizeof(val))
static void cb_putc(struct cbuf *cb, char c)
{
if (WARN_ON(!cb_wsz(cb)))
return;
*cb->w_ptr = c;
cb_pinc(cb, cb->w_ptr);
}
#undef static
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// XXX: TODO: gdb is single-thread, no locking currently.
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
static u32 ugdb_report_quiesce(u32 action, struct utrace_engine *engine,
unsigned long event)
{
return UTRACE_STOP;
}
static const struct utrace_engine_ops ugdb_utrace_ops = {
.report_quiesce = ugdb_report_quiesce,
};
struct ugdb_context {
struct pid *pid;
struct task_struct *tracee;
struct utrace_engine *engine;
struct list_head node;
};
struct ugdb {
char g_ibuf[PACKET_SIZE];
int g_ilen;
struct cbuf g_cbuf;
unsigned char g_csum;
bool g_no_ack;
int g_err;
struct list_head g_attached;
struct ugdb_context *g_current;
};
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->pid = pid;
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));
err = utrace_control_pid(pid, context->engine,
UTRACE_INTERRUPT);
if (err && err != -EINPROGRESS)
goto free_engine;
list_add_tail(&context->node, &ugdb->g_attached);
if (0)
ugdb->g_current = context;
return "S05";
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)
{
if (ugdb->g_current == context)
ugdb->g_current = NULL;
list_del(&context->node);
utrace_control_pid(context->pid, context->engine, UTRACE_DETACH);
utrace_engine_put(context->engine);
put_pid(context->pid);
kfree(context);
}
static void ugdb_detach_all(struct ugdb *ugdb)
{
struct ugdb_context *context, *tmp;
list_for_each_entry_safe(context, tmp, &ugdb->g_attached, node)
ugdb_detach_one(ugdb, context);
}
static struct ugdb_context *find_context(struct ugdb *ugdb, int nr)
{
struct ugdb_context *context;
list_for_each_entry(context, &ugdb->g_attached, node)
if (pid_vnr(context->pid) == nr)
return context;
return NULL;
}
static char *handle_hg(struct ugdb *ugdb, char *cmd)
{
int nr = simple_strtoul(cmd, NULL, 16);
if (!ugdb->g_current || pid_vnr(ugdb->g_current->pid) != nr)
ugdb->g_current = find_context(ugdb, nr);
if (ugdb->g_current)
return "OK";
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 regs;
struct task_struct *task;
int rn;
if (!context)
goto err;
task = pid_task(context->pid, PIDTYPE_PID);
if (!task)
goto err;
if (utrace_prepare_examine(task, context->engine, &exam))
goto err;
view = task_user_regset_view(task);
rset = view->regsets + REGSET_GENERAL;
memset(®s, 0x66, sizeof regs);
rset->get(task, rset, 0, sizeof(regs), ®s, NULL);
if (utrace_finish_examine(task, context->engine, &exam))
goto err;
cb_putc(&ugdb->g_cbuf, '$');
for (rn = 0; rn < ARRAY_SIZE(x86_64_regmap); ++rn) {
int offs = x86_64_regmap[rn];
if (offs < 0)
continue;
if (offs > (sizeof(regs) - sizeof(long))) {
printk(KERN_INFO "XXX: x86_64_regmap is wrong!\n");
}
cb_hexcopy_from(&ugdb->g_cbuf,
offs + (void*)®s, sizeof(long));
}
cb_putc(&ugdb->g_cbuf, '#');
return NULL;
err:
return "E01";
}
// !!!!!!!! UNCOMMENT THIS TO COMPILE !!!!!!!!!!!!!!!!!!!!!!
//#define access_process_vm(task, addr, buf, len, rw) -EFAULT
static void apvm(struct ugdb *ugdb, struct task_struct *task,
unsigned long addr, unsigned long size)
{
unsigned char mem_buf[256];
char *e01 = "E01";
cb_putc(&ugdb->g_cbuf, '$');
while (size > 0) {
int chunk, ret;
chunk = min(size, sizeof(mem_buf));
ret = access_process_vm(task, addr, mem_buf, chunk, 0);
if (ret <= 0)
break;
e01 = "";
cb_hexcopy_from(&ugdb->g_cbuf, mem_buf, ret);
size -= ret;
addr += ret;
}
cb_printf(&ugdb->g_cbuf, "%s#", e01);
}
static 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;
sscanf(cmd, "m%lx,%lx", &addr, &size);
if (!context)
goto err;
task = pid_task(context->pid, PIDTYPE_PID);
if (!task)
goto err;
if (utrace_prepare_examine(task, context->engine, &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 char *handle_d(struct ugdb *ugdb)
{
if (!ugdb->g_current) {
printk(KERN_INFO "XXX: NULL detach\n");
return "E01";
}
ugdb_detach_one(ugdb, ugdb->g_current);
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)
{
char *rc = NULL;
switch (cmd[0]) {
case '!':
rc = "OK";
break;
case '?':
rc = "W00";
break;
case 'q':
if (EQ(cmd, "qSupported")) {
cb_printf(&ugdb->g_cbuf,
"$PacketSize=%x;QStartNoAckMode+",
PACKET_SIZE);
} else if (EQ(cmd, "qTStatus")) {
rc = "T0";
} else if (EQ(cmd, "qfThreadInfo")) {
if (ugdb->g_current) {
int nr = pid_vnr(ugdb->g_current->pid);
cb_printf(&ugdb->g_cbuf, "$m%x#", nr);
return;
}
} else if (EQ(cmd, "qsThreadInfo")) {
rc = "l";
}
break;
case 'g':
rc = handle_g(ugdb);
if (!rc)
return;
break;
case 'm':
rc = handle_m(ugdb, cmd);
if (!rc)
return;
break;
case 'D':
rc = handle_d(ugdb);
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, "Hg")) {
rc = handle_hg(ugdb, cmd);
}
}
if (rc)
cb_printf(&ugdb->g_cbuf, "$%s", rc);
cb_putc(&ugdb->g_cbuf, '#');
}
static void process_commands(struct ugdb *ugdb)
{
char *cmds = ugdb->g_ibuf;
int todo = ugdb->g_ilen;
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);
cb_putc(&ugdb->g_cbuf, '-');
break;
case '-':
printk(KERN_INFO "XXX: got NACK!\n");
case '+':
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)
cb_putc(&ugdb->g_cbuf, '+');
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;
cb_init(&ugdb->g_cbuf);
INIT_LIST_HEAD(&ugdb->g_attached);
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_all(ugdb);
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 (!cb_rsz(&ugdb->g_cbuf)) {
// XXX: what can we do ??????????????????
printk(KERN_INFO "XXX: NOTHING TO REPLY\n");
copy_to_user(ubuf, "$#00", 4);
return 4;
}
count = cb_copy_to_user(&ugdb->g_cbuf, &ugdb->g_csum, 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,
};
#define PROC_NAME "ugdb"
struct proc_dir_entry *ugdb_pde;
static int __init ugdb_init(void)
{
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);