diff options
Diffstat (limited to '')
-rw-r--r-- | src/peekfd.c | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/src/peekfd.c b/src/peekfd.c new file mode 100644 index 0000000..36dff04 --- /dev/null +++ b/src/peekfd.c @@ -0,0 +1,466 @@ +/* + * peekfd.c - Intercept file descriptor read and writes + * + * Copyright (C) 2007 Trent Waddington <trent.waddington@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <stdio.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/syscall.h> +#include <asm/ptrace.h> +#include <byteswap.h> +#include <endian.h> +#include <sys/user.h> +#include <stdlib.h> +#include <getopt.h> +#include <ctype.h> +#include <dirent.h> +#include <stdint.h> +#include <errno.h> +#include <string.h> + +#include "i18n.h" + +#ifdef ARM64 +#include <sys/uio.h> +#include <linux/elf.h> +#endif + +#ifdef I386 + #define REG_ORIG_ACCUM orig_eax + #define REG_ACCUM eax + #define REG_PARAM1 ebx + #define REG_PARAM2 ecx + #define REG_PARAM3 edx +#elif X86_64 + #define REG_ORIG_ACCUM orig_rax + #define REG_ACCUM rax + #define REG_PARAM1 rdi + #define REG_PARAM2 rsi + #define REG_PARAM3 rdx +#elif PPC + #if !defined(__WORDSIZE) + #include <bits/reg.h> + #endif + + #define REG_ORIG_ACCUM gpr[0] + #define REG_ACCUM gpr[3] + #define REG_PARAM1 orig_gpr3 + #define REG_PARAM2 gpr[4] + #define REG_PARAM3 gpr[5] +#ifndef PT_ORIG_R3 + #define PT_ORIG_R3 34 +#endif +#elif defined(ARM) +#ifndef __ARM_EABI__ +#error arm oabi not supported +#endif + #define REG_ORIG_ACCUM ARM_r7 + #define REG_ACCUM ARM_r0 + #define REG_PARAM1 ARM_ORIG_r0 + #define REG_PARAM2 ARM_r1 + #define REG_PARAM3 ARM_r2 + +#elif defined(ARM64) + #define REG_ORIG_ACCUM regs[8] + #define REG_ACCUM regs[0] + #define REG_PARAM1 regs[0] + #define REG_PARAM2 regs[1] + #define REG_PARAM3 regs[2] + + + +#elif defined(MIPS) +#ifndef MIPSEL +#error only little endian supported +#endif + #define REG_ORIG_ACCUM regs[3] + #define REG_ACCUM regs[2] + #define REG_PARAM1 regs[4] + #define REG_PARAM2 regs[5] + #define REG_PARAM3 regs[6] +#elif defined(M68K) + #define REG_ORIG_ACCUM orig_d0 + #define REG_ACCUM d0 + #define REG_PARAM1 d1 + #define REG_PARAM2 d2 + #define REG_PARAM3 d3 +#endif + +#define MAX_ATTACHED_PIDS 1024 +int num_attached_pids = 0; +pid_t attached_pids[MAX_ATTACHED_PIDS]; +int *fds = NULL; + +#ifdef ARM64 +struct user_pt_regs_node { + struct user_pt_regs regs; + struct user_pt_regs_node *user_pt_regs_next; +}; + +void user_pt_regs_insert(struct user_pt_regs_node** user_pt_regs_head, struct user_pt_regs *regs) +{ + struct user_pt_regs_node* new_node = + (struct user_pt_regs_node*) malloc(sizeof(struct user_pt_regs_node)); + + memcpy(&new_node->regs, regs, sizeof(struct user_pt_regs)); + new_node->user_pt_regs_next = (*user_pt_regs_head); + (*user_pt_regs_head) = new_node; +} + +struct user_pt_regs * user_pt_regs_search(struct user_pt_regs_node** user_pt_regs_head, struct user_pt_regs *regs) +{ + struct user_pt_regs_node* current = *user_pt_regs_head; + while (current != NULL) + { + if ((current->regs.REG_ORIG_ACCUM == regs->REG_ORIG_ACCUM) && (current->regs.REG_PARAM2 == regs->REG_PARAM2)) + return ¤t->regs; + current = current->user_pt_regs_next; + } + return NULL; +} + + +int user_pt_regs_delete(struct user_pt_regs_node** user_pt_regs_head, struct user_pt_regs *regs) +{ + struct user_pt_regs_node* temp = *user_pt_regs_head, *prev; + + if (temp != NULL && (&temp->regs == regs)) + { + *user_pt_regs_head = temp->user_pt_regs_next; + free(temp); + return 0; + } + + while (temp != NULL && (&temp->regs != regs)) + { + prev = temp; + temp = temp->user_pt_regs_next; + } + + if (temp == NULL) return -1; + prev->user_pt_regs_next = temp->user_pt_regs_next; + free(temp); + return 0; +} +#endif + +void detach(int signum) { + int i; + for (i = 0; i < num_attached_pids; i++) + ptrace(PTRACE_DETACH, attached_pids[i], 0, 0); + if (fds) + free(fds); + signal(SIGINT, SIG_DFL); + raise(SIGINT); +} + +void attach(pid_t pid) { + if (num_attached_pids >= MAX_ATTACHED_PIDS) + return; + attached_pids[num_attached_pids] = pid; + if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { + fprintf(stderr, _("Error attaching to pid %i\n"), pid); + return; + } + num_attached_pids++; +} + +void print_version() +{ + fprintf(stderr, _("peekfd (PSmisc) %s\n"), VERSION); + fprintf(stderr, _( + "Copyright (C) 2007 Trent Waddington\n\n")); + fprintf(stderr, _( + "PSmisc comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under\n" + "the terms of the GNU General Public License.\n" + "For more information about these matters, see the files named COPYING.\n")); +} + +void usage() { + fprintf(stderr, _( + "Usage: peekfd [-8] [-n] [-c] [-d] [-V] [-h] <pid> [<fd> ..]\n" + " -8, --eight-bit-clean output 8 bit clean streams.\n" + " -n, --no-headers don't display read/write from fd headers.\n" + " -c, --follow peek at any new child processes too.\n" + " -t, --tgid peek at all threads where tgid equals <pid>.\n" + " -d, --duplicates-removed remove duplicate read/writes from the output.\n" + " -V, --version prints version info.\n" + " -h, --help prints this help.\n" + "\n" + " Press CTRL-C to end output.\n")); +} + +int bufdiff(pid_t pid, unsigned char *lastbuf, unsigned long addr, unsigned long len) { + unsigned long i; + for (i = 0; i < len; i++) + if (lastbuf[i] != (ptrace(PTRACE_PEEKTEXT, pid, addr + i, 0) & 0xff)) + return 1; + return 0; +} + +int main(int argc, char **argv) +{ + int eight_bit_clean = 0; + int no_headers = 0; + int follow_forks = 0; + int follow_clones = 0; + int tgid = 0; + int remove_duplicates = 0; + int optc; + int target_pid = 0; + int numfds = 0; + int i; + unsigned long j; + + struct option options[] = { + {"eight-bit-clean", 0, NULL, '8'}, + {"no-headers", 0, NULL, 'n'}, + {"follow", 0, NULL, 'c'}, + {"tgid", 0, NULL, 't'}, + {"duplicates-removed", 0, NULL, 'd'}, + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'V'}, + }; + + /* Setup the i18n */ +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + if (argc < 2) { + usage(); + return 1; + } + + while ((optc = getopt_long(argc, argv, "8nctdhV", options, NULL)) != -1) { + switch(optc) { + case '8': + eight_bit_clean = 1; + break; + case 'n': + no_headers = 1; + break; + case 'c': + follow_forks = 1; + follow_clones = 1; + break; + case 't': + tgid = 1; + follow_clones = 1; + break; + case 'd': + remove_duplicates = 1; + break; + case 'V': + print_version(); + return 1; + case 'h': + case '?': + usage(); + return 1; + } + } + /* First arg off the options is the PID to see */ + if (optind >= argc) { + usage(); + return -1; + } + target_pid = atoi(argv[optind++]); + + if (optind < argc) { + numfds = argc - optind; + fds = malloc(sizeof(int) * numfds); + for (i = 0; i < numfds; i++) + fds[i] = atoi(argv[optind + i]); + } + + attach(target_pid); + + if (tgid) { + DIR *taskdir; + struct dirent *dt; + char taskpath[24]; + + snprintf(taskpath, 24, "/proc/%d/task", target_pid); + + if ((taskdir = opendir(taskpath)) != 0) { + while ((dt = readdir(taskdir)) != NULL) { + int thread = atoi(dt->d_name); + if ((thread != 0) && (thread != target_pid)) + attach(thread); + } + closedir(taskdir); + } + } + + if (num_attached_pids == 0) + return 1; + + signal(SIGINT, detach); + + for (i = 0; i < num_attached_pids; i++) + ptrace(PTRACE_SYSCALL, attached_pids[i], 0, 0); + + /*int count = 0;*/ + int lastfd = numfds > 0 ? fds[0] : 0; + int lastdir = 3; + unsigned char *lastbuf = NULL; + unsigned long last_buf_size = -1; + +#ifdef ARM64 + struct user_pt_regs_node* user_pt_regs_head = NULL; +#endif + + for(;;) { + int status; + pid_t pid = wait(&status); + if (WIFSTOPPED(status)) { +#ifdef PPC + struct pt_regs regs; + regs.gpr[0] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R0, 0); + regs.gpr[3] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R3, 0); + regs.gpr[4] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R4, 0); + regs.gpr[5] = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_R5, 0); + regs.orig_gpr3 = ptrace(PTRACE_PEEKUSER, pid, __WORDSIZE/8 * PT_ORIG_R3, 0); +#elif defined(ARM) + struct pt_regs regs; + ptrace(PTRACE_GETREGS, pid, 0, ®s); + +#elif defined(ARM64) + struct user_pt_regs regs, *old_regs; + struct iovec io; + io.iov_base = ®s; + io.iov_len = sizeof(regs); + + if (ptrace(PTRACE_GETREGSET, pid, (void*) NT_PRSTATUS, (void*) &io) == -1) { + printf("ARM64: PTRACE_GETREGSET: %s\n", strerror(errno)); + return errno; + } + +#elif defined(MIPS) + struct pt_regs regs; + long pc = ptrace(PTRACE_PEEKUSER, pid, 64, 0); + regs.regs[2] = ptrace(PTRACE_PEEKUSER,pid,2,0); + regs.regs[3] = ptrace(PTRACE_PEEKTEXT, pid, pc - 8, 0) & 0xffff; + regs.regs[4] = ptrace(PTRACE_PEEKUSER,pid,4,0); + regs.regs[5] = ptrace(PTRACE_PEEKUSER,pid,5,0); + regs.regs[6] = ptrace(PTRACE_PEEKUSER,pid,6,0); +#else + struct user_regs_struct regs; + ptrace(PTRACE_GETREGS, pid, 0, ®s); +#endif + /*unsigned int b = ptrace(PTRACE_PEEKTEXT, pid, regs.eip, 0);*/ + +#if defined(ARM64) + if (follow_forks && regs.REG_ORIG_ACCUM == SYS_clone) { +#else + if ((follow_forks && regs.REG_ORIG_ACCUM == SYS_fork) + || (follow_clones && regs.REG_ORIG_ACCUM == SYS_clone)) { +#endif + if (regs.REG_ACCUM > 0) + attach(regs.REG_ACCUM); + } + if ((regs.REG_ORIG_ACCUM == SYS_read || regs.REG_ORIG_ACCUM == SYS_write) && (regs.REG_PARAM3 == regs.REG_ACCUM)) { +#ifdef ARM64 + /* ARM64 doesn't expose orig_x0 to user space, + so retrive orig_x0 from older user pt regs */ + old_regs = user_pt_regs_search(&user_pt_regs_head, ®s); + if (old_regs != NULL) { + regs.REG_PARAM1 = old_regs->REG_PARAM1; + user_pt_regs_delete(&user_pt_regs_head, old_regs); + } +#endif + for (i = 0; i < numfds; i++) + if (fds[i] == (int)regs.REG_PARAM1) + break; + if (i != numfds || numfds == 0) { + if ((int)regs.REG_PARAM1 != lastfd || (int)regs.REG_ORIG_ACCUM != lastdir) { + lastfd = regs.REG_PARAM1; + lastdir = regs.REG_ORIG_ACCUM; + if (!no_headers) { + printf("\n%sing fd %i", regs.REG_ORIG_ACCUM == SYS_read ? "read" : "writ", lastfd); + if (tgid) + printf(" (thread %d)", pid); + printf(":\n"); + } + } + if (!remove_duplicates || lastbuf == NULL + || last_buf_size != regs.REG_PARAM3 || + bufdiff(pid, lastbuf, regs.REG_PARAM2, regs.REG_PARAM3)) { + + if (remove_duplicates) { + if (lastbuf) + free(lastbuf); + if ( NULL == (lastbuf = malloc(regs.REG_PARAM3))) { + perror("lastbuf malloc"); + exit(1); + } + last_buf_size = regs.REG_PARAM3; + } + + for (j = 0; j < regs.REG_PARAM3; j++) { +#if BYTE_ORDER == BIG_ENDIAN +#if __WORDSIZE == 64 + unsigned int a = bswap_64(ptrace(PTRACE_PEEKTEXT, pid, regs.REG_PARAM2 + j, 0)); +#else + unsigned int a = bswap_32(ptrace(PTRACE_PEEKTEXT, pid, regs.REG_PARAM2 + j, 0)); +#endif +#else + unsigned int a = ptrace(PTRACE_PEEKTEXT, pid, regs.REG_PARAM2 + j, 0); +#endif + if (remove_duplicates) + lastbuf[j] = a & 0xff; + + if (eight_bit_clean) + putchar(a & 0xff); + else { + if (isprint(a & 0xff) || (a & 0xff) == '\n') + printf("%c", a & 0xff); + else if ((a & 0xff) == 0x0d) + printf("\n"); + else if ((a & 0xff) == 0x7f) + printf("\b"); + else if (a & 0xff) + printf(" [%02x] ", a & 0xff); + } + } + } + fflush(stdout); + } + } +#ifdef ARM64 + else if (regs.REG_ORIG_ACCUM == SYS_read || regs.REG_ORIG_ACCUM == SYS_write) + { + user_pt_regs_insert(&user_pt_regs_head,®s); + } +#endif + ptrace(PTRACE_SYSCALL, pid, 0, 0); + } + } + + return 0; +} |