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

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