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(&regs, 0x66, sizeof regs);
        rset->get(task, rset, 0, sizeof(regs), &regs, 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*)&regs, 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);

Reply via email to