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