| /* ********************************************************** |
| * Copyright (c) 2011-2014 Google, Inc. All rights reserved. |
| * Copyright (c) 2008-2010 VMware, 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 VMware, 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 VMWARE, 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. |
| */ |
| |
| /* compile with make VMAP=1 for a vmap version (makefile defaults to VMSAFE version) */ |
| |
| #include "configure.h" |
| |
| #ifdef WINDOWS |
| # define WIN32_LEAN_AND_MEAN |
| # include <windows.h> |
| # include <io.h> |
| # include "config.h" |
| # include "share.h" |
| #endif |
| |
| #ifdef UNIX |
| # include <errno.h> |
| # include <fcntl.h> |
| # include <unistd.h> |
| # include <sys/stat.h> |
| # include <sys/mman.h> |
| # include <sys/wait.h> |
| #endif |
| |
| #include <string.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <time.h> |
| #include <assert.h> |
| #include <ctype.h> |
| #include "globals_shared.h" |
| #include "dr_config.h" /* MUST be before share.h (it sets HOT_PATCHING_INTERFACE) */ |
| #include "dr_inject.h" |
| #include "dr_frontend.h" |
| |
| typedef enum _action_t { |
| action_none, |
| action_nudge, |
| action_register, |
| action_unregister, |
| action_list, |
| } action_t; |
| |
| static bool verbose; |
| static bool quiet; |
| static bool DR_dll_not_needed = |
| #ifdef STATIC_LIBRARY |
| true |
| #else |
| false |
| #endif |
| ; |
| static bool nocheck; |
| |
| #define die() exit(1) |
| |
| #define fatal(msg, ...) do { \ |
| fprintf(stderr, "ERROR: " msg "\n", ##__VA_ARGS__); \ |
| fflush(stderr); \ |
| exit(1); \ |
| } while (0) |
| |
| /* up to caller to call die() if necessary */ |
| #define error(msg, ...) do { \ |
| fprintf(stderr, "ERROR: " msg "\n", ##__VA_ARGS__); \ |
| fflush(stderr); \ |
| } while (0) |
| |
| #define warn(msg, ...) do { \ |
| if (!quiet) { \ |
| fprintf(stderr, "WARNING: " msg "\n", ##__VA_ARGS__); \ |
| fflush(stderr); \ |
| } \ |
| } while (0) |
| |
| #define info(msg, ...) do { \ |
| if (verbose) { \ |
| fprintf(stderr, "INFO: " msg "\n", ##__VA_ARGS__); \ |
| fflush(stderr); \ |
| } \ |
| } while (0) |
| |
| #ifdef DRCONFIG |
| # define TOOLNAME "drconfig" |
| #elif defined(DRRUN) |
| # define TOOLNAME "drrun" |
| #elif defined(DRINJECT) |
| # define TOOLNAME "drinject" |
| #endif |
| |
| const char *usage_str = |
| #ifdef DRCONFIG |
| "USAGE: "TOOLNAME" [options]\n" |
| " or: "TOOLNAME" [options] [-ops \"<DR options>\"] -c <client> [client options]\n" |
| " or: "TOOLNAME" [options] [-ops \"<DR options>\"] -t <tool> [tool options]\n"; |
| #elif defined(DRRUN) || defined (DRINJECT) |
| "USAGE: "TOOLNAME" [options] <app and args to run>\n" |
| " or: "TOOLNAME" [options] -- <app and args to run>\n" |
| # if defined(DRRUN) |
| " or: "TOOLNAME" [options] [DR options] -- <app and args to run>\n" |
| " or: "TOOLNAME" [options] [DR options] -c <client> [client options]" |
| " -- <app and args to run>\n" |
| " or: "TOOLNAME" [options] [DR options] -t <tool> [tool options]" |
| " -- <app and args to run>\n" |
| # endif |
| ; |
| #endif |
| |
| const char *options_list_str = |
| "\n"TOOLNAME" options (these are distinct from DR runtime options):\n" |
| " -version Display version information\n" |
| " -verbose Display additional information\n" |
| " -quiet Do not display warnings\n" |
| " -nocheck Do not fail due to invalid DynamoRIO installation or app\n" |
| #ifdef DRCONFIG |
| " -reg <process> Register <process> to run under DR\n" |
| " -unreg <process> Unregister <process> from running under DR\n" |
| " -isreg <process> Display whether <process> is registered and if so its\n" |
| " configuration\n" |
| # ifdef WINDOWS |
| " -list_registered Display all registered processes and their configuration\n" |
| # endif /* WINDOWS */ |
| #endif /* DRCONFIG */ |
| " -root <root> DR root directory\n" |
| #if defined(DRCONFIG) || defined(DRRUN) |
| # if defined(MF_API) && defined(PROBE_API) |
| " -mode <mode> DR mode (code, probe, or security)\n" |
| # elif defined(PROBE_API) |
| " -mode <mode> DR mode (code or probe)\n" |
| # elif defined(MF_API) |
| " -mode <mode> DR mode (code or security)\n" |
| # else |
| /* No mode argument, is always code. */ |
| # endif |
| #endif |
| #ifdef DRCONFIG |
| /* FIXME i#840: Syswide NYI on Linux. */ |
| # ifdef WINDOWS |
| " -syswide_on Set up systemwide injection so that registered\n" |
| " applications will run under DR however they are\n" |
| " launched. Otherwise, drinject must be used to\n" |
| " launch a target configured application under DR.\n" |
| " This option requires administrative privileges.\n" |
| " -syswide_off Disable systemwide injection.\n" |
| " This option requires administrative privileges.\n" |
| # endif |
| " -global Use global configuration files instead of local\n" |
| " user-private configuration files. The global\n" |
| " config dir must be set up ahead of time.\n" |
| " This option may require administrative privileges.\n" |
| " If a local file already exists it will take precedence.\n" |
| " -norun Create a configuration that excludes the application\n" |
| " from running under DR control. Useful for following\n" |
| " all child processes except a handful (blacklist).\n" |
| #endif |
| " -debug Use the DR debug library\n" |
| " -32 Target 32-bit or WOW64 applications\n" |
| " -64 Target 64-bit (non-WOW64) applications\n" |
| #if defined(DRCONFIG) || defined(DRRUN) |
| "\n" |
| " -ops \"<options>\" Specify DR runtime options. When specifying\n" |
| " multiple options, enclose the entire list of\n" |
| " options in quotes, or repeat the -ops.\n" |
| " Alternatively, if the application is separated\n" |
| " by \"--\" or if -c or -t is specified, the -ops may be\n" |
| " omitted and DR options listed prior to \"--\", -c,\n" |
| " and -t, without quotes.\n" |
| "\n" |
| " -c <path> <options>*\n" |
| " Registers one client to run alongside DR. Assigns\n" |
| " the client an id of 0. All remaining arguments\n" |
| " until the -- arg before the app are interpreted as\n" |
| " client options. Must come after all drrun and DR\n" |
| " ops. Incompatible with -client. Requires using --\n" |
| " to separate the app executable. Neither the path nor\n" |
| " the options may contain semicolon characters or\n" |
| " all 3 quote characters (\", \', `).\n" |
| "\n" |
| " -client <path> <ID> \"<options>\"\n" |
| " Use -c instead, unless you need to set the client ID.\n" |
| " Registers one or more clients to run alongside DR.\n" |
| " This option is only valid when registering a\n" |
| " process. The -client option takes three arguments:\n" |
| " the full path to a client library, a unique 8-digit\n" |
| " hex ID, and an optional list of client options\n" |
| " (use \"\" to specify no options). Multiple clients\n" |
| " can be installed via multiple -client options. In\n" |
| " this case, clients specified first on the command\n" |
| " line have higher priority. Neither the path nor\n" |
| " the options may contain semicolon characters or\n" |
| " all 3 quote characters (\", \', `).\n" |
| " This option must precede any options to DynamoRIO.\n" |
| #endif |
| #ifdef DRCONFIG |
| "\n" |
| # ifdef WINDOWS |
| " Note that nudging 64-bit processes is not yet supported.\n" |
| " -nudge <process> <client ID> <argument>\n" |
| " Nudge the client with ID <client ID> in all running\n" |
| " processes with name <process>, and pass <argument>\n" |
| " to the nudge callback. <client ID> must be the\n" |
| " 8-digit hex ID of the target client. <argument>\n" |
| " should be a hex literal (0, 1, 3f etc.).\n" |
| " -nudge_pid <process_id> <client ID> <argument>\n" |
| " Nudge the client with ID <client ID> in the process with\n" |
| " id <process_id>, and pass <argument> to the nudge\n" |
| " callback. <client ID> must be the 8-digit hex ID\n" |
| " of the target client. <argument> should be a hex\n" |
| " literal (0, 1, 3f etc.).\n" |
| " -nudge_all <client ID> <argument>\n" |
| " Nudge the client with ID <client ID> in all running\n" |
| " processes and pass <argument> to the nudge callback.\n" |
| " <client ID> must be the 8-digit hex ID of the target\n" |
| " client. <argument> should be a hex literal\n" |
| " (0, 1, 3f etc.)\n" |
| " -nudge_timeout <ms> Max time (in milliseconds) to wait for a nudge to\n" |
| " finish before continuing. The default is an infinite\n" |
| " wait. A value of 0 means don't wait for nudges to\n" |
| " complete." |
| # else /* WINDOWS */ |
| /* FIXME i#840: integrate nudgeunix into drconfig on Unix */ |
| "Note: please use the nudgeunix tool to nudge processes on Unix.\n"; |
| # endif /* !WINDOWS */ |
| #else /* DRCONFIG */ |
| " -no_wait Return immediately: do not wait for application exit.\n" |
| " -s <seconds> Kill the application if it runs longer than the\n" |
| " specified number of seconds.\n" |
| " -m <minutes> Kill the application if it runs longer than the\n" |
| " specified number of minutes.\n" |
| " -h <hours> Kill the application if it runs longer than the\n" |
| " specified number of hours.\n" |
| # ifdef UNIX |
| " -killpg Create a new process group for the app. If the app\n" |
| " times out, kill the entire process group. This forces\n" |
| " the child to be a new process with a new pid, rather\n" |
| " than reusing the parent's pid.\n" |
| # endif |
| " -stats Print /usr/bin/time-style elapsed time and memory used.\n" |
| " -mem Print memory usage statistics.\n" |
| " -pidfile <file> Print the pid of the child process to the given file.\n" |
| " -no_inject Run the application natively.\n" |
| # ifdef UNIX /* FIXME i#725: Windows attach NYI */ |
| " -early Whether to use early injection.\n" |
| " -attach <pid> Attach to the process with the given pid. Pass 0\n" |
| " for pid to launch and inject into a new process.\n" |
| " -logdir <dir> Logfiles will be stored in this directory.\n" |
| # endif |
| " -use_dll <dll> Inject given dll instead of configured DR dll.\n" |
| " -force Inject regardless of configuration.\n" |
| " -exit0 Return a 0 exit code instead of the app's exit code.\n" |
| "\n" |
| " <app and args> Application command line to execute under DR.\n" |
| #endif /* !DRCONFIG */ |
| ; |
| |
| static bool |
| file_exists(const char *path) |
| { |
| bool ret = false; |
| return (drfront_access(path, DRFRONT_EXIST, &ret) == DRFRONT_SUCCESS && ret); |
| } |
| |
| #if defined(DRRUN) || defined(DRINJECT) |
| static bool |
| search_env(const char *fname, const char *env_var, char *full_path, |
| const size_t full_path_size) |
| { |
| bool ret = false; |
| if (drfront_searchenv(fname, env_var, full_path, |
| full_path_size, &ret) != DRFRONT_SUCCESS || !ret) { |
| full_path[0] = '\0'; |
| return false; |
| } |
| return true; |
| } |
| #endif |
| |
| #ifdef UNIX |
| # ifndef DRCONFIG |
| static int |
| GetLastError(void) |
| { |
| return errno; |
| } |
| # endif /* DRCONFIG */ |
| #endif /* UNIX */ |
| |
| static void |
| get_absolute_path(const char *src, char *buf, size_t buflen/*# elements*/) |
| { |
| drfront_status_t sc = drfront_get_absolute_path(src, buf, buflen); |
| if (sc != DRFRONT_SUCCESS) |
| fatal("failed (status=%d) to convert %s to an absolute path", sc, src); |
| } |
| |
| static char tool_list[MAXIMUM_PATH]; |
| |
| static void |
| print_tool_list(void) |
| { |
| #ifdef DRRUN |
| if (tool_list[0] != '\0') |
| fprintf(stderr, " available tools include: %s\n", tool_list); |
| #endif |
| } |
| |
| /* i#1509: we want to list the available tools for the -t option. |
| * Since we don't have a dir iterator we use a list of tools |
| * in a text file tools/list{32,64} which we create at |
| * install time. Thus we only expect to have it for a package build. |
| */ |
| static void |
| read_tool_list(const char *dr_root, dr_platform_t dr_platform) |
| { |
| FILE *f; |
| char list_file[MAXIMUM_PATH]; |
| size_t sofar = 0; |
| const char *arch = IF_X64_ELSE("64", "32"); |
| if (dr_platform == DR_PLATFORM_32BIT) |
| arch = "32"; |
| else if (dr_platform == DR_PLATFORM_64BIT) |
| arch = "64"; |
| _snprintf(list_file, BUFFER_SIZE_ELEMENTS(list_file), |
| "%s/tools/list%s", dr_root, arch); |
| NULL_TERMINATE_BUFFER(list_file); |
| /* XXX i#943: we need to use _tfopen() on windows */ |
| f = fopen(list_file, "r"); |
| if (f == NULL) { |
| /* no visible error: we only expect to have a list for a package build */ |
| return; |
| } |
| while (fgets(tool_list + sofar, |
| (int)(BUFFER_SIZE_ELEMENTS(tool_list) - sofar - 1/*space*/), |
| f) != NULL) { |
| NULL_TERMINATE_BUFFER(tool_list); |
| sofar += strlen(tool_list + sofar); |
| tool_list[sofar - 1] = ','; /* replace newline with comma */ |
| /* add space */ |
| if (sofar < BUFFER_SIZE_ELEMENTS(tool_list)) |
| tool_list[sofar++] = ' '; |
| } |
| fclose(f); |
| tool_list[sofar-2] = '\0'; |
| NULL_TERMINATE_BUFFER(tool_list); |
| } |
| |
| #define usage(list_ops, msg, ...) do { \ |
| if ((msg)[0] != '\0') \ |
| fprintf(stderr, "ERROR: " msg "\n\n", ##__VA_ARGS__); \ |
| fprintf(stderr, "%s", usage_str); \ |
| print_tool_list(); \ |
| if (list_ops) { \ |
| fprintf(stderr, "%s", options_list_str); \ |
| } else { \ |
| fprintf(stderr, "Run with -help to see "TOOLNAME" option list\n"); \ |
| } \ |
| die(); \ |
| } while (0) |
| |
| /* Unregister a process */ |
| bool unregister_proc(const char *process, process_id_t pid, |
| bool global, dr_platform_t dr_platform) |
| { |
| dr_config_status_t status = dr_unregister_process(process, pid, global, dr_platform); |
| if (status == DR_PROC_REG_INVALID) { |
| error("no existing registration for %s", process == NULL ? "<null>" : process); |
| return false; |
| } |
| else if (status == DR_FAILURE) { |
| error("unregistration failed for %s", process == NULL ? "<null>" : process); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| /* Check if the provided root directory actually has the files we |
| * expect. Returns whether a fatal problem. |
| */ |
| static bool check_dr_root(const char *dr_root, bool debug, |
| dr_platform_t dr_platform, bool preinject) |
| { |
| int i; |
| char buf[MAXIMUM_PATH]; |
| bool ok = true; |
| bool nowarn = false; |
| |
| const char *checked_files[] = { |
| #ifdef WINDOWS |
| "lib32\\drpreinject.dll", |
| "lib32\\release\\dynamorio.dll", |
| "lib32\\debug\\dynamorio.dll", |
| "lib64\\drpreinject.dll", |
| "lib64\\release\\dynamorio.dll", |
| "lib64\\debug\\dynamorio.dll" |
| #elif defined(MACOS) |
| "lib32/debug/libdrpreload.dylib", |
| "lib32/debug/libdynamorio.dylib", |
| "lib32/release/libdrpreload.dylib", |
| "lib32/release/libdynamorio.dylib", |
| "lib64/debug/libdrpreload.dylib", |
| "lib64/debug/libdynamorio.dylib", |
| "lib64/release/libdrpreload.dylib", |
| "lib64/release/libdynamorio.dylib" |
| #else /* LINUX */ |
| "lib32/debug/libdrpreload.so", |
| "lib32/debug/libdynamorio.so", |
| "lib32/release/libdrpreload.so", |
| "lib32/release/libdynamorio.so", |
| "lib64/debug/libdrpreload.so", |
| "lib64/debug/libdynamorio.so", |
| "lib64/release/libdrpreload.so", |
| "lib64/release/libdynamorio.so" |
| #endif |
| }; |
| |
| const char *arch = IF_X64_ELSE("lib64", "lib32"); |
| if (dr_platform == DR_PLATFORM_32BIT) |
| arch = "lib32"; |
| else if (dr_platform == DR_PLATFORM_64BIT) |
| arch = "lib64"; |
| |
| if (DR_dll_not_needed) { |
| /* assume user knows what he's doing */ |
| return true; |
| } |
| |
| /* don't warn if running from a build dir (i#458) which we attempt to detect |
| * by looking for CMakeCache.txt in the root dir |
| * (warnings can also be suppressed via -quiet) |
| */ |
| _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s/%s", dr_root, "CMakeCache.txt"); |
| if (file_exists(buf)) |
| nowarn = true; |
| |
| for (i=0; i<BUFFER_SIZE_ELEMENTS(checked_files); i++) { |
| _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s/%s", dr_root, checked_files[i]); |
| if (!file_exists(buf)) { |
| ok = false; |
| if (!nocheck && |
| ((preinject && strstr(checked_files[i], "drpreinject")) || |
| (!preinject && debug && strstr(checked_files[i], "debug") != NULL) || |
| (!preinject && !debug && strstr(checked_files[i], "release") != NULL)) && |
| strstr(checked_files[i], arch) != NULL) { |
| /* We don't want to create a .1config file that won't be freed |
| * b/c the core is never injected |
| */ |
| error("cannot find required file %s\n" |
| "Use -root to specify a proper DynamoRIO root directory.", buf); |
| return false; |
| } else if (!nowarn) { |
| warn("cannot find %s: is this an incomplete installation?", buf); |
| } |
| } |
| } |
| if (!ok && !nowarn) |
| warn("%s does not appear to be a valid DynamoRIO root", dr_root); |
| return true; |
| } |
| |
| /* Register a process to run under DR */ |
| bool register_proc(const char *process, |
| process_id_t pid, |
| bool global, |
| const char *dr_root, |
| const dr_operation_mode_t dr_mode, |
| bool debug, |
| dr_platform_t dr_platform, |
| const char *extra_ops) |
| { |
| dr_config_status_t status; |
| |
| assert(dr_root != NULL); |
| if (!file_exists(dr_root)) { |
| error("cannot access DynamoRIO root directory %s", dr_root); |
| return false; |
| } |
| #ifdef CLIENT_INTERFACE |
| if (dr_mode == DR_MODE_NONE) { |
| error("you must provide a DynamoRIO mode"); |
| return false; |
| } |
| #endif |
| |
| /* warn if the DR root directory doesn't look right, unless -norun, |
| * in which case don't bother |
| */ |
| if (dr_mode != DR_MODE_DO_NOT_RUN && |
| !check_dr_root(dr_root, debug, dr_platform, false)) |
| return false; |
| |
| if (dr_process_is_registered(process, pid, global, dr_platform, |
| NULL, NULL, NULL, NULL)) { |
| warn("overriding existing registration"); |
| if (!unregister_proc(process, pid, global, dr_platform)) |
| return false; |
| } |
| |
| status = dr_register_process(process, pid, global, dr_root, dr_mode, |
| debug, dr_platform, extra_ops); |
| |
| if (status != DR_SUCCESS) { |
| /* USERPROFILE is not set by default over cygwin ssh */ |
| #ifdef WINDOWS |
| char buf[MAXIMUM_PATH]; |
| if (drfront_get_env_var("USERPROFILE", buf, |
| BUFFER_SIZE_ELEMENTS(buf)) == DRFRONT_ERROR && |
| drfront_get_env_var("DYNAMORIO_CONFIGDIR", buf, |
| BUFFER_SIZE_ELEMENTS(buf)) == DRFRONT_ERROR) { |
| error("process %s registration failed: " |
| "neither USERPROFILE nor DYNAMORIO_CONFIGDIR env var set!", |
| process == NULL ? "<null>" : process); |
| } else |
| #endif |
| error("process %s registration failed", process == NULL ? "<null>" : process); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| /* Check if the specified client library actually exists. */ |
| void check_client_lib(const char *client_lib) |
| { |
| if (!file_exists(client_lib)) { |
| warn("%s does not exist", client_lib); |
| } |
| } |
| |
| |
| bool register_client(const char *process_name, |
| process_id_t pid, |
| bool global, |
| dr_platform_t dr_platform, |
| client_id_t client_id, |
| const char *path, |
| const char *options) |
| { |
| size_t priority; |
| dr_config_status_t status; |
| |
| if (!dr_process_is_registered(process_name, pid, global, dr_platform, |
| NULL, NULL, NULL, NULL)) { |
| error("can't register client: process %s is not registered", |
| process_name == NULL ? "<null>" : process_name); |
| return false; |
| } |
| |
| check_client_lib(path); |
| |
| /* just append to the existing client list */ |
| priority = dr_num_registered_clients(process_name, pid, global, dr_platform); |
| |
| status = dr_register_client(process_name, pid, global, dr_platform, client_id, |
| priority, path, options); |
| |
| if (status != DR_SUCCESS) { |
| if (status == DR_CONFIG_STRING_TOO_LONG) { |
| error("client %s registration failed: option string too long: \"%s\"", |
| path == NULL ? "<null>" : path, options); |
| } else if (status == DR_CONFIG_OPTIONS_INVALID) { |
| error("client %s registration failed: options cannot contain ';' or all " |
| "3 quote types: %s", |
| path == NULL ? "<null>" : path, options); |
| } else { |
| error("client %s registration failed with error code %d", |
| path == NULL ? "<null>" : path, status); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| #if defined(WINDOWS) || defined(DRRUN) || defined(DRCONFIG) |
| static const char * |
| platform_name(dr_platform_t platform) |
| { |
| return (platform == DR_PLATFORM_64BIT |
| IF_X64(|| platform == DR_PLATFORM_DEFAULT)) ? |
| "64-bit" : "32-bit/WOW64"; |
| } |
| #endif |
| |
| /* FIXME i#840: Port registered process iterator. */ |
| #ifdef WINDOWS |
| static void |
| list_process(char *name, bool global, dr_platform_t platform, |
| dr_registered_process_iterator_t *iter) |
| { |
| char name_buf[MAXIMUM_PATH] = {0}; |
| char root_dir_buf[MAXIMUM_PATH] = {0}; |
| dr_operation_mode_t dr_mode; |
| bool debug; |
| char dr_options[DR_MAX_OPTIONS_LENGTH] = {0}; |
| dr_client_iterator_t *c_iter; |
| |
| if (name == NULL) { |
| dr_registered_process_iterator_next(iter, name_buf, root_dir_buf, &dr_mode, |
| &debug, dr_options); |
| name = name_buf; |
| } else if (!dr_process_is_registered(name, 0, global, platform, root_dir_buf, |
| &dr_mode, &debug, dr_options)) { |
| printf("Process %s not registered for %s\n", name, platform_name(platform)); |
| return; |
| } |
| |
| if (dr_mode == DR_MODE_DO_NOT_RUN) { |
| printf("Process %s registered to NOT RUN on %s\n", name, platform_name(platform)); |
| } else { |
| printf("Process %s registered for %s\n", name, platform_name(platform)); |
| } |
| printf("\tRoot=\"%s\" Debug=%s\n\tOptions=\"%s\"\n", |
| root_dir_buf, debug ? "yes" : "no", dr_options); |
| |
| c_iter = dr_client_iterator_start(name, 0, global, platform); |
| while (dr_client_iterator_hasnext(c_iter)) { |
| client_id_t id; |
| size_t client_pri; |
| char client_path[MAXIMUM_PATH] = {0}; |
| char client_opts[DR_MAX_OPTIONS_LENGTH] = {0}; |
| |
| dr_client_iterator_next(c_iter, &id, &client_pri, client_path, client_opts); |
| |
| printf("\tClient=0x%08x Priority=%d\n\t\tPath=\"%s\"\n\t\tOptions=\"%s\"\n", |
| id, (uint)client_pri, client_path, client_opts); |
| } |
| dr_client_iterator_stop(c_iter); |
| } |
| #endif /* WINDOWS */ |
| |
| #ifndef DRCONFIG |
| /* i#200/PR 459481: communicate child pid via file */ |
| static void |
| write_pid_to_file(const char *pidfile, process_id_t pid) |
| { |
| FILE *f = fopen(pidfile, "w"); |
| if (f == NULL) { |
| warn("cannot open %s: %d\n", pidfile, GetLastError()); |
| } else { |
| char pidbuf[16]; |
| ssize_t written; |
| _snprintf(pidbuf, BUFFER_SIZE_ELEMENTS(pidbuf), "%d\n", pid); |
| NULL_TERMINATE_BUFFER(pidbuf); |
| written = fwrite(pidbuf, 1, strlen(pidbuf), f); |
| assert(written == strlen(pidbuf)); |
| fclose(f); |
| } |
| } |
| #endif /* DRCONFIG */ |
| |
| #if defined(DRCONFIG) || defined(DRRUN) |
| static void |
| append_client(const char *client, int id, const char *client_ops, |
| char client_paths[MAX_CLIENT_LIBS][MAXIMUM_PATH], |
| client_id_t client_ids[MAX_CLIENT_LIBS], |
| const char *client_options[MAX_CLIENT_LIBS], |
| size_t *num_clients) |
| { |
| /* We support an empty client for native -t usage */ |
| if (client[0] != '\0') { |
| get_absolute_path(client, client_paths[*num_clients], |
| BUFFER_SIZE_ELEMENTS(client_paths[*num_clients])); |
| NULL_TERMINATE_BUFFER(client_paths[*num_clients]); |
| info("client %d path: %s", (int)*num_clients, client_paths[*num_clients]); |
| } |
| client_ids[*num_clients] = id; |
| client_options[*num_clients] = client_ops; |
| (*num_clients)++; |
| } |
| #endif |
| |
| /* Appends a space-separated option string to buf. A space is appended only if |
| * the buffer is non-empty. Aborts on buffer overflow. Always null terminates |
| * the string. |
| * XXX: Use print_to_buffer. |
| */ |
| static void |
| add_extra_option(char *buf, size_t bufsz, size_t *sofar, const char *fmt, ...) |
| { |
| ssize_t len; |
| va_list ap; |
| if (*sofar > 0 && *sofar < bufsz) |
| buf[(*sofar)++] = ' '; /* Add a space. */ |
| |
| va_start(ap, fmt); |
| len = vsnprintf(buf + *sofar, bufsz - *sofar, fmt, ap); |
| va_end(ap); |
| |
| if (len < 0 || (size_t)len >= bufsz) { |
| error("option string too long, buffer overflow"); |
| die(); |
| } |
| *sofar += len; |
| /* be paranoid: though usually many calls in a row and could delay until end */ |
| buf[bufsz-1] = '\0'; |
| } |
| |
| #if defined(DRCONFIG) || defined(DRRUN) |
| /* Returns the path to the client library. Appends to extra_ops. |
| * A tool config file must contain one of these line types: |
| * CLIENT_ABS=<absolute path to client> |
| * CLIENT_REL=<path to client relative to DR root> |
| * It can contain as many DR_OP= lines as desired. Each must contain |
| * one DynamoRIO option token: |
| * DR_OP=<DR option token> |
| * It can also contain TOOL_OP= lines for tool options, though normally |
| * tool default options should just be set in the tool: |
| * TOOL_OP=<tool option token> |
| * We take one token per line rather than a string of options to avoid |
| * having to do any string parsing. |
| * DR ops go last (thus, user can't override); tool ops go first. |
| * |
| * We also support tools with their own frontend launcher via the following |
| * tool config file lines: |
| * FRONTEND_ABS=<absolute path to frontend> |
| * FRONTEND_REL=<path to frontend relative to DR root> |
| * If either is present, drrun will launch the frontend and pass it the |
| * tool options followed by the app and its options. |
| * The path to DR can be included in the frontend options via this line: |
| * TOOL_OP_DR_PATH |
| * The options to DR can be included in a single token, preceded by a prefix, |
| * via this line: |
| * TOOL_OP_DR_BUNDLE=<prefix> |
| * |
| * A notification message can be presented to the user with: |
| * USER_NOTICE=This tool is currently experimental. Please report issues to <url>. |
| */ |
| static bool |
| read_tool_file(const char *toolname, const char *dr_root, dr_platform_t dr_platform, |
| char *client, size_t client_size, |
| char *ops, size_t ops_size, size_t *ops_sofar, |
| char *tool_ops, size_t tool_ops_size, size_t *tool_ops_sofar, |
| char *native_path OUT, size_t native_path_size) |
| { |
| FILE *f; |
| char config_file[MAXIMUM_PATH]; |
| char line[MAXIMUM_PATH]; |
| bool found_client = false; |
| const char *arch = IF_X64_ELSE("64", "32"); |
| if (dr_platform == DR_PLATFORM_32BIT) |
| arch = "32"; |
| else if (dr_platform == DR_PLATFORM_64BIT) |
| arch = "64"; |
| _snprintf(config_file, BUFFER_SIZE_ELEMENTS(config_file), |
| "%s/tools/%s.drrun%s", dr_root, toolname, arch); |
| NULL_TERMINATE_BUFFER(config_file); |
| info("reading tool config file %s", config_file); |
| /* XXX i#943: we need to use _tfopen() on windows */ |
| f = fopen(config_file, "r"); |
| if (f == NULL) { |
| error("cannot find tool config file %s", config_file); |
| return false; |
| } |
| while (fgets(line, BUFFER_SIZE_ELEMENTS(line), f) != NULL) { |
| ssize_t len; |
| NULL_TERMINATE_BUFFER(line); |
| len = strlen(line) - 1; |
| while (len >= 0 && (line[len] == '\n' || line[len] == '\r')) { |
| line[len] = '\0'; |
| len--; |
| } |
| if (line[0] == '#') { |
| continue; |
| } else if (strstr(line, "CLIENT_REL=") == line) { |
| _snprintf(client, client_size, "%s/%s", dr_root, |
| line + strlen("CLIENT_REL=")); |
| client[client_size-1] = '\0'; |
| found_client = true; |
| } else if (strstr(line, "CLIENT_ABS=") == line) { |
| strncpy(client, line + strlen("CLIENT_ABS="), client_size); |
| found_client = true; |
| } else if (strstr(line, "DR_OP=") == line) { |
| add_extra_option(ops, ops_size, ops_sofar, "%s", line + strlen("DR_OP=")); |
| } else if (strstr(line, "TOOL_OP=") == line) { |
| add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, |
| "%s", line + strlen("TOOL_OP=")); |
| # ifdef DRRUN /* native only supported for drrun */ |
| } else if (strstr(line, "FRONTEND_ABS=") == line) { |
| _snprintf(native_path, native_path_size, "%s", |
| line + strlen("FRONTEND_ABS=")); |
| native_path[native_path_size-1] = '\0'; |
| found_client = true; |
| } else if (strstr(line, "FRONTEND_REL=") == line) { |
| _snprintf(native_path, native_path_size, "%s/%s", dr_root, |
| line + strlen("FRONTEND_REL=")); |
| native_path[native_path_size-1] = '\0'; |
| found_client = true; |
| } else if (strstr(line, "TOOL_OP_DR_PATH") == line) { |
| add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, |
| "%s", dr_root); |
| } else if (strstr(line, "TOOL_OP_DR_BUNDLE=") == line) { |
| add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, |
| "%s `%s`", line + strlen("TOOL_OP_DR_BUNDLE="), ops); |
| # endif |
| } else if (strstr(line, "USER_NOTICE=") == line) { |
| warn("%s", line + strlen("USER_NOTICE=")); |
| } else if (line[0] != '\0') { |
| error("tool config file is malformed: unknown line %s", line); |
| return false; |
| } |
| } |
| fclose(f); |
| return found_client; |
| } |
| #endif /* DRCONFIG || DRRUN */ |
| |
| #ifdef DRRUN |
| /* This parser modifies the string, adding nulls to split it up in place. |
| * Caller should continue iterating until *token == NULL. |
| */ |
| static char * |
| split_option_token(char *s, char **token OUT, bool split) |
| { |
| bool quoted = false; |
| char endquote = '\0'; |
| if (s == NULL) { |
| *token = NULL; |
| return NULL; |
| } |
| /* first skip leading whitespace */ |
| while (*s != '\0' && isspace(*s)) |
| s++; |
| if (*s == '\"' || *s == '\'' || *s == '`') { |
| quoted = true; |
| endquote = *s; |
| s++; |
| } |
| *token = (*s == '\0' ? NULL : s); |
| while (*s != '\0' && |
| ((!quoted && !isspace(*s)) || (quoted && *s != endquote))) |
| s++; |
| if (*s == '\0') |
| return NULL; |
| else { |
| if (quoted && !split) |
| s++; |
| if (split && *s != '\0') |
| *s++ = '\0'; |
| return s; |
| } |
| } |
| |
| /* Caller must free() the returned argv array. |
| * This routine writes to tool_ops. |
| */ |
| static const char ** |
| switch_to_native_tool(const char **app_argv, const char *native_tool, |
| char *tool_ops) |
| { |
| const char **new_argv, **arg; |
| char *s, *token; |
| uint count, i; |
| for (arg = app_argv, count = 0; *arg != NULL; arg++, count++) |
| ; /* empty */ |
| for (s = split_option_token(tool_ops, &token, false/*do not mutate*/); |
| token != NULL; |
| s = split_option_token(s, &token, false/*do not mutate*/)) { |
| count++; |
| } |
| count++; /* for native_tool path */ |
| count++; /* for "--" */ |
| count++; /* for NULL */ |
| new_argv = (const char **) malloc(count*sizeof(char*)); |
| i = 0; |
| new_argv[i++] = native_tool; |
| for (s = split_option_token(tool_ops, &token, true); |
| token != NULL; |
| s = split_option_token(s, &token, true)) { |
| new_argv[i++] = token; |
| } |
| new_argv[i++] = "--"; |
| for (arg = app_argv; *arg != NULL; arg++) |
| new_argv[i++] = *arg; |
| new_argv[i++] = NULL; |
| assert(i == count); |
| if (verbose) { |
| char buf[MAXIMUM_PATH]; |
| char *c = buf; |
| for (i = 0; i < count - 1; i++) { |
| c += _snprintf(c, BUFFER_SIZE_ELEMENTS(buf) - (c - buf), |
| " \"%s\"", new_argv[i]); |
| } |
| info("native tool cmdline: %s", buf); |
| } |
| return new_argv; |
| } |
| #endif /* DRRUN */ |
| |
| int main(int argc, char *argv[]) |
| { |
| char *dr_root = NULL; |
| char client_paths[MAX_CLIENT_LIBS][MAXIMUM_PATH]; |
| #if defined(DRCONFIG) || defined(DRRUN) |
| char *process = NULL; |
| const char *client_options[MAX_CLIENT_LIBS] = {NULL,}; |
| client_id_t client_ids[MAX_CLIENT_LIBS] = {0,}; |
| size_t num_clients = 0; |
| char single_client_ops[DR_MAX_OPTIONS_LENGTH]; |
| #endif |
| #ifndef DRINJECT |
| # if defined(MF_API) || defined(PROBE_API) |
| /* must set -mode */ |
| dr_operation_mode_t dr_mode = DR_MODE_NONE; |
| # else |
| /* only one choice so no -mode */ |
| # ifdef CLIENT_INTERFACE |
| dr_operation_mode_t dr_mode = DR_MODE_CODE_MANIPULATION; |
| # else |
| dr_operation_mode_t dr_mode = DR_MODE_NONE; |
| # endif |
| # endif |
| #endif /* !DRINJECT */ |
| char extra_ops[MAX_OPTIONS_STRING]; |
| size_t extra_ops_sofar = 0; |
| #ifdef DRCONFIG |
| action_t action = action_none; |
| #endif |
| bool use_debug = false; |
| dr_platform_t dr_platform = DR_PLATFORM_DEFAULT; |
| #ifdef WINDOWS |
| /* FIXME i#840: Implement nudges on Linux. */ |
| bool nudge_all = false; |
| process_id_t nudge_pid = 0; |
| client_id_t nudge_id = 0; |
| uint64 nudge_arg = 0; |
| bool list_registered = false; |
| uint nudge_timeout = INFINITE; |
| bool syswide_on = false; |
| bool syswide_off = false; |
| #endif /* WINDOWS */ |
| bool global = false; |
| #if defined(DRRUN) || defined(DRINJECT) |
| char *pidfile = NULL; |
| bool showstats = false; |
| bool showmem = false; |
| bool force_injection = false; |
| bool inject = true; |
| int limit = 0; /* in seconds */ |
| char *drlib_path = NULL; |
| int exitcode; |
| # ifdef WINDOWS |
| time_t start_time, end_time; |
| # else |
| bool use_ptrace = false; |
| bool kill_group = false; |
| # endif |
| char *app_name; |
| char full_app_name[MAXIMUM_PATH]; |
| const char **app_argv; |
| char custom_dll[MAXIMUM_PATH]; |
| int errcode; |
| void *inject_data; |
| bool success; |
| bool exit0 = false; |
| #endif |
| int i; |
| #ifndef DRINJECT |
| size_t j; |
| #endif |
| char buf[MAXIMUM_PATH]; |
| char default_root[MAXIMUM_PATH]; |
| char *c; |
| #if defined(DRCONFIG) || defined(DRRUN) |
| char native_tool[MAXIMUM_PATH]; |
| #endif |
| #ifdef DRRUN |
| void *tofree = NULL; |
| bool configure = true; |
| #endif |
| |
| memset(client_paths, 0, sizeof(client_paths)); |
| extra_ops[0] = '\0'; |
| #if defined(DRCONFIG) || defined(DRRUN) |
| native_tool[0] = '\0'; |
| #endif |
| |
| /* default root: we assume this tool is in <root>/bin{32,64}/dr*.exe */ |
| get_absolute_path(argv[0], buf, BUFFER_SIZE_ELEMENTS(buf)); |
| NULL_TERMINATE_BUFFER(buf); |
| c = buf + strlen(buf) - 1; |
| while (*c != '\\' && *c != '/' && c > buf) |
| c--; |
| _snprintf(c+1, BUFFER_SIZE_ELEMENTS(buf) - (c+1-buf), ".."); |
| NULL_TERMINATE_BUFFER(buf); |
| get_absolute_path(buf, default_root, BUFFER_SIZE_ELEMENTS(default_root)); |
| NULL_TERMINATE_BUFFER(default_root); |
| dr_root = default_root; |
| info("default root: %s", default_root); |
| |
| /* we re-read the tool list if the root or platform change */ |
| read_tool_list(dr_root, dr_platform); |
| |
| /* parse command line */ |
| for (i=1; i<argc; i++) { |
| |
| /* params with no arg */ |
| if (strcmp(argv[i], "-verbose") == 0 || |
| strcmp(argv[i], "-v") == 0) { |
| verbose = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-quiet") == 0) { |
| quiet = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-nocheck") == 0) { |
| nocheck = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-debug") == 0) { |
| use_debug = true; |
| continue; |
| } |
| else if (!strcmp(argv[i], "-version")) { |
| #if defined(BUILD_NUMBER) && defined(VERSION_NUMBER) |
| printf(TOOLNAME" version %s -- build %d\n", STRINGIFY(VERSION_NUMBER), BUILD_NUMBER); |
| #elif defined(BUILD_NUMBER) |
| printf(TOOLNAME" custom build %d -- %s\n", BUILD_NUMBER, __DATE__); |
| #else |
| printf(TOOLNAME" custom build -- %s, %s\n", __DATE__, __TIME__); |
| #endif |
| exit(0); |
| } |
| #ifdef DRCONFIG |
| # ifdef WINDOWS |
| /* FIXME i#840: These are NYI for Linux. */ |
| else if (!strcmp(argv[i], "-list_registered")) { |
| action = action_list; |
| list_registered = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-syswide_on") == 0) { |
| syswide_on = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-syswide_off") == 0) { |
| syswide_off = true; |
| continue; |
| } |
| # endif |
| else if (strcmp(argv[i], "-global") == 0) { |
| global = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-norun") == 0) { |
| dr_mode = DR_MODE_DO_NOT_RUN; |
| continue; |
| } |
| #endif |
| else if (strcmp(argv[i], "-32") == 0) { |
| dr_platform = DR_PLATFORM_32BIT; |
| read_tool_list(dr_root, dr_platform); |
| continue; |
| } |
| else if (strcmp(argv[i], "-64") == 0) { |
| dr_platform = DR_PLATFORM_64BIT; |
| read_tool_list(dr_root, dr_platform); |
| continue; |
| } |
| #if defined(DRRUN) || defined(DRINJECT) |
| else if (strcmp(argv[i], "-stats") == 0) { |
| showstats = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-mem") == 0) { |
| showmem = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-no_inject") == 0 || |
| /* support old drinjectx param name */ |
| strcmp(argv[i], "-noinject") == 0) { |
| DR_dll_not_needed = true; |
| inject = false; |
| continue; |
| } |
| else if (strcmp(argv[i], "-force") == 0) { |
| force_injection = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-no_wait") == 0) { |
| limit = -1; |
| continue; |
| } |
| # ifdef UNIX |
| else if (strcmp(argv[i], "-use_ptrace") == 0) { |
| /* Undocumented option for using ptrace on a fresh process. */ |
| use_ptrace = true; |
| continue; |
| } |
| else if (strcmp(argv[i], "-attach") == 0) { |
| const char *pid_str = argv[++i]; |
| process_id_t pid = strtoul(pid_str, NULL, 10); |
| if (pid == ULONG_MAX) |
| usage(false, "-attach expects an integer pid"); |
| if (pid != 0) |
| usage(false, "attaching to running processes is not yet implemented"); |
| use_ptrace = true; |
| /* FIXME: use pid below to attach. */ |
| continue; |
| } |
| else if (strcmp(argv[i], "-early") == 0) { |
| /* Appending -early_inject to extra_ops communicates our intentions |
| * to drinjectlib. |
| */ |
| add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), |
| &extra_ops_sofar, "-early_inject"); |
| continue; |
| } |
| # endif /* UNIX */ |
| else if (strcmp(argv[i], "-exit0") == 0) { |
| exit0 = true; |
| continue; |
| } |
| #endif |
| else if (strcmp(argv[i], "-help") == 0 || |
| strcmp(argv[i], "--help") == 0 || |
| strcmp(argv[i], "-h") == 0) { |
| usage(true, ""/* no error msg */); |
| continue; |
| } |
| /* all other flags have an argument -- make sure it exists */ |
| else if (argv[i][0] == '-' && i == argc - 1) { |
| usage(false, "invalid arguments"); |
| } |
| |
| /* params with an arg */ |
| if (strcmp(argv[i], "-root") == 0 || |
| /* support -dr_home alias used by script */ |
| strcmp(argv[i], "-dr_home") == 0) { |
| dr_root = argv[++i]; |
| read_tool_list(dr_root, dr_platform); |
| } |
| else if (strcmp(argv[i], "-logdir") == 0) { |
| /* Accept this for compatibility with the old drrun shell script. */ |
| const char *dir = argv[++i]; |
| if (!file_exists(dir)) |
| usage(false, "-logdir %s does not exist", dir); |
| add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), |
| &extra_ops_sofar, "-logdir `%s`", dir); |
| continue; |
| } |
| #ifdef DRCONFIG |
| else if (strcmp(argv[i], "-reg") == 0) { |
| if (action != action_none) { |
| usage(false, "more than one action specified"); |
| } |
| action = action_register; |
| process = argv[++i]; |
| } |
| else if (strcmp(argv[i], "-unreg") == 0) { |
| if (action != action_none) { |
| usage(false, "more than one action specified"); |
| } |
| action = action_unregister; |
| process = argv[++i]; |
| } |
| else if (strcmp(argv[i], "-isreg") == 0) { |
| if (action != action_none) { |
| usage(false, "more than one action specified"); |
| } |
| action = action_list; |
| process = argv[++i]; |
| } |
| # ifdef WINDOWS |
| /* FIXME i#840: Nudge is NYI for Linux. */ |
| else if (strcmp(argv[i], "-nudge_timeout") == 0) { |
| nudge_timeout = strtoul(argv[++i], NULL, 10); |
| } |
| else if (strcmp(argv[i], "-nudge") == 0 || |
| strcmp(argv[i], "-nudge_pid") == 0 || |
| strcmp(argv[i], "-nudge_all") == 0){ |
| if (action != action_none) { |
| usage(false, "more than one action specified"); |
| } |
| if (i + 2 >= argc || |
| (strcmp(argv[i], "-nudge_all") != 0 && i + 3 >= argc)) { |
| usage(false, "too few arguments to -nudge"); |
| } |
| action = action_nudge; |
| if (strcmp(argv[i], "-nudge") == 0) |
| process = argv[++i]; |
| else if (strcmp(argv[i], "-nudge_pid") == 0) |
| nudge_pid = strtoul(argv[++i], NULL, 10); |
| else |
| nudge_all = true; |
| nudge_id = strtoul(argv[++i], NULL, 16); |
| nudge_arg = _strtoui64(argv[++i], NULL, 16); |
| } |
| # endif |
| #endif |
| #if defined(DRCONFIG) || defined(DRRUN) |
| # if defined(MF_API) || defined(PROBE_API) |
| else if (strcmp(argv[i], "-mode") == 0) { |
| char *mode_str = argv[++i]; |
| if (dr_mode == DR_MODE_DO_NOT_RUN) |
| usage(false, "cannot combine -norun with -mode"); |
| if (strcmp(mode_str, "code") == 0) { |
| dr_mode = DR_MODE_CODE_MANIPULATION; |
| } |
| # ifdef MF_API |
| else if (strcmp(mode_str, "security") == 0) { |
| dr_mode = DR_MODE_MEMORY_FIREWALL; |
| } |
| # endif |
| # ifdef PROBE_API |
| else if (strcmp(mode_str, "probe") == 0) { |
| dr_mode = DR_MODE_PROBE; |
| } |
| # endif |
| else { |
| usage(false, "unknown mode: %s", mode_str); |
| } |
| } |
| # endif |
| else if (strcmp(argv[i], "-client") == 0) { |
| if (num_clients == MAX_CLIENT_LIBS) { |
| error("Maximum number of clients is %d", MAX_CLIENT_LIBS); |
| die(); |
| } |
| else { |
| const char *client; |
| int id; |
| const char *ops; |
| if (i + 3 >= argc) { |
| usage(false, "too few arguments to -client"); |
| } |
| |
| /* Support relative client paths: very useful! */ |
| client = argv[++i]; |
| id = strtoul(argv[++i], NULL, 16); |
| ops = argv[++i]; |
| append_client(client, id, ops, client_paths, client_ids, |
| client_options, &num_clients); |
| } |
| } |
| else if (strcmp(argv[i], "-ops") == 0) { |
| /* support repeating the option (i#477) */ |
| add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), |
| &extra_ops_sofar, "%s", argv[++i]); |
| } |
| #endif |
| #if defined(DRRUN) || defined(DRINJECT) |
| else if (strcmp(argv[i], "-pidfile") == 0) { |
| pidfile = argv[++i]; |
| } |
| else if (strcmp(argv[i], "-use_dll") == 0) { |
| DR_dll_not_needed = true; |
| /* Support relative path: very useful! */ |
| get_absolute_path(argv[++i], custom_dll, BUFFER_SIZE_ELEMENTS(custom_dll)); |
| NULL_TERMINATE_BUFFER(custom_dll); |
| drlib_path = custom_dll; |
| } |
| else if (strcmp(argv[i], "-s") == 0) { |
| limit = atoi(argv[++i]); |
| if (limit <= 0) |
| usage(false, "invalid time"); |
| } |
| else if (strcmp(argv[i], "-m") == 0) { |
| limit = atoi(argv[++i])*60; |
| if (limit <= 0) |
| usage(false, "invalid time"); |
| } |
| else if (strcmp(argv[i], "-h") == 0) { |
| limit = atoi(argv[++i])*3600; |
| if (limit <= 0) |
| usage(false, "invalid time"); |
| } |
| # ifdef UNIX |
| else if (strcmp(argv[i], "-killpg") == 0) { |
| kill_group = true; |
| } |
| # endif |
| #endif |
| #if defined(DRCONFIG) || defined(DRRUN) |
| /* if there are still options, assume user is using -- to separate and pass |
| * through options to DR. we do not handle mixing DR options with tool |
| * options: DR must come last. we would need to generate code here from |
| * optionsx.h to do otherwise, or to sanity check the DR options here. |
| */ |
| else if (argv[i][0] == '-') { |
| while (i<argc) { |
| if (strcmp(argv[i], "-c") == 0 || |
| strcmp(argv[i], "-t") == 0 || |
| strcmp(argv[i], "--") == 0) { |
| break; |
| } |
| add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), |
| &extra_ops_sofar, "%s", argv[i]); |
| i++; |
| } |
| if (i < argc && |
| (strcmp(argv[i], "-t") == 0 || |
| strcmp(argv[i], "-c") == 0)) { |
| const char *client; |
| char client_buf[MAXIMUM_PATH]; |
| size_t client_sofar = 0; |
| if (i + 1 >= argc) |
| usage(false, "too few arguments to %s", argv[i]); |
| if (num_clients != 0) |
| usage(false, "Cannot use -client with %s.", argv[i]); |
| client = argv[++i]; |
| single_client_ops[0] = '\0'; |
| |
| if (strcmp(argv[i-1], "-t") == 0) { |
| /* Client-requested DR default options come last, so they |
| * cannot be overridden by DR options passed here. |
| * The user must use -c or -client to do that. |
| */ |
| if (!read_tool_file(client, dr_root, dr_platform, |
| client_buf, BUFFER_SIZE_ELEMENTS(client_buf), |
| extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), |
| &extra_ops_sofar, |
| single_client_ops, |
| BUFFER_SIZE_ELEMENTS(single_client_ops), |
| &client_sofar, |
| native_tool, BUFFER_SIZE_ELEMENTS(native_tool))) |
| usage(false, "unknown %s tool \"%s\" requested", |
| platform_name(dr_platform), client); |
| client = client_buf; |
| } |
| |
| /* Treat everything up to -- or end of argv as client args. */ |
| i++; |
| while (i < argc && strcmp(argv[i], "--") != 0) { |
| # ifdef DRCONFIG |
| if (action == action_none && strcmp(argv[i], "-reg") == 0) { |
| warn("-reg is taken as a client option!"); |
| } |
| # endif /* DRCONFIG */ |
| add_extra_option(single_client_ops, |
| BUFFER_SIZE_ELEMENTS(single_client_ops), |
| &client_sofar, "%s", argv[i]); |
| i++; |
| } |
| append_client(client, 0, single_client_ops, client_paths, |
| client_ids, client_options, &num_clients); |
| } |
| if (i < argc && strcmp(argv[i], "--") == 0) { |
| i++; |
| goto done_with_options; |
| } |
| } |
| #else /* DRINJECT */ |
| else if (strcmp(argv[i], "--") == 0) { |
| i++; |
| goto done_with_options; |
| } |
| #endif |
| else { |
| #ifdef DRCONFIG |
| usage(false, "unknown option: %s", argv[i]); |
| #else |
| /* start of app and its args */ |
| break; |
| #endif |
| } |
| } |
| |
| #if defined(DRCONFIG) || defined(DRRUN) || defined(DRINJECT) |
| done_with_options: |
| #endif |
| |
| #if defined(DRRUN) || defined(DRINJECT) |
| if (i >= argc) |
| usage(false, "%s", "no app specified"); |
| app_name = argv[i++]; |
| search_env(app_name, "PATH", full_app_name, BUFFER_SIZE_ELEMENTS(full_app_name)); |
| NULL_TERMINATE_BUFFER(full_app_name); |
| if (full_app_name[0] == '\0') { |
| /* may need to append .exe, FIXME : other executable types */ |
| char tmp_buf[MAXIMUM_PATH]; |
| _snprintf(tmp_buf, BUFFER_SIZE_ELEMENTS(tmp_buf), |
| "%s%s", app_name, ".exe"); |
| NULL_TERMINATE_BUFFER(tmp_buf); |
| search_env(tmp_buf, "PATH", full_app_name, BUFFER_SIZE_ELEMENTS(full_app_name)); |
| } |
| if (full_app_name[0] == '\0') { |
| /* last try */ |
| get_absolute_path(app_name, full_app_name, BUFFER_SIZE_ELEMENTS(full_app_name)); |
| NULL_TERMINATE_BUFFER(full_app_name); |
| } |
| if (full_app_name[0] != '\0') |
| app_name = full_app_name; |
| info("targeting application: \"%s\"", app_name); |
| |
| /* note that we want target app name as part of cmd line |
| * (hence &argv[i - 1]) |
| * (FYI: if we were using WinMain, the pzsCmdLine passed in |
| * does not have our own app name in it) |
| */ |
| app_argv = (const char **) &argv[i - 1]; |
| if (verbose) { |
| c = buf; |
| for (i = 0; app_argv[i] != NULL; i++) { |
| c += _snprintf(c, BUFFER_SIZE_ELEMENTS(buf) - (c - buf), |
| " \"%s\"", app_argv[i]); |
| } |
| info("app cmdline: %s", buf); |
| } |
| # ifdef DRRUN |
| if (native_tool[0] != '\0') { |
| app_name = native_tool; |
| inject = false; |
| configure = false; |
| app_argv = switch_to_native_tool(app_argv, native_tool, |
| /* this will be changed, but we don't |
| * need it again |
| */ |
| (char *)client_options[0]); |
| tofree = (void *) app_argv; |
| } |
| # endif |
| #else |
| if (i < argc) |
| usage(false, "%s", "invalid extra arguments specified"); |
| #endif |
| |
| #ifdef WINDOWS |
| /* FIXME i#900: This doesn't work on Linux, and doesn't do the right thing |
| * on Windows. |
| */ |
| /* PR 244206: set the registry view before any registry access */ |
| set_dr_platform(dr_platform); |
| #endif |
| |
| #ifdef DRCONFIG |
| if (action == action_register) { |
| if (!register_proc(process, 0, global, dr_root, dr_mode, |
| use_debug, dr_platform, extra_ops)) |
| die(); |
| for (j=0; j<num_clients; j++) { |
| if (!register_client(process, 0, global, dr_platform, client_ids[j], |
| client_paths[j], client_options[j])) |
| die(); |
| } |
| } |
| else if (action == action_unregister) { |
| if (!unregister_proc(process, 0, global, dr_platform)) |
| die(); |
| } |
| # ifndef WINDOWS |
| else { |
| usage(false, "no action specified"); |
| } |
| # else /* WINDOWS */ |
| /* FIXME i#840: Nudge NYI on Linux. */ |
| else if (action == action_nudge) { |
| int count = 1; |
| dr_config_status_t res = DR_SUCCESS; |
| if (nudge_all) |
| res = dr_nudge_all(nudge_id, nudge_arg, nudge_timeout, &count); |
| else if (nudge_pid != 0) { |
| res = dr_nudge_pid(nudge_pid, nudge_id, nudge_arg, nudge_timeout); |
| if (res == DR_NUDGE_PID_NOT_INJECTED) |
| printf("process %d is not running under DR\n", nudge_pid); |
| if (res != DR_SUCCESS && res != DR_NUDGE_TIMEOUT) { |
| count = 0; |
| res = ERROR_SUCCESS; |
| } |
| } else |
| res = dr_nudge_process(process, nudge_id, nudge_arg, nudge_timeout, &count); |
| |
| printf("%d processes nudged\n", count); |
| if (res == DR_NUDGE_TIMEOUT) |
| printf("timed out waiting for nudge to complete"); |
| else if (res != DR_SUCCESS) |
| printf("nudge operation failed, verify adequate permissions for this operation."); |
| } |
| # ifdef WINDOWS |
| /* FIXME i#840: Process iterator NYI for Linux. */ |
| else if (action == action_list) { |
| if (!list_registered) |
| list_process(process, global, dr_platform, NULL); |
| else /* list all */ { |
| dr_registered_process_iterator_t *iter = |
| dr_registered_process_iterator_start(dr_platform, global); |
| printf("Registered %s processes for %s\n", |
| global ? "global" : "local", platform_name(dr_platform)); |
| while (dr_registered_process_iterator_hasnext(iter)) |
| list_process(NULL, global, dr_platform, iter); |
| dr_registered_process_iterator_stop(iter); |
| } |
| } |
| # endif |
| else if (!syswide_on && !syswide_off) { |
| usage(false, "no action specified"); |
| } |
| if (syswide_on) { |
| DWORD platform; |
| if (get_platform(&platform) != ERROR_SUCCESS) |
| platform = PLATFORM_UNKNOWN; |
| if (platform >= PLATFORM_WIN_8) { |
| /* FIXME i#1522: enable AppInit for non-WOW64 on win8+ |
| * FIXME i#1035: enable AppInit for WOW64 win8+ |
| */ |
| error("syswide_on is not yet supported on Windows 8+"); |
| die(); |
| } |
| if (!check_dr_root(dr_root, false, dr_platform, true)) |
| die(); |
| /* If this is the first setting of AppInit on NT, warn about reboot */ |
| if (!dr_syswide_is_on(dr_platform, dr_root)) { |
| if (platform == PLATFORM_WIN_NT_4) { |
| warn("on Windows NT, applications will not be taken over until reboot"); |
| } |
| else if (platform >= PLATFORM_WIN_7) { |
| /* i#323 will fix this but good to warn the user */ |
| warn("on Windows 7+, syswide_on relaxes system security by removing certain code signing requirements"); |
| } |
| } |
| if (dr_register_syswide(dr_platform, dr_root) != ERROR_SUCCESS) { |
| /* PR 233108: try to give more info on whether a privilege failure */ |
| warn("syswide set failed: re-run as administrator"); |
| } |
| } |
| if (syswide_off) { |
| if (dr_unregister_syswide(dr_platform, dr_root) != ERROR_SUCCESS) { |
| /* PR 233108: try to give more info on whether a privilege failure */ |
| warn("syswide set failed: re-run as administrator"); |
| } |
| } |
| # endif /* WINDOWS */ |
| return 0; |
| #else /* DRCONFIG */ |
| if (!global) { |
| /* i#939: attempt to work w/o any HOME/USERPROFILE by using a temp dir */ |
| dr_get_config_dir(global, true/*use temp*/, buf, BUFFER_SIZE_ELEMENTS(buf)); |
| } |
| # ifdef UNIX |
| /* On Linux, we use exec by default to create the app process. This matches |
| * our drrun shell script and makes scripting easier for the user. |
| */ |
| if (limit == 0 && !use_ptrace && !kill_group) { |
| info("will exec %s", app_name); |
| errcode = dr_inject_prepare_to_exec(app_name, app_argv, &inject_data); |
| } else |
| # endif /* UNIX */ |
| { |
| errcode = dr_inject_process_create(app_name, app_argv, &inject_data); |
| info("created child with pid %d for %s", |
| dr_inject_get_process_id(inject_data), app_name); |
| } |
| # ifdef UNIX |
| if (limit != 0 && kill_group) { |
| /* Move the child to its own process group. */ |
| process_id_t child_pid = dr_inject_get_process_id(inject_data); |
| int res = setpgid(child_pid, child_pid); |
| if (res < 0) { |
| perror("ERROR in setpgid"); |
| goto error; |
| } |
| } |
| # endif |
| if (errcode == ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE |
| /* Check whether -32/64 is specified, but only for Linux as we do |
| * not support cross-arch on Windows yet (i#803). |
| */ |
| IF_UNIX(&& dr_platform != IF_X64_ELSE(DR_PLATFORM_32BIT, |
| DR_PLATFORM_64BIT))) { |
| if (nocheck) { |
| /* Allow override for cases like i#1224 */ |
| warn("Target process %s appears to be for the wrong architecture.", app_name); |
| warn("Attempting to run anyway, but it may run natively if injection fails."); |
| errcode = 0; |
| } else { |
| /* For Windows, better error message than the FormatMessage */ |
| error("Target process %s is for the wrong architecture", app_name); |
| goto error; /* the process was still created */ |
| } |
| } |
| if (errcode != 0 |
| IF_UNIX(&& errcode != ERROR_IMAGE_MACHINE_TYPE_MISMATCH_EXE)) { |
| IF_WINDOWS(int sofar =) |
| _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), |
| "Failed to create process for \"%s\": ", app_name); |
| # ifdef WINDOWS |
| if (sofar > 0) { |
| FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
| NULL, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| (LPTSTR) buf + sofar, |
| BUFFER_SIZE_ELEMENTS(buf) - sofar*sizeof(char), NULL); |
| } |
| # endif /* WINDOWS */ |
| NULL_TERMINATE_BUFFER(buf); |
| error("%s", buf); |
| goto error; |
| } |
| |
| /* i#200/PR 459481: communicate child pid via file */ |
| if (pidfile != NULL) |
| write_pid_to_file(pidfile, dr_inject_get_process_id(inject_data)); |
| |
| # ifdef DRRUN |
| /* even if !inject we create a config file, for use running standalone API |
| * apps. if user doesn't want a config file, should use "drinject -noinject". |
| */ |
| if (configure) { |
| process = dr_inject_get_image_name(inject_data); |
| if (!register_proc(process, dr_inject_get_process_id(inject_data), global, |
| dr_root, dr_mode, use_debug, dr_platform, extra_ops)) |
| goto error; |
| for (j=0; j<num_clients; j++) { |
| if (!register_client(process, dr_inject_get_process_id(inject_data), global, |
| dr_platform, client_ids[j], |
| client_paths[j], client_options[j])) |
| goto error; |
| } |
| } |
| # endif |
| |
| # ifdef UNIX |
| if (use_ptrace) { |
| if (!dr_inject_prepare_to_ptrace(inject_data)) { |
| error("unable to use ptrace"); |
| goto error; |
| } else { |
| info("using ptrace to inject"); |
| } |
| } |
| if (kill_group) { |
| /* Move the child to its own process group. */ |
| bool res = dr_inject_prepare_new_process_group(inject_data); |
| if (!res) { |
| error("error moving child to new process group"); |
| goto error; |
| } |
| } |
| # endif |
| |
| if (inject && !dr_inject_process_inject(inject_data, force_injection, drlib_path)) { |
| error("unable to inject: did you forget to run drconfig first?"); |
| goto error; |
| } |
| |
| IF_WINDOWS(start_time = time(NULL);) |
| |
| if (!dr_inject_process_run(inject_data)) { |
| error("unable to run"); |
| goto error; |
| } |
| |
| # ifdef WINDOWS |
| if (limit == 0 && dr_inject_using_debug_key(inject_data)) { |
| info("%s", "Using debugger key injection"); |
| limit = -1; /* no wait */ |
| } |
| # endif |
| |
| if (limit >= 0) { |
| # ifdef WINDOWS |
| double wallclock; |
| # endif |
| uint64 limit_millis = limit * 1000; |
| info("waiting %sfor app to exit...", (limit <= 0) ? "forever " : ""); |
| success = dr_inject_wait_for_child(inject_data, limit_millis); |
| # ifdef WINDOWS |
| end_time = time(NULL); |
| wallclock = difftime(end_time, start_time); |
| if (showstats || showmem) |
| dr_inject_print_stats(inject_data, (int) wallclock, showstats, showmem); |
| # endif |
| if (!success) |
| info("timeout after %d seconds\n", limit); |
| } else { |
| success = true; /* Don't kill the child if we're not waiting. */ |
| } |
| |
| exitcode = dr_inject_process_exit(inject_data, !success/*kill process*/); |
| |
| if (limit < 0) |
| exitcode = 0; /* Return success if we didn't wait. */ |
| |
| if (exit0) |
| exitcode = 0; |
| |
| /* FIXME i#840: We can't actually match exit status on Linux perfectly |
| * since the kernel reserves most of the bits for signal codes. At the |
| * very least, we should ensure if the app exits with a signal we exit |
| * non-zero. |
| */ |
| return exitcode; |
| |
| error: |
| /* we created the process suspended so if we later had an error be sure |
| * to kill it instead of leaving it hanging |
| */ |
| if (inject_data != NULL) |
| dr_inject_process_exit(inject_data, true/*kill process*/); |
| # ifdef DRRUN |
| if (tofree != NULL) |
| free(tofree); |
| # endif |
| return 1; |
| #endif /* !DRCONFIG */ |
| } |
| |