source: neercs/trunk/src/mytrace.c @ 3961

Last change on this file since 3961 was 3942, checked in by Pascal Terjan, 11 years ago
  • Drop withespaces
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 18.2 KB
Line 
1/*
2 *  neercs        console-based window manager
3 *  Copyright (c) 2008 Pascal Terjan
4 *            (c) 2008 Sam Hocevar <sam@zoy.org>
5 *                All Rights Reserved
6 *
7 *  $Id: mytrace.c 3942 2009-11-18 15:31:49Z pterjan $
8 *
9 *  This program is free software. It comes without any warranty, to
10 *  the extent permitted by applicable law. You can redistribute it
11 *  and/or modify it under the terms of the Do What The Fuck You Want
12 *  To Public License, Version 2, as published by Sam Hocevar. See
13 *  http://sam.zoy.org/wtfpl/COPYING for more details.
14 */
15
16#include "config.h"
17
18#include <errno.h>
19#include <fcntl.h>
20#include <limits.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24
25#if defined USE_GRAB
26#   include <sys/ioctl.h>
27#   include <sys/ptrace.h>
28#   include <sys/stat.h>
29#   include <sys/syscall.h>
30#   include <sys/user.h>
31#   include <sys/wait.h>
32#endif
33
34#include "neercs.h"
35#include "mytrace.h"
36
37#if defined USE_GRAB
38static int memcpy_from_target(struct mytrace *t,
39                              char* dest, long src, size_t n);
40static int memcpy_into_target(struct mytrace *t,
41                              long dest, char const *src, size_t n);
42static long remote_syscall(struct mytrace *t, long call,
43                           long arg1, long arg2, long arg3);
44#   if defined DEBUG
45static void print_registers(pid_t pid);
46#   else
47#       define print_registers(x) do {} while(0)
48#   endif
49
50#define X(x) #x
51#define STRINGIFY(x) X(x)
52
53#define SYSCALL_X86     0x80cd  /* CD 80 = int $0x80 */
54#define SYSCALL_X86_NEW 0xf3eb  /* EB F3 = jmp <__kernel_vsyscall+0x3> */
55#define SYSENTER        0x340f  /* 0F 34 = sysenter */
56#define SYSCALL_AMD64   0x050fL /* 0F 05 = syscall */
57
58#if defined __x86_64__
59#   define RAX rax
60#   define RBX rbx
61#   define RCX rcx
62#   define RDX rdx
63#   define RSP rsp
64#   define RBP rbp
65#   define RIP rip
66#   define RDI rdi
67#   define RSI rsi
68#   define FMT "%016lx"
69#else
70#   define RAX eax
71#   define RBX ebx
72#   define RCX ecx
73#   define RDX edx
74#   define RSP esp
75#   define RBP ebp
76#   define RIP eip
77#   define RDI edi
78#   define RSI esi
79#   define FMT "%08lx"
80#endif
81
82#define MYCALL_OPEN     0
83#define MYCALL_CLOSE    1
84#define MYCALL_WRITE    2
85#define MYCALL_DUP2     3
86#define MYCALL_SETPGID  4
87#define MYCALL_SETSID   5
88#define MYCALL_KILL     6
89#define MYCALL_FORK     7
90#define MYCALL_EXIT     8
91#define MYCALL_EXECVE   9
92#define MYCALL_IOCTL   10
93
94#if defined __x86_64__
95/* from unistd_32.h on an amd64 system */
96int syscalls32[] = { 5, 6, 4, 63, 57, 66, 37, 2, 1, 11, 54 };
97int syscalls64[] =
98#else
99int syscalls32[] =
100#endif
101    { SYS_open, SYS_close, SYS_write, SYS_dup2, SYS_setpgid, SYS_setsid,
102      SYS_kill, SYS_fork, SYS_exit, SYS_execve, SYS_ioctl };
103
104char const *syscallnames[] =
105    { "open", "close", "write", "dup2", "setpgid", "setsid", "kill", "fork",
106      "exit", "execve", "ioctl" };
107
108#endif /* USE_GRAB */
109
110struct mytrace
111{
112    pid_t pid, child;
113};
114
115struct mytrace* mytrace_attach(long int pid)
116{
117#if defined USE_GRAB
118    struct mytrace *t;
119    int status;
120
121    if(ptrace(PTRACE_ATTACH, pid, 0, 0) < 0)
122    {
123        perror("PTRACE_ATTACH (attach)");
124        return NULL;
125    }
126    if(waitpid(pid, &status, 0) < 0)
127    {
128        perror("waitpid");
129        return NULL;
130    }
131    if(!WIFSTOPPED(status))
132    {
133        fprintf(stderr, "traced process was not stopped\n");
134        ptrace(PTRACE_DETACH, pid, 0, 0);
135        return NULL;
136    }
137
138    t = malloc(sizeof(struct mytrace));
139    t->pid = pid;
140    t->child = 0;
141
142    return t;
143#else
144    errno = ENOSYS;
145    return NULL;
146#endif
147}
148
149struct mytrace* mytrace_fork(struct mytrace *t)
150{
151#if defined USE_GRAB
152    struct mytrace *child;
153
154    ptrace(PTRACE_SETOPTIONS, t->pid, NULL, PTRACE_O_TRACEFORK);
155    remote_syscall(t, MYCALL_FORK, 0, 0, 0);
156    waitpid(t->child, NULL, 0);
157
158    child = malloc(sizeof(struct mytrace));
159    child->pid = t->child;
160    child->child = 0;
161
162    return child;
163#else
164    errno = ENOSYS;
165    return NULL;
166#endif
167}
168
169int mytrace_detach(struct mytrace *t)
170{
171#if defined USE_GRAB
172    ptrace(PTRACE_DETACH, t->pid, 0, 0);
173    free(t);
174
175    return 0;
176#else
177    errno = ENOSYS;
178    return -1;
179#endif
180}
181
182long mytrace_getpid(struct mytrace *t)
183{
184#if defined USE_GRAB
185    return t->pid;
186#else
187    errno = ENOSYS;
188    return -1;
189#endif
190}
191
192int mytrace_open(struct mytrace *t, char const *path, int mode)
193{
194#if defined USE_GRAB
195    char backup_data[4096];
196    struct user_regs_struct regs;
197    size_t size = strlen(path) + 1;
198    int ret, err;
199
200    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
201    {
202        perror("PTRACE_GETREGS (open)\n");
203        return -1;
204    }
205
206    /* Backup the data that we will use */
207    if(memcpy_from_target(t, backup_data, regs.RSP, size) < 0)
208        return -1;
209
210    memcpy_into_target(t, regs.RSP, path, size);
211
212    ret = remote_syscall(t, MYCALL_OPEN, regs.RSP, O_RDWR, 0755);
213    err = errno;
214
215    /* Restore the data */
216    memcpy_into_target(t, regs.RSP, backup_data, size);
217
218    errno = err;
219    return ret;
220#else
221    errno = ENOSYS;
222    return -1;
223#endif
224}
225
226int mytrace_close(struct mytrace *t, int fd)
227{
228#if defined USE_GRAB
229    return remote_syscall(t, MYCALL_CLOSE, fd, 0, 0);
230#else
231    errno = ENOSYS;
232    return -1;
233#endif
234}
235
236int mytrace_write(struct mytrace *t, int fd, char const *data, size_t len)
237{
238#if defined USE_GRAB
239    struct user_regs_struct regs;
240    char *backup_data;
241    int ret, err;
242
243    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
244    {
245        perror("PTRACE_GETREGS (write)\n");
246        return -1;
247    }
248
249    backup_data = malloc(len);
250
251    /* Backup the data that we will use */
252    if(memcpy_from_target(t, backup_data, regs.RSP, len) < 0)
253        return -1;
254
255    memcpy_into_target(t, regs.RSP, data, len);
256
257    ret = remote_syscall(t, MYCALL_WRITE, fd, regs.RSP, len);
258    err = errno;
259
260    /* Restore the data */
261    memcpy_into_target(t, regs.RSP, backup_data, len);
262
263    errno = err;
264    return ret;
265#else
266    errno = ENOSYS;
267    return -1;
268#endif
269}
270
271int mytrace_dup2(struct mytrace *t, int oldfd, int newfd)
272{
273#if defined USE_GRAB
274    return remote_syscall(t, MYCALL_DUP2, oldfd, newfd, 0);
275#else
276    errno = ENOSYS;
277    return -1;
278#endif
279}
280
281int mytrace_setpgid(struct mytrace *t, long pid, long pgid)
282{
283#if defined USE_GRAB
284    return remote_syscall(t, MYCALL_SETPGID, pid, pgid, 0);
285#else
286    errno = ENOSYS;
287    return -1;
288#endif
289}
290
291int mytrace_setsid(struct mytrace *t)
292{
293#if defined USE_GRAB
294    return remote_syscall(t, MYCALL_SETSID, 0, 0, 0);
295#else
296    errno = ENOSYS;
297    return -1;
298#endif
299}
300
301int mytrace_kill(struct mytrace *t, long pid, int sig)
302{
303#if defined USE_GRAB
304    return remote_syscall(t, MYCALL_KILL, pid, sig, 0);
305#else
306    errno = ENOSYS;
307    return -1;
308#endif
309}
310
311int mytrace_exit(struct mytrace *t, int status)
312{
313#if defined USE_GRAB
314    ptrace(PTRACE_SETOPTIONS, t->pid, NULL, PTRACE_O_TRACEEXIT);
315    return remote_syscall(t, MYCALL_EXIT, status, 0, 0);
316#else
317    errno = ENOSYS;
318    return -1;
319#endif
320}
321
322int mytrace_exec(struct mytrace *t, char const *command)
323{
324#if defined USE_GRAB
325    struct user_regs_struct regs;
326    char *env, *p;
327    long p2, envaddr, argvaddr, envptraddr;
328    char envpath[PATH_MAX+1];
329    ssize_t envsize = 16*1024;
330    int ret, fd, l, l2;
331    char *nullp = NULL;
332    ssize_t r;
333
334    ptrace(PTRACE_SETOPTIONS, t->pid, NULL, PTRACE_O_TRACEEXEC);
335
336    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
337    {
338        perror("PTRACE_GETREGS (exec)\n");
339        return -1;
340    }
341
342    debug("PTRACE_GETREGS done");
343    env = malloc(envsize);
344    if (!env)
345        return -1;
346
347    snprintf(envpath, PATH_MAX, "/proc/%d/environ", t->pid);
348
349    fd = open(envpath, O_RDONLY);
350    if (fd == -1)
351        return -1;
352    r = read(fd, env, envsize);
353    close(fd);
354    if (r == -1)
355        return -1;
356    while (r == envsize)
357    {
358        free(env);
359        env = malloc(envsize);
360        if (!env)
361            return -1;
362        fd = open(envpath, O_RDONLY);
363        r = read(fd, env, envsize);
364        close(fd);
365        if (r == -1)
366            return -1;
367    }
368    envsize = r;
369    l2 = sizeof(char *); /* Size of a pointer */
370    p2 = regs.RSP;
371
372    /* First argument is the command string */
373    l = strlen(command)+1;
374    memcpy_into_target(t, p2, command, l);
375    p2 += l;
376
377    /* Second argument is argv */
378    argvaddr = p2;
379    /* argv[0] is a pointer to the command string */
380    memcpy_into_target(t, p2, (char *)&regs.RSP, l2);
381    p2 += l2;
382    /* Then follows a NULL pointer */
383    memcpy_into_target(t, p2, (char *)&nullp, l2);
384    p2 += l2;
385
386    /* Third argument is the environment */
387    /* First, copy all the strings */
388    memcpy_into_target(t, p2, env, envsize);
389    envaddr = p2;
390    p2 += envsize;
391    /* Then write an array of pointers to the strings */
392    envptraddr = p2;
393    p = env;
394    while (p < env+envsize)
395    {
396        long diffp = p - env + envaddr;
397        memcpy_into_target(t, p2, (char *)&diffp, l2);
398        p2 += l2;
399        p += strlen(p)+1;
400    }
401    /* And have a NULL pointer at the end of the array */
402    memcpy_into_target(t, p2, (char *)&nullp, l2);
403    free(env);
404
405    ret = remote_syscall(t, MYCALL_EXECVE, regs.RSP, argvaddr, envptraddr);
406
407    return ret;
408#else
409    errno = ENOSYS;
410    return -1;
411#endif
412}
413
414int mytrace_tcgets(struct mytrace *t, int fd, struct termios *tos)
415{
416#if defined USE_GRAB
417    char backup_data[4096];
418    struct user_regs_struct regs;
419    size_t size = sizeof(struct termios);
420    int ret, err;
421
422    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
423    {
424        perror("PTRACE_GETREGS (tcgets)\n");
425        return -1;
426    }
427
428    /* Backup the data that we will use */
429    if(memcpy_from_target(t, backup_data, regs.RSP, size) < 0)
430        return -1;
431
432    ret = remote_syscall(t, MYCALL_IOCTL, fd,TCGETS, regs.RSP);
433    err = errno;
434
435    memcpy_from_target(t, (char *)tos, regs.RSP, size);
436
437    /* Restore the data */
438    memcpy_into_target(t, regs.RSP, backup_data, size);
439
440    errno = err;
441    return ret;
442#else
443    errno = ENOSYS;
444    return -1;
445#endif
446}
447
448int mytrace_tcsets(struct mytrace *t, int fd, struct termios *tos)
449{
450#if defined USE_GRAB
451    char backup_data[4096];
452    struct user_regs_struct regs;
453    size_t size = sizeof(struct termios);
454    int ret, err;
455
456    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
457    {
458        perror("PTRACE_GETREGS (tcsets)\n");
459        return -1;
460    }
461
462    /* Backup the data that we will use */
463    if(memcpy_from_target(t, backup_data, regs.RSP, size) < 0)
464        return -1;
465
466    memcpy_into_target(t, regs.RSP, (char *)tos, size);
467
468    ret = remote_syscall(t, MYCALL_IOCTL, fd, TCSETS, regs.RSP);
469    err = errno;
470
471    /* Restore the data */
472    memcpy_into_target(t, regs.RSP, backup_data, size);
473
474    errno = err;
475    return ret;
476#else
477    errno = ENOSYS;
478    return -1;
479#endif
480}
481
482int mytrace_sctty(struct mytrace *t, int fd)
483{
484#if defined USE_GRAB
485    ptrace(PTRACE_SETOPTIONS, t->pid, NULL, PTRACE_O_TRACEEXIT);
486    return remote_syscall(t, MYCALL_IOCTL, fd, TIOCSCTTY, 0);
487#else
488    errno = ENOSYS;
489    return -1;
490#endif
491}
492
493/*
494 * XXX: the following functions are local
495 */
496
497#if defined USE_GRAB
498static int memcpy_from_target(struct mytrace *t,
499                              char* dest, long src, size_t n)
500{
501    static int const align = sizeof(long) - 1;
502
503    while(n)
504    {
505        long data;
506        size_t todo = sizeof(long) - (src & align);
507
508        if(n < todo)
509            todo = n;
510
511        data = ptrace(PTRACE_PEEKTEXT, t->pid, src - (src & align), 0);
512        if(errno)
513        {
514            perror("ptrace_peektext (memcpy_from_target)");
515            return -1;
516        }
517        memcpy(dest, (char *)&data + (src & align), todo);
518
519        dest += todo;
520        src += todo;
521        n -= todo;
522    }
523
524    return 0;
525}
526
527static int memcpy_into_target(struct mytrace *t,
528                              long dest, char const *src, size_t n)
529{
530    static int const align = sizeof(long) - 1;
531
532    while(n)
533    {
534        long data;
535        size_t todo = sizeof(long) - (dest & align);
536
537        if(n < todo)
538            todo = n;
539        if(todo != sizeof(long))
540        {
541            data = ptrace(PTRACE_PEEKTEXT, t->pid, dest - (dest & align), 0);
542            if(errno)
543            {
544                perror("ptrace_peektext (memcpy_into_target)");
545                return -1;
546            }
547        }
548
549        memcpy((char *)&data + (dest & align), src, todo);
550        if(ptrace(PTRACE_POKETEXT, t->pid, dest - (dest & align), data) < 0)
551        {
552            perror("ptrace_poketext (memcpy_into_target)");
553            return -1;
554        }
555
556        src += todo;
557        dest += todo;
558        n -= todo;
559    }
560
561    return 0;
562}
563
564static long remote_syscall(struct mytrace *t, long call,
565                           long arg1, long arg2, long arg3)
566{
567    /* Method for remote syscall:
568     *  - wait until the traced application exits from a syscall
569     *  - save registers
570     *  - rewind eip/rip to point on the syscall instruction
571     *  - single step: execute syscall instruction
572     *  - retrieve resulting registers
573     *  - restore registers */
574    struct user_regs_struct regs, oldregs;
575    long oinst;
576    int bits;
577    int offset = 2;
578
579    if(call < 0 || call >= (long)(sizeof(syscallnames)/sizeof(*syscallnames)))
580    {
581        fprintf(stderr, "unknown remote syscall %li\n", call);
582        return -1;
583    }
584
585    debug("remote syscall %s(0x%lx, 0x%lx, 0x%lx)",
586          syscallnames[call], arg1, arg2, arg3);
587
588#if defined __x86_64__
589    bits = 64;
590#else
591    bits = 32;
592#endif
593
594    for(;;)
595    {
596        if(ptrace(PTRACE_GETREGS, t->pid, NULL, &oldregs) < 0)
597        {
598            perror("PTRACE_GETREGS (syscall 1)\n");
599            return -1;
600        }
601
602        oinst = ptrace(PTRACE_PEEKTEXT, t->pid, oldregs.RIP - 2, 0) & 0xffff;
603
604#if defined __x86_64__
605        if(oinst == SYSCALL_AMD64)
606            break;
607#endif
608        if(oinst == SYSCALL_X86 || oinst == SYSCALL_X86_NEW)
609        {
610            bits = 32;
611            break;
612        }
613
614        if(ptrace(PTRACE_SYSCALL, t->pid, NULL, 0) < 0)
615        {
616            perror("ptrace_syscall (1)");
617            return -1;
618        }
619        waitpid(t->pid, NULL, 0);
620        if(ptrace(PTRACE_SYSCALL, t->pid, NULL, 0) < 0)
621        {
622            perror("ptrace_syscall (2)");
623            return -1;
624        }
625        waitpid(t->pid, NULL, 0);
626    }
627
628    print_registers(t->pid);
629
630    if(oinst == SYSCALL_X86_NEW)
631    {
632        /*  Get back to sysenter */
633        while((ptrace(PTRACE_PEEKTEXT, t->pid, oldregs.RIP - offset, 0) & 0xffff) != 0x340f)
634            offset++;
635        oldregs.RBP = oldregs.RSP;
636    }
637
638    regs = oldregs;
639    regs.RIP = regs.RIP - offset;
640#if defined __x86_64__
641    if(bits == 64)
642    {
643        regs.RAX = syscalls64[call];
644        regs.RDI = arg1;
645        regs.RSI = arg2;
646        regs.RDX = arg3;
647    }
648    else
649#endif
650    {
651        regs.RAX = syscalls32[call];
652        regs.RBX = arg1;
653        regs.RCX = arg2;
654        regs.RDX = arg3;
655    }
656
657    if(ptrace(PTRACE_SETREGS, t->pid, NULL, &regs) < 0)
658    {
659        perror("PTRACE_SETREGS (syscall 1)\n");
660        return -1;
661    }
662
663    for(;;)
664    {
665        int status;
666
667        print_registers(t->pid);
668
669        if(ptrace(PTRACE_SINGLESTEP, t->pid, NULL, NULL) < 0)
670        {
671            perror("PTRACE_SINGLESTEP (syscall)\n");
672            return -1;
673        }
674        waitpid(t->pid, &status, 0);
675
676        if(WIFEXITED(status))
677            return 0;
678
679        if(!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP)
680            continue;
681
682        /* Fuck Linux: there is no macro for this */
683        switch((status >> 16) & 0xffff)
684        {
685        case PTRACE_EVENT_FORK:
686            if(ptrace(PTRACE_GETEVENTMSG, t->pid, 0, &t->child) < 0)
687            {
688                perror("PTRACE_GETEVENTMSG (syscall)\n");
689                return -1;
690            }
691            debug("PTRACE_GETEVENTMSG %d", t->child);
692            continue;
693        case PTRACE_EVENT_EXIT:
694            debug("PTRACE_EVENT_EXIT");
695            /* The process is about to exit, don't do anything else */
696            return 0;
697        case PTRACE_EVENT_EXEC:
698            debug("PTRACE_EVENT_EXEC");
699            return 0;
700        }
701
702        break;
703    }
704
705    print_registers(t->pid);
706
707    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
708    {
709        perror("PTRACE_GETREGS (syscall 2)\n");
710        return -1;
711    }
712
713    if(ptrace(PTRACE_SETREGS, t->pid, NULL, &oldregs) < 0)
714    {
715        perror("PTRACE_SETREGS (syscall 2)\n");
716        return -1;
717    }
718    print_registers(t->pid);
719
720    debug("syscall %s returned %ld", syscallnames[call], regs.RAX);
721
722    if((long)regs.RAX < 0)
723    {
724        errno = -(long)regs.RAX;
725        perror("syscall");
726        return -1;
727    }
728
729    return regs.RAX;
730}
731
732/* For debugging purposes only. Prints register and stack information. */
733#if defined DEBUG
734static void print_registers(pid_t pid)
735{
736    union { long int l; unsigned char data[sizeof(long int)]; } inst;
737    struct user_regs_struct regs;
738    int i;
739
740    if(ptrace(PTRACE_GETREGS, pid, NULL, &regs) < 0)
741    {
742        perror("PTRACE_GETREGS (syscall 2)");
743        exit(errno);
744    }
745
746    fprintf(stderr, "  / %s: "FMT"   ", STRINGIFY(RAX), regs.RAX);
747    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RBX), regs.RBX);
748    fprintf(stderr, "  | %s: "FMT"   ", STRINGIFY(RCX), regs.RCX);
749    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RDX), regs.RDX);
750    fprintf(stderr, "  | %s: "FMT"   ", STRINGIFY(RDI), regs.RDI);
751    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RSI), regs.RSI);
752    fprintf(stderr, "  | %s: "FMT"   ", STRINGIFY(RSP), regs.RSP);
753    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RIP), regs.RIP);
754
755    inst.l = ptrace(PTRACE_PEEKTEXT, pid, regs.RIP - 4, 0);
756    fprintf(stderr, "  | code: ... %02x %02x %02x %02x <---> ",
757            inst.data[0], inst.data[1], inst.data[2], inst.data[3]);
758    inst.l = ptrace(PTRACE_PEEKTEXT, pid, regs.RIP, 0);
759    fprintf(stderr, "%02x %02x %02x %02x ...\n",
760            inst.data[0], inst.data[1], inst.data[2], inst.data[3]);
761
762    fprintf(stderr, "  \\ stack: ... ");
763    for(i = -16; i < 24; i += sizeof(long))
764    {
765        inst.l = ptrace(PTRACE_PEEKDATA, pid, regs.RSP + i, 0);
766#if defined __x86_64__
767        fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x ",
768                inst.data[0], inst.data[1], inst.data[2], inst.data[3],
769                inst.data[4], inst.data[5], inst.data[6], inst.data[7]);
770#else
771        fprintf(stderr, "%02x %02x %02x %02x ",
772                inst.data[0], inst.data[1], inst.data[2], inst.data[3]);
773#endif
774        if(i == 0)
775            fprintf(stderr, "[%s] ", STRINGIFY(RSP));
776    }
777    fprintf(stderr, "...\n");
778}
779#endif /* DEBUG */
780
781#endif /* USE_GRAB */
Note: See TracBrowser for help on using the repository browser.