#include #include #include #include #include #include #include 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(®s, 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 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);