From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 26408 invoked by alias); 28 Jul 2010 18:19:41 -0000 Mailing-List: contact archer-help@sourceware.org; run by ezmlm Sender: Precedence: bulk List-Post: List-Help: List-Subscribe: List-Id: Received: (qmail 26122 invoked by uid 22791); 28 Jul 2010 18:19:36 -0000 X-SWARE-Spam-Status: No, hits=-3.0 required=5.0 tests=AWL,BAYES_40,KAM_STOCKTIP,RCVD_IN_DNSWL_HI,SPF_HELO_PASS,TW_RR,T_FRT_STOCK2,T_RP_MATCHES_RCVD X-Spam-Check-By: sourceware.org Date: Wed, 28 Jul 2010 18:19:00 -0000 From: Oleg Nesterov To: Roland McGrath Cc: archer@sourceware.org, utrace-devel@redhat.com Subject: gdbstub initial code, another approach Message-ID: <20100728181702.GA26678@redhat.com> References: <20100716205147.GA26313@redhat.com> <20100721170400.GA30978@redhat.com> <20100721204203.D040C400B6@magilla.sf.frob.com> <20100723173134.GA29717@redhat.com> <20100726142759.GA17171@redhat.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="IJpNTDwzlM2Ie8A6" Content-Disposition: inline In-Reply-To: <20100726142759.GA17171@redhat.com> User-Agent: Mutt/1.5.18 (2008-05-17) X-SW-Source: 2010-q3/txt/msg00062.txt.bz2 --IJpNTDwzlM2Ie8A6 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-length: 1207 On 07/26, Oleg Nesterov wrote: > > I decided to take a bit different approach, we will see if it > makes sense in the longer term. Please see the attached files, - ugdb.c The kernel module which implements the basic user-space API on top of utrace. Of course, this API should be discussed. - gdbstub The simple user-space gdbserver written in perl which works with ugdb API. Limitations: - this is just initial code, again. - doesn't work in all-stop mode (should be simple to implement). - currently it only supports attach, stop, cont, detach and exit. - the testing was very limited. I played with it about an hour and didn't find any problems, vut that is all. However, please note that this time the code is clearly opened for improvements. I stronly believe this is the only sane approach. Even for prototyping. No, _especially_ for prototyping! Btw, gdb crashes very often right after (gdb) set target-async on (gdb) set non-stop (gdb) file mt-program (gdb) target extended-remote :port (gdb) attach its_pid I didn't even try to investigate (this doesn't happen when it works with the real gdbserver). Just retry, gdb is buggy. What do you think? Oleg. --IJpNTDwzlM2Ie8A6 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="ugdb.c" Content-length: 13085 #include #include #include #include #include #include #include struct ugdb_thread { int t_tid; int t_stop; int t_exit; struct pid *t_spid; struct ugdb *t_ugdb; struct utrace_engine *t_engine; struct list_head t_threads; struct list_head t_stopped; }; struct ugdb { spinlock_t u_llock; struct list_head u_threads; struct list_head u_stopped; wait_queue_head_t u_wait; }; // XXX: gdb is single-thread, no locking currently. #define T printk(KERN_INFO "TRACE: %s:%d\n", __FUNCTION__, __LINE__) static struct ugdb_thread *ugdb_create_thread(struct ugdb *ugdb, int tid) { struct pid *spid; struct ugdb_thread *thread; int err; err = -ESRCH; spid = find_get_pid(tid); if (!spid) goto err; err = -ENOMEM; thread = kzalloc(sizeof(*thread), GFP_KERNEL); if (!thread) goto free_pid; thread->t_tid = tid; thread->t_spid = spid; thread->t_ugdb = ugdb; INIT_LIST_HEAD(&thread->t_stopped); spin_lock(&ugdb->u_llock); list_add_tail(&thread->t_threads, &ugdb->u_threads); spin_unlock(&ugdb->u_llock); return thread; free_pid: put_pid(spid); err: return ERR_PTR(err); } static void ugdb_destroy_thread(struct ugdb_thread *thread) { struct ugdb *ugdb = thread->t_ugdb; spin_lock(&ugdb->u_llock); list_del(&thread->t_stopped); list_del(&thread->t_threads); spin_unlock(&ugdb->u_llock); put_pid(thread->t_spid); kfree(thread); } static struct ugdb_thread *ugdb_find_thread(struct ugdb *ugdb, int tid) { struct ugdb_thread *thread; spin_lock(&ugdb->u_llock); list_for_each_entry(thread, &ugdb->u_threads, t_threads) { if (thread->t_tid == tid) goto found; } thread = NULL; found: spin_unlock(&ugdb->u_llock); return thread; } static int ugdb_set_events(struct ugdb_thread *thread, unsigned long events) { return utrace_set_events_pid(thread->t_spid, thread->t_engine, events); } static int ugdb_control(struct ugdb_thread *thread, enum utrace_resume_action action) { return utrace_control_pid(thread->t_spid, thread->t_engine, action); } static const struct utrace_engine_ops ugdb_utrace_ops; static struct ugdb_thread *ugdb_attach_thread(struct ugdb *ugdb, int tid) { struct ugdb_thread *thread; void *errp; int err; thread = ugdb_create_thread(ugdb, tid); if (IS_ERR(thread)) { errp = thread; goto err; } thread->t_engine = utrace_attach_pid(thread->t_spid, UTRACE_ATTACH_CREATE, &ugdb_utrace_ops, thread); if (IS_ERR(thread->t_engine)) { errp = thread->t_engine; goto free_thread; } err = ugdb_set_events(thread, UTRACE_EVENT(QUIESCE) | UTRACE_EVENT(DEATH)); if (err) { errp = ERR_PTR(-ESRCH); goto free_engine; } return thread; free_engine: ugdb_control(thread, UTRACE_DETACH); utrace_engine_put(thread->t_engine); free_thread: ugdb_destroy_thread(thread); err: return errp; } static void ugdb_detach_thread(struct ugdb_thread *thread) { int ret; ret = ugdb_control(thread, UTRACE_DETACH); if (ret == -EINPROGRESS) utrace_barrier_pid(thread->t_spid, thread->t_engine); utrace_engine_put(thread->t_engine); ugdb_destroy_thread(thread); } static struct ugdb *ugdb_create(void) { struct ugdb *ugdb; int err; err = -ENODEV; // XXX: ugly. proc_reg_open() should take care. if (!try_module_get(THIS_MODULE)) goto out; err = -ENOMEM; ugdb = kzalloc(sizeof(*ugdb), GFP_KERNEL); if (!ugdb) goto put_module; spin_lock_init(&ugdb->u_llock); INIT_LIST_HEAD(&ugdb->u_threads); INIT_LIST_HEAD(&ugdb->u_stopped); init_waitqueue_head(&ugdb->u_wait); return ugdb; put_module: module_put(THIS_MODULE); out: return ERR_PTR(err); } static void ugdb_destroy(struct ugdb *ugdb) { struct ugdb_thread *thread; while (!list_empty(&ugdb->u_threads)) { thread = list_first_entry(&ugdb->u_threads, struct ugdb_thread, t_threads); ugdb_detach_thread(thread); } BUG_ON(!list_empty(&ugdb->u_stopped)); module_put(THIS_MODULE); kfree(ugdb); } // XXX: Of course, this all is racy -------------------------------------------- enum { STOP_RUN, STOP_REQ, STOP_ACK, STOP_FIN, }; static u32 ugdb_report_quiesce(u32 action, struct utrace_engine *engine, unsigned long event) { struct ugdb_thread *thread = engine->data; struct ugdb *ugdb = thread->t_ugdb; if (event == UTRACE_EVENT(DEATH)) { thread->t_exit = current->exit_code | INT_MIN; goto ack; } if (thread->t_stop == STOP_RUN) return UTRACE_RESUME; if (thread->t_stop != STOP_REQ) printk(KERN_INFO "XXX: %d, report_quiesce bad c_stop: %d\n", thread->t_tid, thread->t_stop); ack: thread->t_stop = STOP_ACK; // SIGKILL can re-add to stopped if (list_empty(&thread->t_stopped)) { spin_lock(&ugdb->u_llock); list_add_tail(&thread->t_stopped, &ugdb->u_stopped); spin_unlock(&ugdb->u_llock); } wake_up_all(&ugdb->u_wait); return UTRACE_STOP; } static u32 ugdb_report_death(struct utrace_engine *engine, bool group_dead, int signal) { return UTRACE_RESUME; } static const struct utrace_engine_ops ugdb_utrace_ops = { .report_quiesce = ugdb_report_quiesce, .report_death = ugdb_report_death, }; static int ugdb_stop_thread(struct ugdb_thread *thread) { int err; if (thread->t_stop != STOP_RUN) return 0; thread->t_stop = STOP_REQ; // XXX: we don't do UTRACE_STOP! this means we can't // stop TASK_STOPEED task. temporarily. err = ugdb_control(thread, UTRACE_INTERRUPT); if (err && err != -EINPROGRESS) return err; return 1; } static int ugdb_cont_thread(struct ugdb_thread *thread) { struct ugdb *ugdb = thread->t_ugdb; if (thread->t_stop == STOP_RUN) return 0; if (!list_empty(&thread->t_stopped)) { spin_lock(&ugdb->u_llock); list_del_init(&thread->t_stopped); spin_unlock(&ugdb->u_llock); } thread->t_stop = STOP_RUN; ugdb_control(thread, UTRACE_RESUME); return 1; } static struct task_struct * ugdb_prepare_examine(struct ugdb_thread *thread, struct utrace_examiner *exam) { struct task_struct *task; if (!thread) return ERR_PTR(-ESRCH); task = pid_task(thread->t_spid, PIDTYPE_PID); if (!task) return ERR_PTR(-ESRCH); for (;;) { if (fatal_signal_pending(current)) return ERR_PTR(-EINTR); if (thread->t_stop == STOP_RUN) { printk(KERN_INFO "XXX: %d unexpected STOP_RUN\n", thread->t_tid); return ERR_PTR(-EBUSY); } if (thread->t_stop != STOP_REQ) { int err = utrace_prepare_examine(task, thread->t_engine, exam); if (err == 0) return task; if (err == -ESRCH) return ERR_PTR(err); } schedule_timeout_interruptible(1); } } // ----------------------------------------------------------------------------- #define UGDB_ATTACH (0x666 + 1) #define UGDB_DETACH (0x666 + 2) #define UGDB_STOP (0x666 + 3) #define UGDB_CONT (0x666 + 4) #define UGDB_GETEV (0x666 + 5) #define UGDB_PEEKMEM (0x666 + 6) #define UGDB_POKEMEM (0x666 + 7) static int ugdb_attach(struct ugdb *ugdb, int tid) { struct ugdb_thread *thread = ugdb_find_thread(ugdb, tid); if (thread) return -EALREADY; thread = ugdb_attach_thread(ugdb, tid); if (IS_ERR(thread)) return PTR_ERR(thread); return 0; } static int ugdb_detach(struct ugdb *ugdb, int tid) { struct ugdb_thread *thread = ugdb_find_thread(ugdb, tid); if (!thread) return -ESRCH; ugdb_detach_thread(thread); return 0; } static int ugdb_stop(struct ugdb *ugdb, int tid) { struct ugdb_thread *thread = ugdb_find_thread(ugdb, tid); if (!thread) return -ESRCH; return ugdb_stop_thread(thread); } static int ugdb_cont(struct ugdb *ugdb, int tid) { struct ugdb_thread *thread = ugdb_find_thread(ugdb, tid); if (!thread) return -ESRCH; return ugdb_cont_thread(thread); } enum { UGDB_EV_STOP, UGDB_EV_EXIT, }; struct ugdb_event { int ev_tid; int ev_type; union { unsigned ev_data; }; }; static int ugdb_getev(struct ugdb *ugdb, struct ugdb_event __user *uev) { struct ugdb_thread *thread; struct ugdb_event ev; if (list_empty(&ugdb->u_threads)) return -ECHILD; if (list_empty(&ugdb->u_stopped)) return -EWOULDBLOCK; spin_lock(&ugdb->u_llock); thread = list_first_entry(&ugdb->u_stopped, struct ugdb_thread, t_stopped); list_del_init(&thread->t_stopped); spin_unlock(&ugdb->u_llock); ev.ev_tid = thread->t_tid; ev.ev_type = UGDB_EV_STOP; if (thread->t_exit) { ev.ev_type = UGDB_EV_EXIT; ev.ev_data = thread->t_exit & 0xffff; } if (copy_to_user(uev, &ev, sizeof(ev))) return -EFAULT; return 0; } struct ugdb_xmem { long tid; void __user *src, *dst; unsigned long len; }; static typeof(access_process_vm) *u_access_process_vm; static int ugdb_peekmem(struct ugdb *ugdb, struct ugdb_xmem __user *uxmem) { struct ugdb_xmem xmem; struct utrace_examiner exam; struct ugdb_thread *thread; struct task_struct *task; char mbuf[256]; int size; if (copy_from_user(&xmem, uxmem, sizeof(xmem))) return -EFAULT; thread = ugdb_find_thread(ugdb, xmem.tid); task = ugdb_prepare_examine(thread, &exam); if (IS_ERR(task)) return PTR_ERR(task); size = 0; while (xmem.len) { int chunk = min(xmem.len, sizeof (mbuf)); chunk = u_access_process_vm(task, (unsigned long)xmem.src, mbuf, chunk, 0); if (chunk <= 0) break; if (copy_to_user(xmem.dst, mbuf, chunk)) { size = -EFAULT; break; } xmem.src += chunk; xmem.dst += chunk; xmem.len -= chunk; size += chunk; } if (utrace_finish_examine(task, thread->t_engine, &exam)) size = -ESRCH; return size; } // XXX: temporarily hack !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #define UGDB_GETREGS (0x666 + 100) #define REGSET_GENERAL 0 struct ugdb_getregs { long tid; void __user *addr; }; static int get_regset(struct task_struct *task, void __user *uaddr) { return copy_regset_to_user(task, task_user_regset_view(current), REGSET_GENERAL, 0, sizeof(struct user_regs_struct), uaddr); } static int ugdb_getregs(struct ugdb *ugdb, struct ugdb_getregs __user *uregs) { struct ugdb_getregs regs; struct utrace_examiner exam; struct ugdb_thread *thread; struct task_struct *task; int err; if (WARN_ON(sizeof(struct user_regs_struct) != 216)) return -EFAULT; if (copy_from_user(®s, uregs, sizeof(regs))) return -EFAULT; thread = ugdb_find_thread(ugdb, regs.tid); task = ugdb_prepare_examine(thread, &exam); if (IS_ERR(task)) return PTR_ERR(task); err = get_regset(task, regs.addr); if (utrace_finish_examine(task, thread->t_engine, &exam)) return -ESRCH; return err; } static long ugdb_f_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ugdb *ugdb = file->private_data; int ret = -EINVAL; switch (cmd) { case UGDB_ATTACH: ret = ugdb_attach(ugdb, arg); break; case UGDB_DETACH: ret = ugdb_detach(ugdb, arg); break; case UGDB_STOP: ret = ugdb_stop(ugdb, arg); break; case UGDB_CONT: ret = ugdb_cont(ugdb, arg); break; case UGDB_GETEV: ret = ugdb_getev(ugdb, (void __user*)arg); break; case UGDB_PEEKMEM: ret = ugdb_peekmem(ugdb, (void __user*)arg); break; case UGDB_GETREGS: ret = ugdb_getregs(ugdb, (void __user*)arg); break; } return ret; } static unsigned int ugdb_f_poll(struct file *file, poll_table *wait) { struct ugdb *ugdb = file->private_data; unsigned int mask; poll_wait(file, &ugdb->u_wait, wait); mask = 0; if (!list_empty(&ugdb->u_stopped)) mask |= POLLIN; return mask; } // ----------------------------------------------------------------------------- static int ugdb_f_open(struct inode *inode, struct file *file) { nonseekable_open(inode, file); file->private_data = ugdb_create(); return IS_ERR(file->private_data) ? PTR_ERR(file->private_data) : 0; } static int ugdb_f_release(struct inode *inode, struct file *file) { ugdb_destroy(file->private_data); return 0; } static const struct file_operations ugdb_f_ops = { .open = ugdb_f_open, .unlocked_ioctl = ugdb_f_ioctl, .poll = ugdb_f_poll, .release = ugdb_f_release, }; #include struct kallsyms_sym { const char *name; unsigned long addr; }; static int kallsyms_on_each_symbol_cb(void *data, const char *name, struct module *mod, unsigned long addr) { struct kallsyms_sym *sym = data; if (strcmp(name, sym->name)) return 0; sym->addr = addr; return 1; } // XXX: kallsyms_lookup_name() is not exported in 2.6.32 static bool lookup_unexported(void) { struct kallsyms_sym sym; sym.name = "access_process_vm"; if (!kallsyms_on_each_symbol(kallsyms_on_each_symbol_cb, &sym)) goto err; u_access_process_vm = (void*)sym.addr; return true; err: printk(KERN_ERR "ugdb: can't lookup %s\n", sym.name); return false; } #define PROC_NAME "ugdb" struct proc_dir_entry *ugdb_pde; static int __init ugdb_init(void) { if (!lookup_unexported()) return -ESRCH; ugdb_pde = proc_create(PROC_NAME, S_IFREG|S_IRUGO|S_IWUGO, NULL, &ugdb_f_ops); if (!ugdb_pde) return -EBADF; return 0; } static void __exit ugdb_exit(void) { remove_proc_entry(PROC_NAME, NULL); } MODULE_LICENSE("GPL"); module_init(ugdb_init); module_exit(ugdb_exit); --IJpNTDwzlM2Ie8A6 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=gdbstub Content-length: 12781 #!/usr/bin/perl -w #----------------------------------------------------------------------------- package utrace; use strict; use warnings FATAL => qw(all); use feature qw(switch); sub pr { main::pr(@_) } my $f_ugdb; use constant { # not really needed ECHILD => 10, EAGAIN => 11, UGDB_ATTACH => (0x666 + 1), UGDB_DETACH => (0x666 + 2), UGDB_STOP => (0x666 + 3), UGDB_CONT => (0x666 + 4), UGDB_GETEV => (0x666 + 5), UGDB_PEEKMEM => (0x666 + 6), UGDB_POKEMEM => (0x666 + 7), # XXX: temporarily hack !!!!! UGDB_GETREGS => (0x666 + 100), UGDB_EV_STOP => 0, UGDB_EV_EXIT => 1, }; sub create { sysopen $f_ugdb, '/proc/ugdb', 0 or return; return $f_ugdb; } sub attach_thread { my ($pid, $tid) = @_; defined ioctl $f_ugdb, UGDB_ATTACH, 0+$tid; } sub detach_thread { my ($tid) = @_; defined ioctl $f_ugdb, UGDB_DETACH, 0+$tid; } sub ck_true { defined (my $r = shift) or return; $r == 1 or pr "WARN! should be true"; 1; } sub stop_thread { my ($tid) = @_; ck_true ioctl $f_ugdb, UGDB_STOP, 0+$tid; } sub cont_thread { my ($tid) = @_; ck_true ioctl $f_ugdb, UGDB_CONT, 0+$tid; } sub get_event { defined ioctl $f_ugdb, UGDB_GETEV, my $event = 'x' x 64 or do { # just a sanity check return if $! == EAGAIN || $! == ECHILD; return (0, 'EV_ERROR', "[errno: $!]"); }; my ($tid, $type, $data) = unpack 'i!i!a*', $event; my @event; given ($type) { when (UGDB_EV_STOP) { @event = 'EV_STOP'; } when (UGDB_EV_EXIT) { @event = ('EV_EXIT', unpack 'I', $data); } @event = ('EV_UNKNOWN', $type); } $tid, @event; } sub read_mem { my ($tid, $addr, $size) = @_; my $mbuf = 'x' x $size; my $pbuf = unpack 'L!', pack 'P', $mbuf; my $xmem = pack 'L!4', 0+$tid, +$addr, 0+$pbuf, $size; my $r = ioctl $f_ugdb, UGDB_PEEKMEM, $xmem or return; substr $mbuf, 0, $r; } sub get_regs { my $tid = 0+shift; my $regs = 'x' x 216; # struct user_regs_struct my $pregs = unpack 'L!', pack 'P', $regs; my $xregs = pack 'L!2', 0+$tid, 0+$pregs; defined ioctl $f_ugdb, UGDB_GETREGS, $xregs or return; $regs; } #----------------------------------------------------------------------------- package main; use strict; use feature qw(state switch); use warnings FATAL => qw(all); no warnings 'portable'; #hex sub pr { print STDERR "@_\n" } =pod tread: T_PID T_TID pid, tid T_XID pPID.TID T_STP undef, false==pending, or STOP_REPLY process: P_PID pid P_TID list of sub-threads both: S_KEY = sorting key =cut #----------------------------------------------------------------------------- sub err { pr "ERR!! @_"; return; } sub hv($) { sprintf '%02x', 0+shift; } sub hs($) { unpack 'H*', shift // return undef; } sub shex($) { my $h = shift; ($h =~ s/^-//) ? -hex $h : +hex $h; } sub __s_key { sort { $a->{S_KEY} <=> $b->{S_KEY} } values %{+shift} } my ($O_NOACK, $O_NOSTOP); my ($S_KEY, $P_NUM, %P_ALL, %T_ALL) = (0, 0); my ($G_CURR, $C_CURR); sub select_threads { my ($pid, $tid) = @_; $pid < 0 || $tid < 0 and return err "unexpected multi-THREAD-ID" unless wantarray; return unless %T_ALL; if ($tid > 0) { my $t = $T_ALL{$tid} || return; $t->{T_PID} == $pid || return if $pid > 0; return $t; } my @p; if ($pid > 0) { @p = $P_ALL{$pid} || return; } else { @p = __s_key \%P_ALL; splice @p, 1 unless $pid; } my @t = map { my @t = __s_key $_->{P_TID} or die; splice @t, 1 unless $tid; @t; } @p; die unless @t; wantarray ? @t : $t[0]; } sub select_one_thread { scalar select_threads @_; } sub attach_thread { my ($p, $tid) = @_; my $pid = $p->{P_PID}; die if $T_ALL{$tid} || $p->{P_TID}{$tid}; utrace::attach_thread $pid, $tid or return err "attach $tid failed: $!"; $T_ALL{$tid} = $p->{P_TID}{$tid} = { S_KEY => ++$S_KEY, T_PID => $pid, T_TID => $tid, T_XID => sprintf('p%x.%x', $pid, $tid), T_STP => undef, }; } sub detach_thread { my ($p, $t) = @_; my $tid = $t->{T_TID}; utrace::detach_thread $tid, $t->{T_STP} or err "detach $tid: $!"; $_ && $_ == $t and undef $_ for $G_CURR, $C_CURR; $t == delete $T_ALL{$tid} or die; $t == delete $p->{P_TID}{$tid} or die; return $t; } sub detach_process { my $p = shift; detach_thread $p, $_ for values %{$p->{P_TID}}; $p == delete $P_ALL{$p->{P_PID}} or die; if (--$P_NUM <= 0) { die if $P_NUM; die if %T_ALL; die if %P_ALL; die if $G_CURR || $C_CURR; } die if keys %{$p->{P_TID}}; return $p; } sub proc_list_pid { my $pid = shift; my @tid = map /(\d+)\z/, glob "/proc/$pid/task/*"; # do not return an empty list! @tid ? @tid : $pid; } sub c_attach { # XXX: todo !!!!!! $O_NOSTOP || return err "Sorry, all-stop mode is not implemented yet."; my $pid = shex shift; $P_ALL{$pid} and return err "$pid already attached"; $P_NUM++; $P_ALL{$pid} = my $p = { S_KEY => ++$S_KEY, P_PID => $pid, }; for my $tid (proc_list_pid $pid) { attach_thread $p, $tid or do { detach_process $p; return; }; } # seems not strictly necessary ? $G_CURR = $p->{P_TID}{$pid} if !$O_NOSTOP; $p; } sub c_detach { detach_process $P_ALL{shex shift} || return; } # for ptrace plugin sub utrace::stop_pending { my $t = $T_ALL{+shift} || return; defined $t->{T_STP} && !$t->{T_STP}; } sub stop_one_thread { my $t = shift; unless (defined $t->{T_STP}) { utrace::stop_thread $t->{T_TID} or return err "stop $t->{T_TID}: $!"; $t->{T_STP} = ''; } $t; } sub stop_threads { stop_one_thread $_ for @{+shift}; } sub cont_one_thread { my $t = shift; if (defined $t->{T_STP}) { utrace::cont_thread $t->{T_TID}, $t->{T_STP} or return err "cont $t->{T_TID}: $!"; undef $t->{T_STP}; } $t; } sub cont_threads { cont_one_thread $_ for @{+shift}; } sub c_thread_info { 'm' . join ',', map $_->{T_XID}, select_threads -1, -1; } sub c_setcurr { my ($w, $pid, $tid) = @_; my $t = select_one_thread shex $pid, shex $tid or return; $w eq 'g' ? ($G_CURR = $t) : $w eq 'c' ? ($C_CURR = $t) : err "H$w not implemented"; } sub c_ck_alive { my ($pid, $tid) = @_; my $t = select_one_thread shex $pid, shex $tid or return; $t; } # include/gdb/signals.h:target_signal (incomplete) my @to_gdb_sigmap = ( (7, 10), (10,30), (12,31), (17,20), (18,19), (19,17), (20,18), (23,16), (29,23), (30,32), (31,12)); sub sig_to_gdb($) { my $sig = shift; state $map = {@to_gdb_sigmap}; $map->{$sig} || $sig; } sub sig_from_gdb($) { my $sig = shift; state $map = {reverse @to_gdb_sigmap}; $map->{$sig} || $sig; } # gdb-7.1/gdb/gdbserver/linux-x86-low.c my @x86_64_regmap = ( 10, 5, 11, 12, 13, 14, 4, 19, 9, 8, 7, 6, 3, 2, 1, 0, 16, 18, 17, 20, 23, 24, 25, 26, -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, 15); sub c_get_regs { my $regs = utrace::get_regs $G_CURR->{T_TID} or return undef; my @regs = unpack 'L!*', $regs; hs pack 'L!*', map { $_ >= 0 ? $regs[$_] // die : 0; } @x86_64_regmap; } sub c_read_mem { my ($addr, $size) = @_; hs utrace::read_mem $G_CURR->{T_TID}, hex $addr, hex $size; } my $RE_HEX = qr/([a-f\d]+)/; my $RE_PID = qr/(-?[a-f\d]+)/; my $RE_XID = qr/p$RE_PID.$RE_PID/; sub c_vcont { my %seen; for (split ';', shift) { my ($cmd, $pid, $tid) = /^ ([^:]+) (?: : $RE_XID )? \z/x or return err "vCont: can't parse '$_'"; ($pid, $tid) = defined $pid ? (shex $pid, shex $tid) : (-1, -1); my $handler; given ($cmd) { when ('t') { $handler = \&stop_threads } when ('c') { $handler = \&cont_threads } return err "vCont;$cmd is not implemented!"; } my @threads = grep !$seen{$_->{T_XID}}++, select_threads $pid, $tid or err "vCont: no threads in '$_'"; $handler->(\@threads); } scalar %seen; } # XXX: this all is wrong. blame gdb!!! sub hack_exit { my ($tid, $code) = @_; my $t = $T_ALL{$tid} || die; my $p = $P_ALL{$t->{T_PID}} or die; detach_thread $p, $t; return unless $p->{P_PID} == $tid; # main thread dies. report the group exit. detach_process $p; my $stp; if ($code & 0xff) { $stp = 'X' . hv sig_to_gdb($code & 0xff); } else { $stp = 'W' . hv(($code >> 8) & 0xff); } my $xid = hv $p->{P_PID}; $stp .= ";process:$xid"; return $stp; } my ($V_CUR, %V_STP, @V_STP); sub handle_event { my ($tid, $ev_name, @ev_data) = @_; my ($t, $stp); given ($ev_name) { when ('EV_STOP') { $stp = 'T00' } when ('EV_SIGNAL') { $stp = 'T' . hv sig_to_gdb $ev_data[0] } when ('EV_EXIT') { # XXX!!!!!! I do not know what to do with # the current limitations. The dirty hack # for now $stp = hack_exit $tid, $ev_data[0] or return; push @V_STP, +{ T_STP => $stp, }; return; } die "unimplemented event: tid=$tid $ev_name, @ev_data"; } $t = $T_ALL{$tid} || die; $t->{T_STP} = $stp . "thread:$t->{T_XID};"; push @V_STP, $t unless $V_STP{$t->{T_XID}}++; } sub get_notification { @V_STP && !$V_CUR && $V_STP[0]{T_STP}; } sub c_vstopped { @V_STP || err 'unexpected vStopped'; ++$V_CUR < @V_STP and return $V_STP[$V_CUR]{T_STP} || die; ($V_CUR, %V_STP, @V_STP) = (); return 'OK'; } sub handle_cmd { given ($_) { when (/^qSupported (.*multiprocess\+)?/x) { $1 || die "ERR!! need multiprocess\n"; @_ = join ';', qw{ PacketSize=400 QStartNoAckMode+ QNonStop+ multiprocess+}; } when ('vCont?') { @_ = 'vCont;t;c;C;s;S' } @_ = 'OK'; when ('QStartNoAckMode') { $O_NOACK = 1 } when (/^QNonStop:([01])/) { $O_NOSTOP = !!$1 } when ([qw'!']) {} @_ = undef; when (/^vAttach; $RE_PID \z/x) { @_ = $O_NOSTOP ? 'OK':'S05' if c_attach $1 } when (/^D; $RE_PID \z/x) { @_ = 'OK' if c_detach $1 } when (/^H (.) $RE_XID \z/x) { @_ = 'OK' if c_setcurr $1, $2, $3 } when ('qC') { @_ = 'QC' . $G_CURR->{T_XID} if $G_CURR } when (/^T $RE_XID \z/x) { @_ = 'OK' if c_ck_alive $1, $2 } when ('qfThreadInfo') { @_ = c_thread_info if %T_ALL } when ('qsThreadInfo') { @_ = 'l' } when (/^vCont;(.*)/) { @_ = 'OK' if c_vcont $1 } when ('vStopped') { @_ = c_vstopped } when ('g') { @_ = c_get_regs if $G_CURR } when (/^m $RE_HEX , $RE_HEX \z/x) { @_ = c_read_mem $1, $2 if $G_CURR } when (/^[GM]/) { } # uninplemented ... @_ = ''; when ('qTStatus') { @_ = 'T0' } when ('?') { @_ = $O_NOSTOP ? 'OK' : 'W00' } } return @_; } #----------------------------------------------------------------------------- my ($F_UGDB, $F_CONN, $F_OCMD); use constant { # ARCH DEPENDANT FIONBIO => 0x5421, EAGAIN => 11, }; sub echo { my $str = "@_"; substr($str, 62) = '...' if length $str > 64; pr $str; }; sub __put { # XXX: doesn't support NONBLOCK or short writes my $w = syswrite $F_OCMD, my $pkt = join '', @_; ($w ||= 0) == length $pkt or die 'ERR!! conn put(', length $pkt, ')', "failed: $w $!\n"; } sub __put_pkt { my $sym = shift; my $pkt = join '', @_; my $csm = 0; $csm += ord $1 while $pkt =~ /(.)/sg; __put $sym, $pkt, '#', hv $csm % 256; echo '<=', $pkt; } sub put_p { __put_pkt '$', @_; } sub put_n { __put_pkt '%', @_; } sub get_p { state $buf = ''; for (;;) { $buf =~ s/^\+*//; $buf =~ s/^\$ ([^#]*) \#..//x and $_ = $1, last; if ($buf =~ s/^([^\$]+)//s) { err "bad cmd or nack: $1"; } elsif (!sysread $F_CONN, $buf, 4096, length $buf) { return if $! == EAGAIN; pr 'conn closed:', $! || 'EOF'; exit; } } __put '+' unless $O_NOACK; echo '=>', $_; 1; } sub process_cmds { while (get_p) { my ($r, @r) = handle_cmd or next; put_p $r // 'E01', @r; } } sub process_ugdb { while (my @ev = utrace::get_event) { handle_event @ev; } my $n = get_notification; put_n 'Stop:' . $n if $n; } sub main_loop { ($F_CONN, $F_OCMD) = @_; $F_UGDB = utrace::create or die "ERR!! can't create utrace fd: $!\n"; ioctl $F_CONN, FIONBIO, pack 'i!', 1 or die $!; ioctl $F_UGDB, FIONBIO, pack 'i!', 1 or die $!; for (my $rfd = '';;) { vec($rfd, fileno $F_CONN, 1) = 1; vec($rfd, fileno $F_UGDB, 1) = 1; 0 < select $rfd, undef, undef, undef or next; # EINTR process_cmds if vec $rfd, fileno $F_CONN, 1; process_ugdb if vec $rfd, fileno $F_UGDB, 1; } } sub wait_for_connect { my $port = 0+shift; socket my $sk, 2,1,0 or return err "sock create: $!"; defined setsockopt $sk, 1,2,1 or return err "sock reuseaddr: $!"; bind $sk, pack 'Sna12', 2, $port, '' or return err "sock bind $port port: $!"; listen $sk, 2 or return err "sock listen: $!"; pr "wait for connection on $port port ..."; accept my $conn, $sk or return err "sock accept: $!"; return $conn; } sub main { my $port = 2000; if (@_) { $port = shift; die "Usage: $0 [port]\n" if @_ || $port =~ /\D/; } my $conn = wait_for_connect $port or exit; main_loop $conn, $conn; } main @ARGV; --IJpNTDwzlM2Ie8A6--