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

Last change on this file since 3870 was 3870, checked in by pterjan, 5 years ago
  • Preserve errno when needed, and return -1 when it is set
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 16.4 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$
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, err;
197
198    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
199    {
200        perror("PTRACE_GETREGS (open)\n");
201        return -1;
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    err = errno;
212
213    /* Restore the data */
214    memcpy_into_target(t, regs.RSP, backup_data, size);
215
216    errno = err;
217    return ret;
218#else
219    errno = ENOSYS;
220    return -1;
221#endif
222}
223
224int mytrace_close(struct mytrace *t, int fd)
225{
226#if defined USE_GRAB
227    return remote_syscall(t, MYCALL_CLOSE, fd, 0, 0);
228#else
229    errno = ENOSYS;
230    return -1;
231#endif
232}
233
234int mytrace_write(struct mytrace *t, int fd, char const *data, size_t len)
235{
236#if defined USE_GRAB
237    struct user_regs_struct regs;
238    char *backup_data;
239    int ret, err;
240
241    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
242    {
243        perror("PTRACE_GETREGS (write)\n");
244        return -1;
245    }
246
247    backup_data = malloc(len);
248
249    /* Backup the data that we will use */
250    if(memcpy_from_target(t, backup_data, regs.RSP, len) < 0)
251        return -1;
252
253    memcpy_into_target(t, regs.RSP, data, len);
254
255    ret = remote_syscall(t, MYCALL_WRITE, fd, regs.RSP, len);
256    err = errno;
257
258    /* Restore the data */
259    memcpy_into_target(t, regs.RSP, backup_data, len);
260
261    errno = err;
262    return ret;
263#else
264    errno = ENOSYS;
265    return -1;
266#endif
267}
268
269int mytrace_dup2(struct mytrace *t, int oldfd, int newfd)
270{
271#if defined USE_GRAB
272    return remote_syscall(t, MYCALL_DUP2, oldfd, newfd, 0);
273#else
274    errno = ENOSYS;
275    return -1;
276#endif
277}
278
279int mytrace_setpgid(struct mytrace *t, long pid, long pgid)
280{
281#if defined USE_GRAB
282    return remote_syscall(t, MYCALL_SETPGID, pid, pgid, 0);
283#else
284    errno = ENOSYS;
285    return -1;
286#endif
287}
288
289int mytrace_setsid(struct mytrace *t)
290{
291#if defined USE_GRAB
292    return remote_syscall(t, MYCALL_SETSID, 0, 0, 0);
293#else
294    errno = ENOSYS;
295    return -1;
296#endif
297}
298
299int mytrace_kill(struct mytrace *t, long pid, int sig)
300{
301#if defined USE_GRAB
302    return remote_syscall(t, MYCALL_KILL, pid, sig, 0);
303#else
304    errno = ENOSYS;
305    return -1;
306#endif
307}
308
309int mytrace_exit(struct mytrace *t, int status)
310{
311#if defined USE_GRAB
312    ptrace(PTRACE_SETOPTIONS, t->pid, NULL, PTRACE_O_TRACEEXIT);
313    return remote_syscall(t, MYCALL_EXIT, status, 0, 0);
314#else
315    errno = ENOSYS;
316    return -1;
317#endif
318}
319
320int mytrace_exec(struct mytrace *t, char const *command)
321{
322#if defined USE_GRAB
323    struct user_regs_struct regs;
324    char *env, *p;
325    long p2, envaddr, argvaddr, envptraddr;
326    char envpath[PATH_MAX+1];
327    ssize_t envsize = 16*1024;
328    int ret, fd, l, l2;
329    char *nullp = NULL;
330    ssize_t r;
331
332    ptrace(PTRACE_SETOPTIONS, t->pid, NULL, PTRACE_O_TRACEEXEC);
333
334    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
335    {
336        perror("PTRACE_GETREGS (exec)\n");
337        return -1;
338    }
339
340    debug("PTRACE_GETREGS done");
341    env = malloc(envsize);
342    if (!env)
343        return -1;
344
345    snprintf(envpath, PATH_MAX, "/proc/%d/environ", t->pid);
346
347    fd = open(envpath, O_RDONLY);
348    if (fd == -1)
349        return -1;
350    r = read(fd, env, envsize);
351    close(fd);
352    if (r == -1)
353        return -1;
354    while (r == envsize)
355    {
356        free(env);
357        env = malloc(envsize);
358        if (!env)
359            return -1;
360        fd = open(envpath, O_RDONLY);
361        r = read(fd, env, envsize);
362        close(fd);
363        if (r == -1)
364            return -1;
365    }
366    envsize = r;
367    l2 = sizeof(char *); /* Size of a pointer */
368    p2 = regs.RSP;
369
370    /* First argument is the command string */
371    l = strlen(command)+1;
372    memcpy_into_target(t, p2, command, l);
373    p2 += l;
374
375    /* Second argument is argv */
376    argvaddr = p2;
377    /* argv[0] is a pointer to the command string */
378    memcpy_into_target(t, p2, (char *)&regs.RSP, l2);
379    p2 += l2;
380    /* Then follows a NULL pointer */
381    memcpy_into_target(t, p2, (char *)&nullp, l2);
382    p2 += l2;
383
384    /* Third argument is the environment */
385    /* First, copy all the strings */
386    memcpy_into_target(t, p2, env, envsize);
387    envaddr = p2;
388    p2 += envsize;
389    /* Then write an array of pointers to the strings */
390    envptraddr = p2;
391    p = env;
392    while (p < env+envsize)
393    {
394        long diffp = p - env + envaddr;
395        memcpy_into_target(t, p2, (char *)&diffp, l2);
396        p2 += l2;
397        p += strlen(p)+1;
398    }
399    /* And have a NULL pointer at the end of the array */
400    memcpy_into_target(t, p2, (char *)&nullp, l2);
401    free(env);
402
403    ret = remote_syscall(t, MYCALL_EXECVE, regs.RSP, argvaddr, envptraddr);
404
405    return ret;
406#else
407    errno = ENOSYS;
408    return -1;
409#endif
410}
411
412/*
413 * XXX: the following functions are local
414 */
415
416#if defined USE_GRAB
417static int memcpy_from_target(struct mytrace *t,
418                              char* dest, long src, size_t n)
419{
420    static int const align = sizeof(long) - 1;
421
422    while(n)
423    {
424        long data;
425        size_t todo = sizeof(long) - (src & align);
426
427        if(n < todo)
428            todo = n;
429
430        data = ptrace(PTRACE_PEEKTEXT, t->pid, src - (src & align), 0);
431        if(errno)
432        {
433            perror("ptrace_peektext (memcpy_from_target)");
434            return -1;
435        }
436        memcpy(dest, (char *)&data + (src & align), todo);
437
438        dest += todo;
439        src += todo;
440        n -= todo;
441    }
442
443    return 0;
444}
445
446static int memcpy_into_target(struct mytrace *t,
447                              long dest, char const *src, size_t n)
448{
449    static int const align = sizeof(long) - 1;
450
451    while(n)
452    {
453        long data;
454        size_t todo = sizeof(long) - (dest & align);
455
456        if(n < todo)
457            todo = n;
458        if(todo != sizeof(long))
459        {
460            data = ptrace(PTRACE_PEEKTEXT, t->pid, dest - (dest & align), 0);
461            if(errno)
462            {
463                perror("ptrace_peektext (memcpy_into_target)");
464                return -1;
465            }
466        }
467
468        memcpy((char *)&data + (dest & align), src, todo);
469        ptrace(PTRACE_POKETEXT, t->pid, dest - (dest & align), data);
470        if(errno)
471        {
472            perror("ptrace_poketext (memcpy_into_target)");
473            return -1;
474        }
475
476        src += todo;
477        dest += todo;
478        n -= todo;
479    }
480
481    return 0;
482}
483
484static long remote_syscall(struct mytrace *t, long call,
485                           long arg1, long arg2, long arg3)
486{
487    /* Method for remote syscall:
488     *  - wait until the traced application exits from a syscall
489     *  - save registers
490     *  - rewind eip/rip to point on the syscall instruction
491     *  - single step: execute syscall instruction
492     *  - retrieve resulting registers
493     *  - restore registers */
494    struct user_regs_struct regs, oldregs;
495    long oinst;
496    int bits;
497    int offset = 2;
498
499    if(call < 0 || call >= (long)(sizeof(syscallnames)/sizeof(*syscallnames)))
500    {
501        fprintf(stderr, "unknown remote syscall %li\n", call);
502        return -1;
503    }
504
505    debug("remote syscall %s(0x%lx, 0x%lx, 0x%lx)",
506          syscallnames[call], arg1, arg2, arg3);
507
508#if defined __x86_64__
509    bits = 64;
510#else
511    bits = 32;
512#endif
513
514    for(;;)
515    {
516        if(ptrace(PTRACE_GETREGS, t->pid, NULL, &oldregs) < 0)
517        {
518            perror("PTRACE_GETREGS (syscall 1)\n");
519            return -1;
520        }
521
522        oinst = ptrace(PTRACE_PEEKTEXT, t->pid, oldregs.RIP - 2, 0) & 0xffff;
523
524#if defined __x86_64__
525        if(oinst == SYSCALL_AMD64)
526            break;
527#endif
528        if(oinst == SYSCALL_X86 || oinst == SYSCALL_X86_NEW)
529        {
530            bits = 32;
531            break;
532        }
533
534        if(ptrace(PTRACE_SYSCALL, t->pid, NULL, 0) < 0)
535        {
536            perror("ptrace_syscall (1)");
537            return -1;
538        }
539        waitpid(t->pid, NULL, 0);
540        if(ptrace(PTRACE_SYSCALL, t->pid, NULL, 0) < 0)
541        {
542            perror("ptrace_syscall (2)");
543            return -1;
544        }
545        waitpid(t->pid, NULL, 0);
546    }
547
548    print_registers(t->pid);
549
550    if(oinst == SYSCALL_X86_NEW)
551    {
552        /*  Get back to sysenter */
553        while((ptrace(PTRACE_PEEKTEXT, t->pid, oldregs.RIP - offset, 0) & 0xffff) != 0x340f)
554            offset++;
555        oldregs.RBP = oldregs.RSP;
556    }
557
558    regs = oldregs;
559    regs.RIP = regs.RIP - offset;
560#if defined __x86_64__
561    if(bits == 64)
562    {
563        regs.RAX = syscalls64[call];
564        regs.RDI = arg1;
565        regs.RSI = arg2;
566        regs.RDX = arg3;
567    }
568    else
569#endif
570    {
571        regs.RAX = syscalls32[call];
572        regs.RBX = arg1;
573        regs.RCX = arg2;
574        regs.RDX = arg3;
575    }
576
577    if(ptrace(PTRACE_SETREGS, t->pid, NULL, &regs) < 0)
578    {
579        perror("PTRACE_SETREGS (syscall 1)\n");
580        return -1;
581    }
582
583    for(;;)
584    {
585        int status;
586
587        print_registers(t->pid);
588
589        if(ptrace(PTRACE_SINGLESTEP, t->pid, NULL, NULL) < 0)
590        {
591            perror("PTRACE_SINGLESTEP (syscall)\n");
592            return -1;
593        }
594        waitpid(t->pid, &status, 0);
595
596        if(WIFEXITED(status))
597            return 0;
598
599        if(!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP)
600            continue;
601
602        /* Fuck Linux: there is no macro for this */
603        switch((status >> 16) & 0xffff)
604        {
605        case PTRACE_EVENT_FORK:
606            if(ptrace(PTRACE_GETEVENTMSG, t->pid, 0, &t->child) < 0)
607            {
608                perror("PTRACE_GETEVENTMSG (syscall)\n");
609                return -1;
610            }
611            debug("PTRACE_GETEVENTMSG %d", t->child);
612            continue;
613        case PTRACE_EVENT_EXIT:
614            debug("PTRACE_EVENT_EXIT");
615            /* The process is about to exit, don't do anything else */
616            return 0;
617        case PTRACE_EVENT_EXEC:
618            debug("PTRACE_EVENT_EXEC");
619            return 0;
620        }
621
622        break;
623    }
624
625    print_registers(t->pid);
626
627    if(ptrace(PTRACE_GETREGS, t->pid, NULL, &regs) < 0)
628    {
629        perror("PTRACE_GETREGS (syscall 2)\n");
630        return -1;
631    }
632
633    if(ptrace(PTRACE_SETREGS, t->pid, NULL, &oldregs) < 0)
634    {
635        perror("PTRACE_SETREGS (syscall 2)\n");
636        return -1;
637    }
638    print_registers(t->pid);
639
640    debug("syscall %s returned %ld", syscallnames[call], regs.RAX);
641
642    if((long)regs.RAX < 0)
643    {
644        errno = -(long)regs.RAX;
645        perror("syscall");
646        return -1;
647    }
648
649    return regs.RAX;
650}
651
652/* For debugging purposes only. Prints register and stack information. */
653#if defined DEBUG
654static void print_registers(pid_t pid)
655{
656    union { long int l; unsigned char data[sizeof(long int)]; } inst;
657    struct user_regs_struct regs;
658    int i;
659
660    if(ptrace(PTRACE_GETREGS, pid, NULL, &regs) < 0)
661    {
662        perror("PTRACE_GETREGS (syscall 2)");
663        exit(errno);
664    }
665
666    fprintf(stderr, "  / %s: "FMT"   ", STRINGIFY(RAX), regs.RAX);
667    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RBX), regs.RBX);
668    fprintf(stderr, "  | %s: "FMT"   ", STRINGIFY(RCX), regs.RCX);
669    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RDX), regs.RDX);
670    fprintf(stderr, "  | %s: "FMT"   ", STRINGIFY(RDI), regs.RDI);
671    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RSI), regs.RSI);
672    fprintf(stderr, "  | %s: "FMT"   ", STRINGIFY(RSP), regs.RSP);
673    fprintf(stderr, "%s: "FMT"\n", STRINGIFY(RIP), regs.RIP);
674
675    inst.l = ptrace(PTRACE_PEEKTEXT, pid, regs.RIP - 4, 0);
676    fprintf(stderr, "  | code: ... %02x %02x %02x %02x <---> ",
677            inst.data[0], inst.data[1], inst.data[2], inst.data[3]);
678    inst.l = ptrace(PTRACE_PEEKTEXT, pid, regs.RIP, 0);
679    fprintf(stderr, "%02x %02x %02x %02x ...\n",
680            inst.data[0], inst.data[1], inst.data[2], inst.data[3]);
681
682    fprintf(stderr, \\ stack: ... ");
683    for(i = -16; i < 24; i += sizeof(long))
684    {
685        inst.l = ptrace(PTRACE_PEEKDATA, pid, regs.RSP + i, 0);
686#if defined __x86_64__
687        fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x ",
688                inst.data[0], inst.data[1], inst.data[2], inst.data[3],
689                inst.data[4], inst.data[5], inst.data[6], inst.data[7]);
690#else
691        fprintf(stderr, "%02x %02x %02x %02x ",
692                inst.data[0], inst.data[1], inst.data[2], inst.data[3]);
693#endif
694        if(i == 0)
695            fprintf(stderr, "[%s] ", STRINGIFY(RSP));
696    }
697    fprintf(stderr, "...\n");
698}
699#endif /* DEBUG */
700
701#endif /* USE_GRAB */
Note: See TracBrowser for help on using the repository browser.