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

Reply via email to