Proper make clean - added deletion of detach code
[retty.git] / retty.c
blob98b65eca598c6b91e35b195b827ee63c4a3b7c2c
1 /* retty.c - attach process to current terminal
3 * Usage: retty PID
5 * PID is the pid of a running process.
7 * retty works on x86 Linux.
9 * Copyright (c) 2006 Petr Baudis, Jan Sembera
12 #define _GNU_SOURCE // grantpt & family
13 #include <signal.h>
14 #include <sys/ioctl.h>
15 #include <termios.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <fcntl.h>
20 #include <sys/ptrace.h>
21 #include <sys/user.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <sys/select.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <stdbool.h>
29 #define VERSION "1/sqrt(2)"
32 void sigwinch(int x);
34 static int oldin, oldout, olderr, die, intr;
35 pid_t pid = 0;
36 bool forking = 0;
37 struct termios t_orig;
40 /* Write NLONG 4 byte words from BUF into PID starting
41 at address POS. Calling process must be attached to PID. */
42 static int
43 write_mem(pid_t pid, unsigned long *buf, int nlong, unsigned long pos)
45 unsigned long *p;
46 int i;
48 for (p = buf, i = 0; i < nlong; p++, i++)
49 if (0 > ptrace(PTRACE_POKEDATA, pid, pos+(i*4), *p))
50 return -1;
51 return 0;
55 static void
56 inject_attach(pid_t pid, int n, char ptsname[])
58 struct user_regs_struct regs;
59 unsigned long codeaddr, ptsnameaddr;
60 int waitst;
62 static char attach_code[] = {
63 #include "bc-attach.i"
66 /* Attach */
67 if (0 > ptrace(PTRACE_ATTACH, pid, 0, 0)) {
68 fprintf(stderr, "cannot attach to %d\n", pid);
69 exit(1);
71 waitpid(pid, NULL, 0);
72 ptrace(PTRACE_GETREGS, pid, 0, &regs);
75 /* Code injecting */
77 /* push EIP */
78 regs.esp -= 4;
79 ptrace(PTRACE_POKEDATA, pid, regs.esp, regs.eip);
81 /* finish code and push it */
82 regs.esp -= sizeof(attach_code);
83 codeaddr = regs.esp;
84 printf("codesize: %x codeaddr: %lx\n", sizeof(attach_code), codeaddr);
85 *((int*)&attach_code[sizeof(attach_code)-5]) = sizeof(attach_code) + n*4 + 4;
86 if (0 > write_mem(pid, (unsigned long*)&attach_code, sizeof(attach_code)/sizeof(long), regs.esp)) {
87 fprintf(stderr, "cannot write attach_code\n");
88 exit(1);
91 /* push ptsname[] */
92 regs.esp -= n*4;
93 ptsnameaddr = regs.esp;
94 if (0 > write_mem(pid, (unsigned long*)ptsname, n, regs.esp)) {
95 fprintf(stderr, "cannot write bla argument (%s)\n",
96 strerror(errno));
97 exit(1);
100 /* push ptsname */
101 /* FIXME: This is superfluous now, change bytecode to use lea */
102 regs.esp -= 4;
103 ptrace(PTRACE_POKEDATA, pid, regs.esp, ptsnameaddr);
105 regs.eip = codeaddr+8;
106 printf("stack: %lx eip: %lx sub:%x\n", regs.esp, regs.eip, (int) attach_code[sizeof(attach_code)-5]);
109 /* Run the bytecode */
110 ptrace(PTRACE_SETREGS, pid, 0, &regs);
111 sigwinch(0); // bytecode will raise another SIGWINCH later so it will get sync'd thru
112 // interrupt any syscall with the WINCH (typically read() ;)
113 do {
114 ptrace(PTRACE_CONT, pid, 0, (void*) SIGWINCH);
115 wait(&waitst);
116 if (!WIFSTOPPED(waitst)) {
117 fprintf(stderr, "attached task not stopped\n");
118 exit(1);
120 } while (WSTOPSIG(waitst) != SIGWINCH);
122 /* Grab backed up fds from stack */
123 ptrace(PTRACE_GETREGS, pid, 0, &regs);
124 oldin = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x8, NULL);
125 oldout = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x4, NULL);
126 olderr = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x0, NULL);
127 printf("oldfds (esp: %lx): %d, %d, %d\n", regs.esp, oldin, oldout, olderr);
129 /* Let go */
130 ptrace(PTRACE_DETACH, pid, 0, (void*) SIGWINCH);
134 try_detach() {
135 static int detached = 0;
136 if (detached > 0) return 0;
137 if (0 > ptrace(PTRACE_ATTACH, pid, 0, 0)) {
138 return -1;
140 detached++;
141 return 0;
144 static void
145 inject_detach(pid_t pid, int fd0, int fd1, int fd2)
147 struct user_regs_struct regs;
148 unsigned long codeaddr;
150 static char detach_code[] = {
151 #include "bc-detach.i"
154 /* Attach */
155 (void) try_detach();
156 waitpid(pid, NULL, 0);
157 ptrace(PTRACE_GETREGS, pid, 0, &regs);
160 /* Code injecting */
162 /* push EIP */
163 regs.esp -= 4;
164 ptrace(PTRACE_POKEDATA, pid, regs.esp, regs.eip);
166 /* finish code and push it */
167 regs.esp -= sizeof(detach_code);
168 codeaddr = regs.esp;
169 printf("codesize: %x codeaddr: %lx\n", sizeof(detach_code), codeaddr);
170 *((int*)&detach_code[sizeof(detach_code)-5]) = sizeof(detach_code) + 4 + 4 + 4;
171 if (0 > write_mem(pid, (unsigned long*)&detach_code, sizeof(detach_code)/sizeof(long), regs.esp)) {
172 fprintf(stderr, "cannot write detach_code\n");
173 exit(1);
176 /* push fds */
177 regs.esp -= 4;
178 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd0);
179 regs.esp -= 4;
180 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd1);
181 regs.esp -= 4;
182 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd2);
184 regs.eip = codeaddr+8;
185 printf("stack: %lx eip: %lx sub:%x\n", regs.esp, regs.eip, (int) detach_code[sizeof(detach_code)-5]);
188 /* Detach and continue */
189 ptrace(PTRACE_SETREGS, pid, 0, &regs);
190 kill(pid, SIGWINCH); // interrupt any syscall (typically read() ;)
191 ptrace(PTRACE_DETACH, pid, 0, 0);
195 int ptm;
197 void
198 sigwinch(int x)
200 struct winsize w;
201 ioctl(1, TIOCGWINSZ, &w);
202 ioctl(ptm, TIOCSWINSZ, &w);
205 void
206 sigint(int x)
208 intr = 1;
211 void
212 cleanup(int x)
214 static int cleanups;
215 if ((x != 0) && try_detach()) return;
216 if (cleanups++ > 0) return;
217 if (!try_detach()) inject_detach(pid, oldin, oldout, olderr);
218 ioctl(0, TCSETS, &t_orig);
219 die = 1;
222 ssize_t
223 process_escapes(char *buf, ssize_t *len)
225 static enum { ST_NONE, ST_ENTER, ST_ESCAPE } state;
226 ssize_t i;
227 for (i = 0; i < *len; i++) {
228 //fprintf(stderr, "[state=%d %d/%d char=%x]\n", state, i, *len - 1, buf[i]);
229 switch (state) {
230 case ST_NONE:
231 if (buf[i] == '\n' || buf[i] == '\r')
232 state = ST_ENTER;
233 break;
234 case ST_ENTER:
235 if (buf[i] == '`') {
236 state = ST_ESCAPE;
237 memmove(buf + i, buf + i + 1, *len - i - 1);
238 (*len)--; i--;
239 } else {
240 state = ST_NONE;
242 break;
243 case ST_ESCAPE:
244 state = ST_NONE;
245 switch (buf[i]) {
246 case '.':
247 case 'd':
248 if (try_detach()) {
249 printf("Detach request aborted - ptrace unsuccessful\n");
250 memmove(buf + i, buf + i + 1, *len - i - 1);
251 (*len)--; i--;
252 break;
253 } else return i-2+1;
254 case '?':
255 printf("Supported escape sequences:\n");
256 printf("`. - return the process to its original terminal\n");
257 printf("`d - return the process to its original terminal\n");
258 printf("`? - this message\n");
259 printf("`` - send the escape character by typing it twice\n");
260 printf("(Note that escapes are only recognized immediately after newline.)\n");
261 memmove(buf + i, buf + i + 1, *len - i - 1);
262 (*len)--; i--;
263 break;
264 case '`':
265 break;
266 default:
267 memmove(buf + i + 1, buf + i, *len - i);
268 buf[i] = '`';
269 (*len)++; i++;
270 break;
272 break;
276 return 0;
279 void
280 version(void) {
281 printf("retty %s\n", VERSION);
282 printf("Copyright (c) 2006 Petr Baudis, Jan Sembera\n");
283 printf("This program is licensed under GNU GPL version 2 and no later.\n");
286 void
287 usage(char *pname) {
288 printf("Usage: \n");
289 #if 0
290 printf(" %s [-h] [-v] [-d fd[,fd[..]]] [-f] [-a arch] PID \n\n", pname);
291 #else
292 printf(" %s [-h] [-v] PID \n\n", pname);
293 #endif
294 printf(" -h This help\n");
295 printf(" -v Shows version of retty\n");
296 #if 0
297 printf(" -d fd,... List of file descriptors to be attached, separated by comma\n");
298 printf(" If not specified, default is 0, 1 and 2.\n");
299 printf(" -f Use forking code instead of standard code. Beware that this might\n");
300 printf(" cause some very unexpected behaviour.\n");
301 printf(" -a arch Selects architecture on which the target process is running.\n");
302 printf(" Normally, retty will select the platform itself, but there are\n");
303 printf(" some specific cases that require manual selection\n");
304 #endif
305 printf(" PID PID of process that will be attached\n");
308 void
309 setpid(char *pidchar, char *argv) {
310 char *x;
312 pid = strtol(pidchar, &x, 0);
313 if ((!x) || (*x)) {
314 fprintf(stderr, "PID specified incorrectly. Aborting.\n");
315 usage(argv);
316 exit(EXIT_FAILURE);
321 main(int argc, char *argv[])
323 int n;
324 char *arg;
325 char *pts;
327 while (1) {
328 int res;
330 res = getopt(argc, argv, "hvd:fa:o:");
331 if (res == -1) break;
333 switch (res) {
334 case 'h':
335 usage(argv[0]);
336 exit(EXIT_SUCCESS);
337 break;
339 case 'v':
340 version();
341 exit(EXIT_SUCCESS);
342 break;
344 case 'd':
345 fprintf(stderr, "File descriptor alteration not yet implemented\n");
346 break;
348 case 'f':
349 fprintf(stderr, "Forking not yet implemented\n");
350 forking = 1;
351 break;
353 case 'a':
354 fprintf(stderr, "Architecture selection not yet implemented\n");
355 break;
357 default:
358 usage(argv[0]);
359 exit(EXIT_FAILURE);
360 break;
365 if (optind < argc) {
366 char *x;
368 pid = strtol(argv[optind], &x, 0);
369 if ((!x) || (*x)) {
370 fprintf(stderr, "PID specified incorrectly. Aborting.\n");
371 usage(argv[0]);
372 exit(EXIT_FAILURE);
375 } else {
376 usage(argv[0]);
377 exit(EXIT_FAILURE);
380 /* Setup pty */
381 ptm = getpt();
382 grantpt(ptm);
383 unlockpt(ptm);
384 pts = ptsname(ptm);
385 tcflush(ptm, TCIOFLUSH);
386 //(void) ioctl(ptm, TIOCEXCL, (char *) 0);
388 n = strlen(pts)+1;
389 n = n/4 + (n%4 ? 1 : 0);
390 arg = malloc(n*sizeof(unsigned long));
391 memcpy(arg, pts, n*4);
393 signal(SIGWINCH, sigwinch);
394 signal(SIGINT, sigint); // breaks stuff
397 inject_attach(pid, n, arg);
399 ioctl(0, TCGETS, &t_orig);
401 signal(SIGTERM, cleanup);
402 //signal(SIGINT, cleanup);
403 signal(SIGQUIT, cleanup);
404 signal(SIGPIPE, cleanup);
406 while (!die) {
407 static struct termios t;
408 fd_set fds;
410 while (intr) {
411 char ibuf = t.c_cc[VINTR];
412 write(ptm, &ibuf, 1);
413 intr--;
416 FD_ZERO(&fds);
417 FD_SET(ptm, &fds);
418 FD_SET(0, &fds);
419 if (select(ptm+1, &fds, NULL, NULL, NULL) < 0) {
420 if (errno == EINTR || errno == EAGAIN)
421 continue;
422 perror("select()");
423 break;
426 ioctl(ptm, TCGETS, &t);
427 // we keep 0 raw and let the pts do the terminal work
428 t.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON);
429 ioctl(0, TCSETS, &t);
431 if (FD_ISSET(ptm, &fds)) {
432 char buf[256];
433 ssize_t len = read(ptm, buf, 256);
434 if (len < 0 && errno != EINTR && errno != EAGAIN) {
435 break;
437 write(1, buf, len);
440 if (FD_ISSET(0, &fds)) {
441 char buf[2*256];
442 ssize_t len = read(0, buf, 256);
443 ssize_t stop;
444 stop = process_escapes(buf, &len);
445 if (stop) {
446 write(ptm, buf, stop-1);
447 break;
449 write(ptm, buf, len);
453 cleanup(0);
455 return 0;