On 07/13, Oleg Nesterov wrote: > > And now I am going to rewrite this code without adding the new > functionality (this shouldn't take much time, hopefully today).
Next iteration. Misc changes, but still in the initial stage. The only visible change is that cont/ctrl-c pretends to work. Note that currently I am not even trying to do something meaningful with utrace. My only goal for now is to implement the very basic things like attach/detach/stop/cont/exit correctly from the remote protocol pov. And I want to do this "rightly", then we will discuss utrace issues. 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> 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)) 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 { NO_STOP, STOP_REQ, STOP_ACK, }; struct ugdb_context { int c_stop; const char *fake_rc; 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 (context->c_stop == NO_STOP) return UTRACE_RESUME; context->c_stop = STOP_ACK; wake_up_all(&context->c_ugdb->g_wait); return UTRACE_STOP; } static const struct utrace_engine_ops ugdb_utrace_ops = { .report_quiesce = ugdb_report_quiesce, }; static int ugdb_stop_one(struct ugdb_context *context, const char *fake_rc) { int err; 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 == NO_STOP) return -EALREADY; context->c_stop = NO_STOP; 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) { pb_packs(&ugdb->g_pbuf, context->fake_rc); context->fake_rc = NULL; } } 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 (;;) { int err; if (fatal_signal_pending(current)) return -EINTR; if (context->c_stop == NO_STOP) { printk(KERN_INFO "XXX: unexpected NO_STOP\n"); return -EINVAL; } if (context->c_stop != STOP_ACK) { schedule_timeout_interruptible(1); continue; } 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) { // XXX: MULTIPID 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->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)); 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 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; if (!nr) return ugdb->g_current; 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) { // XXX: MULTIPID 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 *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 (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); if (!context) goto err; 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"; } // !!!!!!!! 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, 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 = 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 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 (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 char *handle_d(struct ugdb *ugdb) { // XXX: MULTIPID 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 = ""; 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 'D': rc = handle_d(ugdb); break; case 'c': ugdb_c_all(ugdb, NULL); rc = NULL; break; case 'q': if (EQ(cmd, "qSupported")) { pb_packf(&ugdb->g_pbuf, "PacketSize=%x;QStartNoAckMode+", PACKET_SIZE); rc = NULL; } else if (EQ(cmd, "qfThreadInfo")) { if (ugdb->g_current) { // XXX: MULTIPID int nr = pid_vnr(ugdb->g_current->pid); pb_packf(&ugdb->g_pbuf, "m%x", nr); rc = NULL; } } else if (EQ(cmd, "qsThreadInfo")) { rc = "l"; } else if (EQ(cmd, "qC")) { if (ugdb->g_current) { // XXX: MULTIPID int nr = pid_vnr(ugdb->g_current->pid); pb_packf(&ugdb->g_pbuf, "m%x", nr); rc = NULL; } } 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, "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 '\3': 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_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 (!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, }; #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);