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

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