blob: a6b5c49a480ac6d29774755f4592dda5956e009a [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2022 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.
*/
#include "configure.h"
#ifdef WINDOWS
# define WIN32_LEAN_AND_MEAN
# define UNICODE
# define _UNICODE
# 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
#ifdef LINUX
# include <sys/syscall.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"
#ifdef LINUX
/* XXX: It would be cleaner to have a header for this and have nudgesig.c be in its
* own static library instead of compiled separately for the core and drdeploy.
*/
extern bool
create_nudge_signal_payload(siginfo_t *info DR_PARAM_OUT, uint action_mask,
client_id_t client_id, uint flags, uint64 client_arg);
#endif
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 = false;
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"
" or: " TOOLNAME " [options] [DR options] -c32 <32-bit-client> [client options]"
" -- -c64 <64-bit-client> [client 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 (blocklist).\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"
" -t <toolname> Registers a pre-configured tool to run alongside DR.\n"
" A tool is a client with a configuration file\n"
" that sets the client options and path, providing a\n"
" convenient launching command via this -t parameter.\n"
# ifdef DRRUN
" Available tools include: %s.\n"
# endif
"\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"
" -c32 <32-bit-path> <options>* -- -c64 <64-bit-path> <options>*\n"
" Registers two versions of one client to run alongside\n"
" DR. Use this to specify two versions of a client to\n"
" handle applications which create other-bitwidth child\n"
" processes. The options for the 32-bit client are\n"
" separated from the \"-c64\" by \"--\". The behavior\n"
" otherwise matches \"-c\".\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"
" -detach <pid> \n"
" Detach from the process with the given pid.\n"
"\n"
# ifdef WINDOWS
" -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 drnudgeunix into drconfig on Unix */
"Note: please use the drnudgeunix 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"
" -static Do not inject under the assumption that the application\n"
" is statically linked with DynamoRIO. Instead, trigger\n"
" automated takeover.\n"
# ifndef MACOS /* XXX i#1285: private loader NYI on MacOS */
" -late Requests late injection.\n"
# endif
# ifdef UNIX /* FIXME i#725: Windows attach NYI */
# ifndef MACOS /* XXX i#1285: private loader NYI on MacOS */
" -early Requests early injection (the default).\n"
# endif
" -logdir <dir> Logfiles will be stored in this directory.\n"
# endif
# ifdef UNIX
" -attach <pid> Attach to the process with the given pid.\n"
" Attaching is an experimental feature and is not yet\n"
" as well-supported as launching a new process.\n"
# endif
# ifdef WINDOWS
" -attach <pid> Attach to the process with the given pid.\n"
" Attaching is an experimental feature and is not yet\n"
" as well-supported as launching a new process.\n"
" Attaching to a process in the middle of a blocking\n"
" system call could fail.\n"
" Try takeover_sleep and larger takeovers to increase\n"
" the chances of success:\n"
" -takeover_sleep Sleep 1 millisecond between takeover attempts.\n"
" -takeovers <num> Number of takeover attempts. Defaults to 8.\n"
" The larger, the more likely attach will succeed,\n"
" however, the attach process will take longer.\n"
# endif
" -use_dll <dll> Inject given dll instead of configured DR dll.\n"
" -use_alt_dll <dll> Use the given dll as the alternate-bitwidth DR dll.\n"
" -tool_dir <dir> Directory containing tool configuration files.\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
does_file_exist(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);
}
/* Opens a filename and mode that are in utf8 */
static FILE *
fopen_utf8(const char *path, const char *mode)
{
#ifdef WINDOWS
TCHAR wpath[MAXIMUM_PATH];
TCHAR wmode[MAXIMUM_PATH];
if (drfront_char_to_tchar(path, wpath, BUFFER_SIZE_ELEMENTS(wpath)) !=
DRFRONT_SUCCESS ||
drfront_char_to_tchar(mode, wmode, BUFFER_SIZE_ELEMENTS(wmode)) !=
DRFRONT_SUCCESS)
return NULL;
return _tfopen(wpath, wmode);
#else
return fopen(path, mode);
#endif
}
static char tool_list[MAXIMUM_PATH];
static void
print_tool_list(FILE *stream)
{
#ifdef DRRUN
if (tool_list[0] != '\0')
fprintf(stream, " 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_toolconfig_dir, dr_platform_t dr_platform)
{
FILE *f;
char list_file[MAXIMUM_PATH];
size_t sofar = 0;
const char *arch = IF_X64_ELSE("64", "32");
/* clear global tool list on re-read */
tool_list[0] = '\0';
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/list%s", dr_toolconfig_dir,
arch);
NULL_TERMINATE_BUFFER(list_file);
f = fopen_utf8(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 { \
FILE *stream = (list_ops == true) ? stdout : stderr; \
if ((msg)[0] != '\0') \
fprintf(stderr, "ERROR: " msg "\n\n", ##__VA_ARGS__); \
fprintf(stream, "%s", usage_str); \
print_tool_list(stream); \
if (list_ops) { \
fprintf(stream, options_list_str, tool_list); \
exit(0); \
} else { \
fprintf(stream, "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. On success,
* fills in dr_lib_path and dr_alt_lib_path if they are non-NULL.
*/
static bool
expand_dr_root(const char *dr_root, bool debug, dr_platform_t dr_platform, bool preinject,
bool report, DR_PARAM_OUT char *dr_lib_path, size_t dr_lib_path_sz,
DR_PARAM_OUT char *dr_alt_lib_path, size_t dr_alt_lib_path_sz)
{
int i;
char buf[MAXIMUM_PATH];
bool ok = true;
/* FIXME i#1569: port DynamoRIO to AArch64 so we can enable the check warning */
bool nowarn = IF_X86_ELSE(false, true);
typedef struct _file_entry_t {
const char *suffix;
bool is_core;
bool is_debug;
bool is_preinject;
dr_platform_t platform;
} file_entry_t;
const file_entry_t checked_files[] = {
#ifdef WINDOWS
{ "lib32\\drpreinject.dll", false, false, true, DR_PLATFORM_32BIT },
{ "lib32\\release\\dynamorio.dll", true, false, false, DR_PLATFORM_32BIT },
{ "lib32\\debug\\dynamorio.dll", true, true, false, DR_PLATFORM_32BIT },
{ "lib64\\drpreinject.dll", false, false, true, DR_PLATFORM_64BIT },
{ "lib64\\release\\dynamorio.dll", true, false, false, DR_PLATFORM_64BIT },
{ "lib64\\debug\\dynamorio.dll", true, true, false, DR_PLATFORM_64BIT },
#elif defined(MACOS)
{ "lib32/debug/libdrpreload.dylib", false, true, true, DR_PLATFORM_32BIT },
{ "lib32/debug/libdynamorio.dylib", true, true, false, DR_PLATFORM_32BIT },
{ "lib32/release/libdrpreload.dylib", false, false, true, DR_PLATFORM_32BIT },
{ "lib32/release/libdynamorio.dylib", true, false, false, DR_PLATFORM_32BIT },
{ "lib64/debug/libdrpreload.dylib", true, false, true, DR_PLATFORM_64BIT },
{ "lib64/debug/libdynamorio.dylib", true, true, false, DR_PLATFORM_64BIT },
{ "lib64/release/libdrpreload.dylib", false, false, true, DR_PLATFORM_64BIT },
{ "lib64/release/libdynamorio.dylib", true, false, false, DR_PLATFORM_64BIT },
#else /* LINUX */
/* With early injection the default, we don't require preload to exist. */
{ "lib32/debug/libdynamorio.so", true, true, false, DR_PLATFORM_32BIT },
{ "lib32/release/libdynamorio.so", true, false, false, DR_PLATFORM_32BIT },
{ "lib64/debug/libdynamorio.so", true, true, false, DR_PLATFORM_64BIT },
{ "lib64/release/libdynamorio.so", true, false, false, DR_PLATFORM_64BIT },
#endif
};
if (dr_platform == DR_PLATFORM_DEFAULT)
dr_platform = IF_X64_ELSE(DR_PLATFORM_64BIT, DR_PLATFORM_32BIT);
if (DR_dll_not_needed) {
/* An explicit path was passed so don't require a regular installation. */
nowarn = 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 (does_file_exist(buf))
nowarn = true;
bool found_lib = false;
if (dr_alt_lib_path != NULL)
dr_alt_lib_path[0] = '\0';
for (i = 0; i < BUFFER_SIZE_ELEMENTS(checked_files); i++) {
_snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s/%s", dr_root,
checked_files[i].suffix);
if (does_file_exist(buf)) {
if (checked_files[i].is_core &&
((debug && checked_files[i].is_debug) ||
(!debug && !checked_files[i].is_debug))) {
if (checked_files[i].platform == dr_platform) {
found_lib = true;
if (dr_lib_path != NULL) {
_snprintf(dr_lib_path, dr_lib_path_sz, "%s", buf);
dr_lib_path[dr_lib_path_sz - 1] = '\0';
}
} else {
if (dr_alt_lib_path != NULL) {
_snprintf(dr_alt_lib_path, dr_alt_lib_path_sz, "%s", buf);
dr_alt_lib_path[dr_alt_lib_path_sz - 1] = '\0';
}
}
}
} else {
ok = false;
if (!nocheck &&
((preinject && checked_files[i].is_preinject) ||
(!preinject && debug && checked_files[i].is_debug) ||
(!preinject && !debug && !checked_files[i].is_debug)) &&
checked_files[i].platform == dr_platform) {
/* We don't want to create a .1config file that won't be freed
* b/c the core is never injected
*/
if (report) {
error("cannot find required file %s\n"
"Use -root to specify a proper DynamoRIO root directory.",
buf);
}
return false;
} else {
if (checked_files[i].platform == DR_PLATFORM_DEFAULT) {
/* Support a single-bitwidth package. */
ok = true;
} else if (!nowarn)
warn("cannot find %s: is this an incomplete installation?", buf);
}
}
}
assert(found_lib);
if (!ok && !nowarn)
warn("%s does not appear to be a valid DynamoRIO root", dr_root);
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,
bool report)
{
return expand_dr_root(dr_root, debug, dr_platform, preinject, report, NULL, 0, NULL,
0);
}
/* 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, const char *custom_dll, const char *custom_alt_dll)
{
dr_config_status_t status;
assert(dr_root != NULL);
if (!does_file_exist(dr_root)) {
error("cannot access DynamoRIO root directory %s", dr_root);
return false;
}
if (dr_mode == DR_MODE_NONE) {
error("you must provide a DynamoRIO mode");
return false;
}
char dr_lib_path[MAXIMUM_PATH];
char dr_alt_lib_path[MAXIMUM_PATH];
if (custom_dll != NULL) {
strncpy(dr_lib_path, custom_dll, BUFFER_SIZE_ELEMENTS(dr_lib_path));
NULL_TERMINATE_BUFFER(dr_lib_path);
} else {
bool check_ok =
expand_dr_root(dr_root, debug, dr_platform, false /*!pre*/,
dr_mode != DR_MODE_DO_NOT_RUN /*report*/, dr_lib_path,
BUFFER_SIZE_ELEMENTS(dr_lib_path), dr_alt_lib_path,
BUFFER_SIZE_ELEMENTS(dr_alt_lib_path));
/* 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_ok)
return false;
}
if (custom_alt_dll != NULL) {
/* Overwrite what expand_dr_root found if an alt is specified. */
strncpy(dr_alt_lib_path, custom_alt_dll, BUFFER_SIZE_ELEMENTS(dr_alt_lib_path));
NULL_TERMINATE_BUFFER(dr_alt_lib_path);
}
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 */
char buf[MAXIMUM_PATH];
#ifdef WINDOWS
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
if (status == DR_CONFIG_DIR_NOT_FOUND) {
dr_get_config_dir(global, true /*tmp*/, buf, BUFFER_SIZE_ELEMENTS(buf));
error("process %s registration failed: check config dir %s permissions",
process == NULL ? "<null>" : process, buf);
#ifdef ANDROID
error("for Android apps, set TMPDIR to /data/data/com.your.app");
#endif
} else {
error("process %s registration failed",
process == NULL ? "<null>" : process);
}
#ifdef WINDOWS
}
#endif
return false;
}
status = dr_register_inject_paths(
process, pid, global, dr_platform, dr_lib_path[0] == '\0' ? NULL : dr_lib_path,
dr_alt_lib_path[0] == '\0' ? NULL : dr_alt_lib_path);
if (status != DR_SUCCESS)
warn("failed to specify DynamoRIO library paths: falling back to defaults");
return true;
}
/* Check if the specified client library actually exists. */
void
check_client_lib(const char *client_lib)
{
if (!does_file_exist(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,
bool is_alt_bitwidth, 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);
info("registering client with id=%d path=|%s| ops=|%s|%s", client_id, path, options,
is_alt_bitwidth ? " alt-bitwidth" : "");
dr_config_client_t info;
info.struct_size = sizeof(info);
info.id = client_id;
info.priority = priority;
info.path = (char *)path;
info.options = (char *)options;
info.is_alt_bitwidth = is_alt_bitwidth;
status = dr_register_client_ex(process_name, pid, global, dr_platform, &info);
if (status != DR_SUCCESS) {
if (status == DR_CONFIG_STRING_TOO_LONG) {
error("client %s registration failed: option string too long: \"%s\"", path,
options);
} else if (status == DR_CONFIG_OPTIONS_INVALID) {
error("client %s registration failed: options cannot contain ';' or all "
"3 quote types: %s",
path, options);
} else {
error("client %s registration failed with error code %d", 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_utf8(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), PIDFMT "\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, bool is_alt_bitwidth,
char client_paths[MAX_CLIENT_LIBS][MAXIMUM_PATH],
client_id_t client_ids[MAX_CLIENT_LIBS],
const char *client_options[MAX_CLIENT_LIBS],
bool alt_bitwidth[MAX_CLIENT_LIBS], size_t *num_clients)
{
size_t index = *num_clients;
/* Handle "-c32 -c64" order for native 64-bit where we want to swap the 32
* and 64 to get the alt last.
*/
if (index > 0 && id == client_ids[index - 1] && alt_bitwidth[index - 1] &&
!is_alt_bitwidth) {
/* Insert this one before the prior one by first copying the prior to index. */
client_ids[index] = client_ids[index - 1];
alt_bitwidth[index] = alt_bitwidth[index - 1];
_snprintf(client_paths[index], BUFFER_SIZE_ELEMENTS(client_paths[index]), "%s",
client_paths[index - 1]);
NULL_TERMINATE_BUFFER(client_paths[index]);
client_options[index] = client_options[index - 1];
index = index - 1;
}
/* We support an empty client for native -t usage */
if (client[0] != '\0') {
get_absolute_path(client, client_paths[index],
BUFFER_SIZE_ELEMENTS(client_paths[index]));
NULL_TERMINATE_BUFFER(client_paths[index]);
info("client %d path: %s", (int)index, client_paths[index]);
}
client_ids[index] = id;
client_options[index] = client_ops;
alt_bitwidth[index] = is_alt_bitwidth;
(*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, or two if
* they are a pair of CLIENT32_* and CLIENT64_* specifiers:
* CLIENT_ABS=<absolute path to client>
* CLIENT_REL=<path to client relative to DR root>
* CLIENT32_ABS=<absolute path to 32-bit client>
* CLIENT32_REL=<path to 32-bit client relative to DR root>
* CLIENT64_ABS=<absolute path to 64-bit client>
* CLIENT64_REL=<path to 64-bit 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, const char *dr_toolconfig_dir,
dr_platform_t dr_platform, char *client, size_t client_size,
char *alt_client, size_t alt_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 DR_PARAM_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/%s.drrun%s",
dr_toolconfig_dir, toolname, arch);
NULL_TERMINATE_BUFFER(config_file);
info("reading tool config file %s", config_file);
f = fopen_utf8(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;
if (native_path[0] != '\0') {
add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, "\"%s\"",
client);
}
} else if (strstr(line, IF_X64_ELSE("CLIENT64_REL=", "CLIENT32_REL=")) == line) {
_snprintf(client, client_size, "%s/%s", dr_root,
line + strlen(IF_X64_ELSE("CLIENT64_REL=", "CLIENT32_REL=")));
client[client_size - 1] = '\0';
found_client = true;
if (native_path[0] != '\0') {
add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, "\"%s\"",
client);
}
} else if (strstr(line, IF_X64_ELSE("CLIENT32_REL=", "CLIENT64_REL=")) == line) {
_snprintf(alt_client, alt_size, "%s/%s", dr_root,
line + strlen(IF_X64_ELSE("CLIENT32_REL=", "CLIENT64_REL=")));
alt_client[alt_size - 1] = '\0';
if (!does_file_exist(alt_client)) {
alt_client[0] = '\0';
}
if (native_path[0] != '\0') {
add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, "\"%s\"",
alt_client);
}
} else if (strstr(line, "CLIENT_ABS=") == line) {
strncpy(client, line + strlen("CLIENT_ABS="), client_size);
found_client = true;
if (native_path[0] != '\0') {
add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, "\"%s\"",
client);
}
} else if (strstr(line, IF_X64_ELSE("CLIENT64_ABS=", "CLIENT32_ABS=")) == line) {
strncpy(client, line + strlen(IF_X64_ELSE("CLIENT64_ABS=", "CLIENT32_ABS=")),
client_size);
found_client = true;
if (native_path[0] != '\0') {
add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, "\"%s\"",
client);
}
} else if (strstr(line, IF_X64_ELSE("CLIENT32_ABS=", "CLIENT64_ABS=")) == line) {
strncpy(alt_client,
line + strlen(IF_X64_ELSE("CLIENT32_ABS=", "CLIENT64_ABS=")),
alt_size);
if (!does_file_exist(alt_client)) {
alt_client[0] = '\0';
}
if (native_path[0] != '\0') {
add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, "\"%s\"",
alt_client);
}
} else if (strstr(line, "DR_OP=") == line) {
if (strcmp(line, "DR_OP=") != 0) {
add_extra_option(ops, ops_size, ops_sofar, "\"%s\"",
line + strlen("DR_OP="));
}
} else if (strstr(line, "TOOL_OP=") == line) {
if (strcmp(line, "TOOL_OP=") != 0) {
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) {
if (strcmp(line, "TOOL_OP_DR_BUNDLE=") != 0) {
add_extra_option(tool_ops, tool_ops_size, tool_ops_sofar, "%s `%s`",
line + strlen("TOOL_OP_DR_BUNDLE="), ops);
}
# else
} else if (strstr(line, "FRONTEND_ABS=") == line ||
strstr(line, "FRONTEND_REL=") == line ||
strstr(line, "TOOL_OP_DR_PATH") == line ||
strstr(line, "TOOL_OP_DR_BUNDLE=") == line) {
usage(false, "this tool's config only works with drrun, not drconfig");
return false;
# 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 DR_PARAM_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 * 2];
char *c = buf;
for (i = 0; i < count - 1; i++) {
ssize_t len = _snprintf(c, BUFFER_SIZE_ELEMENTS(buf) - (c - buf), " \"%s\"",
new_argv[i]);
if (len < 0 || (size_t)len >= BUFFER_SIZE_ELEMENTS(buf) - (c - buf))
break;
c += len;
}
NULL_TERMINATE_BUFFER(buf);
info("native tool cmdline: %s", buf);
}
return new_argv;
}
#endif /* DRRUN */
int
_tmain(int argc, TCHAR *targv[])
{
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,
};
bool alt_bitwidth[MAX_CLIENT_LIBS];
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 */
dr_operation_mode_t dr_mode = DR_MODE_CODE_MANIPULATION;
# 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;
bool use_late_injection = 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;
uint detach_timeout = DETACH_RECOMMENDED_TIMEOUT;
bool syswide_on = false;
bool syswide_off = false;
#endif /* WINDOWS */
bool global = false;
int exitcode;
#if defined(DRRUN) || defined(DRINJECT)
char *pidfile = NULL;
bool force_injection = false;
bool inject = true;
int limit = 0; /* in seconds */
# ifdef WINDOWS
bool showstats = false;
bool showmem = false;
time_t start_time, end_time;
# else
bool use_ptrace = false;
bool kill_group = false;
# endif
process_id_t attach_pid = 0;
char *app_name = NULL;
char full_app_name[MAXIMUM_PATH];
const char **app_argv;
int errcode;
void *inject_data;
bool success;
bool exit0 = false;
#endif
#if defined(DRCONFIG)
# if defined(WINDOWS) || defined(LINUX)
process_id_t detach_pid = 0;
# endif
#endif
char *drlib_path = NULL;
#if defined(DRCONFIG) || defined(DRRUN)
char *drlib_alt_path = NULL;
char custom_alt_dll[MAXIMUM_PATH];
#endif
char custom_dll[MAXIMUM_PATH];
char *dr_toolconfig_dir = NULL;
char custom_toolconfig_dir[MAXIMUM_PATH];
int i;
#ifndef DRINJECT
size_t j;
#endif
char buf[MAXIMUM_PATH];
char default_root[MAXIMUM_PATH];
char default_toolconfig_dir[MAXIMUM_PATH];
char *c;
#if defined(DRCONFIG) || defined(DRRUN)
char native_tool[MAXIMUM_PATH];
#endif
#ifdef DRRUN
char exe[MAXIMUM_PATH];
void *tofree = NULL;
bool configure = true;
#endif
char **argv;
drfront_status_t sc;
#if defined(WINDOWS) && !defined(_UNICODE)
# error _UNICODE must be defined
#else
/* Convert to UTF-8 if necessary */
sc = drfront_convert_args((const TCHAR **)targv, &argv, argc);
if (sc != DRFRONT_SUCCESS)
fatal("failed to process args: %d", sc);
#endif
memset(client_paths, 0, sizeof(client_paths));
extra_ops[0] = '\0';
#if defined(DRCONFIG) || defined(DRRUN)
native_tool[0] = '\0';
#endif
/* Quick pass to set verbose for info() logs before main parsing. */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-verbose") == 0 || strcmp(argv[i], "-v") == 0) {
verbose = true;
break;
}
if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "-t") == 0 ||
strcmp(argv[i], "-c32") == 0 || strcmp(argv[i], "-c64") == 0 ||
strcmp(argv[i], "--") == 0)
break;
}
/* 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);
_snprintf(default_toolconfig_dir, BUFFER_SIZE_ELEMENTS(default_toolconfig_dir),
"%s/%s", dr_root, "tools");
NULL_TERMINATE_BUFFER(default_toolconfig_dir);
dr_toolconfig_dir = default_toolconfig_dir;
info("default toolconfig dir: %s", default_toolconfig_dir);
/* we re-read the tool list if the root, platform or toolconfig dir change */
read_tool_list(dr_toolconfig_dir, 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_toolconfig_dir, dr_platform);
continue;
} else if (strcmp(argv[i], "-64") == 0) {
dr_platform = DR_PLATFORM_64BIT;
read_tool_list(dr_toolconfig_dir, dr_platform);
continue;
}
#if defined(DRRUN) || defined(DRINJECT)
# ifdef WINDOWS
else if (strcmp(argv[i], "-stats") == 0) {
showstats = true;
continue;
} else if (strcmp(argv[i], "-mem") == 0) {
showmem = true;
continue;
}
# endif
else if (strcmp(argv[i], "-no_inject") == 0 ||
/* support old drinjectx param name */
strcmp(argv[i], "-noinject") == 0 || strcmp(argv[i], "-static") == 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;
}
# ifndef MACOS /* XXX i#1285: private loader NYI on MacOS */
else if (strcmp(argv[i], "-late") == 0) {
/* Appending -no_early_inject to extra_ops communicates our intentions
* to drinjectlib on UNIX, as well as the core for all platforms.
*/
add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), &extra_ops_sofar,
"-no_early_inject");
# ifdef WINDOWS
use_late_injection = true;
# endif
continue;
}
# endif
else if (strcmp(argv[i], "-attach") == 0) {
if (i + 1 >= argc)
usage(false, "attach requires a process id");
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: '%s'", pid_str);
if (pid == 0) {
usage(false, "attach passed an invalid pid: '%s'", pid_str);
}
attach_pid = pid;
# ifdef UNIX
use_ptrace = true;
# endif
# ifdef WINDOWS
use_late_injection = true;
add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), &extra_ops_sofar,
"-skip_terminating_threads");
# endif
continue;
} else if (strcmp(argv[i], "-takeovers") == 0) {
const char *num_attemps = argv[++i];
add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), &extra_ops_sofar,
"-takeover_attempts %s", num_attemps);
continue;
} else if (strcmp(argv[i], "-takeover_sleep") == 0) {
add_extra_option(extra_ops, BUFFER_SIZE_ELEMENTS(extra_ops), &extra_ops_sofar,
"-sleep_between_takeovers");
continue;
}
# ifdef UNIX
else if (strcmp(argv[i], "-use_ptrace") == 0) {
/* Undocumented option for using ptrace on a fresh process. */
use_ptrace = true;
continue;
}
# ifndef MACOS /* XXX i#1285: private loader NYI on MacOS */
else if (strcmp(argv[i], "-early") == 0) {
/* Now the default: left here just for back-compat */
continue;
}
# endif
# 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];
/* Modify the default toolconfig dir only.
* If toolconfig dir is specified explicitly, dr_toolconfig_dir points
* to other buffer, hence is not affected by overwriting the default.
*/
_snprintf(default_toolconfig_dir,
BUFFER_SIZE_ELEMENTS(default_toolconfig_dir), "%s/%s", dr_root,
"tools");
NULL_TERMINATE_BUFFER(default_toolconfig_dir);
read_tool_list(dr_toolconfig_dir, 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 (!does_file_exist(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
# if defined(WINDOWS) || defined(LINUX)
else if (strcmp(argv[i], "-detach") == 0) {
if (i + 1 >= argc)
usage(false, "detach requires a process id");
const char *pid_str = argv[++i];
process_id_t pid = strtoul(pid_str, NULL, 10);
if (pid == ULONG_MAX)
usage(false, "detach expects an integer pid: '%s'", pid_str);
if (pid == 0) {
usage(false, "detach passed an invalid pid: '%s'", pid_str);
}
detach_pid = pid;
}
# 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, false, client_paths, client_ids,
client_options, alt_bitwidth, &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
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;
}
#if defined(DRCONFIG) || defined(DRRUN)
else if (strcmp(argv[i], "-use_alt_dll") == 0) {
get_absolute_path(argv[++i], custom_alt_dll,
BUFFER_SIZE_ELEMENTS(custom_alt_dll));
NULL_TERMINATE_BUFFER(custom_alt_dll);
drlib_alt_path = custom_alt_dll;
}
#endif
else if (strcmp(argv[i], "-tool_dir") == 0) {
get_absolute_path(argv[++i], custom_toolconfig_dir,
BUFFER_SIZE_ELEMENTS(custom_toolconfig_dir));
NULL_TERMINATE_BUFFER(custom_toolconfig_dir);
dr_toolconfig_dir = custom_toolconfig_dir;
read_tool_list(dr_toolconfig_dir, dr_platform);
}
#if defined(DRRUN) || defined(DRINJECT)
else if (strcmp(argv[i], "-pidfile") == 0) {
pidfile = argv[++i];
} 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] == '-') {
bool expect_extra_double_dash = false;
while (i < argc) {
if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "-t") == 0 ||
strcmp(argv[i], "-c32") == 0 || strcmp(argv[i], "-c64") == 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 ||
strcmp(argv[i], "-c32") == 0 || strcmp(argv[i], "-c64") == 0)) {
const char *client;
char client_buf[MAXIMUM_PATH];
char alt_buf[MAXIMUM_PATH];
alt_buf[0] = '\0';
size_t client_sofar = 0;
bool is_alt_bitwidth = false;
if (i + 1 >= argc)
usage(false, "too few arguments to %s", argv[i]);
if (num_clients != 0 &&
(strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "-t") == 0))
usage(false, "Cannot use -client with %s.", argv[i]);
if (strcmp(argv[i], "-c32") == 0)
expect_extra_double_dash = true;
if (strcmp(argv[i], IF_X64_ELSE("-c32", "-c64")) == 0)
is_alt_bitwidth = true;
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_toolconfig_dir, dr_platform,
client_buf, BUFFER_SIZE_ELEMENTS(client_buf),
alt_buf, BUFFER_SIZE_ELEMENTS(alt_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, is_alt_bitwidth, client_paths,
client_ids, client_options, alt_bitwidth, &num_clients);
if (alt_buf[0] != '\0') {
append_client(alt_buf, 0, single_client_ops, true, client_paths,
client_ids, client_options, alt_bitwidth, &num_clients);
}
}
if (i < argc && strcmp(argv[i], "--") == 0 &&
(!expect_extra_double_dash ||
(i + 1 < argc && strcmp(argv[i + 1], "-c64") != 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)
# ifdef DRRUN
if (attach_pid != 0) {
ssize_t size = 0;
# ifdef UNIX
char exe_str[MAXIMUM_PATH];
_snprintf(exe_str, BUFFER_SIZE_ELEMENTS(exe_str), "/proc/%d/exe", attach_pid);
NULL_TERMINATE_BUFFER(exe_str);
size = readlink(exe_str, exe, BUFFER_SIZE_ELEMENTS(exe));
if (size > 0) {
if (size < BUFFER_SIZE_ELEMENTS(exe))
exe[size] = '\0';
else
NULL_TERMINATE_BUFFER(exe);
} else {
usage(false, "attach to invalid pid");
}
# endif /* UNIX */
app_name = exe;
}
/* Support no app if the tool has its own frontend, under the assumption
* it may have post-processing or other features.
*/
if (attach_pid == 0 && (i < argc || native_tool[0] == '\0')) {
# endif
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);
# ifdef DRRUN
}
# endif
/* 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
/* support running out of a debug build dir */
if (!use_debug &&
!check_dr_root(dr_root, false, dr_platform, false /*!pre*/, false /*!report*/) &&
check_dr_root(dr_root, true, dr_platform, false /*!pre*/, false /*!report*/)) {
info("debug build directory detected: switching to debug build");
use_debug = true;
}
#ifdef DRCONFIG
if (verbose) {
dr_get_config_dir(global, true /*use temp*/, buf, BUFFER_SIZE_ELEMENTS(buf));
info("configuration directory is \"%s\"", buf);
}
if (action == action_register) {
if (!register_proc(process, 0, global, dr_root, dr_mode, use_debug, dr_platform,
extra_ops, drlib_path, drlib_alt_path))
die();
for (j = 0; j < num_clients; j++) {
if (!register_client(process, 0, global, dr_platform, client_ids[j],
client_paths[j], alt_bitwidth[j], client_options[j]))
die();
}
} else if (action == action_unregister) {
if (!unregister_proc(process, 0, global, dr_platform))
die();
}
# if defined(WINDOWS) || defined(LINUX)
else if (detach_pid != 0) {
# ifdef WINDOWS
dr_config_status_t res = detach(detach_pid, TRUE, detach_timeout);
if (res != DR_SUCCESS)
error("unable to detach: check pid and system ptrace permissions");
# else
siginfo_t info;
uint action_mask = NUDGE_FREE_ARG;
client_id_t client_id = 0;
uint64 client_arg = 0;
bool success =
create_nudge_signal_payload(&info, action_mask, 0, client_id, client_arg);
assert(success); /* failure means kernel's sigqueueinfo has changed */
/* send the nudge */
i = syscall(SYS_rt_sigqueueinfo, detach_pid, NUDGESIG_SIGNUM, &info);
if (i < 0)
fprintf(stderr, "nudge FAILED with error %d\n", i);
# endif
}
# endif
# ifndef WINDOWS
else {
usage(false, "no action specified");
}
# else /* WINDOWS */
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 " PIDFMT " is not running under DR\n", nudge_pid);
if (res != DR_SUCCESS && res != DR_NUDGE_TIMEOUT) {
count = 0;
}
} 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\n");
else if (res != DR_SUCCESS)
printf("nudge operation failed, verify permissions and parameters.\n");
}
# 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 &&
IF_X64_ELSE(
dr_platform != DR_PLATFORM_32BIT,
(dr_platform == DR_PLATFORM_64BIT || !is_wow64(GetCurrentProcess())))) {
/* FIXME i#1522: enable AppInit for non-WOW64 on win8+ */
error("syswide_on is not yet supported on Windows 8+ non-WOW64");
die();
}
if (!check_dr_root(dr_root, false, dr_platform, true /*pre*/, true /*report*/))
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 */
exitcode = 0;
goto cleanup;
#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));
info("configuration directory is \"%s\"", buf);
}
# ifdef UNIX
/* i#1676: detect whether under gdb */
char path_buf[MAXIMUM_PATH];
_snprintf(path_buf, BUFFER_SIZE_ELEMENTS(path_buf), "/proc/%d/exe", getppid());
NULL_TERMINATE_BUFFER(path_buf);
i = readlink(path_buf, buf, BUFFER_SIZE_ELEMENTS(buf));
if (i > 0) {
if (i < BUFFER_SIZE_ELEMENTS(buf))
buf[i] = '\0';
else
NULL_TERMINATE_BUFFER(buf);
}
/* 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 if (attach_pid != 0) {
/* We always try to avoid hanging on a blocked syscall. */
errcode = dr_inject_prepare_to_attach(attach_pid, app_name,
/*wait_syscall=*/false, &inject_data);
} else
# elif defined(WINDOWS)
if (attach_pid != 0) {
errcode = dr_inject_process_attach(attach_pid, &inject_data, &app_name);
} else
# endif /* WINDOWS */
{
errcode = dr_inject_process_create(app_name, app_argv, &inject_data);
info("created child with pid " PIDFMT " for %s",
dr_inject_get_process_id(inject_data), app_name);
}
# ifdef WINDOWS
if (use_late_injection)
dr_inject_use_late_injection(inject_data);
# endif
# 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,
drlib_path, drlib_alt_path))
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],
alt_bitwidth[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)) {
# ifdef DRRUN
if (attach_pid != 0) {
error("unable to attach; check pid and system ptrace permissions");
goto error;
}
error("unable to inject: exec of |%s| failed", drlib_path);
# else
error("unable to inject: did you forget to run drconfig first?");
# endif
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, attach_pid != 0 ? false : !success /*kill process*/);
if (limit < 0)
exitcode = 0; /* Return success if we didn't wait. */
if (exit0)
exitcode = 0;
goto cleanup;
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,
attach_pid != 0 ? false : true /*kill process*/);
}
# ifdef DRRUN
if (tofree != NULL)
free(tofree);
# endif
exitcode = 1;
#endif /* !DRCONFIG */
cleanup:
sc = drfront_cleanup_args(argv, argc);
if (sc != DRFRONT_SUCCESS)
fatal("failed to free memory for args: %d", sc);
/* 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;
}