1 /* retty.c - attach process to current terminal
5 * PID is the pid of a running process.
7 * retty works on x86 Linux.
9 * Copyright (c) 2006 Petr Baudis, Jan Sembera
20 #define _GNU_SOURCE // grantpt & family
22 #include <sys/ioctl.h>
29 #include <sys/ptrace.h>
31 #include <sys/types.h>
33 #include <sys/select.h>
43 static int oldin
, oldout
, olderr
, die
, intr
;
44 int stin
= 0, sout
= 1, serr
= 2;
47 struct termios t_orig
;
50 /* Write NLONG 4 byte words from BUF into PID starting
51 at address POS. Calling process must be attached to PID. */
53 write_mem(pid_t pid
, unsigned long *buf
, int nlong
, unsigned long pos
)
58 for (p
= buf
, i
= 0; i
< nlong
; p
++, i
++)
59 if (0 > ptrace(PTRACE_POKEDATA
, pid
, pos
+(i
*4), *p
))
66 poke_32(unsigned char *data
, off_t offset
, uint32_t val
)
68 *((uint32_t *)(&data
[offset
])) = val
;
73 dump_code(unsigned char *code
, size_t size
)
76 for (i
= 0; i
< size
; i
++) {
80 printf("0x%02x, ", code
[i
]);
87 inject_attach(pid_t pid
, int n
, char ptsname
[])
89 struct user_regs_struct regs
;
90 unsigned long codeaddr
, ptsnameaddr
;
93 int fd_cervena
= stin
, fd_zelena
= sout
, fd_modra
= serr
;
94 int fd_fialova
= stin
, fd_oranzova
= sout
, fd_bezova
= serr
;
95 int fd_zluta
= stin
, fd_bila
= sout
, fd_cerna
= serr
;
96 int fd_hnusna
= stin
, fd_cokoladova
= sout
, fd_vanilkova
= serr
;
98 static unsigned char attach_code
[] = {
99 // this is not how it looks like *hint* *hint*
100 #include "bc-attach.i"
104 dump_code(attach_code
, sizeof(attach_code
));
108 if (0 > ptrace(PTRACE_ATTACH
, pid
, 0, 0)) {
109 fprintf(stderr
, "cannot attach to %d\n", pid
);
112 waitpid(pid
, NULL
, 0);
113 ptrace(PTRACE_GETREGS
, pid
, 0, ®s
);
120 ptrace(PTRACE_POKEDATA
, pid
, regs
.esp
, regs
.eip
);
122 /* finish code and push it */
123 regs
.esp
-= sizeof(attach_code
);
125 printf("codesize: %x codeaddr: %lx\n", sizeof(attach_code
), codeaddr
);
126 *((int*)&attach_code
[sizeof(attach_code
)-5]) = sizeof(attach_code
) + n
*4 + 4;
127 if (0 > write_mem(pid
, (unsigned long*)&attach_code
, sizeof(attach_code
)/sizeof(long), regs
.esp
)) {
128 fprintf(stderr
, "cannot write attach_code\n");
134 ptsnameaddr
= regs
.esp
;
135 if (0 > write_mem(pid
, (unsigned long*)ptsname
, n
, regs
.esp
)) {
136 fprintf(stderr
, "cannot write bla argument (%s)\n",
142 /* FIXME: This is superfluous now, change bytecode to use lea */
144 ptrace(PTRACE_POKEDATA
, pid
, regs
.esp
, ptsnameaddr
);
146 regs
.eip
= codeaddr
+8;
147 printf("stack: %lx eip: %lx sub:%x\n", regs
.esp
, regs
.eip
, (int) attach_code
[sizeof(attach_code
)-5]);
150 /* Run the bytecode */
151 ptrace(PTRACE_SETREGS
, pid
, 0, ®s
);
152 sigwinch(0); // bytecode will raise another SIGWINCH later so it will get sync'd thru
153 // interrupt any syscall with the WINCH (typically read() ;)
155 ptrace(PTRACE_CONT
, pid
, 0, (void*) SIGWINCH
);
157 if (!WIFSTOPPED(waitst
)) {
158 fprintf(stderr
, "attached task not stopped\n");
161 } while (WSTOPSIG(waitst
) != SIGWINCH
);
163 /* Grab backed up fds from stack */
164 ptrace(PTRACE_GETREGS
, pid
, 0, ®s
);
165 oldin
= ptrace(PTRACE_PEEKDATA
, pid
, regs
.esp
+ 0x8, NULL
);
166 oldout
= ptrace(PTRACE_PEEKDATA
, pid
, regs
.esp
+ 0x4, NULL
);
167 olderr
= ptrace(PTRACE_PEEKDATA
, pid
, regs
.esp
+ 0x0, NULL
);
168 printf("oldfds (esp: %lx): %d, %d, %d\n", regs
.esp
, oldin
, oldout
, olderr
);
171 ptrace(PTRACE_DETACH
, pid
, 0, (void*) SIGWINCH
);
176 static int detached
= 0;
177 if (detached
> 0) return 0;
178 if (0 > ptrace(PTRACE_ATTACH
, pid
, 0, 0)) {
186 inject_detach(pid_t pid
, int fd0
, int fd1
, int fd2
)
188 struct user_regs_struct regs
;
189 unsigned long codeaddr
;
191 int fd_zelena
= stin
, fd_cervena
= sout
, fd_vyblita
= serr
;
192 int fd_modra
= stin
, fd_smoulova
= sout
, fd_hneda
= serr
;
194 static unsigned char detach_code
[] = {
195 // this is not how it looks like either *hint* *hint*
196 #include "bc-detach.i"
201 waitpid(pid
, NULL
, 0);
202 ptrace(PTRACE_GETREGS
, pid
, 0, ®s
);
209 ptrace(PTRACE_POKEDATA
, pid
, regs
.esp
, regs
.eip
);
211 /* finish code and push it */
212 regs
.esp
-= sizeof(detach_code
);
214 printf("codesize: %x codeaddr: %lx\n", sizeof(detach_code
), codeaddr
);
215 *((int*)&detach_code
[sizeof(detach_code
)-5]) = sizeof(detach_code
) + 4 + 4 + 4;
216 if (0 > write_mem(pid
, (unsigned long*)&detach_code
, sizeof(detach_code
)/sizeof(long), regs
.esp
)) {
217 fprintf(stderr
, "cannot write detach_code\n");
223 ptrace(PTRACE_POKEDATA
, pid
, regs
.esp
, fd0
);
225 ptrace(PTRACE_POKEDATA
, pid
, regs
.esp
, fd1
);
227 ptrace(PTRACE_POKEDATA
, pid
, regs
.esp
, fd2
);
229 regs
.eip
= codeaddr
+8;
230 printf("stack: %lx eip: %lx sub:%x\n", regs
.esp
, regs
.eip
, (int) detach_code
[sizeof(detach_code
)-5]);
233 /* Detach and continue */
234 ptrace(PTRACE_SETREGS
, pid
, 0, ®s
);
235 kill(pid
, SIGWINCH
); // interrupt any syscall (typically read() ;)
236 ptrace(PTRACE_DETACH
, pid
, 0, 0);
246 ioctl(1, TIOCGWINSZ
, &w
);
247 ioctl(ptm
, TIOCSWINSZ
, &w
);
260 if ((x
!= 0) && try_detach()) return;
261 if (cleanups
++ > 0) return;
262 if (!try_detach()) inject_detach(pid
, oldin
, oldout
, olderr
);
263 ioctl(0, TCSETS
, &t_orig
);
268 process_escapes(char *buf
, ssize_t
*len
)
270 static enum { ST_NONE
, ST_ENTER
, ST_ESCAPE
} state
;
272 for (i
= 0; i
< *len
; i
++) {
273 //fprintf(stderr, "[state=%d %d/%d char=%x]\n", state, i, *len - 1, buf[i]);
276 if (buf
[i
] == '\n' || buf
[i
] == '\r')
282 memmove(buf
+ i
, buf
+ i
+ 1, *len
- i
- 1);
294 printf("Detach request aborted - ptrace unsuccessful\n");
295 memmove(buf
+ i
, buf
+ i
+ 1, *len
- i
- 1);
300 printf("Supported escape sequences:\n");
301 printf("`. - return the process to its original terminal\n");
302 printf("`d - return the process to its original terminal\n");
303 printf("`? - this message\n");
304 printf("`` - send the escape character by typing it twice\n");
305 printf("(Note that escapes are only recognized immediately after newline.)\n");
306 memmove(buf
+ i
, buf
+ i
+ 1, *len
- i
- 1);
312 memmove(buf
+ i
+ 1, buf
+ i
, *len
- i
);
326 printf("retty %s\n", VERSION
);
327 printf("Copyright (c) 2006 Petr Baudis, Jan Sembera\n");
328 printf("This program is licensed under GNU GPL version 2 and no later.\n");
334 printf(" %s [-h] [-v] [-0 fd] [-1 fd] [-2 fd] PID \n\n", pname
);
336 printf(" -h This help\n");
337 printf(" -v Shows version of retty\n\n");
339 printf(" -0 fd Specify input file descriptor of target process (default 0)\n");
340 printf(" -1 fd Specify output file descriptor of target process (default 1)\n");
341 printf(" -2 fd Specify error file descriptor of target process (default 2)\n\n");
343 printf(" PID PID of process that will be attached (required)\n");
347 setpid(char *pidchar
, char *argv
) {
350 pid
= strtol(pidchar
, &x
, 0);
352 fprintf(stderr
, "PID specified incorrectly. Aborting.\n");
359 main(int argc
, char *argv
[])
369 res
= getopt(argc
, argv
, "hv0:1:2:");
370 if (res
== -1) break;
384 stin
= strtol(optarg
, &c
, 10);
385 if ((*optarg
== '\0') || (*c
!= '\0')) {
386 fprintf(stderr
, "Wrong stdin specification\n");
392 sout
= strtol(optarg
, &c
, 10);
393 if ((*optarg
== '\0') || (*c
!= '\0')) {
394 fprintf(stderr
, "Wrong stdout specification\n");
400 serr
= strtol(optarg
, &c
, 10);
401 if ((*optarg
== '\0') || (*c
!= '\0')) {
402 fprintf(stderr
, "Wrong stderr specification\n");
418 pid
= strtol(argv
[optind
], &x
, 0);
420 fprintf(stderr
, "PID specified incorrectly. Aborting.\n");
435 tcflush(ptm
, TCIOFLUSH
);
436 //(void) ioctl(ptm, TIOCEXCL, (char *) 0);
439 n
= n
/4 + (n
%4 ? 1 : 0);
440 arg
= malloc(n
*sizeof(unsigned long));
441 memcpy(arg
, pts
, n
*4);
443 signal(SIGWINCH
, sigwinch
);
444 signal(SIGINT
, sigint
); // breaks stuff
447 inject_attach(pid
, n
, arg
);
449 ioctl(0, TCGETS
, &t_orig
);
451 signal(SIGTERM
, cleanup
);
452 //signal(SIGINT, cleanup);
453 signal(SIGQUIT
, cleanup
);
454 signal(SIGPIPE
, cleanup
);
457 static struct termios t
;
461 char ibuf
= t
.c_cc
[VINTR
];
462 write(ptm
, &ibuf
, 1);
469 if (select(ptm
+1, &fds
, NULL
, NULL
, NULL
) < 0) {
470 if (errno
== EINTR
|| errno
== EAGAIN
)
476 ioctl(ptm
, TCGETS
, &t
);
477 // we keep 0 raw and let the pts do the terminal work
478 t
.c_lflag
&= ~(ECHO
|ECHOE
|ECHOK
|ECHONL
|ICANON
);
479 ioctl(0, TCSETS
, &t
);
481 if (FD_ISSET(ptm
, &fds
)) {
483 ssize_t len
= read(ptm
, buf
, 256);
484 if (len
< 0 && errno
!= EINTR
&& errno
!= EAGAIN
) {
490 if (FD_ISSET(0, &fds
)) {
492 ssize_t len
= read(0, buf
, 256);
494 stop
= process_escapes(buf
, &len
);
496 write(ptm
, buf
, stop
-1);
499 write(ptm
, buf
, len
);