| /* ********************************************************** |
| * Copyright (c) 2012-2014 Google, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of Google, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Simple reimplementation of the dr_inject API for Linux. |
| * |
| * To match the Windows API, we fork a child and suspend it before the call to |
| * exec. |
| */ |
| |
| #include "configure.h" |
| #include "globals_shared.h" |
| #include "../config.h" /* for get_config_val_other_app */ |
| #include "../globals.h" |
| #ifdef LINUX |
| # include "include/syscall.h" /* for SYS_ptrace */ |
| #else |
| # include <sys/syscall.h> |
| #endif |
| #include "instrument.h" |
| #include "instr.h" |
| #include "instr_create.h" |
| #include "decode.h" |
| #include "disassemble.h" |
| #include "os_private.h" |
| #include "module.h" |
| #include "module_private.h" |
| #include "dr_inject.h" |
| |
| #include <assert.h> |
| #ifndef MACOS |
| /* If we don't define _EXTERNALIZE_CTYPE_INLINES_*, we get errors vs tolower |
| * in globals.h; if we do, we get errors on isspace missing. We solve that |
| * by just by supplying our own isspace. |
| */ |
| # include <ctype.h> |
| #endif |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/ptrace.h> |
| #include <sys/user.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #ifdef MACOS |
| /* The type is just "int", and the values are different, so we use the Linux |
| * type name to match the Linux constant names. |
| */ |
| enum __ptrace_request { |
| PTRACE_TRACEME = PT_TRACE_ME, |
| PTRACE_CONT = PT_CONTINUE, |
| PTRACE_KILL = PT_KILL, |
| PTRACE_ATTACH = PT_ATTACH, |
| PTRACE_DETACH = PT_DETACH, |
| PTRACE_SINGLESTEP = PT_STEP, |
| }; |
| |
| static int inline |
| isspace(int c) |
| { |
| return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || |
| c == '\v'); |
| } |
| #endif |
| |
| static bool verbose = false; |
| |
| /* Set from a signal handler. |
| */ |
| static volatile int timeout_expired; |
| |
| typedef enum _inject_method_t { |
| INJECT_EARLY, /* Works with self or child. */ |
| INJECT_LD_PRELOAD, /* Works with self or child. */ |
| INJECT_PTRACE /* Doesn't work with exec_self. */ |
| } inject_method_t; |
| |
| /* Opaque type to users, holds our state */ |
| typedef struct _dr_inject_info_t { |
| process_id_t pid; |
| const char *exe; /* full path of executable */ |
| const char *image_name; /* basename of exe */ |
| const char **argv; /* array of arguments */ |
| int pipe_fd; |
| |
| bool exec_self; /* this process will exec the app */ |
| inject_method_t method; |
| |
| bool killpg; |
| bool exited; |
| int exitcode; |
| } dr_inject_info_t; |
| |
| bool |
| inject_ptrace(dr_inject_info_t *info, const char *library_path); |
| |
| #ifdef LINUX /* XXX i#1290: implement on MacOS */ |
| static long |
| our_ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); |
| #endif |
| |
| /******************************************************************************* |
| * Core compatibility layer |
| */ |
| |
| /* Never actually called, but needed to link in config.c. */ |
| const char * |
| get_application_short_name(void) |
| { |
| ASSERT(false); |
| return ""; |
| } |
| |
| /* Shadow DR's internal_error so assertions work in standalone mode. DR tries |
| * to use safe_read to take a stack trace, but none of its signal handlers are |
| * installed, so it will segfault before it prints our error. |
| */ |
| void |
| internal_error(const char *file, int line, const char *expr) |
| { |
| fprintf(stderr, "ASSERT failed: %s:%d (%s)\n", file, line, expr); |
| fflush(stderr); |
| abort(); |
| } |
| |
| bool |
| ignore_assert(const char *assert_stmt, const char *expr) |
| { |
| return false; |
| } |
| |
| void |
| report_dynamorio_problem(dcontext_t *dcontext, uint dumpcore_flag, |
| app_pc exception_addr, app_pc report_ebp, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| fprintf(stderr, "DynamoRIO problem: "); |
| vfprintf(stderr, fmt, ap); |
| fprintf(stderr, "\n"); |
| va_end(ap); |
| fflush(stderr); |
| abort(); |
| } |
| |
| /******************************************************************************* |
| * Injection implementation |
| */ |
| |
| /* Environment modifications before executing the child process for LD_PRELOAD |
| * injection. |
| */ |
| static void |
| pre_execve_ld_preload(const char *dr_path) |
| { |
| char ld_lib_path[MAX_OPTIONS_STRING]; |
| const char *last_slash = NULL; |
| const char *mode_slash = NULL; |
| const char *lib_slash = NULL; |
| const char *cur_path = getenv("LD_LIBRARY_PATH"); |
| const char *cur = dr_path; |
| /* Find last three occurrences of '/'. */ |
| while (*cur != '\0') { |
| if (*cur == '/') { |
| lib_slash = mode_slash; |
| mode_slash = last_slash; |
| last_slash = cur; |
| } |
| cur++; |
| } |
| /* dr_path should be absolute and have at least three components. */ |
| ASSERT(lib_slash != NULL && last_slash != NULL); |
| ASSERT(strncmp(lib_slash, "/lib32", 5) == 0 || |
| strncmp(lib_slash, "/lib64", 5) == 0); |
| /* Put both DR's path and the extension path on LD_LIBRARY_PATH. We only |
| * need the extension path if -no_private_loader is used. |
| */ |
| snprintf(ld_lib_path, BUFFER_SIZE_ELEMENTS(ld_lib_path), |
| "%.*s:%.*s/ext%.*s%s%s", |
| last_slash - dr_path, dr_path, /* DR path */ |
| lib_slash - dr_path, dr_path, /* pre-ext path */ |
| last_slash - lib_slash, lib_slash, /* libNN component */ |
| cur_path == NULL ? "" : ":", |
| cur_path == NULL ? "" : cur_path); |
| NULL_TERMINATE_BUFFER(ld_lib_path); |
| #ifdef MACOS |
| setenv("DYLD_LIBRARY_PATH", ld_lib_path, true/*overwrite*/); |
| /* XXX: why does it not work w/o the full path? */ |
| snprintf(ld_lib_path, BUFFER_SIZE_ELEMENTS(ld_lib_path), |
| "%.*s/%s:%.*s/%s", |
| last_slash - dr_path, dr_path, "libdrpreload.dylib", |
| last_slash - dr_path, dr_path, "libdynamorio.dylib"); |
| setenv("DYLD_INSERT_LIBRARIES", ld_lib_path, true/*overwrite*/); |
| /* This is required to use DYLD_INSERT_LIBRARIES on apps that use |
| * two-level naming, but it can cause an app to run incorrectly. |
| * Long-term we'll want a true early injector. |
| */ |
| setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", true/*overwrite*/); |
| #else |
| setenv("LD_LIBRARY_PATH", ld_lib_path, true/*overwrite*/); |
| setenv("LD_PRELOAD", "libdynamorio.so libdrpreload.so", true/*overwrite*/); |
| #endif |
| if (verbose) { |
| printf("Setting LD_USE_LOAD_BIAS for PIEs so the loader will honor " |
| "DR's preferred base. (i#719)\n" |
| "Set LD_USE_LOAD_BIAS=0 prior to injecting if this is a " |
| "problem.\n"); |
| } |
| setenv("LD_USE_LOAD_BIAS", "1", false/*!overwrite, let user set it*/); |
| } |
| |
| /* Environment modifications before executing the child process for early |
| * injection. |
| */ |
| static void |
| pre_execve_early(const char *exe) |
| { |
| setenv(DYNAMORIO_VAR_EXE_PATH, exe, true/*overwrite*/); |
| } |
| |
| static process_id_t |
| fork_suspended_child(const char *exe, const char **argv, int fds[2]) |
| { |
| process_id_t pid = fork(); |
| if (pid == 0) { |
| /* child, suspend before exec */ |
| char pipe_cmd[MAXIMUM_PATH]; |
| ssize_t nread; |
| size_t sofar = 0; |
| const char *real_exe = NULL; |
| const char *arg; |
| close(fds[1]); /* Close writer in child, keep reader. */ |
| do { |
| nread = read(fds[0], pipe_cmd + sofar, |
| BUFFER_SIZE_BYTES(pipe_cmd) - sofar); |
| sofar += nread; |
| } while (nread > 0 && sofar < BUFFER_SIZE_BYTES(pipe_cmd)-1); |
| pipe_cmd[sofar] = '\0'; |
| close(fds[0]); /* Close reader before exec. */ |
| arg = pipe_cmd; |
| /* The first token is the command and the rest is an argument. */ |
| while (*arg != '\0' && !isspace(*arg)) |
| arg++; |
| while (*arg != '\0' && isspace(*arg)) |
| arg++; |
| if (pipe_cmd[0] == '\0') { |
| /* If nothing was written to the pipe, let it run natively. */ |
| real_exe = exe; |
| } else if (strstr(pipe_cmd, "ld_preload ") == pipe_cmd) { |
| pre_execve_ld_preload(arg); |
| real_exe = exe; |
| } else if (strcmp("ptrace", pipe_cmd) == 0) { |
| /* If using ptrace, we're already attached and will walk across the |
| * execv. |
| */ |
| real_exe = exe; |
| } else if (strstr(pipe_cmd, "exec_dr ") == pipe_cmd) { |
| pre_execve_early(exe); |
| real_exe = arg; |
| } |
| #ifdef STATIC_LIBRARY |
| setenv("DYNAMORIO_TAKEOVER_IN_INIT", "1", true/*overwrite*/); |
| #endif |
| execv(real_exe, (char **) argv); |
| /* If execv returns, there was an error. */ |
| exit(-1); |
| } |
| return pid; |
| } |
| |
| static void |
| write_pipe_cmd(int pipe_fd, const char *cmd) |
| { |
| ssize_t towrite = strlen(cmd); |
| ssize_t written = 0; |
| if (verbose) |
| fprintf(stderr, "writing cmd: %s\n", cmd); |
| while (towrite > 0) { |
| ssize_t nwrote = write(pipe_fd, cmd + written, towrite); |
| if (nwrote <= 0) |
| break; |
| towrite -= nwrote; |
| written += nwrote; |
| } |
| } |
| |
| static bool |
| inject_early(dr_inject_info_t *info, const char *library_path) |
| { |
| if (info->exec_self) { |
| /* exec DR with the original command line and set an environment |
| * variable pointing to the real exe. |
| */ |
| pre_execve_early(info->exe); |
| execv(library_path, (char **) info->argv); |
| return false; /* if execv returns, there was an error */ |
| } else { |
| /* Write the path to DR to the pipe. */ |
| char cmd[MAXIMUM_PATH]; |
| snprintf(cmd, BUFFER_SIZE_ELEMENTS(cmd), "exec_dr %s", library_path); |
| NULL_TERMINATE_BUFFER(cmd); |
| write_pipe_cmd(info->pipe_fd, cmd); |
| } |
| return true; |
| } |
| |
| static bool |
| inject_ld_preload(dr_inject_info_t *info, const char *library_path) |
| { |
| if (info->exec_self) { |
| pre_execve_ld_preload(library_path); |
| execv(info->exe, (char **) info->argv); |
| return false; /* if execv returns, there was an error */ |
| } else { |
| /* Write the path to DR to the pipe. */ |
| char cmd[MAXIMUM_PATH]; |
| snprintf(cmd, BUFFER_SIZE_ELEMENTS(cmd), "ld_preload %s", library_path); |
| NULL_TERMINATE_BUFFER(cmd); |
| write_pipe_cmd(info->pipe_fd, cmd); |
| } |
| return true; |
| } |
| |
| static dr_inject_info_t * |
| create_inject_info(const char *exe, const char **argv) |
| { |
| dr_inject_info_t *info = calloc(sizeof(*info), 1); |
| info->exe = exe; |
| info->argv = argv; |
| info->image_name = strrchr(exe, '/'); |
| info->image_name = (info->image_name == NULL ? exe : info->image_name + 1); |
| info->exited = false; |
| info->killpg = false; |
| info->exitcode = -1; |
| return info; |
| } |
| |
| static bool |
| module_get_platform_path(const char *exe_path, dr_platform_t *platform) |
| { |
| file_t fd = os_open(exe_path, OS_OPEN_READ); |
| bool res = false; |
| if (fd != INVALID_FILE) { |
| res = module_get_platform(fd, platform); |
| os_close(fd); |
| } |
| return res; |
| } |
| |
| static bool |
| exe_is_right_bitwidth(const char *exe, int *errcode) |
| { |
| dr_platform_t platform; |
| if (!module_get_platform_path(exe, &platform)) { |
| *errcode = errno; |
| if (*errcode == 0) |
| *errcode = ESRCH; |
| return false; |
| } |
| /* Check if the executable is the right bitwidth and set errcode to be |
| * special code WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE if not. |
| * The caller may decide what to do, e.g., terminate with error or |
| * continue with cross arch executable. |
| * XXX: i#1176 and DrM-i#1037, we need a long term solution to |
| * support cross-arch injection. |
| */ |
| if (platform != IF_X64_ELSE(DR_PLATFORM_64BIT, DR_PLATFORM_32BIT)) { |
| *errcode = WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE; |
| return false; |
| } |
| return true; |
| } |
| |
| /* Returns 0 on success. |
| */ |
| DR_EXPORT |
| int |
| dr_inject_process_create(const char *exe, const char **argv, void **data OUT) |
| { |
| int r; |
| int fds[2]; |
| dr_inject_info_t *info = create_inject_info(exe, argv); |
| int errcode = 0; |
| if (!exe_is_right_bitwidth(exe, &errcode) && |
| /* WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE is just a warning on Unix, |
| * so we carry on but be sure to return the code. |
| */ |
| errcode != WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE) { |
| /* return here if couldn't find app */ |
| goto error; |
| } |
| /* Create a pipe to a forked child and have it block on the pipe. */ |
| r = pipe(fds); |
| if (r != 0) |
| goto error; |
| info->pid = fork_suspended_child(exe, argv, fds); |
| close(fds[0]); /* Close reader, keep writer. */ |
| info->pipe_fd = fds[1]; |
| info->exec_self = false; |
| info->method = INJECT_LD_PRELOAD; |
| |
| if (info->pid == -1) |
| goto error; |
| *data = info; |
| return errcode; |
| |
| error: |
| free(info); |
| return errno; |
| } |
| |
| DR_EXPORT |
| int |
| dr_inject_prepare_to_exec(const char *exe, const char **argv, void **data OUT) |
| { |
| dr_inject_info_t *info = create_inject_info(exe, argv); |
| int errcode = 0; |
| *data = info; |
| if (!exe_is_right_bitwidth(exe, &errcode) && |
| errcode != WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE) { |
| free(info); |
| return errcode; |
| } |
| info->pid = getpid(); |
| info->pipe_fd = 0; /* No pipe. */ |
| info->exec_self = true; |
| info->method = INJECT_LD_PRELOAD; |
| #ifdef STATIC_LIBRARY |
| setenv("DYNAMORIO_TAKEOVER_IN_INIT", "1", true/*overwrite*/); |
| #endif |
| return errcode; |
| } |
| |
| DR_EXPORT |
| bool |
| dr_inject_prepare_to_ptrace(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| if (data == NULL) |
| return false; |
| if (info->exec_self) |
| return false; |
| info->method = INJECT_PTRACE; |
| return true; |
| } |
| |
| DR_EXPORT |
| bool |
| dr_inject_prepare_new_process_group(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| int res; |
| if (data == NULL) |
| return false; |
| if (info->exec_self) |
| return false; |
| /* Put the child in its own process group. */ |
| res = setpgid(info->pid, info->pid); |
| if (res < 0) |
| return false; |
| info->killpg = true; |
| return true; |
| } |
| |
| DR_EXPORT |
| process_id_t |
| dr_inject_get_process_id(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| return info->pid; |
| } |
| |
| DR_EXPORT |
| char * |
| dr_inject_get_image_name(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| return (char *) info->image_name; |
| } |
| |
| /* FIXME: Use the parser in options.c. The implementation here will find |
| * options in quoted strings, like the client options string. |
| */ |
| static bool |
| option_present(const char *dr_ops, const char *op) |
| { |
| size_t oplen = strlen(op); |
| const char *cur = strstr(dr_ops, op); |
| return (cur != NULL && |
| (cur[oplen] == '\0' || isspace(cur[oplen])) && |
| (cur == dr_ops || isspace(cur[-1]))); |
| } |
| |
| DR_EXPORT |
| bool |
| dr_inject_process_inject(void *data, bool force_injection, |
| const char *library_path) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| char dr_path_buf[MAXIMUM_PATH]; |
| char dr_ops[MAX_OPTIONS_STRING]; |
| dr_platform_t platform; |
| |
| if (!module_get_platform_path(info->exe, &platform)) |
| return false; /* couldn't read header */ |
| |
| if (!get_config_val_other_app(info->image_name, info->pid, platform, |
| DYNAMORIO_VAR_OPTIONS, dr_ops, |
| BUFFER_SIZE_ELEMENTS(dr_ops), NULL, |
| NULL, NULL)) { |
| return false; |
| } |
| |
| if (info->method == INJECT_LD_PRELOAD && |
| option_present(dr_ops, "-early_inject")) { |
| info->method = INJECT_EARLY; |
| } |
| |
| #ifdef STATIC_LIBRARY |
| return true; /* Do nothing. DR will takeover by itself. */ |
| #endif |
| |
| /* Read the autoinject var from the config file if the caller didn't |
| * override it. |
| */ |
| if (library_path == NULL) { |
| if (!get_config_val_other_app(info->image_name, info->pid, platform, |
| DYNAMORIO_VAR_AUTOINJECT, dr_path_buf, |
| BUFFER_SIZE_ELEMENTS(dr_path_buf), NULL, |
| NULL, NULL)) { |
| return false; |
| } |
| library_path = dr_path_buf; |
| } |
| |
| switch (info->method) { |
| case INJECT_EARLY: |
| return inject_early(info, library_path); |
| case INJECT_LD_PRELOAD: |
| return inject_ld_preload(info, library_path); |
| case INJECT_PTRACE: |
| #ifdef LINUX /* XXX i#1290: implement on MacOS */ |
| return inject_ptrace(info, library_path); |
| #else |
| return false; |
| #endif |
| } |
| |
| return false; |
| } |
| |
| /* We get the signal, we set the volatile, which is guaranteed to be signal |
| * safe. waitpid should return EINTR after we receive the signal. |
| */ |
| static void |
| alarm_handler(int sig) |
| { |
| timeout_expired = true; |
| } |
| |
| DR_EXPORT |
| bool |
| dr_inject_process_run(void *data) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| if (info->exec_self) { |
| /* If we're injecting with LD_PRELOAD or STATIC_LIBRARY, we already set |
| * up the environment. If not, then let the app run natively. |
| */ |
| execv(info->exe, (char **) info->argv); |
| return false; /* if execv returns, there was an error */ |
| } else { |
| if (info->method == INJECT_PTRACE) { |
| #ifdef LINUX /* XXX i#1290: implement on MacOS */ |
| our_ptrace(PTRACE_DETACH, info->pid, NULL, NULL); |
| #else |
| return false; |
| #endif |
| } |
| /* Close the pipe. */ |
| close(info->pipe_fd); |
| info->pipe_fd = 0; |
| } |
| return true; |
| } |
| |
| DR_EXPORT |
| bool |
| dr_inject_wait_for_child(void *data, uint64 timeout_millis) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| pid_t res; |
| |
| timeout_expired = false; |
| if (timeout_millis > 0) { |
| /* Set a timer ala runstats. */ |
| struct sigaction act; |
| struct itimerval timer; |
| |
| /* Set an alarm handler. */ |
| memset(&act, 0, sizeof(act)); |
| act.sa_handler = alarm_handler; |
| sigaction(SIGALRM, &act, NULL); |
| |
| /* No interval, one shot only. */ |
| timer.it_interval.tv_sec = 0; |
| timer.it_interval.tv_usec = 0; |
| timer.it_value.tv_sec = timeout_millis / 1000; |
| timer.it_value.tv_usec = (timeout_millis % 1000) * 1000; |
| setitimer(ITIMER_REAL, &timer, NULL); |
| } |
| |
| do { |
| res = waitpid(info->pid, &info->exitcode, 0); |
| } while (res != info->pid && res != -1 && |
| /* The signal handler sets this and makes waitpid return EINTR. */ |
| !timeout_expired); |
| info->exited = (res == info->pid); |
| return info->exited; |
| } |
| |
| DR_EXPORT |
| int |
| dr_inject_process_exit(void *data, bool terminate) |
| { |
| dr_inject_info_t *info = (dr_inject_info_t *) data; |
| int status; |
| if (info->exited) { |
| /* If it already exited when we waited on it above, then we *cannot* |
| * wait on it again or try to kill it, or we might target some new |
| * process with the same pid. |
| */ |
| status = info->exitcode; |
| } else if (info->exec_self) { |
| status = -1; /* We never injected, must have been some other error. */ |
| } else if (terminate) { |
| /* We use SIGKILL to match Windows, which doesn't provide the app a |
| * chance to clean up. |
| */ |
| if (info->killpg) { |
| /* i#501: Kill app subprocesses to prevent hangs. */ |
| killpg(info->pid, SIGKILL); |
| } else { |
| kill(info->pid, SIGKILL); |
| } |
| /* Do a blocking wait to get the real status code. This shouldn't take |
| * long since we just sent an unblockable SIGKILL. |
| */ |
| waitpid(info->pid, &status, 0); |
| } else { |
| /* Use WNOHANG to match our Windows semantics, which does not block if |
| * the child hasn't exited. The status returned is probably not useful, |
| * but the caller shouldn't look at it if they haven't waited for the |
| * app to terminate. |
| */ |
| waitpid(info->pid, &status, WNOHANG); |
| } |
| if (info->pipe_fd != 0) |
| close(info->pipe_fd); |
| free(info); |
| return status; |
| } |
| |
| #ifdef LINUX /* XXX i#1290: implement on MacOS */ |
| /******************************************************************************* |
| * ptrace injection code |
| */ |
| |
| enum { MAX_SHELL_CODE = 4096 }; |
| |
| #ifdef X86 |
| # define USER_REGS_TYPE user_regs_struct |
| # define REG_PC_FIELD IF_X64_ELSE(rip, eip) |
| # define REG_SP_FIELD IF_X64_ELSE(rsp, esp) |
| # define REG_RETVAL_FIELD IF_X64_ELSE(rax, eax) |
| #elif defined(ARM) |
| # ifndef X64 |
| /* On AArch32, glibc uses user_regs instead of user_regs_struct. |
| * struct user_regs { |
| * unsigned long int uregs[18]; |
| * }; |
| * - uregs[0..15] are for [r0..r15], |
| * - uregs[16] is for cpsr, |
| * - uregs[17] is for "orig_r0". |
| */ |
| # define USER_REGS_TYPE user_regs |
| # define REG_PC_FIELD uregs[15] /* r15 in user_regs */ |
| # define REG_SP_FIELD uregs[13] /* r13 in user_regs */ |
| /* On ARM, all reg args are also reg retvals. */ |
| # define REG_RETVAL_FIELD uregs[0] /* r0 in user_regs */ |
| # else |
| # error AArch64 is not supported |
| # endif |
| #endif |
| |
| enum { REG_PC_OFFSET = offsetof(struct USER_REGS_TYPE, REG_PC_FIELD) }; |
| |
| #define APP instrlist_append |
| |
| static bool op_exec_gdb = false; |
| |
| /* Used to pass data into the remote mapping routines. */ |
| static dr_inject_info_t *injector_info; |
| static file_t injector_dr_fd; |
| static file_t injectee_dr_fd; |
| |
| typedef struct _enum_name_pair_t { |
| const int enum_val; |
| const char * const enum_name; |
| } enum_name_pair_t; |
| |
| /* Ptrace request enum name mapping. The complete enumeration is in |
| * sys/ptrace.h. |
| */ |
| static const enum_name_pair_t pt_req_map[] = { |
| {PTRACE_TRACEME, "PTRACE_TRACEME"}, |
| {PTRACE_PEEKTEXT, "PTRACE_PEEKTEXT"}, |
| {PTRACE_PEEKDATA, "PTRACE_PEEKDATA"}, |
| {PTRACE_PEEKUSER, "PTRACE_PEEKUSER"}, |
| {PTRACE_POKETEXT, "PTRACE_POKETEXT"}, |
| {PTRACE_POKEDATA, "PTRACE_POKEDATA"}, |
| {PTRACE_POKEUSER, "PTRACE_POKEUSER"}, |
| {PTRACE_CONT, "PTRACE_CONT"}, |
| {PTRACE_KILL, "PTRACE_KILL"}, |
| {PTRACE_SINGLESTEP, "PTRACE_SINGLESTEP"}, |
| {PTRACE_GETREGS, "PTRACE_GETREGS"}, |
| {PTRACE_SETREGS, "PTRACE_SETREGS"}, |
| {PTRACE_GETFPREGS, "PTRACE_GETFPREGS"}, |
| {PTRACE_SETFPREGS, "PTRACE_SETFPREGS"}, |
| {PTRACE_ATTACH, "PTRACE_ATTACH"}, |
| {PTRACE_DETACH, "PTRACE_DETACH"}, |
| {PTRACE_GETFPXREGS, "PTRACE_GETFPXREGS"}, |
| {PTRACE_SETFPXREGS, "PTRACE_SETFPXREGS"}, |
| {PTRACE_SYSCALL, "PTRACE_SYSCALL"}, |
| {PTRACE_SETOPTIONS, "PTRACE_SETOPTIONS"}, |
| {PTRACE_GETEVENTMSG, "PTRACE_GETEVENTMSG"}, |
| {PTRACE_GETSIGINFO, "PTRACE_GETSIGINFO"}, |
| {PTRACE_SETSIGINFO, "PTRACE_SETSIGINFO"}, |
| {0} |
| }; |
| |
| /* Ptrace syscall wrapper, for logging. |
| * XXX: We could call libc's ptrace instead of using dynamorio_syscall. |
| * Initially I used the raw syscall to avoid adding a libc import, but calling |
| * libc from the injector process should always work. |
| */ |
| static long |
| our_ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data) |
| { |
| long r = dynamorio_syscall(SYS_ptrace, 4, request, pid, addr, data); |
| if (verbose && |
| /* Don't log reads and writes. */ |
| request != PTRACE_POKEDATA && |
| request != PTRACE_PEEKDATA) { |
| const enum_name_pair_t *pair = NULL; |
| int i; |
| for (i = 0; pt_req_map[i].enum_name != NULL; i++) { |
| if (pt_req_map[i].enum_val == request) { |
| pair = &pt_req_map[i]; |
| break; |
| } |
| } |
| ASSERT(pair != NULL); |
| fprintf(stderr, "\tptrace(%s, %d, %p, %p) -> %ld %s\n", |
| pair->enum_name, (int)pid, addr, data, r, strerror(-r)); |
| } |
| return r; |
| } |
| #define ptrace DO_NOT_USE_ptrace_USE_our_ptrace |
| |
| /* Copies memory from traced process into parent. |
| */ |
| static bool |
| ptrace_read_memory(pid_t pid, void *dst, void *src, size_t len) |
| { |
| uint i; |
| ptr_int_t *dst_reg = dst; |
| ptr_int_t *src_reg = src; |
| ASSERT(len % sizeof(ptr_int_t) == 0); /* FIXME handle */ |
| for (i = 0; i < len / sizeof(ptr_int_t); i++) { |
| /* We use a raw syscall instead of the libc wrapper, so the value read |
| * is stored in the data pointer instead of being returned in r. |
| */ |
| long r = our_ptrace(PTRACE_PEEKDATA, pid, &src_reg[i], &dst_reg[i]); |
| if (r < 0) |
| return false; |
| } |
| return true; |
| } |
| |
| /* Copies memory from parent into traced process. |
| */ |
| static bool |
| ptrace_write_memory(pid_t pid, void *dst, void *src, size_t len) |
| { |
| uint i; |
| ptr_int_t *dst_reg = dst; |
| ptr_int_t *src_reg = src; |
| ASSERT(len % sizeof(ptr_int_t) == 0); /* FIXME handle */ |
| for (i = 0; i < len / sizeof(ptr_int_t); i++) { |
| long r = our_ptrace(PTRACE_POKEDATA, pid, &dst_reg[i], |
| (void *) src_reg[i]); |
| if (r < 0) |
| return false; |
| } |
| return true; |
| } |
| |
| |
| /* Push a pointer to a string to the stack. We create a fake instruction with |
| * raw bytes equal to the string we want to put in the injectee. The call will |
| * pass these invalid instruction bytes, and the return address on the stack |
| * will point to the string. |
| */ |
| static void |
| gen_push_string(void *dc, instrlist_t *ilist, const char *msg) |
| { |
| #ifdef X86 |
| instr_t *after_msg = INSTR_CREATE_label(dc); |
| instr_t *msg_instr = instr_build_bits(dc, OP_UNDECODED, strlen(msg) + 1); |
| APP(ilist, INSTR_CREATE_call(dc, opnd_create_instr(after_msg))); |
| instr_set_raw_bytes(msg_instr, (byte*)msg, strlen(msg) + 1); |
| instr_set_raw_bits_valid(msg_instr, true); |
| APP(ilist, msg_instr); |
| APP(ilist, after_msg); |
| #else |
| /* FIXME i#1551: NYI on ARM */ |
| ASSERT_NOT_IMPLEMENTED(false); |
| #endif /* X86 */ |
| } |
| |
| static void |
| gen_syscall(void *dc, instrlist_t *ilist, int sysnum, uint num_opnds, |
| opnd_t *args) |
| { |
| #ifdef X86 |
| uint i; |
| ASSERT(num_opnds <= MAX_SYSCALL_ARGS); |
| APP(ilist, INSTR_CREATE_mov_imm |
| (dc, opnd_create_reg(DR_REG_XAX), OPND_CREATE_INTPTR(sysnum))); |
| for (i = 0; i < num_opnds; i++) { |
| if (opnd_is_immed_int(args[i]) || opnd_is_instr(args[i])) { |
| APP(ilist, INSTR_CREATE_mov_imm |
| (dc, opnd_create_reg(syscall_regparms[i]), args[i])); |
| } else if (opnd_is_base_disp(args[i])) { |
| APP(ilist, INSTR_CREATE_mov_ld |
| (dc, opnd_create_reg(syscall_regparms[i]), args[i])); |
| } |
| } |
| /* XXX: Reuse create_syscall_instr() in emit_utils.c. */ |
| # ifdef X64 |
| APP(ilist, INSTR_CREATE_syscall(dc)); |
| # else |
| APP(ilist, INSTR_CREATE_int(dc, OPND_CREATE_INT8((char)0x80))); |
| # endif |
| #else |
| /* FIXME i#1551: NYI on ARM */ |
| ASSERT_NOT_IMPLEMENTED(false); |
| #endif /* X86 */ |
| } |
| |
| #if 0 /* Useful for debugging gen_syscall and gen_push_string. */ |
| static void |
| gen_print(void *dc, instrlist_t *ilist, const char *msg) |
| { |
| opnd_t args[MAX_SYSCALL_ARGS]; |
| args[0] = OPND_CREATE_INTPTR(2); |
| args[1] = OPND_CREATE_MEMPTR(DR_REG_XSP, 0); /* msg is on TOS. */ |
| args[2] = OPND_CREATE_INTPTR(strlen(msg)); |
| gen_push_string(dc, ilist, msg); |
| gen_syscall(dc, ilist, SYSNUM_NO_CANCEL(SYS_write), 3, args); |
| } |
| #endif |
| |
| static void |
| unexpected_trace_event(process_id_t pid, int sig_expected, int sig_actual) |
| { |
| if (verbose) { |
| app_pc err_pc; |
| our_ptrace(PTRACE_PEEKUSER, pid, (void *)REG_PC_OFFSET, &err_pc); |
| fprintf(stderr, "Unexpected trace event. Expected %s, got signal %d " |
| "at pc: %p\n", strsignal(sig_expected), sig_actual, |
| err_pc); |
| } |
| } |
| |
| static bool |
| wait_until_signal(process_id_t pid, int sig) |
| { |
| int status; |
| int r = waitpid(pid, &status, 0); |
| if (r < 0) |
| return false; |
| if (WIFSTOPPED(status) && WSTOPSIG(status) == sig) { |
| return true; |
| } else { |
| unexpected_trace_event(pid, sig, WSTOPSIG(status)); |
| return false; |
| } |
| } |
| |
| /* Continue until the next SIGTRAP. Returns false and prints an error message |
| * if the next trap is not a breakpoint. |
| */ |
| static bool |
| continue_until_break(process_id_t pid) |
| { |
| long r = our_ptrace(PTRACE_CONT, pid, NULL, NULL); |
| if (r < 0) |
| return false; |
| return wait_until_signal(pid, SIGTRAP); |
| } |
| |
| /* Injects the code in ilist into the injectee and runs it, returning the value |
| * left in the return value register at the end of ilist execution. Frees |
| * ilist. Returns -EUNATCH if anything fails before executing the syscall. |
| */ |
| static ptr_int_t |
| injectee_run_get_retval(dr_inject_info_t *info, void *dc, instrlist_t *ilist) |
| { |
| struct USER_REGS_TYPE regs; |
| byte shellcode[MAX_SHELL_CODE]; |
| byte orig_code[MAX_SHELL_CODE]; |
| app_pc end_pc; |
| size_t code_size; |
| ptr_int_t ret; |
| app_pc pc; |
| long r; |
| ptr_int_t failure = -EUNATCH; /* Unlikely to be used by most syscalls. */ |
| |
| /* Get register state before executing the shellcode. */ |
| r = our_ptrace(PTRACE_GETREGS, info->pid, NULL, ®s); |
| if (r < 0) |
| return r; |
| |
| /* Use the current PC's page, since it's executable. Our shell code is |
| * always less than one page, so we won't overflow. |
| */ |
| pc = (app_pc)ALIGN_BACKWARD(regs.REG_PC_FIELD, PAGE_SIZE); |
| |
| /* Append an int3 so we can catch the break. */ |
| APP(ilist, XINST_CREATE_debug_instr(dc)); |
| if (verbose) { |
| fprintf(stderr, "injecting code:\n"); |
| #if defined(INTERNAL) || defined(DEBUG) || defined(CLIENT_INTERFACE) |
| /* XXX: This disas call aborts on our raw bytes instructions. Can we |
| * teach DR's disassembler to avoid those instrs? |
| */ |
| instrlist_disassemble(dc, pc, ilist, STDERR); |
| #endif |
| } |
| |
| /* Encode ilist into shellcode. */ |
| end_pc = instrlist_encode_to_copy(dc, ilist, shellcode, pc, |
| &shellcode[MAX_SHELL_CODE], true/*jmp*/); |
| code_size = end_pc - &shellcode[0]; |
| code_size = ALIGN_FORWARD(code_size, sizeof(reg_t)); |
| ASSERT(code_size <= MAX_SHELL_CODE); |
| instrlist_clear_and_destroy(dc, ilist); |
| |
| /* Copy shell code into injectee at the current PC. */ |
| if (!ptrace_read_memory(info->pid, orig_code, pc, code_size) || |
| !ptrace_write_memory(info->pid, pc, shellcode, code_size)) |
| return failure; |
| |
| /* Run it! */ |
| our_ptrace(PTRACE_POKEUSER, info->pid, (void *)REG_PC_OFFSET, pc); |
| if (!continue_until_break(info->pid)) |
| return failure; |
| |
| /* Get return value. */ |
| ret = failure; |
| r = our_ptrace(PTRACE_PEEKUSER, info->pid, |
| (void *)offsetof(struct USER_REGS_TYPE, REG_RETVAL_FIELD), &ret); |
| if (r < 0) |
| return r; |
| |
| /* Put back original code and registers. */ |
| if (!ptrace_write_memory(info->pid, pc, orig_code, code_size)) |
| return failure; |
| r = our_ptrace(PTRACE_SETREGS, info->pid, NULL, ®s); |
| if (r < 0) |
| return r; |
| |
| return ret; |
| } |
| |
| /* Call sys_open in the child. */ |
| static int |
| injectee_open(dr_inject_info_t *info, const char *path, int flags, mode_t mode) |
| { |
| void *dc = GLOBAL_DCONTEXT; |
| instrlist_t *ilist = instrlist_create(dc); |
| opnd_t args[MAX_SYSCALL_ARGS]; |
| int num_args = 0; |
| gen_push_string(dc, ilist, path); |
| args[num_args++] = OPND_CREATE_MEMPTR(REG_XSP, 0); |
| args[num_args++] = OPND_CREATE_INTPTR(flags); |
| args[num_args++] = OPND_CREATE_INTPTR(mode); |
| ASSERT(num_args <= MAX_SYSCALL_ARGS); |
| gen_syscall(dc, ilist, SYSNUM_NO_CANCEL(SYS_open), num_args, args); |
| return injectee_run_get_retval(info, dc, ilist); |
| } |
| |
| static void * |
| injectee_mmap(dr_inject_info_t *info, void *addr, size_t sz, int prot, |
| int flags, int fd, off_t offset) |
| { |
| void *dc = GLOBAL_DCONTEXT; |
| instrlist_t *ilist = instrlist_create(dc); |
| opnd_t args[MAX_SYSCALL_ARGS]; |
| int num_args = 0; |
| args[num_args++] = OPND_CREATE_INTPTR(addr); |
| args[num_args++] = OPND_CREATE_INTPTR(sz); |
| args[num_args++] = OPND_CREATE_INTPTR(prot); |
| args[num_args++] = OPND_CREATE_INTPTR(flags); |
| args[num_args++] = OPND_CREATE_INTPTR(fd); |
| args[num_args++] = OPND_CREATE_INTPTR(IF_X64_ELSE(offset, offset >> 12)); |
| ASSERT(num_args <= MAX_SYSCALL_ARGS); |
| /* XXX: Regular mmap gives EBADR on ia32, but mmap2 works. */ |
| gen_syscall(dc, ilist, IF_X64_ELSE(SYS_mmap, SYS_mmap2), num_args, args); |
| return (void *) injectee_run_get_retval(info, dc, ilist); |
| } |
| |
| /* Do an mmap syscall in the injectee, parallel to the os_map_file prototype. |
| * Passed to elf_loader_map_phdrs to map DR into the injectee. Uses the globals |
| * injector_dr_fd to injectee_dr_fd to map the former to the latter. |
| */ |
| static byte * |
| injectee_map_file(file_t f, size_t *size INOUT, uint64 offs, app_pc addr, |
| uint prot, map_flags_t map_flags) |
| { |
| int fd; |
| int flags = 0; |
| app_pc r; |
| if (TEST(MAP_FILE_COPY_ON_WRITE, map_flags)) |
| flags |= MAP_PRIVATE; |
| if (TEST(MAP_FILE_FIXED, map_flags)) |
| flags |= MAP_FIXED; |
| /* MAP_FILE_IMAGE is a nop on Linux. */ |
| if (f == injector_dr_fd) |
| fd = injectee_dr_fd; |
| else |
| fd = f; |
| if (fd == -1) { |
| flags |= MAP_ANONYMOUS; |
| } |
| r = injectee_mmap(injector_info, addr, *size, memprot_to_osprot(prot), |
| flags, fd, offs); |
| if (!mmap_syscall_succeeded(r)) { |
| int err = -(int)(ptr_int_t)r; |
| printf("injectee_mmap(%d, %p, %p, 0x%x, 0x%lx, 0x%x) -> (%d): %s\n", |
| fd, addr, (void *)*size, memprot_to_osprot(prot), (long)offs, |
| flags, err, strerror(err)); |
| return NULL; |
| } |
| return r; |
| } |
| |
| /* Do an munmap syscall in the injectee. */ |
| static bool |
| injectee_unmap(byte *addr, size_t size) |
| { |
| void *dc = GLOBAL_DCONTEXT; |
| instrlist_t *ilist = instrlist_create(dc); |
| opnd_t args[MAX_SYSCALL_ARGS]; |
| ptr_int_t r; |
| int num_args = 0; |
| args[num_args++] = OPND_CREATE_INTPTR(addr); |
| args[num_args++] = OPND_CREATE_INTPTR(size); |
| ASSERT(num_args <= MAX_SYSCALL_ARGS); |
| gen_syscall(dc, ilist, SYS_munmap, num_args, args); |
| r = injectee_run_get_retval(injector_info, dc, ilist); |
| if (r < 0) { |
| printf("injectee_munmap(%p, %p) -> %p\n", |
| addr, (void *) size, (void *)r); |
| return false; |
| } |
| return true; |
| } |
| |
| /* Do an mprotect syscall in the injectee. */ |
| static bool |
| injectee_prot(byte *addr, size_t size, uint prot/*MEMPROT_*/) |
| { |
| void *dc = GLOBAL_DCONTEXT; |
| instrlist_t *ilist = instrlist_create(dc); |
| opnd_t args[MAX_SYSCALL_ARGS]; |
| ptr_int_t r; |
| int num_args = 0; |
| args[num_args++] = OPND_CREATE_INTPTR(addr); |
| args[num_args++] = OPND_CREATE_INTPTR(size); |
| args[num_args++] = OPND_CREATE_INTPTR(memprot_to_osprot(prot)); |
| ASSERT(num_args <= MAX_SYSCALL_ARGS); |
| gen_syscall(dc, ilist, SYS_mprotect, num_args, args); |
| r = injectee_run_get_retval(injector_info, dc, ilist); |
| if (r < 0) { |
| printf("injectee_prot(%p, %p, %x) -> %d\n", |
| addr, (void *) size, prot, (int)r); |
| return false; |
| } |
| return true; |
| } |
| |
| /* Convert a user_regs_struct used by the ptrace API into DR's priv_mcontext_t |
| * struct. |
| */ |
| static void |
| user_regs_to_mc(priv_mcontext_t *mc, struct USER_REGS_TYPE *regs) |
| { |
| #ifdef X86 |
| # ifdef X64 |
| mc->rip = (app_pc)regs->rip; |
| mc->rax = regs->rax; |
| mc->rcx = regs->rcx; |
| mc->rdx = regs->rdx; |
| mc->rbx = regs->rbx; |
| mc->rsp = regs->rsp; |
| mc->rbp = regs->rbp; |
| mc->rsi = regs->rsi; |
| mc->rdi = regs->rdi; |
| mc->r8 = regs->r8 ; |
| mc->r9 = regs->r9 ; |
| mc->r10 = regs->r10; |
| mc->r11 = regs->r11; |
| mc->r12 = regs->r12; |
| mc->r13 = regs->r13; |
| mc->r14 = regs->r14; |
| mc->r15 = regs->r15; |
| # else |
| mc->eip = (app_pc)regs->eip; |
| mc->eax = regs->eax; |
| mc->ecx = regs->ecx; |
| mc->edx = regs->edx; |
| mc->ebx = regs->ebx; |
| mc->esp = regs->esp; |
| mc->ebp = regs->ebp; |
| mc->esi = regs->esi; |
| mc->edi = regs->edi; |
| # endif |
| #elif defined(ARM) |
| # ifdef X64 |
| # error AArch64 is not supported |
| # else |
| mc->r0 = regs->uregs[0]; |
| mc->r1 = regs->uregs[1]; |
| mc->r2 = regs->uregs[2]; |
| mc->r3 = regs->uregs[3]; |
| mc->r4 = regs->uregs[4]; |
| mc->r5 = regs->uregs[5]; |
| mc->r6 = regs->uregs[6]; |
| mc->r7 = regs->uregs[7]; |
| mc->r8 = regs->uregs[8]; |
| mc->r9 = regs->uregs[9]; |
| mc->r10 = regs->uregs[10]; |
| mc->r11 = regs->uregs[11]; |
| mc->r12 = regs->uregs[12]; |
| mc->r13 = regs->uregs[13]; |
| mc->r14 = regs->uregs[14]; |
| mc->r15 = regs->uregs[15]; |
| mc->cpsr = regs->uregs[16]; |
| # endif /* 64/32-bit */ |
| #endif /* X86/ARM */ |
| } |
| |
| /* Detach from the injectee and re-exec ourselves as gdb with --pid. This is |
| * useful for debugging initialization in the injectee. |
| * XXX: This is racy. I have to insert os_thread_sleep(500) in takeover_ptrace() |
| * in order for this to work. |
| */ |
| static void |
| detach_and_exec_gdb(process_id_t pid, const char *library_path) |
| { |
| char pid_str[16]; /* long enough for a decimal string pid */ |
| const char *argv[20]; /* 20 is long enough for our gdb command. */ |
| int num_args = 0; |
| char add_symfile[MAXIMUM_PATH]; |
| |
| /* Get the text start, quick and dirty. */ |
| file_t f = os_open(library_path, OS_OPEN_READ); |
| uint64 size64; |
| os_get_file_size_by_handle(f, &size64); |
| size_t size = (size_t) size64; |
| byte *base = os_map_file(f, &size, 0, NULL, MEMPROT_READ, MAP_FILE_COPY_ON_WRITE); |
| app_pc text_start = (app_pc) module_get_text_section(base, size); |
| os_unmap_file(base, size); |
| os_close(f); |
| |
| our_ptrace(PTRACE_DETACH, pid, NULL, NULL); |
| snprintf(pid_str, BUFFER_SIZE_ELEMENTS(pid_str), "%d", pid); |
| NULL_TERMINATE_BUFFER(pid_str); |
| argv[num_args++] = "/usr/bin/gdb"; |
| argv[num_args++] = "--quiet"; |
| argv[num_args++] = "--pid"; |
| argv[num_args++] = pid_str; |
| argv[num_args++] = "-ex"; |
| argv[num_args++] = "set confirm off"; |
| argv[num_args++] = "-ex"; |
| snprintf(add_symfile, BUFFER_SIZE_ELEMENTS(add_symfile), |
| "add-symbol-file %s "PFX, library_path, text_start); |
| NULL_TERMINATE_BUFFER(add_symfile); |
| argv[num_args++] = add_symfile; |
| argv[num_args++] = NULL; |
| ASSERT(num_args < BUFFER_SIZE_ELEMENTS(argv)); |
| execv("/usr/bin/gdb", (char **)argv); |
| ASSERT(false && "failed to exec gdb?"); |
| } |
| |
| bool |
| inject_ptrace(dr_inject_info_t *info, const char *library_path) |
| { |
| long r; |
| int dr_fd; |
| struct USER_REGS_TYPE regs; |
| ptrace_stack_args_t args; |
| app_pc injected_base; |
| app_pc injected_dr_start; |
| elf_loader_t loader; |
| int status; |
| int signal; |
| |
| /* Attach to the process in question. */ |
| r = our_ptrace(PTRACE_ATTACH, info->pid, NULL, NULL); |
| if (r < 0) { |
| if (verbose) { |
| fprintf(stderr, "PTRACE_ATTACH failed with error: %s\n", |
| strerror(-r)); |
| } |
| return false; |
| } |
| if (!wait_until_signal(info->pid, SIGSTOP)) |
| return false; |
| |
| if (info->pipe_fd != 0) { |
| /* For children we created, walk it across the execve call. */ |
| write_pipe_cmd(info->pipe_fd, "ptrace"); |
| close(info->pipe_fd); |
| info->pipe_fd = 0; |
| if (our_ptrace(PTRACE_SETOPTIONS, info->pid, NULL, |
| (void *)PTRACE_O_TRACEEXEC) < 0) |
| return false; |
| if (!continue_until_break(info->pid)) |
| return false; |
| } |
| |
| /* Open libdynamorio.so as readonly in the child. */ |
| dr_fd = injectee_open(info, library_path, O_RDONLY, 0); |
| if (dr_fd < 0) { |
| if (verbose) { |
| fprintf(stderr, "Unable to open libdynamorio.so in injectee (%d): " |
| "%s\n", -dr_fd, strerror(-dr_fd)); |
| } |
| return false; |
| } |
| |
| /* Call our private loader, but perform the mmaps in the child process |
| * instead of the parent. |
| */ |
| if (!elf_loader_read_headers(&loader, library_path)) |
| return false; |
| /* XXX: Have to use globals to communicate to injectee_map_file. =/ */ |
| injector_info = info; |
| injector_dr_fd = loader.fd; |
| injectee_dr_fd = dr_fd; |
| injected_base = elf_loader_map_phdrs(&loader, true/*fixed*/, |
| injectee_map_file, injectee_unmap, |
| injectee_prot, false/*!reachable*/); |
| if (injected_base == NULL) { |
| if (verbose) |
| fprintf(stderr, "Unable to mmap libdynamorio.so in injectee\n"); |
| return false; |
| } |
| /* Looking up exports through ptrace is hard, so we use the e_entry from |
| * the ELF header with different arguments. |
| * XXX: Actually look up an export. |
| */ |
| injected_dr_start = (app_pc) loader.ehdr->e_entry + loader.load_delta; |
| elf_loader_destroy(&loader); |
| |
| our_ptrace(PTRACE_GETREGS, info->pid, NULL, ®s); |
| |
| /* Create an injection context and "push" it onto the stack of the injectee. |
| * If you need to pass more info to the injected child process, this is a |
| * good place to put it. |
| */ |
| memset(&args, 0, sizeof(args)); |
| user_regs_to_mc(&args.mc, ®s); |
| args.argc = ARGC_PTRACE_SENTINEL; |
| |
| /* We need to send the home directory over. It's hard to find the |
| * environment in the injectee, and even if we could HOME might be |
| * different. |
| */ |
| strncpy(args.home_dir, getenv("HOME"), BUFFER_SIZE_ELEMENTS(args.home_dir)); |
| NULL_TERMINATE_BUFFER(args.home_dir); |
| |
| #if defined(X86) || defined(ARM) |
| regs.REG_SP_FIELD -= REDZONE_SIZE; /* Need to preserve x64 red zone. */ |
| regs.REG_SP_FIELD -= sizeof(args); /* Allocate space for args. */ |
| regs.REG_SP_FIELD = ALIGN_BACKWARD(regs.REG_SP_FIELD, REGPARM_END_ALIGN); |
| ptrace_write_memory(info->pid, (void *)regs.REG_SP_FIELD, |
| &args, sizeof(args)); |
| #else |
| # error "depends on arch stack growth direction" |
| #endif |
| |
| regs.REG_PC_FIELD = (ptr_int_t) injected_dr_start; |
| our_ptrace(PTRACE_SETREGS, info->pid, NULL, ®s); |
| |
| if (op_exec_gdb) { |
| detach_and_exec_gdb(info->pid, library_path); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* This should run something equivalent to dynamorio_app_init(), and then |
| * return. |
| * XXX: we can actually fault during dynamorio_app_init() due to safe_reads, |
| * so we have to expect SIGSEGV and let it be delivered. |
| */ |
| signal = 0; |
| do { |
| /* Continue or deliver pending signal from status. */ |
| r = our_ptrace(PTRACE_CONT, info->pid, NULL, (void *)(ptr_int_t)signal); |
| if (r < 0) |
| return false; |
| r = waitpid(info->pid, &status, 0); |
| if (r < 0 || !WIFSTOPPED(status)) |
| return false; |
| signal = WSTOPSIG(status); |
| } while (signal == SIGSEGV); |
| |
| /* When we get SIGTRAP, DR has initialized. */ |
| if (signal != SIGTRAP) { |
| unexpected_trace_event(info->pid, SIGTRAP, signal); |
| return false; |
| } |
| |
| /* We've stopped the injectee prior to dynamo_start. If we detach now, it |
| * will continue into dynamo_start(). |
| */ |
| return true; |
| } |
| |
| #endif /* LINUX */ |