source: trunk/tools/neercs/old/mytrace.c @ 2183

Last change on this file since 2183 was 2183, checked in by sam, 7 years ago

build: fix the WTFPL site URL in all code comments.

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