gdbstub initial code, v10
Oleg Nesterov
oleg@redhat.com
Mon Sep 20 03:31:00 GMT 2010
Back to ugdb.
Changes: implement stepi, this also means breakpoints work too.
Notes:
- almost untested, probably needs more fixes
- syscall-stepping doesn't work correctly (should be simple)
- don't look at handle_setregs/handle_set_one_reg, I did
this on purpose to be sure I really understand what gdb
actually does
Well. This wasn't simple because I nearly got heart attack twice
when I was writing this change ;)
However, it turns out that ptrace-utrace (old or recently changed)
is innocent.
I found 2 problems, the 1st one is /usr/bin/gdb bug, another one
(I believe) is utrace core bug. I'll report tomorrow.
Oleg.
-------------- next part --------------
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/utrace.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/regset.h>
#include <asm/uaccess.h>
static int o_remote_debug;
module_param_named(echo, o_remote_debug, bool, 0);
#define BUFFER_SIZE 1024
#define PACKET_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;
}
enum {
U_STOP_IDLE = 0,
U_STOP_PENDING,
U_STOP_SENT,
};
struct ugdb {
struct list_head u_processes;
struct list_head u_stopped;
int u_stop_state;
struct mutex u_mutex;
spinlock_t u_slock;
struct ugdb_thread
*u_cur_tinfo,
*u_cur_hg,
*u_cur_hc;
wait_queue_head_t u_wait;
int u_err;
struct pbuf u_pbuf;
char u_cbuf[PACKET_SIZE];
int u_clen;
sigset_t u_sig_ign;
unsigned int
u_no_ack:1,
u_allstop:1;
};
static inline void ugdb_ck_stopped(struct ugdb *ugdb)
{
spin_lock(&ugdb->u_slock);
WARN_ON(!list_empty(&ugdb->u_stopped) &&
ugdb->u_stop_state == U_STOP_IDLE);
WARN_ON(list_empty(&ugdb->u_stopped) &&
ugdb->u_stop_state == U_STOP_PENDING);
spin_unlock(&ugdb->u_slock);
}
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;
INIT_LIST_HEAD(&ugdb->u_processes);
INIT_LIST_HEAD(&ugdb->u_stopped);
mutex_init(&ugdb->u_mutex);
spin_lock_init(&ugdb->u_slock);
init_waitqueue_head(&ugdb->u_wait);
pb_init(&ugdb->u_pbuf);
return ugdb;
put_module:
module_put(THIS_MODULE);
out:
return ERR_PTR(err);
}
#define P_DETACHING (1 << 1)
#define P_ZOMBIE (1 << 2)
struct ugdb_process {
int p_pid;
int p_state;
struct list_head p_threads;
struct ugdb *p_ugdb;
struct list_head p_processes;
};
static inline bool process_alive(struct ugdb_process *process)
{
return !(process->p_state & P_ZOMBIE);
}
static inline void mark_process_dead(struct ugdb_process *process)
{
process->p_state |= P_ZOMBIE;
}
static struct ugdb_process *ugdb_create_process(struct ugdb *ugdb, int pid_nr)
{
struct ugdb_process *process;
process = kzalloc(sizeof(*process), GFP_KERNEL);
if (!process)
return NULL;
process->p_pid = pid_nr;
process->p_ugdb = ugdb;
INIT_LIST_HEAD(&process->p_threads);
list_add_tail(&process->p_processes, &ugdb->u_processes);
return process;
}
#define T_STOP_RUN 0
#define T_STOP_REQ (1 << 0) /* requested by gdb */
#define T_STOP_ALL (1 << 1) /* vCont;c:pX.-1, for report_clone */
#define T_STOP_ACK (1 << 2) /* visible to vStopped */
#define T_STOP_STOPPED (1 << 3) /* reported as stopped to gdb */
/* TASK_TRACED + deactivated ? */
#define T_EV_NONE 0
#define T_EV_EXIT (1 << 24)
#define T_EV_SIGN (2 << 24)
#define T_EV_TYPE(event) ((0xff << 24) & (event))
#define T_EV_DATA(event) (~(0xff << 24) & (event))
struct ugdb_thread {
int t_tid;
int t_stop_state;
int t_stop_event;
bool t_step; // XXX: move me into t_stop_event
siginfo_t *t_siginfo;
struct ugdb *t_ugdb;
struct ugdb_process *t_process;
struct list_head t_threads;
struct list_head t_stopped;
struct pid *t_spid;
struct utrace_engine *t_engine;
};
static inline bool thread_alive(struct ugdb_thread *thread)
{
WARN_ON((thread->t_tid != 0) != process_alive(thread->t_process));
return thread->t_tid != 0;
}
static inline void mark_thread_dead(struct ugdb_thread *thread)
{
mark_process_dead(thread->t_process);
thread->t_tid = 0;
}
/*
* The thread should be alive, and it can't pass ugdb_report_death()
* if the caller holds ugdb->u_mutex. However the tracee can be
* reaped anyway, pid_task() can return NULL after detach_pid().
*/
static inline struct task_struct *thread_to_task(struct ugdb_thread *thread)
{
BUG_ON(!thread_alive(thread));
return pid_task(thread->t_spid, PIDTYPE_PID);
}
static struct ugdb_thread *ugdb_create_thread(struct ugdb_process *process,
struct pid *spid)
{
struct ugdb_thread *thread;
thread = kzalloc(sizeof(*thread), GFP_KERNEL);
if (!thread)
return NULL;
thread->t_tid = pid_vnr(spid);
thread->t_spid = get_pid(spid);
thread->t_process = process;
thread->t_ugdb = process->p_ugdb;
INIT_LIST_HEAD(&thread->t_stopped);
list_add_tail(&thread->t_threads, &process->p_threads);
return thread;
}
static void ugdb_del_stopped(struct ugdb *ugdb, struct ugdb_thread *thread)
{
if (!list_empty(&thread->t_stopped)) {
WARN_ON(!(thread->t_stop_state & T_STOP_ACK));
spin_lock(&ugdb->u_slock);
list_del_init(&thread->t_stopped);
if (!(thread->t_stop_state & T_STOP_STOPPED)) {
if (ugdb->u_stop_state == U_STOP_PENDING &&
list_empty(&ugdb->u_stopped))
ugdb->u_stop_state = U_STOP_IDLE;
}
spin_unlock(&ugdb->u_slock);
}
thread->t_stop_state = T_STOP_RUN;
}
static void ugdb_destroy_thread(struct ugdb_thread *thread)
{
struct ugdb *ugdb = thread->t_ugdb;
ugdb_ck_stopped(ugdb);
ugdb_del_stopped(ugdb, thread);
/* NULL if attach fails */
if (thread->t_engine)
utrace_engine_put(thread->t_engine);
list_del(&thread->t_threads);
put_pid(thread->t_spid);
kfree(thread);
}
static int ugdb_set_events(struct ugdb_thread *thread,
unsigned long events)
{
WARN_ON(!thread_alive(thread));
events |= (UTRACE_EVENT(CLONE) | UTRACE_EVENT(DEATH) |
UTRACE_EVENT_SIGNAL_ALL);
//XXX: I think utrace_get_signal() is buggy !!!!!!!!!!!!
events |= UTRACE_EVENT(QUIESCE);
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)
{
// XXX: temporary racy check
WARN_ON(!thread_alive(thread) && action != UTRACE_DETACH);
return utrace_control_pid(thread->t_spid, thread->t_engine,
action);
}
static const struct utrace_engine_ops ugdb_utrace_ops;
static void ugdb_detach_thread(struct ugdb_thread *thread, bool running)
{
int ret;
ret = ugdb_control(thread, UTRACE_DETACH);
/* engine->flags == 0, it can't run a callback */
if (!running)
return;
/* callbacks are no longer possible */
if (!ret)
return;
if (ret == -EINPROGRESS) {
/*
* We raced with a callback, it can't be ->report_death().
* However, we can not use utrace_barrier_pid(), it can
* hang "forever" until the next utrace_resume() if it
* sees ->ops == &utrace_detached_ops set by us, but the
* tracee is no longer running.
*
* But: we have no choice.
*/
do {
ret = utrace_barrier_pid(thread->t_spid,
thread->t_engine);
} while (ret == -ERESTARTSYS);
} else {
/*
* Nothing can help us to synchronize with ->report_death.
* We do not know if it was already called or not, we can't
* know if it is running. utrace_barrier_pid() can't help,
* it can return zero and then later ->report_death() will
* be called. Or it can return -ESRCH just because the task
* was alredy released and pid_task() == NULL, but this
* doesn't mean ->report_death() can't be called later.
*
* Fortunately, we know that the tracee is dying or dead,
* engine->ops should be changed after ugdb_report_death()
* returns UTRACE_DETACH.
*/
while (thread->t_engine->ops == &ugdb_utrace_ops) {
schedule_timeout_uninterruptible(1);
}
}
}
/*
* returns NULL if raced with exit(), or ERR_PTR().
*/
static struct ugdb_thread *ugdb_attach_thread(struct ugdb_process *process,
struct pid *spid)
{
struct ugdb_thread *thread;
struct utrace_engine *engine;
struct task_struct *task;
thread = ugdb_create_thread(process, spid);
if (!thread)
goto err;
engine = utrace_attach_pid(thread->t_spid, UTRACE_ATTACH_CREATE,
&ugdb_utrace_ops, thread);
if (IS_ERR(engine))
goto free_thread;
thread->t_engine = engine;
if (ugdb_set_events(thread, 0))
goto detach_thread;
return thread;
detach_thread:
ugdb_detach_thread(thread, false);
free_thread:
ugdb_destroy_thread(thread);
err:
rcu_read_lock();
task = pid_task(spid, PIDTYPE_PID);
if (task && task->exit_state)
task = NULL;
rcu_read_unlock();
return task ? ERR_PTR(-ENOMEM) : NULL;
}
static inline bool is_subthread(struct ugdb_process *process,
struct ugdb_thread *thread)
{
return thread && thread->t_process == process;
}
static inline void ugdb_reset_tinfo(struct ugdb *ugdb)
{
ugdb->u_cur_tinfo = NULL;
}
static void ugdb_destroy_process(struct ugdb_process *process)
{
struct ugdb *ugdb = process->p_ugdb;
struct ugdb_thread *thread;
mutex_lock(&ugdb->u_mutex);
process->p_state |= P_DETACHING;
list_del(&process->p_processes);
if (is_subthread(process, ugdb->u_cur_hg))
ugdb->u_cur_hg = NULL;
if (is_subthread(process, ugdb->u_cur_hc))
ugdb->u_cur_hc = NULL;
/* I hope gdb won't do detach from under qfThreadInfo */
if (ugdb->u_cur_tinfo) {
printk(KERN_WARNING "ugdb: detach from under qfThreadInfo\n");
ugdb_reset_tinfo(ugdb);
}
mutex_unlock(&ugdb->u_mutex);
while (!list_empty(&process->p_threads)) {
thread = list_first_entry(&process->p_threads,
struct ugdb_thread, t_threads);
ugdb_detach_thread(thread, true);
ugdb_destroy_thread(thread);
}
BUG_ON(!list_empty(&process->p_threads));
kfree(process);
}
static void ugdb_destroy(struct ugdb *ugdb)
{
struct ugdb_process *process;
while (!list_empty(&ugdb->u_processes)) {
process = list_first_entry(&ugdb->u_processes,
struct ugdb_process, p_processes);
ugdb_destroy_process(process);
}
BUG_ON(!list_empty(&ugdb->u_processes));
BUG_ON(!list_empty(&ugdb->u_stopped));
module_put(THIS_MODULE);
kfree(ugdb);
}
static struct pid *get_next_pid(struct pid *main, struct pid *curr)
{
struct task_struct *task;
struct sighand_struct *sighand;
struct pid *next = NULL;
rcu_read_lock();
/*
* If task/sighand is NULL we return NULL. This is fine if
* the caller is get_first_pid(), we should abort attaching.
*
* But this can also happen if curr was already attached,
* and this is wrong. Fortunately, this is very unlikely
* case. The attached sub-thread can't pass ->report_death,
* if it was reaped the caller of release_task() must be
* ptracer who re-parented this thread.
*/
task = pid_task(curr, PIDTYPE_PID);
if (!task)
goto unlock_rcu;
// XXX: we need lock_task_sighand() but it is not exported,
// so we ran race with de_thread().
sighand = rcu_dereference(task->sighand);
if (!sighand)
goto unlock_rcu;
spin_lock_irq(&sighand->siglock);
for (;;) {
task = next_thread(task);
// XXX: if main is not leader we can race with exec.
if (task_pid(task) == main)
break;
if (!task->exit_state) {
next = get_pid(task_pid(task));
break;
}
}
spin_unlock_irq(&sighand->siglock);
unlock_rcu:
rcu_read_unlock();
return next;
}
static struct pid *get_first_pid(struct pid *main)
{
struct task_struct *leader;
rcu_read_lock();
leader = pid_task(main, PIDTYPE_PID);
if (leader && leader->exit_state)
leader = NULL;
rcu_read_unlock();
/*
* The group-leader is alive, try to attach. If it exits
* before utrace_set_events(), get_first_pid() will be
* called again and it will notice ->exit_state != 0.
*/
if (leader)
return get_pid(main);
/*
* Try to find the live sub-thread. If the whole group
* is dead it returns NULL and the caller aborts.
*/
return get_next_pid(main, main);
}
static int ugdb_attach_all_threads(struct ugdb *ugdb,
struct ugdb_process *process,
struct pid *main_pid)
{
struct ugdb_thread *thread;
struct pid *curr_pid;
mutex_lock(&ugdb->u_mutex);
for (;;) {
curr_pid = get_first_pid(main_pid);
if (!curr_pid)
goto abort;
thread = ugdb_attach_thread(process, curr_pid);
put_pid(curr_pid);
if (IS_ERR(thread))
goto abort;
if (thread)
break;
}
for (;;) {
struct pid *next_pid;
next_pid = get_next_pid(main_pid, curr_pid);
if (!next_pid)
break;
thread = ugdb_attach_thread(process, next_pid);
put_pid(next_pid);
if (IS_ERR(thread))
goto abort;
if (!thread)
continue;
curr_pid = next_pid;
}
mutex_unlock(&ugdb->u_mutex);
return 0;
abort:
mutex_unlock(&ugdb->u_mutex);
return -1;
}
static int ugdb_attach(struct ugdb *ugdb, int pid_nr)
{
struct pid *main_pid;
struct ugdb_process *process;
int err;
// XXX: check if exists
// XXX: check if group leader ?
err = -ESRCH;
main_pid = find_get_pid(pid_nr);
if (!main_pid)
goto out;
err = -ENOMEM;
process = ugdb_create_process(ugdb, pid_nr);
if (!process)
goto free_pid;
err = ugdb_attach_all_threads(ugdb, process, main_pid);
if (err)
ugdb_destroy_process(process);
free_pid:
put_pid(main_pid);
out:
return err;
}
static struct ugdb_process *ugdb_find_process(struct ugdb *ugdb, int pid)
{
struct ugdb_process *process;
list_for_each_entry(process, &ugdb->u_processes, p_processes) {
if (process->p_pid == pid)
return process;
}
return NULL;
}
static struct ugdb_thread *ugdb_find_thread(struct ugdb *ugdb, int pid, int tid)
{
struct ugdb_process *process;
struct ugdb_thread *thread;
list_for_each_entry(process, &ugdb->u_processes, p_processes) {
if (unlikely(!process_alive(process)))
continue;
if (pid && process->p_pid != pid)
continue;
list_for_each_entry(thread, &process->p_threads, t_threads) {
if (WARN_ON(!thread_alive(thread)))
continue;
if (!tid || thread->t_tid == tid)
return thread;
}
if (pid)
break;
}
return NULL;
}
static int ugdb_detach(struct ugdb *ugdb, int pid)
{
struct ugdb_process *process = ugdb_find_process(ugdb, pid);
if (!process)
return -1;
ugdb_destroy_process(process);
return 0;
}
#define CUR_TINFO_END ((struct ugdb_thread *)1)
static struct ugdb_thread *ugdb_advance_tinfo(struct ugdb *ugdb)
{
struct ugdb_thread *cur, *nxt;
struct ugdb_process *process;
cur = ugdb->u_cur_tinfo;
if (cur == CUR_TINFO_END) {
ugdb->u_cur_tinfo = NULL;
return NULL;
}
if (!cur) {
list_for_each_entry(process, &ugdb->u_processes, p_processes) {
if (unlikely(!process_alive(process)))
continue;
if (!list_empty(&process->p_threads)) {
cur = list_first_entry(&process->p_threads,
struct ugdb_thread, t_threads);
break;
}
}
if (!cur)
return NULL;
}
process = cur->t_process;
if (list_is_last(&cur->t_threads, &process->p_threads)) {
nxt = CUR_TINFO_END;
list_for_each_entry_continue(process, &ugdb->u_processes, p_processes) {
if (unlikely(!process_alive(process)))
continue;
if (!list_empty(&process->p_threads)) {
nxt = list_first_entry(&process->p_threads,
struct ugdb_thread, t_threads);
break;
}
}
} else {
nxt = list_first_entry(&cur->t_threads,
struct ugdb_thread, t_threads);
}
ugdb->u_cur_tinfo = nxt;
return cur;
}
// -----------------------------------------------------------------------------
static bool ugdb_add_stopped(struct ugdb_thread *thread, int stop_event)
{
struct ugdb *ugdb = thread->t_ugdb;
bool ret = false;
ugdb_ck_stopped(ugdb);
spin_lock(&ugdb->u_slock);
if (stop_event == T_EV_NONE) {
if (WARN_ON(thread->t_stop_state & T_STOP_ACK))
goto unlock;
if (WARN_ON(!list_empty(&thread->t_stopped)))
goto unlock;
/* raced with ugdb_cont_thread() */
if (!(thread->t_stop_state & T_STOP_REQ))
goto unlock;
}
if (thread->t_stop_state & T_STOP_ACK) {
if (thread->t_stop_state & T_STOP_STOPPED)
/*
* Alas, we can't report this event. We already
* reported T00 and there is no way to inform gdb
* the state of tracee was changed.
*/
goto unlock;
} else {
WARN_ON(thread->t_stop_state & T_STOP_STOPPED);
thread->t_stop_state |= T_STOP_ACK;
list_add_tail(&thread->t_stopped, &ugdb->u_stopped);
if (ugdb->u_stop_state == U_STOP_IDLE) {
ugdb->u_stop_state = U_STOP_PENDING;
wake_up_all(&ugdb->u_wait);
}
}
thread->t_stop_event = stop_event;
ret = true;
unlock:
spin_unlock(&ugdb->u_slock);
return ret;
}
static void ugdb_process_exit(struct ugdb_thread *thread)
{
struct ugdb *ugdb = thread->t_ugdb;
int status;
BUG_ON(!thread_alive(thread));
ugdb_del_stopped(ugdb, thread);
mark_thread_dead(thread);
// XXX: OOPS, we can't read ->signal->group_exit_code !!!
status = current->exit_code;
if (ugdb_add_stopped(thread, T_EV_EXIT | status))
return;
WARN_ON(1);
}
static int ugdb_stop_thread(struct ugdb_thread *thread, bool all)
{
struct ugdb *ugdb = thread->t_ugdb;
int err;
WARN_ON(!thread_alive(thread));
ugdb_ck_stopped(ugdb);
if (thread->t_stop_state != T_STOP_RUN) {
if (!all || (thread->t_stop_state & T_STOP_ALL))
return 0;
/*
* Otherwise we should set T_STOP_ALL anyway,
*
* (gdb) interrupt &
* (gbd) interrupt -a &
*
* to ensure -a actually works if it races with clone.
*/
}
err = -EALREADY;
spin_lock(&ugdb->u_slock);
if (thread->t_stop_state == T_STOP_RUN) {
thread->t_stop_state = T_STOP_REQ;
err = 0;
}
/*
* We hold ugdb->u_mutex, we can't race with ugdb_report_clone().
* ugdb->u_slock protects us against ugdb_add_stopped(). We can
* change ->t_stop_state even if we did not initiate this stop.
*/
if (all)
thread->t_stop_state |= T_STOP_ALL;
spin_unlock(&ugdb->u_slock);
if (err)
return 0;
// XXX: we don't do UTRACE_STOP! this means we can't
// stop TASK_STOPEED task. Need to discuss jctl issues.
// if we do UTRACE_STOP we should call ugdb_add_stopped().
ugdb_set_events(thread, UTRACE_EVENT(QUIESCE));
err = ugdb_control(thread, UTRACE_INTERRUPT);
if (err && err != -EINPROGRESS)
return err;
return 1;
}
static int ugdb_cont_thread(struct ugdb_thread *thread, bool all, bool step)
{
struct ugdb *ugdb = thread->t_ugdb;
int ret;
WARN_ON(!thread_alive(thread));
ugdb_ck_stopped(ugdb);
// XXX: gdb shouldn't explicitly cont an unreported thread
WARN_ON(!all && !(thread->t_stop_state & T_STOP_STOPPED));
if (thread->t_stop_state == T_STOP_RUN)
return 0;
spin_lock(&ugdb->u_slock);
/*
* Nothing to do except clear the pending T_STOP_REQ.
*/
ret = 0;
if (!(thread->t_stop_state & T_STOP_ACK))
goto set_run;
/*
* Alas. Thanks to remote protocol, we can't cont this
* thread. We probably already sent the notification, we
* can do nothing except ack that %Stop later in response
* to vStopped.
*
* OTOH, gdb shouldn't send 'c' if this thread was not
* reported as stopped. However, this means that gdb can
* see the new %Stop:T00 notification after vCont;c:pX.-1,
* it should handle this case correctly anyway. I hope.
*
* If this stop was not initiated by gdb we should not
* cancel it too, this event should be reported first.
*/
ret = -1;
if (!(thread->t_stop_state & T_STOP_STOPPED))
goto unlock;
ret = 1;
list_del_init(&thread->t_stopped);
set_run:
thread->t_stop_state = T_STOP_RUN;
unlock:
spin_unlock(&ugdb->u_slock);
if (ret >= 0) {
unsigned long events = 0;
enum utrace_resume_action action = UTRACE_RESUME;
thread->t_step = step;
if (step) {
// events |= UTRACE_EVENT(SYSCALL_EXIT)
action = UTRACE_SINGLESTEP;
}
// XXX: OK, this all is racy, and I do not see any
// solution except: implement UTRACE_STOP_STICKY and
// move this code up under the lock, or add
// utrace_engine_ops->notify_stopped().
// 1. UTRACE_RESUME is racy, this is fixeable.
// 2. we need utrace_barrier() to close the race
// with the callback which is going to return
// UTRACE_STOP, but:
// a) we can deadlock (solveable)
// b) in this case UTRACE_RESUME can race with
// another stop initiated by tracee itself.
ugdb_set_events(thread, events);
ugdb_control(thread, action);
}
return ret;
}
static struct ugdb_thread *ugdb_next_stopped(struct ugdb *ugdb)
{
struct ugdb_thread *thread = NULL;
// XXX: temporary racy check
WARN_ON(ugdb->u_stop_state == U_STOP_IDLE);
spin_lock(&ugdb->u_slock);
if (list_empty(&ugdb->u_stopped)) {
ugdb->u_stop_state = U_STOP_IDLE;
} else {
ugdb->u_stop_state = U_STOP_SENT;
thread = list_first_entry(&ugdb->u_stopped,
struct ugdb_thread, t_stopped);
thread->t_stop_state |= T_STOP_STOPPED;
list_del_init(&thread->t_stopped);
}
spin_unlock(&ugdb->u_slock);
return thread;
}
// -----------------------------------------------------------------------------
static bool ugdb_stop_pending(struct ugdb_thread *thread)
{
if (!(thread->t_stop_state & T_STOP_REQ))
return false;
if (!(thread->t_stop_state & T_STOP_ACK))
return ugdb_add_stopped(thread, T_EV_NONE);
return true;
}
static u32 ugdb_report_quiesce(u32 action, struct utrace_engine *engine,
unsigned long event)
{
struct ugdb_thread *thread = engine->data;
WARN_ON(!thread_alive(thread));
/* ensure SIGKILL can't race with stop/cont in progress */
if (event != UTRACE_EVENT(DEATH)) {
if (ugdb_stop_pending(thread))
return UTRACE_STOP;
}
return utrace_resume_action(action);
}
static int cont_signal(struct ugdb_thread *thread,
struct k_sigaction *return_ka)
{
int signr = T_EV_DATA(thread->t_stop_event);
siginfo_t *info = thread->t_siginfo;
thread->t_siginfo = NULL;
if (WARN_ON(!valid_signal(signr)))
return 0;
if (!signr)
return signr;
/*
* Update the siginfo structure if the signal has changed.
*/
if (info->si_signo != signr) {
info->si_signo = signr;
info->si_errno = 0;
info->si_code = SI_USER;
info->si_pid = 0;
info->si_uid = 0;
}
/* If the (new) signal is now blocked, requeue it. */
if (sigismember(¤t->blocked, signr)) {
send_sig_info(signr, info, current);
signr = 0;
} else {
spin_lock_irq(¤t->sighand->siglock);
*return_ka = current->sighand->action[signr - 1];
spin_unlock_irq(¤t->sighand->siglock);
}
return signr;
}
static u32 ugdb_report_signal(u32 action, struct utrace_engine *engine,
struct pt_regs *regs,
siginfo_t *info,
const struct k_sigaction *orig_ka,
struct k_sigaction *return_ka)
{
struct ugdb_thread *thread = engine->data;
struct ugdb *ugdb = thread->t_ugdb;
int signr;
WARN_ON(!thread_alive(thread));
switch (utrace_signal_action(action)) {
case UTRACE_SIGNAL_HANDLER:
if (WARN_ON(thread->t_siginfo))
thread->t_siginfo = NULL;
if (thread->t_step) {
// user_single_step_siginfo(current, regs, info);
memset(info, 0, sizeof(*info));
info->si_signo = SIGTRAP;
break;
}
/* Fall through */
default:
if (orig_ka)
break;
/*
* It was UTRACE_SIGNAL_REPORT, but another tracer has
* changed utrace_report->result to deliver or stop.
* Fall through.
*/
case UTRACE_SIGNAL_REPORT:
if (thread->t_siginfo) {
if (WARN_ON(thread->t_siginfo != info))
return action;
WARN_ON(T_EV_TYPE(thread->t_stop_event) != T_EV_SIGN);
signr = cont_signal(thread, return_ka);
if (signr) {
/*
* Consider:
*
* (gdb) signal SIG &
* (gdb) interrupt
*
* We shouldn't miss the new stop request, so
* we do not return from here.
*/
action = UTRACE_RESUME | UTRACE_SIGNAL_DELIVER;
}
}
if (ugdb_stop_pending(thread))
return UTRACE_STOP | utrace_signal_action(action);
if (thread->t_step)
return UTRACE_SINGLESTEP | utrace_signal_action(action);
return action;
}
WARN_ON(thread->t_siginfo);
signr = info->si_signo;
if (WARN_ON(!signr || !valid_signal(signr)))
return action;
if (sigismember(&ugdb->u_sig_ign, signr))
return action;
if (ugdb_add_stopped(thread, T_EV_SIGN | signr)) {
thread->t_siginfo = info;
/*
* Make sure the subsequent UTRACE_SIGNAL_REPORT clears
* ->t_siginfo before return from get_signal_to_deliver().
*/
if (utrace_control(current, engine, UTRACE_INTERRUPT))
WARN_ON(1);
return UTRACE_STOP | UTRACE_SIGNAL_IGN;
}
/*
* We already reported T00 to gdb. We can't change our state,
* we are already stopped from gdb pov. Push back this signal
* to report it later, after "continue".
*
* Not multitrace-friendly.
*/
return UTRACE_STOP | UTRACE_SIGNAL_REPORT | UTRACE_SIGNAL_HOLD;
}
static bool is_already_attached(struct ugdb_process *process,
struct task_struct *task)
{
struct ugdb_thread *thread;
if (likely(!task_utrace_flags(task)))
return false;
/*
* Currently there is no way to know if it was attached by us.
* We can't trust utrace_attach_task(UTRACE_ATTACH_MATCH_OPS),
* ugdb attaches without UTRACE_ATTACH_EXCLUSIVE. We have to
* check every attached thread.
*
* This is really bad, but without multitracing this can only
* happen in unlikely case right after ugdb_attach_all_threads().
*/
list_for_each_entry(thread, &process->p_threads, t_threads) {
if (thread->t_spid == task_pid(task))
return true;
}
return false;
}
static u32 ugdb_report_clone(u32 action, struct utrace_engine *engine,
unsigned long clone_flags,
struct task_struct *task)
{
struct ugdb_thread *thread = engine->data;
struct ugdb_process *process = thread->t_process;
struct ugdb *ugdb = thread->t_ugdb;
struct ugdb_thread *new_thread;
WARN_ON(!thread_alive(thread));
if (!(clone_flags & CLONE_THREAD))
goto out;
mutex_lock(&ugdb->u_mutex);
if (process->p_state & P_DETACHING)
goto unlock;
/*
* This can only happen if we raced with ugdb_attach() which
* could attach both current and the new PF_STARTING child.
*/
if (unlikely(is_already_attached(process, task)))
goto unlock;
new_thread = ugdb_attach_thread(process, task_pid(task));
BUG_ON(!new_thread);
if (WARN_ON(IS_ERR(new_thread)))
goto unlock;
if (thread->t_stop_state & T_STOP_ALL)
ugdb_stop_thread(new_thread, false);
unlock:
mutex_unlock(&ugdb->u_mutex);
out:
return utrace_resume_action(action);
}
static u32 ugdb_report_death(struct utrace_engine *engine,
bool group_dead, int signal)
{
struct ugdb_thread *thread = engine->data;
struct ugdb_process *process = thread->t_process;
struct ugdb *ugdb = thread->t_ugdb;
WARN_ON(!thread_alive(thread));
mutex_lock(&ugdb->u_mutex);
if (process->p_state & P_DETACHING)
goto unlock;
if (ugdb->u_cur_hg == thread)
ugdb->u_cur_hg = NULL;
if (ugdb->u_cur_hc == thread)
ugdb->u_cur_hc = NULL;
if (ugdb->u_cur_tinfo == thread)
ugdb_advance_tinfo(ugdb);
if (list_is_singular(&process->p_threads))
ugdb_process_exit(thread);
else
ugdb_destroy_thread(thread);
unlock:
mutex_unlock(&ugdb->u_mutex);
return UTRACE_DETACH;
}
static const struct utrace_engine_ops ugdb_utrace_ops = {
.report_quiesce = ugdb_report_quiesce,
.report_signal = ugdb_report_signal,
.report_clone = ugdb_report_clone,
.report_death = ugdb_report_death,
};
// -----------------------------------------------------------------------------
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-1))
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, char pref)
{
WARN_ON(pb->pkt);
pb_putc(pb, pref);
pb->pkt = pb->cur;
}
static inline void pb_start(struct pbuf *pb)
{
return __pb_start(pb, '$');
}
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) {
/* pb_qfer() can write '%' */
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 int pb_qfer(struct pbuf *pb, const void *_data, int len, bool more)
{
const unsigned char *data = _data;
int i;
if (pb_room(pb) < 3 + len * 2) {
WARN_ON(1);
return -EOVERFLOW;
}
pb_start(pb);
pb_putc(pb, more ? 'm' : 'l');
for (i = 0; i < len; ++i) {
unsigned char c = data[i];
if (c == '$' || c == '#' || c == '}' || c == '*') {
pb_putc(pb, '}');
c ^= 0x20;
}
pb_putc(pb, c);
}
pb_end(pb);
return 0;
}
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 + 1;
}
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 (!copy)
return -EAGAIN;
if (o_remote_debug)
printk(KERN_INFO "<= %.*s\n", min(copy, 64), pb->buf);
if (copy_to_user(ubuf, pb->buf, copy))
return -EFAULT;
pb_flush(pb, copy);
return copy;
}
// -----------------------------------------------------------------------------
// XXX: include/gdb/signals.h:target_signal
// incomplete: 7, 29, rt?
static int to_gdb_sigmap[] = {
[SIGHUP] = 1,
[SIGINT] = 2,
[SIGQUIT] = 3,
[SIGILL] = 4,
[SIGTRAP] = 5,
[SIGABRT] = 6,
[SIGIOT] = 0, /* ??? */
[SIGBUS] = 10,
[SIGFPE] = 8,
[SIGKILL] = 9,
[SIGUSR1] = 30,
[SIGSEGV] = 11,
[SIGUSR2] = 31,
[SIGPIPE] = 13,
[SIGALRM] = 14,
[SIGTERM] = 15,
[SIGSTKFLT] = 0, /* ??? */
[SIGCHLD] = 20,
[SIGCONT] = 19,
[SIGSTOP] = 17,
[SIGTSTP] = 18,
[SIGTTIN] = 21,
[SIGTTOU] = 22,
[SIGURG] = 16,
[SIGXCPU] = 24,
[SIGXFSZ] = 25,
[SIGVTALRM] = 26,
[SIGPROF] = 27,
[SIGWINCH] = 28,
[SIGIO] = 23,
[SIGPWR] = 32,
[SIGSYS] = 12,
};
static int sig_to_gdb(unsigned sig)
{
if (sig < ARRAY_SIZE(to_gdb_sigmap) && to_gdb_sigmap[sig])
return to_gdb_sigmap[sig];
return sig;
}
static int sig_from_gdb(unsigned sig)
{
int i;
// XXX: valid_signal() is wrong, gdb has its own idea
// about signals. fix to_gdb_sigmap[].
if (!sig || !valid_signal(sig))
return 0;
for (i = 0; i < ARRAY_SIZE(to_gdb_sigmap); i++) {
if (to_gdb_sigmap[i] == sig)
return i;
}
return sig;
}
static int ugdb_report_stopped(struct ugdb *ugdb, bool async)
{
struct ugdb_thread *thread;
int pid, tid, event, data;
struct pbuf *pb;
char ex_r;
mutex_lock(&ugdb->u_mutex);
thread = ugdb_next_stopped(ugdb);
if (!thread)
goto unlock;
event = thread->t_stop_event;
WARN_ON(thread_alive(thread) != (T_EV_TYPE(event) != T_EV_EXIT));
pid = thread->t_process->p_pid;
tid = thread->t_tid;
unlock:
mutex_unlock(&ugdb->u_mutex);
if (!thread)
return false;
pb = &ugdb->u_pbuf;
// XXX: damn, cleanup me...
if (async) {
__pb_start(pb, '%');
pb_puts(pb, "Stop:");
} else {
pb_start(pb);
}
data = T_EV_DATA(event);
switch (T_EV_TYPE(event)) {
case T_EV_EXIT:
if (data & 0xff) {
data = sig_to_gdb(data & 0xff);
ex_r = 'X';
} else {
data >>= 8;
ex_r = 'W';
}
pb_printf(pb, "%c%x;process:%x", ex_r, data, pid);
ugdb_destroy_process(thread->t_process);
break;
case T_EV_SIGN:
case T_EV_NONE:
pb_printf(pb, "T%02xthread:p%x.%x;",
sig_to_gdb(data), pid, tid);
break;
default:
printk(KERN_INFO "ugdb: bad stop event %x\n", event);
}
pb_end(pb);
return true;
}
const char *handle_vstopped(struct ugdb *ugdb)
{
if (ugdb->u_stop_state != U_STOP_SENT)
return "E01";
if (ugdb_report_stopped(ugdb, false))
return NULL;
return "OK";
}
static const char *handle_thread_info(struct ugdb *ugdb, bool start)
{
struct ugdb_thread *thread;
int pid = 0, tid;
mutex_lock(&ugdb->u_mutex);
if (start)
ugdb_reset_tinfo(ugdb);
else if (!ugdb->u_cur_tinfo)
printk(KERN_INFO "ugdb: unexpected qsThreadInfo\n");
thread = ugdb_advance_tinfo(ugdb);
if (thread) {
pid = thread->t_process->p_pid;
tid = thread->t_tid;
}
mutex_unlock(&ugdb->u_mutex);
if (!pid)
return start ? "E01" : "l";
pb_packf(&ugdb->u_pbuf, "mp%x.%x", pid, tid);
return NULL;
}
static char *parse_xid(char *str, int *ppid, bool multi)
{
if (*str == '-') {
str++;
if (multi && *str++ == '1')
*ppid = -1;
else
str = NULL;
} else {
char *cur = str;
*ppid = simple_strtoul(cur, &str, 16);
if (str == cur)
str = NULL;
}
return str;
}
static char *parse_pid_tid(char *str, int *ppid, int *ptid, bool multi)
{
if (*str++ != 'p')
return NULL;
str = parse_xid(str, ppid, multi);
if (!str)
return NULL;
if (*str++ != '.')
return NULL;
str = parse_xid(str, ptid, multi);
if (!str)
return NULL;
return str;
}
static const char *handle_set_cur(struct ugdb *ugdb, char *cmd)
{
struct ugdb_thread **pthread;
int pid, tid;
switch (*cmd++) {
case 'g':
pthread = &ugdb->u_cur_hg;
break;
case 'c':
pthread = &ugdb->u_cur_hc;
break;
default:
goto err;
}
if (!parse_pid_tid(cmd, &pid, &tid, false))
goto err;
mutex_lock(&ugdb->u_mutex);
*pthread = ugdb_find_thread(ugdb, pid, tid);
mutex_unlock(&ugdb->u_mutex);
if (*pthread)
return "OK";
err:
return "E01";
}
static const char *handle_ck_alive(struct ugdb *ugdb, char *cmd)
{
struct ugdb_thread *thread;
int pid = 0, tid;
if (!parse_pid_tid(cmd, &pid, &tid, false))
goto err;
mutex_lock(&ugdb->u_mutex);
thread = ugdb_find_thread(ugdb, pid, tid);
mutex_unlock(&ugdb->u_mutex);
if (thread)
return "OK";
err:
return "E01";
}
static int parse_pid(char *str)
{
int pid;
if (!parse_xid(str, &pid, false))
return 0;
return pid;
}
static const char *handle_vattach(struct ugdb *ugdb, char *cmd)
{
int pid = parse_pid(cmd);
if (pid && !ugdb_attach(ugdb, pid))
return "OK";
return "E01";
}
static const char *handle_detach(struct ugdb *ugdb, char *cmd)
{
int pid;
if (*cmd++ != ';')
goto err;
pid = parse_pid(cmd);
if (pid && !ugdb_detach(ugdb, pid))
return "OK";
err:
return "E01";
}
typedef int (*each_func_t)(struct ugdb_thread *, void *);
static int ugdb_do_each_thread(struct ugdb *ugdb, int pid, int tid,
each_func_t func, void *arg)
{
struct ugdb_process *process;
struct ugdb_thread *thread;
int ret = -ESRCH;
list_for_each_entry(process, &ugdb->u_processes, p_processes) {
if (unlikely(!process_alive(process)))
continue;
if (pid > 0 && process->p_pid != pid)
continue;
list_for_each_entry(thread, &process->p_threads, t_threads) {
if (WARN_ON(!thread_alive(thread)))
continue;
if (tid > 0 && thread->t_tid != tid)
continue;
ret = func(thread, arg);
if (ret)
goto out;
if (tid >= 0)
break;
}
if (pid >= 0)
break;
}
out:
return ret;
}
static int do_stop_thread(struct ugdb_thread *thread, void *arg)
{
ugdb_stop_thread(thread, arg != NULL);
return 0;
}
static int do_cont_thread(struct ugdb_thread *thread, void *arg)
{
ugdb_cont_thread(thread, arg != NULL, false);
return 0;
}
static const char *handle_vcont(struct ugdb *ugdb, char *cmd)
{
int pid, tid;
void *arg;
int ret;
switch (*cmd ++) {
default:
return "E01";
case '?':
return "vCont;t";
case ';':
break;
}
// XXX: Discuss the generic case! currently trivial.
if (*cmd++ != 't')
return "E01";
pid = tid = -1;
if (*cmd++ == ':') {
if (!parse_pid_tid(cmd, &pid, &tid, true))
return "E01";
}
arg = (tid >= 0) ? NULL : (void*)1;
mutex_lock(&ugdb->u_mutex);
// XXX: currently we only report -ESRCH
ret = ugdb_do_each_thread(ugdb, pid, tid, do_stop_thread, arg);
mutex_unlock(&ugdb->u_mutex);
return ret < 0 ? "E01" : "OK";
}
static int thread_cont_signal(struct ugdb_thread *thread, int signr)
{
/*
* T_STOP_STOPPED was set under ->u_slock so we can't race
* with ugdb_add_stopped() and get the wrong t_stop_event.
* And, the tracee never changes it after T_STOP_STOPPED.
*/
switch (T_EV_TYPE(thread->t_stop_event)) {
case T_EV_SIGN:
WARN_ON(!T_EV_DATA(thread->t_stop_event));
thread->t_stop_event = T_EV_SIGN | signr;
break;
default:
if (!signr)
break;
// XXX: temporary hack, will be reported.
// but perhaps this is what we want ???
kill_pid(thread->t_spid, signr, 0);
break;
}
return 0;
}
static const char *handle_c(struct ugdb *ugdb, char *cmd)
{
struct ugdb_thread *thread;
const char *rc = "E01";
int gdbsig, signr = 0;
bool step;
step = (*cmd == 'S' || *cmd == 's');
switch (*cmd++) {
case 'C':
case 'S':
gdbsig = simple_strtoul(cmd, &cmd, 16);
if (!gdbsig)
return rc;
signr = sig_from_gdb(gdbsig);
if (!signr)
printk(KERN_INFO "ugdb: sorry, can't map signal %d\n",
gdbsig);
if (*cmd == ';')
++cmd;
/* fall */
case 'c':
case 's':
if (!*cmd)
break;
printk(KERN_INFO "ugdb: $c ADDR not implemented\n");
return rc;
break;
}
mutex_lock(&ugdb->u_mutex);
thread = ugdb->u_cur_hc;
if (!thread)
goto unlock;
/*
* Otherwise I do not know what to do if sig/step, and anyway
* I don't think gdb can try to cont a thread which was not
* reported as stopped.
*/
if (!(thread->t_stop_state & T_STOP_STOPPED))
goto unlock;
if (thread_cont_signal(thread, signr))
goto unlock;
if (ugdb_cont_thread(thread, false, step) <= 0)
goto unlock;
rc = "OK";
unlock:
mutex_unlock(&ugdb->u_mutex);
return rc;
}
static const char *handle_qpass_signals(struct ugdb *ugdb, char *cmd)
{
sigset_t *set = &ugdb->u_sig_ign;
sigemptyset(set);
while (*cmd) {
char *end;
int sig = simple_strtoul(cmd, &end, 16);
if (cmd == end || *end != ';')
return "E01";
cmd = end + 1;
sig = sig_from_gdb(sig);
if (!sig)
// XXX: to_gdb_sigmap[] incomplete...
// return "E01";
continue;
sigaddset(set, sig);
}
return "OK";
}
// -----------------------------------------------------------------------------
static struct task_struct *
ugdb_prepare_examine(struct ugdb *ugdb, struct utrace_examiner *exam)
{
struct ugdb_thread *thread;
struct task_struct *task;
int err;
mutex_lock(&ugdb->u_mutex);
thread = ugdb->u_cur_hg;
if (!thread || !(thread->t_stop_state & T_STOP_STOPPED))
goto err;
// XXX: u_cur_hg can't exit, we hold the mutex
task = thread_to_task(thread);
if (!task)
goto err;
for (;;) {
if (fatal_signal_pending(current))
goto err;
err = utrace_prepare_examine(task, thread->t_engine, exam);
if (!err)
break;
if (err == -ESRCH)
goto err;
schedule_timeout_interruptible(1);
}
return task;
err:
mutex_unlock(&ugdb->u_mutex);
return NULL;
}
// XXX: we hold the mutex in between, but only because we can't
// use get_task_struct/put_task_struct.
static int
ugdb_finish_examine(struct ugdb *ugdb, struct utrace_examiner *exam)
{
// XXX: u_cur_hg can't exit, we hold the mutex
struct ugdb_thread *thread = ugdb->u_cur_hg;
struct task_struct *task = thread_to_task(thread);
int ret = -ESRCH;
if (task)
ret = utrace_finish_examine(task, thread->t_engine, exam);
mutex_unlock(&ugdb->u_mutex);
return ret;
}
#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_getregs(struct ugdb *ugdb)
{
struct utrace_examiner exam;
struct task_struct *task;
const struct user_regset_view *view;
const struct user_regset *rset;
struct user_regs_struct *pregs;
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");
ugdb->u_err = -EINVAL;
goto err;
}
sz += sizeof(long) * 2;
}
pkt_size = sz;
}
if (pb_room(&ugdb->u_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->u_pbuf, sizeof(*pregs));
BUG_ON(pregs + 1 != (void*)ugdb->u_pbuf.cur + BUFFER_SIZE);
task = ugdb_prepare_examine(ugdb, &exam);
if (!task)
goto err;
view = task_user_regset_view(task);
rset = view->regsets + REGSET_GENERAL;
rset->get(task, rset, 0, sizeof(*pregs), pregs, NULL);
if (ugdb_finish_examine(ugdb, &exam))
goto err;
pb_start(&ugdb->u_pbuf);
for (rn = 0; rn < ARRAY_SIZE(x86_64_regmap); ++rn) {
int offs = x86_64_regmap[rn];
if (offs >= 0)
pb_putbs(&ugdb->u_pbuf, (void*)pregs + offs,
sizeof(long));
}
WARN_ON(pb_room(&ugdb->u_pbuf) < sizeof(*pregs));
pb_end(&ugdb->u_pbuf);
return NULL;
err:
return "E01";
}
static const char *handle_setregs(struct ugdb *ugdb, char *cmd)
{
printk(KERN_INFO "ugdb: unexpected $G packet.\n");
return "";
}
static const char *handle_set_one_reg(struct ugdb *ugdb, char *cmd)
{
unsigned int reg;
long val;
struct utrace_examiner exam;
struct task_struct *task;
if (sscanf(cmd, "%x=%lx", ®, &val) != 2)
goto err;
if (reg != 0x10 && reg != 0x39) {
printk(KERN_INFO "ugdb: unknown reg %x\n", reg);
goto err;
}
// XXX: Hmm.
val = be64_to_cpu(val);
// DO NOT LOOK AT THIS TEMPORARY CODE
task = ugdb_prepare_examine(ugdb, &exam);
if (!task)
goto err;
if (reg == 0x10)
task_pt_regs(task)->ip = val;
else if (reg == 0x39)
task_pt_regs(task)->orig_ax = val;
ugdb_finish_examine(ugdb, &exam);
return "OK";
err:
return "E01";
}
static typeof(access_process_vm) *u_access_process_vm;
static const char *handle_readmem(struct ugdb *ugdb, char *cmd)
{
struct utrace_examiner exam;
struct task_struct *task;
unsigned long addr, size;
unsigned char *mbuf;
int copied;
if (sscanf(cmd, "m%lx,%lx", &addr, &size) != 2)
goto err;
mbuf = pb_alloc_bs(&ugdb->u_pbuf, size);
if (!mbuf) {
printk(KERN_INFO "XXX: apvm(%ld) ENOMEM\n", size);
goto err;
}
task = ugdb_prepare_examine(ugdb, &exam);
if (!task)
goto err;
copied = u_access_process_vm(task, addr, mbuf, size, 0);
if (ugdb_finish_examine(ugdb, &exam))
goto err;
if (copied > 0 ) {
pb_start(&ugdb->u_pbuf);
pb_putbs(&ugdb->u_pbuf, mbuf, size);
pb_end(&ugdb->u_pbuf);
return NULL;
}
err:
return "E01";
}
// XXX: hex_to_bin() after 90378889 commit
#include <linux/ctype.h>
static int cap_hex_to_bin(char ch)
{
if ((ch >= '0') && (ch <= '9'))
return ch - '0';
ch = tolower(ch);
if ((ch >= 'a') && (ch <= 'f'))
return ch - 'a' + 10;
return -1;
}
static int unhex(char *cmd, int size)
{
char *bytes = cmd;
while (size--) {
int lo, hi;
hi = cap_hex_to_bin(*cmd++);
lo = cap_hex_to_bin(*cmd++);
if (lo < 0 || hi < 0)
return -EINVAL;
*bytes++ = (hi << 4) | lo;
}
return 0;
}
static const char *handle_writemem(struct ugdb *ugdb, char *cmd, int len)
{
unsigned long addr, size;
unsigned int skip, written;
struct utrace_examiner exam;
struct task_struct *task;
if (sscanf(cmd, "M%lx,%lx:%n", &addr, &size, &skip) != 2)
goto err;
cmd += skip;
len -= skip;
if (len != 2*size || !size)
goto err;
if (unhex(cmd, size))
goto err;
task = ugdb_prepare_examine(ugdb, &exam);
if (!task)
goto err;
written = u_access_process_vm(task, addr, cmd, size, 1);
if (ugdb_finish_examine(ugdb, &exam))
goto err;
if (written == size)
return "OK";
err:
return "E01";
}
static int ugdb_siginfo_rw(struct ugdb *ugdb, siginfo_t *info, bool write)
{
struct task_struct *task;
struct utrace_examiner exam;
struct sighand_struct *sighand;
siginfo_t *t_siginfo;
int ret = -EINVAL;
/*
* ugdb_prepare_examine() is overkill, but otherwise we can't
* assume task_is_traced(), and this is what ensures we can
* safely read/write ->t_siginfo which points to task's stack.
*/
task = ugdb_prepare_examine(ugdb, &exam);
if (!task)
goto out;
/* OK, task_struct can't go away, but ->sighand can. */
rcu_read_lock();
sighand = rcu_dereference(task->sighand);
if (!sighand)
goto unlock_rcu;
spin_lock_irq(&sighand->siglock);
if (!task_is_traced(task))
goto unlock_siglock;
t_siginfo = ugdb->u_cur_hg->t_siginfo;
if (!t_siginfo)
goto unlock_siglock;
if (write)
*t_siginfo = *info;
else
*info = *t_siginfo;
ret = 0;
unlock_siglock:
spin_unlock_irq(&sighand->siglock);
unlock_rcu:
rcu_read_unlock();
ugdb_finish_examine(ugdb, &exam);
out:
return ret;
}
static const char *handle_siginfo_read(struct ugdb *ugdb, char *cmd)
{
unsigned int off, len;
siginfo_t info;
if (sscanf(cmd, "%x,%x", &off, &len) != 2)
goto err;
if (off >= sizeof(info))
goto err;
if (len > sizeof(info) || off + len > sizeof(info))
len = sizeof(info) - off;
if (ugdb_siginfo_rw(ugdb, &info, false))
goto err;
if (pb_qfer(&ugdb->u_pbuf, &info + off, len,
(off + len < sizeof(info))))
goto err;
// XXX: Oh. we also need x86_siginfo_fixup(). how ugly.
return NULL;
err:
return "E01";
}
// -----------------------------------------------------------------------------
#define EQ(cmd, str) \
(strncmp((cmd), (str), sizeof(str)-1) ? false : \
((cmd) += sizeof(str)-1, true))
static const char *handle_qfer(struct ugdb *ugdb, char *cmd)
{
const char *rc = "E01";
if (EQ(cmd, "siginfo:")) {
if (EQ(cmd, "read::"))
rc = handle_siginfo_read(ugdb, cmd);
}
return rc;
}
static void handle_command(struct ugdb *ugdb, char *cmd, int len)
{
struct pbuf *pb = &ugdb->u_pbuf;
const char *rc = "";
switch (cmd[0]) {
case '!':
case '?':
rc = "OK";
break;
case 'H':
rc = handle_set_cur(ugdb, cmd + 1);
break;
case 'T':
rc = handle_ck_alive(ugdb, cmd + 1);
break;
case 'D':
rc = handle_detach(ugdb, cmd + 1);
break;
case 'g':
rc = handle_getregs(ugdb);
break;
case 'G':
rc = handle_setregs(ugdb, cmd + 1);
break;
case 'P':
rc = handle_set_one_reg(ugdb, cmd + 1);
break;
case 'm':
rc = handle_readmem(ugdb, cmd);
break;
case 'M':
rc = handle_writemem(ugdb, cmd, len);
break;
case 'C':
case 'c':
case 'S':
case 's':
rc = handle_c(ugdb, cmd);
break;
case 'q':
if (EQ(cmd, "qSupported")) {
if (!strstr(cmd, "multiprocess+")) {
printk(KERN_INFO "ugdb: can't work without multiprocess\n");
ugdb->u_err = -EPROTONOSUPPORT;
}
pb_packf(&ugdb->u_pbuf, "PacketSize=%x;%s",
PACKET_SIZE,
"QStartNoAckMode+;QNonStop+;multiprocess+;"
"QPassSignals+;qXfer:siginfo:read+");
rc = NULL;
}
else if (EQ(cmd, "qfThreadInfo")) {
rc = handle_thread_info(ugdb, true);
}
else if (EQ(cmd, "qsThreadInfo")) {
rc = handle_thread_info(ugdb, false);
}
else if (EQ(cmd, "qXfer:")) {
rc = handle_qfer(ugdb, cmd);
}
else if (EQ(cmd, "qTStatus")) {
rc = "T0";
}
break;
case 'Q':
if (EQ(cmd, "QStartNoAckMode")) {
ugdb->u_no_ack = true;
rc = "OK";
}
else if (EQ(cmd, "QNonStop:")) {
if (*cmd != '1') {
printk(KERN_INFO "ugdb: all-stop is not implemented.\n");
ugdb->u_err = -EPROTONOSUPPORT;
}
rc = "OK";
}
else if (EQ(cmd, "QPassSignals:")) {
rc = handle_qpass_signals(ugdb, cmd);
}
break;
case 'v':
if (EQ(cmd, "vAttach;")) {
rc = handle_vattach(ugdb, cmd);
}
else if (EQ(cmd, "vStopped")) {
rc = handle_vstopped(ugdb);
}
else if (EQ(cmd, "vCont")) {
rc = handle_vcont(ugdb, cmd);
}
break;
default:
;
}
if (rc)
pb_packs(pb, rc);
}
static void process_commands(struct ugdb *ugdb)
{
char *cmds = ugdb->u_cbuf;
int todo = ugdb->u_clen;
if (o_remote_debug)
printk(KERN_INFO "=> %.*s\n", ugdb->u_clen, ugdb->u_cbuf);
while (todo) {
char first;
char *c_cmd, *c_end;
int c_len;
first = *cmds++;
todo--;
switch (first) {
default:
printk(KERN_INFO "XXX: unknown chr %02x\n", first);
pb_putc(&ugdb->u_pbuf, '-');
break;
case '-':
printk(KERN_INFO "XXX: got NACK!\n");
ugdb->u_err = -EPROTO;
case '+':
break;
case 0x3:
printk(KERN_INFO "XXX: unexpected CTRL-C\n");
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->u_no_ack)
pb_putc(&ugdb->u_pbuf, '+');
handle_command(ugdb, c_cmd, c_len);
}
}
out:
ugdb->u_clen = todo;
if (todo && cmds > ugdb->u_cbuf)
memmove(ugdb->u_cbuf, cmds, todo);
}
// -----------------------------------------------------------------------------
static int xxx_tinfo(struct ugdb *ugdb)
{
struct ugdb_thread *thread;
int tid = 0;
mutex_lock(&ugdb->u_mutex);
thread = ugdb_advance_tinfo(ugdb);
if (thread)
tid = thread->t_tid;
mutex_unlock(&ugdb->u_mutex);
return tid;
}
static int xxx_sc_threads(struct ugdb *ugdb, int tid, bool sc)
{
void *arg = NULL;
int pid = 0;
int ret;
if (tid < 0) {
pid = -tid;
tid = -1;
arg = (void*)1;
}
mutex_lock(&ugdb->u_mutex);
ret = ugdb_do_each_thread(ugdb, pid, tid,
sc ? do_stop_thread : do_cont_thread,
arg);
mutex_unlock(&ugdb->u_mutex);
return ret;
}
static int xxx_stop(struct ugdb *ugdb, int tid)
{
return xxx_sc_threads(ugdb, tid, true);
}
static int xxx_cont(struct ugdb *ugdb, int tid)
{
return xxx_sc_threads(ugdb, tid, false);
}
static int xxx_get_stopped(struct ugdb *ugdb)
{
struct ugdb_thread *thread;
int tid = 1;
if (ugdb->u_stop_state == U_STOP_IDLE)
return -1;
if (ugdb->u_stop_state == U_STOP_PENDING)
tid = 1000;
thread = ugdb_next_stopped(ugdb);
if (!thread)
return 0;
return tid * thread->t_tid;
}
static int xxx_show_all(struct ugdb *ugdb)
{
struct ugdb_process *process;
struct ugdb_thread *thread;
printk(KERN_INFO "SHOW start ----------------------------------------\n");
mutex_lock(&ugdb->u_mutex);
list_for_each_entry(process, &ugdb->u_processes, p_processes) {
printk(KERN_INFO "PROC: %x\n", process->p_pid);
list_for_each_entry(thread, &process->p_threads, t_threads) {
printk(KERN_INFO " T: %x %p; %p %p\n",
thread->t_tid, thread,
thread->t_spid, pid_task(thread->t_spid, PIDTYPE_PID));
}
}
mutex_unlock(&ugdb->u_mutex);
printk(KERN_INFO "SHOW end ----------------------------------------\n");
return 0;
}
static long ugdb_f_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct ugdb *ugdb = file->private_data;
// XXX: otherwise gdb->get_tty_state(TCGETS, TCSETS, TCFLSH) complains
int ret = 0;
// XXX: temporary debugging hooks, ignore.
switch (cmd) {
case 0x666 + 0:
ret = ugdb_attach(ugdb, arg);
break;
case 0x666 + 1:
ret = ugdb_detach(ugdb, arg);
break;
case 0x666 + 2:
ret = xxx_tinfo(ugdb);
break;
case 0x666 + 3:
ret = xxx_stop(ugdb, arg);
break;
case 0x666 + 4:
ret = xxx_cont(ugdb, arg);
break;
case 0x666 + 5:
ret = xxx_get_stopped(ugdb);
break;
case 0x666 + 6:
ret = xxx_show_all(ugdb);
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 = (POLLOUT | POLLWRNORM);
if (pb_size(&ugdb->u_pbuf) || ugdb->u_stop_state == U_STOP_PENDING)
mask |= (POLLIN | POLLRDNORM);
if (ugdb->u_err)
mask |= POLLERR;
return mask;
}
static ssize_t ugdb_f_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ugdb *ugdb = file->private_data;
struct pbuf *pb = &ugdb->u_pbuf;
if (ugdb->u_err)
return ugdb->u_err;
if (ugdb->u_stop_state == U_STOP_PENDING)
ugdb_report_stopped(ugdb, true);
if (pb_size(pb) > count) {
printk(KERN_INFO "XXX: short read %d %ld\n",
pb_size(pb), count);
}
return pb_copy_to_user(pb, ubuf, count);
}
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->u_err)
return ugdb->u_err;
if (count > PACKET_SIZE - ugdb->u_clen) {
count = PACKET_SIZE - ugdb->u_clen;
printk("XXX: write(%ld,%d) enospc\n", count, ugdb->u_clen);
}
if (copy_from_user(ugdb->u_cbuf + ugdb->u_clen, ubuf, count))
return -EFAULT;
ugdb->u_clen += count;
process_commands(ugdb);
return count;
}
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,
.read = ugdb_f_read,
.write = ugdb_f_write,
.release = ugdb_f_release,
};
#include <linux/kallsyms.h>
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);
More information about the Archer
mailing list