From 0046dc7df2b317f807e3f10c260daaf58c6dbe7a Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 10:21:12 -0500 Subject: [PATCH 1/8] ptrace.c: Improve the wait / advance_to_state logic somewhat. --- ptrace.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ptrace.c b/ptrace.c index 64e6c78..948d42c 100644 --- a/ptrace.c +++ b/ptrace.c @@ -97,10 +97,6 @@ int ptrace_wait(struct ptrace_child *child) { ptrace_command(child, PTRACE_GETEVENTMSG, 0, &child->forked_pid); if (child->state != ptrace_at_syscall) child->state = ptrace_stopped; - if (sig != SIGSTOP && sig != SIGTRAP && sig != SIGCHLD && sig != SIGHUP && sig != SIGCONT) { - child->error = EAGAIN; - return -1; - } } } else { child->error = EINVAL; @@ -114,8 +110,12 @@ int ptrace_advance_to_state(struct ptrace_child *child, int err; while(child->state != desired) { switch(desired) { - case ptrace_at_syscall: case ptrace_after_syscall: + case ptrace_at_syscall: + if (WIFSTOPPED(child->status) && WSTOPSIG(child->status) == SIGSEGV) { + child->error = EAGAIN; + return -1; + } err = ptrace_command(child, PTRACE_SYSCALL, 0, 0); break; case ptrace_running: From 6e5dc87c80c21e3c3cea20c455c485eda7043ea3 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 10:21:32 -0500 Subject: [PATCH 2/8] Try to stop the target with SIGTSTP before attaching. This should both cause the target process to redraw / reinitialize the terminal (since it thinks it's been backgrounded and restarted), and should give you back the old terminal, since it sees the process now running in the "background". --- attach.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/attach.c b/attach.c index 0e6ae8e..2e33ae6 100644 --- a/attach.c +++ b/attach.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include "ptrace.h" #include "reptyr.h" @@ -166,6 +168,44 @@ int ignore_hup(struct ptrace_child *child, unsigned long scratch_page) { return err; } +void wait_for_stop(pid_t pid) { + struct timeval start, now; + struct timespec sleep; + char stat_path[PATH_MAX], buf[256], *p; + int fd; + + snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); + fd = open(stat_path, O_RDONLY); + if (!fd) { + error("Unable to open %s: %s", stat_path, strerror(errno)); + return; + } + gettimeofday(&start, NULL); + while (1) { + gettimeofday(&now, NULL); + if ((now.tv_sec > start.tv_sec && now.tv_usec > start.tv_usec) + || (now.tv_sec - start.tv_sec > 1)) { + error("Timed out waiting for child stop."); + return; + } + lseek(fd, 0, SEEK_SET); + if (read(fd, buf, sizeof buf) <= 0) + return; + p = strchr(buf, ' '); + if (!p) + return; + p = strchr(p+1, ' '); + if (!p) + return; + if (*(p+1) == 'T') + return; + + sleep.tv_sec = 0; + sleep.tv_nsec = 10000000; + nanosleep(&sleep, NULL); + } +} + int attach_child(pid_t pid, const char *pty) { struct ptrace_child child; unsigned long scratch_page = -1; @@ -173,6 +213,8 @@ int attach_child(pid_t pid, const char *pty) { int i; int err = 0; + kill(pid, SIGTSTP); + wait_for_stop(pid); if (ptrace_attach_child(&child, pid)) return child.error; From cac644cfd8e14919e8f015c22e678953dbaff178 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 10:29:18 -0500 Subject: [PATCH 3/8] wait_for_stop: Don't leak an fd, and add comments. --- attach.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/attach.c b/attach.c index 2e33ae6..be4b0ab 100644 --- a/attach.c +++ b/attach.c @@ -168,6 +168,16 @@ int ignore_hup(struct ptrace_child *child, unsigned long scratch_page) { return err; } +/* + * Wait for the specific pid to enter state 'T', or stopped. We have to pull the + * /proc file rather than attaching with ptrace() and doing a wait() because + * half the point of this exercise is for the process's real parent (the shell) + * to see the TSTP. + * + * In case the process is masking or ignoring SIGTSTP, we time out after a + * second and continue with the attach -- it'll still work mostly right, you + * just won't get the old shell back. + */ void wait_for_stop(pid_t pid) { struct timeval start, now; struct timespec sleep; @@ -186,24 +196,29 @@ void wait_for_stop(pid_t pid) { if ((now.tv_sec > start.tv_sec && now.tv_usec > start.tv_usec) || (now.tv_sec - start.tv_sec > 1)) { error("Timed out waiting for child stop."); - return; + break; } + /* + * If anything goes wrong reading or parsing the stat node, just give + * up. + */ lseek(fd, 0, SEEK_SET); if (read(fd, buf, sizeof buf) <= 0) - return; + break; p = strchr(buf, ' '); if (!p) - return; + break; p = strchr(p+1, ' '); if (!p) - return; + break; if (*(p+1) == 'T') - return; + break; sleep.tv_sec = 0; sleep.tv_nsec = 10000000; nanosleep(&sleep, NULL); } + close(fd); } int attach_child(pid_t pid, const char *pty) { From 5053cd878ff20ff46bf161f7dccfdb8f15b4278c Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 10:43:22 -0500 Subject: [PATCH 4/8] Remove some text from BUGS and README for (hopefully) fixed issues. --- BUGS | 6 ------ README | 12 ------------ 2 files changed, 18 deletions(-) diff --git a/BUGS b/BUGS index 3dd951e..95466bf 100644 --- a/BUGS +++ b/BUGS @@ -5,9 +5,3 @@ - Attaching to a process with children doesn't work right. This should be possible to fix -- I just need to ptrace each child individually and do the same games to it. - -- After attaching to a curses application, the arrow keys may not work - right. This is because of terminal state that is configured by - sending control characters to the terminal emulator, instead of - through termios, and so I can't easily restore. Suspending and - resuming the app will often usually perform the appropriate reset. diff --git a/README b/README index e2a6107..2df81cd 100644 --- a/README +++ b/README @@ -21,18 +21,6 @@ background it, you will still have to run "bg" or "fg" in the old terminal. This is likely impossible to fix in a reasonable way without patching your shell.) -After attaching, you may need to send a ^L or similar to ncurses -applications to force them to redraw themselves. With "less", after -attaching, a ^Z will cause it to both redraw and to set up the -terminal application keys so that you can scroll with arrow keys -again. - -After attaching, your old terminal will probably be left in a strange -state, since it will be waiting for the process to quit, but not -getting any output. You can either background your process and do 'bg; -disown', or just close the window -- reptyr should protect your -process from being killed when you do so. - "But wait, isn't this just screenify?" -------------------------------------- From 0d515a4ed05a46eb5566506c8a3d75dc0f265396 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 10:49:57 -0500 Subject: [PATCH 5/8] Use wait4 instead of waitid(). waitid() seems to return EINVAL sometimes, for reasons I don't understand. --- attach.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/attach.c b/attach.c index be4b0ab..c426d7e 100644 --- a/attach.c +++ b/attach.c @@ -141,9 +141,9 @@ int do_setsid(struct ptrace_child *child) { kill(dummy.pid, SIGKILL); ptrace_detach_child(&dummy); ptrace_wait(&dummy); - ptrace_remote_syscall(child, __NR_waitid, - P_PID, dummy.pid, 0, WNOHANG, - 0, 0); + ptrace_remote_syscall(child, __NR_wait4, + dummy.pid, 0, WNOHANG, + 0, 0, 0); return err; } From 73dae02e9b6f6a3264e3bc90e8cfcd3388522146 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 16:40:16 -0500 Subject: [PATCH 6/8] If we fail to attach to the target, SIGCONT it before exiting. --- attach.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/attach.c b/attach.c index c426d7e..1702519 100644 --- a/attach.c +++ b/attach.c @@ -231,8 +231,10 @@ int attach_child(pid_t pid, const char *pty) { kill(pid, SIGTSTP); wait_for_stop(pid); - if (ptrace_attach_child(&child, pid)) + if (ptrace_attach_child(&child, pid)) { + kill(pid, SIGCONT); return child.error; + } if (ptrace_advance_to_state(&child, ptrace_at_syscall)) { err = child.error; From f190aa9ecb212ce9e7d3b086c32baa2c630d88ff Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 16:47:43 -0500 Subject: [PATCH 7/8] Copy the termios settings before we background the target process. If the target /doesn't/ properly initialize the terminal after a SIGTSTP/continue (I'm looking at you, emacs), we need to be sure to grab the terminal state before we suspend it and the shell resets the terminal to cooked mode. --- attach.c | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/attach.c b/attach.c index 1702519..3fff845 100644 --- a/attach.c +++ b/attach.c @@ -221,6 +221,31 @@ void wait_for_stop(pid_t pid) { close(fd); } +int copy_tty_state(pid_t pid, const char *pty) { + char buf[PATH_MAX]; + int fd, err = 0; + struct termios tio; + + snprintf(buf, sizeof buf, "/proc/%d/fd/0", pid); + if ((fd = open(buf, O_RDONLY)) < 0) + return -errno; + + if (tcgetattr(fd, &tio) < 0) { + err = errno; + goto out; + } + close(fd); + + if ((fd = open(pty, O_RDONLY)) < 0) + return -errno; + + if (tcsetattr(fd, TCSANOW, &tio) < 0) + err = errno; +out: + close(fd); + return -err; +} + int attach_child(pid_t pid, const char *pty) { struct ptrace_child child; unsigned long scratch_page = -1; @@ -228,6 +253,9 @@ int attach_child(pid_t pid, const char *pty) { int i; int err = 0; + if ((err = copy_tty_state(pid, pty)) < 0) + return -err; + kill(pid, SIGTSTP); wait_for_stop(pid); @@ -279,21 +307,6 @@ int attach_child(pid_t pid, const char *pty) { debug("Opened the new tty in the child: %d", child_fd); - err = ptrace_remote_syscall(&child, __NR_ioctl, - child_tty_fds[0], TCGETS, scratch_page, - 0, 0, 0); - debug("TCGETS(%d): %d", child_tty_fds[0], err); - if(err < 0) - goto out_close; - err = ptrace_remote_syscall(&child, __NR_ioctl, - child_fd, TCSETS, scratch_page, - 0, 0, 0); - debug("TCSETS: %d", err); - if (err < 0) - goto out_close; - - debug("Copied terminal settings"); - err = ignore_hup(&child, scratch_page); if (err < 0) goto out_close; From c1019cf1807de0b17af4ae2607a376e01dcbbced Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Tue, 25 Jan 2011 17:46:24 -0500 Subject: [PATCH 8/8] Send the child an explicit SIGCONT once we're done. This causes bash to notice that the process is backgrounded, which prevents it from sending a SIGTERM when the original shell exits. --- attach.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/attach.c b/attach.c index 3fff845..c5bb698 100644 --- a/attach.c +++ b/attach.c @@ -355,8 +355,10 @@ int attach_child(pid_t pid, const char *pty) { out_detach: ptrace_detach_child(&child); - if (err == 0) + if (err == 0) { + kill(child.pid, SIGCONT); kill(child.pid, SIGWINCH); + } return err < 0 ? -err : err; }