Ejemplo n.º 1
0
/* see win32/tls.c test for alt. method of starting a detach, this way is preferable
 * since we may at some point disallow the process from detaching itself */
void
detach()
{
    char buf[2*MAX_PATH] = "\"";
    char *tools = getenv("DYNAMORIO_WINTOOLS");
    int size;
    STARTUPINFO sinfo = {sizeof(sinfo), NULL, "", 0, 0, 0, 0, 0, 0, 0, 0,
                         STARTF_USESTDHANDLES, 0, 0, NULL,
                         GetStdHandle(STD_INPUT_HANDLE),
                         GetStdHandle(STD_OUTPUT_HANDLE),
                         GetStdHandle(STD_ERROR_HANDLE)};
    PROCESS_INFORMATION pinfo;
    strncat(buf, tools == NULL ? "" : tools, BUFFER_ROOM_LEFT(buf));
    NULL_TERMINATE_BUFFER(buf);
    strncat(buf, "\\DRcontrol.exe\" -detachexe "PROC_NAME, BUFFER_ROOM_LEFT(buf));
    NULL_TERMINATE_BUFFER(buf);
    print("Detaching\n");
    if (CreateProcess(NULL, buf, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo)) {
        /* This thread will be detached at non-dr intercepted syscall (the wait below)
         * with multiple stacked callbacks (from SendAsyncProc). */
        WaitForSingleObject(pinfo.hProcess, INFINITE);
        CloseHandle(pinfo.hProcess);
        CloseHandle(pinfo.hThread);
    } else {
        print("Detach Failed!\n");
    }
}
Ejemplo n.º 2
0
drfront_status_t
drfront_get_app_full_path(const char *app, OUT char *buf, size_t buflen/*# elements*/)
{
    TCHAR wbuf[MAX_PATH];
    TCHAR wapp[MAX_PATH];
    drfront_status_t status_check = DRFRONT_ERROR;
    bool is_dir = false;

    status_check = drfront_char_to_tchar(app, wapp, BUFFER_SIZE_ELEMENTS(wapp));
    if (status_check != DRFRONT_SUCCESS)
        return status_check;
    _tsearchenv(wapp, _T("PATH"), wbuf);
    NULL_TERMINATE_BUFFER(wbuf);
    status_check = drfront_tchar_to_char(wbuf, buf, buflen);
    if (status_check != DRFRONT_SUCCESS)
        return status_check;
    if (wbuf[0] == _T('\0') ||
        /* DrM-i#1617: we might have a same-name directory on the path */
        (drfront_dir_exists(buf, &is_dir) == DRFRONT_SUCCESS && is_dir)) {
        /* may need to append .exe, FIXME : other executable types */
        TCHAR tmp_buf[MAX_PATH];
        _sntprintf(tmp_buf, BUFFER_SIZE_ELEMENTS(tmp_buf), _T("%s%s"), wapp, _T(".exe"));
        NULL_TERMINATE_BUFFER(wbuf);
        _tsearchenv(tmp_buf, _T("PATH"), wbuf);
    }
    if (wbuf[0] == _T('\0')) {
        /* last try: expand w/ cur dir */
        GetFullPathName(wapp, BUFFER_SIZE_ELEMENTS(wbuf), wbuf, NULL);
        NULL_TERMINATE_BUFFER(wbuf);
    }
    status_check = drfront_tchar_to_char(wbuf, buf, buflen);
    return status_check;
}
Ejemplo n.º 3
0
static void
generate_process_name(process_info_t *pi, WCHAR *name_buf /* OUT */,
                      uint name_buf_length /* elements */)
{
    WCHAR qual_name[MAX_CMDLINE];
    WCHAR qual_args[MAX_CMDLINE];
    int res;
    BOOL use_args = FALSE;

    /* hack: we assume only need qualified names for these hardcoded apps
     * FIXME: read registry to see whether need qualification
     */
    if (wcsicmp(pi->ProcessName, L"svchost.exe") == 0 ||
        wcsicmp(pi->ProcessName, L"msiexec.exe") == 0 ||
        wcsicmp(pi->ProcessName, L"tomcat.exe") == 0 ||
        wcsicmp(pi->ProcessName, L"dllhost.exe") == 0) {
        /* use qualified names */
        res = get_process_cmdline(pi->ProcessID, qual_name,
                                  BUFFER_SIZE_ELEMENTS(qual_name));
        NULL_TERMINATE_BUFFER(qual_name);
        if (res == ERROR_SUCCESS) {
            if (get_commandline_qualifier(qual_name, qual_args,
                                          BUFFER_SIZE_ELEMENTS(qual_args),
                                          /* hack: we assume we only strip svchost,
                                           * and we also strip dllhost here to
                                           * fit more of the GUI in and avoid the
                                           * "Processid" string.
                                           * FIXME: read from registry.
                                           */
                                          (wcsicmp(pi->ProcessName, L"svchost.exe") != 0 &&
                                           wcsicmp(pi->ProcessName, L"dllhost.exe") != 0))) {
                NULL_TERMINATE_BUFFER(qual_args);
                use_args = TRUE;
            } /* else not an error, just no args. e.g.: plain sqlservr.exe */
        } else {
            /* this is an error => notify user */
            _snwprintf(qual_args, BUFFER_SIZE_ELEMENTS(qual_args), L"<error>");
            NULL_TERMINATE_BUFFER(qual_args);
            use_args = TRUE;
        }
    }
    if (use_args) {
        _snwprintf(name_buf, name_buf_length, L"%s-%s",
                   pi->ProcessName, qual_args);
    } else {
        _snwprintf(name_buf, name_buf_length, L"%s", pi->ProcessName);
    }
    NULL_TERMINATE_SIZED_BUFFER(name_buf, name_buf_length);
}
Ejemplo n.º 4
0
static void
event_exit()
{
#ifdef SHOW_RESULTS
    char msg[512];
    int len;
    len = dr_snprintf(msg, sizeof(msg)/sizeof(msg[0]),
                      "Instrumentation results:\n"
                      "  saw %llu memory references\n",
                      num_refs);
    DR_ASSERT(len > 0);
    NULL_TERMINATE_BUFFER(msg);
    DISPLAY_STRING(msg);
#endif /* SHOW_RESULTS */
    code_cache_exit();

    if (!drmgr_unregister_tls_field(tls_index) ||
        !drmgr_unregister_thread_init_event(event_thread_init) ||
        !drmgr_unregister_thread_exit_event(event_thread_exit) ||
        !drmgr_unregister_bb_insertion_event(event_bb_insert) ||
        drreg_exit() != DRREG_SUCCESS)
        DR_ASSERT(false);

    dr_mutex_destroy(mutex);
    drmgr_exit();
}
Ejemplo n.º 5
0
void
symcache_init(const char *symcache_dir_in,
              size_t modsize_cache_threshold)
{
    initialized = true;

    op_modsize_cache_threshold = modsize_cache_threshold;

    hashtable_init_ex(&symcache_table, SYMCACHE_MASTER_TABLE_HASH_BITS,
                      IF_WINDOWS_ELSE(HASH_STRING_NOCASE, HASH_STRING),
                      true/*strdup*/, false/*!synch*/,
                      symcache_free_entry, NULL, NULL);
    symcache_lock = dr_mutex_create();

    dr_snprintf(symcache_dir, BUFFER_SIZE_ELEMENTS(symcache_dir), 
                "%s", symcache_dir_in);
    NULL_TERMINATE_BUFFER(symcache_dir);
    if (!dr_directory_exists(symcache_dir)) {
        if (!dr_create_dir(symcache_dir)) {
            /* check again in case of a race (i#616) */
            if (!dr_directory_exists(symcache_dir)) {
                NOTIFY_ERROR("Unable to create symcache dir %s"NL, symcache_dir);
                ASSERT(false, "unable to create symcache dir");
                dr_abort();
            }
        }
    }
}
Ejemplo n.º 6
0
static void
wrap_pre_SSL_write(void *wrapcxt, OUT void **user_data)
{
    /* int SSL_write(SSL *ssl, const void *buf, int num);
     *
     * ssize_t gnutls_record_send(gnutls_session_t session,
     *                            const void * data, size_t sizeofdata);
     */

    void *ssl = (void *)drwrap_get_arg(wrapcxt, 0);
    unsigned char *buf = (unsigned char *)drwrap_get_arg(wrapcxt, 1);
    size_t sz = (size_t)drwrap_get_arg(wrapcxt, 2);

    /* By generating unique filenames (per SSL context), we are able to
     * simplify logging of SSL traffic (no file locking is required).
     */
    char filename[MAXIMUM_PATH] = { 0 };
    dr_snprintf(filename, BUFFER_SIZE_ELEMENTS(filename), "trace-%x.write", ssl);
    NULL_TERMINATE_BUFFER(filename);
    FILE *fp = fopen(filename, "ab+");
    /* Error handling of logging operations isn't critical - in fact, we don't
     * even know what to do in such error conditions, so we simply return!
     */
    if (!fp) {
        dr_fprintf(STDERR, "Couldn’t open the output file %s\n", filename);
        return;
    }

    /* We assume that SSL_write always succeeds and writes the whole buffer. */
    fwrite(buf, 1, sz, fp);
    fclose(fp);
}
Ejemplo n.º 7
0
drfront_status_t
drfront_dir_try_writable(const char *path, OUT bool *is_writable)
{
    /* It would be convenient to use O_TMPFILE but not all filesystems support it */
    int fd;
    char tmpname[PATH_MAX];
    /* We actually don't care about races w/ other threads or processes running
     * this same code: each syscall should succeed and truncate whatever is there.
     */
#   define TMP_FILE_NAME ".__drfrontendlib_tmp"
    if (is_writable == NULL)
        return DRFRONT_ERROR_INVALID_PARAMETER;
    snprintf(tmpname, BUFFER_SIZE_ELEMENTS(tmpname), "%s/%s", path, TMP_FILE_NAME);
    NULL_TERMINATE_BUFFER(tmpname);
    fd = creat(tmpname, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        drfront_status_t res;
        bool is_dir;
        *is_writable = false;
        res = drfront_dir_exists(path, &is_dir);
        if (res != DRFRONT_SUCCESS)
            return res;
        if (!is_dir)
            return DRFRONT_ERROR_INVALID_PATH;
    } else {
        *is_writable = true;
        close(fd);
        unlink(tmpname);
    }
    return DRFRONT_SUCCESS;
}
Ejemplo n.º 8
0
void
display_error(char *msg)
{
    wchar_t buf[512];
    _snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf), L"%hs", msg);
    NULL_TERMINATE_BUFFER(buf);
    display_error_helper(buf);
}
Ejemplo n.º 9
0
/* Implements a normal path search for fname on the paths in env_var.
 * Resolves symlinks, which is needed to get the right config filename (i#1062).
 */
drfront_status_t
drfront_searchenv(const char *fname, const char *env_var, OUT char *full_path,
                  const size_t full_path_size, OUT bool *ret)
{
    const char *paths = getenv(env_var);
    const char *cur;
    const char *next;
    const char *end;
    char tmp[full_path_size];
    char realpath_buf[PATH_MAX]; /* realpath hardcodes its buffer length */
    drfront_status_t status_check = DRFRONT_ERROR;
    bool access_ret = false;

    if (ret == NULL)
        return DRFRONT_ERROR_INVALID_PARAMETER;

    /* Windows searches the current directory first. */
    /* XXX: realpath resolves symlinks, which we may not want. */
    if (realpath(fname, realpath_buf) != NULL) {
        status_check = drfront_access(realpath_buf, DRFRONT_EXIST, &access_ret);
        if (status_check != DRFRONT_SUCCESS) {
            *ret = false;
            return status_check;
        } else if (access_ret == true) {
            *ret = true;
            snprintf(full_path, full_path_size, "%s", realpath_buf);
            full_path[full_path_size - 1] = '\0';
            return DRFRONT_SUCCESS;
        }
    }

    cur = paths;
    end = strchr(paths, '\0');
    while (cur < end) {
        next = strchr(cur, ':');
        next = (next == NULL ? end : next);
        snprintf(tmp, BUFFER_SIZE_ELEMENTS(tmp),
                 "%.*s/%s", (int)(next - cur), cur, fname);
        NULL_TERMINATE_BUFFER(tmp);
        /* realpath checks for existence too. */
        if (realpath(tmp, realpath_buf) != NULL) {
            status_check = drfront_access(realpath_buf, DRFRONT_EXIST, &access_ret);
            if (status_check != DRFRONT_SUCCESS) {
                *ret = false;
                return status_check;
            } else if (access_ret == true) {
                *ret = true;
                snprintf(full_path, full_path_size, "%s", realpath_buf);
                full_path[full_path_size - 1] = '\0';
                return DRFRONT_SUCCESS;
            }
        }
        cur = next + 1;
    }
    full_path[0] = '\0';
    *ret = false;
    return DRFRONT_ERROR;
}
Ejemplo n.º 10
0
static void
display_error_helper(wchar_t *msg)
{
    wchar_t title_buf[MAX_PATH + 64];
    _snwprintf(title_buf, BUFFER_SIZE_ELEMENTS(title_buf),
               L_PRODUCT_NAME L" Notice: %hs(%hs)", get_application_name(),
               get_application_pid());
    NULL_TERMINATE_BUFFER(title_buf);
    nt_messagebox(msg, title_buf);
}
Ejemplo n.º 11
0
/* XXX i#1440: share w/ Linux */
static bool
handle_strarray_access(sysarg_iter_info_t *ii, const sysinfo_arg_t *arg_info,
                       app_pc start, uint size)
{
    char id[16];
    dr_snprintf(id, BUFFER_SIZE_ELEMENTS(id), "%s%d", "parameter #", arg_info->param);
    NULL_TERMINATE_BUFFER(id);
    check_strarray(ii, (char **)start, arg_info->param, id);
    return true; /* check_strarray checks whole array */
}
Ejemplo n.º 12
0
int
main(int argc, char *argv[])
{
    /* Test reading the symlink via readlink */
    char proc[64];
    char buf[512];
    ssize_t res;
    snprintf(proc, BUFFER_SIZE_ELEMENTS(proc), "/proc/%d/exe", getpid());
    NULL_TERMINATE_BUFFER(proc);
    res = readlink(proc, buf, BUFFER_SIZE_ELEMENTS(buf));
    NULL_TERMINATE_BUFFER(buf);
    if (res > 0 && strrchr(buf, '/') != NULL)
        print("/proc/pid/exe points to %s\n", strrchr(buf, '/') + 1);
    else {
        perror("readlink failed");
        return 1;
    }
    /* XXX: another good test would be to make a thread and use /proc/tid/exe */

    /* Test executing the symlink via execve.
     * We invoked ourselves initially with an arg, to avoid repeated execs.
     */
    if (argc > 1) {
        pid_t child = fork();
        if (child < 0) {
            perror("fork failed");
        } else if (child > 0) {
            pid_t result = waitpid(child, NULL, 0);
            assert(result == child);
            print("child has exited\n");
        } else {
            const char *arg[2] = { proc, NULL };
            /* Update for child's pid */
            snprintf(proc, BUFFER_SIZE_ELEMENTS(proc), "/proc/%d/exe", getpid());
            NULL_TERMINATE_BUFFER(proc);
            res = execve(proc, (char **)arg, NULL /*env*/);
            if (res < 0)
                perror("execve failed");
        }
    }
    return 0;
}
Ejemplo n.º 13
0
void
display_error(char *msg)
{
# ifdef DISABLED /* going with msgbox always! */
    fprintf(FP, msg);
# else
    wchar_t buf[512];
    _snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf), L"%hs", msg);
    NULL_TERMINATE_BUFFER(buf);
    display_error_helper(buf);
# endif
}
Ejemplo n.º 14
0
drcovlib_status_t
drcovlib_init(drcovlib_options_t *ops)
{
    int count = dr_atomic_add32_return_sum(&drcovlib_init_count, 1);
    if (count > 1)
        return DRCOVLIB_SUCCESS;

    if (ops->struct_size != sizeof(options))
        return DRCOVLIB_ERROR_INVALID_PARAMETER;
    if ((ops->flags & (~(DRCOVLIB_DUMP_AS_TEXT|DRCOVLIB_THREAD_PRIVATE))) != 0)
        return DRCOVLIB_ERROR_INVALID_PARAMETER;
    if (TEST(DRCOVLIB_THREAD_PRIVATE, ops->flags)) {
        if (!dr_using_all_private_caches())
            return DRCOVLIB_ERROR_INVALID_SETUP;
        drcov_per_thread = true;
    }
    options = *ops;
    if (options.logdir != NULL)
        dr_snprintf(logdir, BUFFER_SIZE_ELEMENTS(logdir), "%s", ops->logdir);
    else /* default */
        dr_snprintf(logdir, BUFFER_SIZE_ELEMENTS(logdir), ".");
    NULL_TERMINATE_BUFFER(logdir);
    options.logdir = logdir;
    if (options.native_until_thread > 0)
        go_native = true;

    drmgr_init();
    drx_init();

    /* We follow a simple model of the caller requesting the coverage dump,
     * either via calling the exit routine, using its own soft_kills nudge, or
     * an explicit dump call for unusual cases.  This means that drx's
     * soft_kills remains inside the outer later, i.e., the drcov client.  This
     * is the easiest approach for coordinating soft_kills among many libraries.
     * Thus, we do *not* register for an exit event here.
     */

    drmgr_register_thread_init_event(event_thread_init);
    drmgr_register_thread_exit_event(event_thread_exit);
    drmgr_register_bb_instrumentation_event(event_basic_block_analysis, NULL, NULL);
    dr_register_filter_syscall_event(event_filter_syscall);
    drmgr_register_pre_syscall_event(event_pre_syscall);
#ifdef UNIX
    dr_register_fork_init_event(event_fork);
#endif

    tls_idx = drmgr_register_tls_field();
    if (tls_idx == -1)
        return DRCOVLIB_ERROR;

    return event_init();
}
Ejemplo n.º 15
0
static void
check_prctl_whitelist(byte *prctl_arg1)
{
    /* disable instrumentation on seeing prctl(PR_SET_NAME) that does not
     * match any of the specified ,-separated names (PR 574018)
     */
    char nm[PRCTL_NAME_SZ+1];
    ASSERT(options.prctl_whitelist[0] != '\0', "caller should check for empty op");
    if (safe_read(prctl_arg1, PRCTL_NAME_SZ, nm)) {
        bool on_whitelist = false;
        char *s, *next;
        char *list_end = options.prctl_whitelist + strlen(options.prctl_whitelist);
        size_t white_sz;
        NULL_TERMINATE_BUFFER(nm);
        LOG(1, "prctl set name %s\n", nm);
        s = options.prctl_whitelist;
        while (s < list_end) {
            next = strchr(s, ',');
            if (next == NULL)
                white_sz = (list_end - s);
            else
                white_sz = (next - s);
            LOG(2, "comparing \"%s\" with whitelist entry \"%.*s\" sz=%d\n",
                nm, white_sz, s, white_sz);
            if (strncmp(nm, s, white_sz) == 0) {
                LOG(0, "prctl name %s matches whitelist\n", nm);
                on_whitelist = true;
                break;
            }
            s += white_sz + 1 /* skip , itself */;
        }
        if (!on_whitelist) {
            /* ideally: suspend world, then set options, then flush
             * w/o resuming.
             * FIXME: just setting options is unsafe if another thread
             * hits an event and fails to restore state or sthg.
             * Fortunately we expect most uses of PR_SET_NAME to be
             * immediately after forking.
             * Ideally we'd call dr_suspend_all_other_threads()
             * and nest dr_flush_region() inside it but both want
             * the same master lock: should check whether easy to support
             * via internal vars indicating whether lock held.
             */
            ELOGF(0, f_global, "\n*********\nDISABLING MEMORY CHECKING for %s\n", nm);
            options.shadowing = false;
            options.track_allocs = false;
            options.count_leaks = false;
            dr_flush_region(0, ~((ptr_uint_t)0));
        }
    }
}
Ejemplo n.º 16
0
void
loader_init(void)
{
    uint i;
    privmod_t *mod;

    acquire_recursive_lock(&privload_lock);
    VMVECTOR_ALLOC_VECTOR(modlist_areas, GLOBAL_DCONTEXT,
                          VECTOR_SHARED | VECTOR_NEVER_MERGE
                          /* protected by privload_lock */
                          | VECTOR_NO_LOCK,
                          modlist_areas);
    /* os specific loader initialization prologue before finalize the load */
    os_loader_init_prologue();

    /* Process client libs we loaded early but did not finalize */
    for (i = 0; i < privmod_static_idx; i++) {
        /* Transfer to real list so we can do normal processing */
        char name_copy[MAXIMUM_PATH];
        mod = privload_insert(NULL,
                              privmod_static[i].base,
                              privmod_static[i].size,
                              privmod_static[i].name,
                              privmod_static[i].path);
        LOG(GLOBAL, LOG_LOADER, 1, "%s: processing imports for %s\n",
            __FUNCTION__, mod->name);
        /* save a copy for error msg, b/c mod will be unloaded (i#643) */
        snprintf(name_copy, BUFFER_SIZE_ELEMENTS(name_copy), "%s", mod->name);
        NULL_TERMINATE_BUFFER(name_copy);
        if (!privload_load_finalize(mod)) {
            mod = NULL; /* it's been unloaded! */
#ifdef CLIENT_INTERFACE
            SYSLOG(SYSLOG_ERROR, CLIENT_LIBRARY_UNLOADABLE, 5,
                   get_application_name(), get_application_pid(), name_copy,
                   "\n\tUnable to locate imports of client library");
#endif
            os_terminate(NULL, TERMINATE_PROCESS);
            ASSERT_NOT_REACHED();
        }
    }
    /* os specific loader initialization epilogue after finalize the load */
    os_loader_init_epilogue();
    /* FIXME i#338: call loader_thread_init here once get
     * loader_init called after dynamo_thread_init but in a way that
     * works with Windows
     */
    release_recursive_lock(&privload_lock);
}
Ejemplo n.º 17
0
void
internal_error(char *file, int line, char *expr)
{
#ifdef INTERNAL
#    define FILENAME_LENGTH L""
#else
    /* truncate file name to first character */
#    define FILENAME_LENGTH L".1"
#endif
    wchar_t buf[512];
    _snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf),
               L"Preinject Error %" FILENAME_LENGTH L"hs:%d %hs\n", file, line, expr);
    NULL_TERMINATE_BUFFER(buf);
    display_error_helper(buf);
    TerminateProcess(GetCurrentProcess(), (uint)-1);
}
Ejemplo n.º 18
0
static bool
inject_ld_preload(dr_inject_info_t *info, const char *library_path)
{
    if (info->exec_self) {
        pre_execve_ld_preload(library_path);
        execv(info->exe, (char **) info->argv);
        return false;  /* if execv returns, there was an error */
    } else {
        /* Write the path to DR to the pipe. */
        char cmd[MAXIMUM_PATH];
        snprintf(cmd, BUFFER_SIZE_ELEMENTS(cmd), "ld_preload %s", library_path);
        NULL_TERMINATE_BUFFER(cmd);
        write_pipe_cmd(info->pipe_fd, cmd);
    }
    return true;
}
Ejemplo n.º 19
0
/* Environment modifications before executing the child process for LD_PRELOAD
 * injection.
 */
static void
pre_execve_ld_preload(const char *dr_path)
{
    char ld_lib_path[MAX_OPTIONS_STRING];
    const char *last_slash = NULL;
    const char *mode_slash = NULL;
    const char *lib_slash = NULL;
    const char *cur_path = getenv("LD_LIBRARY_PATH");
    const char *cur = dr_path;
    /* Find last three occurrences of '/'. */
    while (*cur != '\0') {
        if (*cur == '/') {
            lib_slash = mode_slash;
            mode_slash = last_slash;
            last_slash = cur;
        }
        cur++;
    }
    /* dr_path should be absolute and have at least three components. */
    ASSERT(lib_slash != NULL && last_slash != NULL);
    ASSERT(strncmp(lib_slash, "/lib32", 5) == 0 ||
           strncmp(lib_slash, "/lib64", 5) == 0);
    /* Put both DR's path and the extension path on LD_LIBRARY_PATH.  We only
     * need the extension path if -no_private_loader is used.
     */
    snprintf(ld_lib_path, BUFFER_SIZE_ELEMENTS(ld_lib_path),
             "%.*s:%.*s/ext%.*s%s%s",
             last_slash - dr_path, dr_path,      /* DR path */
             lib_slash - dr_path, dr_path,       /* pre-ext path */
             last_slash - lib_slash, lib_slash,  /* libNN component */
             cur_path == NULL ? "" : ":",
             cur_path == NULL ? "" : cur_path);
    NULL_TERMINATE_BUFFER(ld_lib_path);
    setenv("LD_LIBRARY_PATH", ld_lib_path, true/*overwrite*/);
    setenv("LD_PRELOAD", "libdynamorio.so libdrpreload.so",
           true/*overwrite*/);
    if (verbose) {
        printf("Setting LD_USE_LOAD_BIAS for PIEs so the loader will honor "
               "DR's preferred base. (i#719)\n"
               "Set LD_USE_LOAD_BIAS=0 prior to injecting if this is a "
               "problem.\n");
    }
    setenv("LD_USE_LOAD_BIAS", "1", false/*!overwrite, let user set it*/);
}
Ejemplo n.º 20
0
static void
display_error_helper(wchar_t *msg)
{
    wchar_t title_buf[MAX_PATH + 64];
    _snwprintf(title_buf, BUFFER_SIZE_ELEMENTS(title_buf),
              L_PRODUCT_NAME L" Notice: %hs(%hs)",
              get_application_name(), get_application_pid());
    NULL_TERMINATE_BUFFER(title_buf);

    /* for unit tests: assume that if a limit is set, we are in a
     *  script so it's ok to just display to stderr. avoids hangs when
     *  an error is encountered. */
    if (limit <= 0)
        nt_messagebox(msg, title_buf);
    else {
        fprintf(FP, "\n\n%ls\n%ls\n\n", title_buf, msg);
        fflush(FP);
    }
}
Ejemplo n.º 21
0
static bool
inject_early(dr_inject_info_t *info, const char *library_path)
{
    if (info->exec_self) {
        /* exec DR with the original command line and set an environment
         * variable pointing to the real exe.
         */
        pre_execve_early(info->exe);
        execv(library_path, (char **) info->argv);
        return false;  /* if execv returns, there was an error */
    } else {
        /* Write the path to DR to the pipe. */
        char cmd[MAXIMUM_PATH];
        snprintf(cmd, BUFFER_SIZE_ELEMENTS(cmd), "exec_dr %s", library_path);
        NULL_TERMINATE_BUFFER(cmd);
        write_pipe_cmd(info->pipe_fd, cmd);
    }
    return true;
}
Ejemplo n.º 22
0
drfront_status_t
drfront_get_absolute_path(const char *src, OUT char *buf, size_t buflen/*# elements*/)
{
    TCHAR wsrc[MAX_PATH];
    TCHAR wdst[MAX_PATH];
    drfront_status_t status_check = DRFRONT_ERROR;
    int res;

    status_check = drfront_char_to_tchar(src, wsrc, BUFFER_SIZE_ELEMENTS(wsrc));
    if (status_check != DRFRONT_SUCCESS)
        return status_check;

    res = GetFullPathName(wsrc, BUFFER_SIZE_ELEMENTS(wdst), wdst, NULL);
    if (res <= 0)
        return DRFRONT_ERROR;

    NULL_TERMINATE_BUFFER(wdst);
    status_check = drfront_tchar_to_char(wdst, buf, buflen);
    return status_check;
}
Ejemplo n.º 23
0
/* i#200/PR 459481: communicate child pid via file.
 * We don't need this on unix b/c we use exec.
 */
static void
write_pid_to_file(const char *pidfile, process_id_t pid)
{
    TCHAR wpidfile[MAXIMUM_PATH];
    HANDLE f;
    char_to_tchar(pidfile, wpidfile, BUFFER_SIZE_ELEMENTS(wpidfile));
    f = CreateFile(wpidfile, GENERIC_WRITE, FILE_SHARE_READ,
                          NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (f == INVALID_HANDLE_VALUE) {
        warn("cannot open %s: %d", pidfile, GetLastError());
    } else {
        char pidbuf[16];
        BOOL ok;
        DWORD written;
        _snprintf(pidbuf, BUFFER_SIZE_ELEMENTS(pidbuf), "%d\n", pid);
        NULL_TERMINATE_BUFFER(pidbuf);
        ok = WriteFile(f, pidbuf, (DWORD)strlen(pidbuf), &written, NULL);
        assert(ok && written == strlen(pidbuf));
        CloseHandle(f);
    }
}
Ejemplo n.º 24
0
static void
handle_pre_execve(void *drcontext)
{
#ifndef USE_DRSYMS
    /* PR 453867: tell postprocess.pl to watch for new logdir and
     * fork a new copy.
     * FIXME: what if syscall fails?  Punting on that for now.
     * Note that if it fails and then a later one succeeds, postprocess.pl
     * will replace the first with the last.
     */
    char logdir[MAXIMUM_PATH]; /* one reason we're not inside os_post_syscall() */
    size_t bytes_read = 0;
    /* Not using safe_read() since we want a partial read if hits page boundary */
    dr_safe_read((void *) dr_syscall_get_param(drcontext, 0),
                 BUFFER_SIZE_BYTES(logdir), logdir, &bytes_read);
    if (bytes_read < BUFFER_SIZE_BYTES(logdir))
        logdir[bytes_read] = '\0';
    NULL_TERMINATE_BUFFER(logdir);
    ELOGF(0, f_fork, "EXEC path=%s\n", logdir);
#endif
}
Ejemplo n.º 25
0
/* caller holds lock */
static const char *
drsym_pecoff_symbol_name(pecoff_data_t *mod, IMAGE_SYMBOL *sym)
{
    static char buf[sizeof(sym->N.ShortName)+1];
    const char *name;
    size_t name_sz;
    if (sym->N.Name.Short == 0) {
        /* longer than 8 chars, so index into string table */
        name = mod->string_table + sym->N.Name.Long;
        name_sz = strlen(name);
    } else {
        const char *c;
        name = (const char *) sym->N.ShortName;
        /* not null-terminated if 8 chars.  caller holds lock so we can
         * use a static buffer to add a NULL, which caller requires.
         */
        for (c = name; c < name + sizeof(sym->N.ShortName); c++) {
            if (*c == '\0')
                break;
        }
        if (c == name + sizeof(sym->N.ShortName)) {
            memcpy(buf, name, sizeof(sym->N.ShortName));
            NULL_TERMINATE_BUFFER(buf);
            name = buf;
        }
    }
#ifndef X64
    /* XXX: all 32-bit pecoff symtables I've seen have leading underscores,
     * which we drop here.  Is this always the case?  It's true for Cygwin
     * gcc 3.4.4 and MinGW gcc 4.6.1.  It's NOT true for MinGWx64 gcc 4.7.0
     * but there's no 4.7 32-bit so I'm not sure whether the next MinGW
     * release is going to break us.  We at least don't remove from "_Z"
     * so we'll work w/ mangled names (though there could be a C name that
     * starts w/ Z that was added by the linker and should start w/ _?)
     */
    if (name[0] == '_' && name[1] != 'Z')
        name++;
#endif
    return name;
}
Ejemplo n.º 26
0
drfront_status_t
drfront_set_client_symbol_search_path(const char *symdir, bool ignore_env,
                                      OUT char *symsrv_path, size_t symsrv_path_sz)
{
    char app_symsrv_path[MAX_SYMSRV_PATH];
    TCHAR wapp_symsrv_path[MAX_SYMSRV_PATH];
    char tmp_srv_path[MAX_SYMSRV_PATH];
    char tmp_symsrv_path[MAX_SYMSRV_PATH];
    char *cur;
    char *end;
    size_t sofar;
    ssize_t len;
    bool has_srv;
    bool dir_exists;
    bool has_ms_symsrv;
    drfront_status_t sc;
    static const char ms_symsrv[] = "http://msdl.microsoft.com/download/symbols";

    if (sym_set_path_func == NULL)
        return DRFRONT_ERROR_LIB_UNSUPPORTED;
    /* If the user set a non-empty _NT_SYMBOL_PATH, then we use that.
     * If not, we use symdir/symbols path and make sure it exists.
     */
    if (ignore_env ||
        drfront_get_env_var("_NT_SYMBOL_PATH", tmp_symsrv_path,
                            BUFFER_SIZE_ELEMENTS(tmp_symsrv_path)) != DRFRONT_SUCCESS ||
        strlen(tmp_symsrv_path) == 0) {
        char pdb_dir[MAXIMUM_PATH];
        _snprintf(pdb_dir, BUFFER_SIZE_ELEMENTS(pdb_dir), "%s/symbols", symdir);
        NULL_TERMINATE_BUFFER(pdb_dir);
        drfront_string_replace_character(pdb_dir, '/', '\\'); /* canonicalize */
        sc = drfront_create_dir(pdb_dir);
        if ((sc != DRFRONT_SUCCESS && sc != DRFRONT_ERROR_FILE_EXISTS) ||
            drfront_access(pdb_dir, DRFRONT_READ, &dir_exists) != DRFRONT_SUCCESS ||
            !dir_exists) {
            DO_DEBUG(DL_WARN,
                     printf("Failed to create directory for symbols: %s\n", pdb_dir);
                     );
Ejemplo n.º 27
0
LSTATUS
WINAPI
redirect_RegOpenKeyExA(
    __in HKEY hKey,
    __in_opt LPCSTR lpSubKey,
    __in_opt DWORD ulOptions,
    __in REGSAM samDesired,
    __out PHKEY phkResult
    )
{
    wchar_t *wkey;
    wchar_t wbuf[MAX_REG_KEY_NAME_LEN];
    int len;
    if (lpSubKey != NULL) {
        len = _snwprintf(wbuf, BUFFER_SIZE_ELEMENTS(wbuf), L"%hs", lpSubKey);
        if (len < 0 || len >= BUFFER_SIZE_ELEMENTS(wbuf))
            return ERROR_INVALID_PARAMETER;
        NULL_TERMINATE_BUFFER(wbuf); /* be paranoid */
        wkey = wbuf;
    } else
        wkey = NULL;
    return redirect_RegOpenKeyExW(hKey, wkey, ulOptions, samDesired, phkResult);
}
Ejemplo n.º 28
0
static void
wrap_post_SSL_read(void *wrapcxt, void *user_data)
{
    struct SSL_read_data *sd = (struct SSL_read_data *)user_data;

    int actual_read = (int)(ptr_int_t)drwrap_get_retval(wrapcxt);

    char filename[MAXIMUM_PATH] = { 0 };
    dr_snprintf(filename, BUFFER_SIZE_ELEMENTS(filename), "trace-%x.read", sd->ssl);
    NULL_TERMINATE_BUFFER(filename);
    FILE *fp = fopen(filename, "ab+");
    if (!fp) {
        dr_fprintf(STDERR, "Couldn’t open the output file %s\n", filename);
        dr_global_free(user_data, sizeof(struct SSL_read_data));
        return;
    }

    if (actual_read > 0) {
        fwrite(sd->read_buffer, 1, actual_read, fp);
    }
    fclose(fp);

    dr_global_free(user_data, sizeof(struct SSL_read_data));
}
Ejemplo n.º 29
0
static bool load_dynamorio_lib(IF_NOT_X64(bool x64_in_wow64))
{
    HMODULE dll = NULL;
    char path[MAX_PATH];
#ifdef DEBUG
    char msg[3 * MAX_PATH];
#endif
    int retval = -1; /* failure */
#ifndef X64
    bool wow64 = is_wow64_process(NT_CURRENT_PROCESS);
    if (x64_in_wow64) {
        ASSERT(wow64);
        retval = get_parameter_64(PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), path, MAX_PATH);
    } else
#endif
        retval = get_parameter(PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), path, MAX_PATH);
    if (IS_GET_PARAMETER_SUCCESS(retval)) {
        dr_marker_t mark;
        VERBOSE_MESSAGE("Loading \"%hs\"", path);
        /* The read_and_verify_dr_marker is the canonical check for dr in a
         * process, we double check against GetModuleHandle here just to be
         * extra safe (in case dr failed to initialize before).  Note that
         * GetModuleHandle won't find dr's dll if we implement certian -hide
         * or early_injection proposals. */
        if (read_and_verify_dr_marker(GetCurrentProcess(), &mark) != DR_MARKER_FOUND &&
            GetModuleHandle(DYNAMORIO_LIBRARY_NAME) == NULL
#ifndef X64    /* these ifdefs are rather ugly: just export all routines in x64 builds? */
            && /* check for 64-bit as well */
            (!wow64 ||
             read_and_verify_dr_marker_64(GetCurrentProcess(), &mark) != DR_MARKER_FOUND)
        /* FIXME PR 251677: need 64-bit early injection to fully test
         * read_and_verify_dr_marker_64
         */
#endif
        ) {
            /* OK really going to load dr now, verify that we are injecting
             * early enough (i.e. user32.dll is statically linked).  This
             * presumes preinject is only used with app_init injection which is
             * currently the case. FIXME - should we also check_sole_thread
             * here?  We can't really handle more then one thread when dr is
             * loading, but this can happen with early remote injected threads
             * many of which (CTRL) are relatively harmless.
             */
            LDR_MODULE *mod = get_ldr_module_by_name(L"user32.dll");
            ASSERT(mod != NULL);
            if (ldr_module_statically_linked(mod)) {
#ifndef X64
                if (x64_in_wow64)
                    dll = load_library_64(path);
                else
#endif
                    dll = LoadLibrary(path);
            } else {
                /* FIXME - would be really nice to communicate this back to
                 * the controller. */
#ifdef DEBUG
                _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
                          PRODUCT_NAME
                          " Error: improper injection - " PRODUCT_NAME
                          " (%s) can't inject into process %s (%s) (user32.dll "
                          "not statically linked)\n",
                          path, get_application_name(), get_application_pid());
                NULL_TERMINATE_BUFFER(msg);
                display_error(msg);
#endif
            }
        } else {
            /* notify failure only in debug builds, otherwise just return */
#ifdef DEBUG
            /* with early injection this becomes even more likely */
            if (read_and_verify_dr_marker(GetCurrentProcess(), &mark) == DR_MARKER_FOUND
#    ifndef X64
                || (wow64 &&
                    read_and_verify_dr_marker_64(GetCurrentProcess(), &mark) ==
                        DR_MARKER_FOUND)
#    endif
            ) {
                /* ok, early injection should always beat this */
#    if VERBOSE
                /* can't readily tell what was expected */
                _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
                          PRODUCT_NAME " ok if early injection, otherwise ERROR: "
                                       "double injection, " PRODUCT_NAME
                                       " (%s) is already loaded "
                                       "in process %s (%s), continuing\n",
                          path, get_application_name(), get_application_pid());
                NULL_TERMINATE_BUFFER(msg);
                display_error(msg);
#    endif /* VERBOSE */
            } else {
                /* if GetModuleHandle finds us but we don't have a marker
                 * we may have failed somehow */
                _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg),
                          PRODUCT_NAME
                          " Error: failed injection, " PRODUCT_NAME " (%s) is "
                          "loaded but not initialized in process %s (%s), continuing\n",
                          path, get_application_name(), get_application_pid());
                NULL_TERMINATE_BUFFER(msg);
                display_error(msg);
            }
#endif /* DEBUG */
            return false;
        }
    } else
        path[0] = 0;
    if (dll == NULL) {
#ifdef DEBUG
        int err = GetLastError();
        _snprintf(msg, BUFFER_SIZE_ELEMENTS(msg), PRODUCT_NAME " Error %d loading %s\n",
                  err, path);
        NULL_TERMINATE_BUFFER(msg);
        display_error(msg);
#endif
        return false;
    } else {
        int_func_t init_func;
        void_func_t take_over_func;
        int res;
#ifndef X64
        if (x64_in_wow64) {
            init_func = (int_func_t)(ptr_uint_t) /*we know <4GB*/
                get_proc_address_64((uint64)dll, "dynamorio_app_init");
            take_over_func = (void_func_t)(ptr_uint_t) /*we know <4GB*/
                get_proc_address_64((uint64)dll, "dynamorio_app_take_over");
            VERBOSE_MESSAGE("dynamorio_app_init: 0x%08x; dynamorio_app_take_over: "
                            "0x%08x\n",
                            init_func, take_over_func);
        } else {
#endif
            init_func = (int_func_t)GetProcAddress(dll, "dynamorio_app_init");
            take_over_func = (void_func_t)GetProcAddress(dll, "dynamorio_app_take_over");
#ifndef X64
        }
#endif
        if (init_func == NULL || take_over_func == NULL) {
            /* unload the library so that it's clear DR is not in control
             * (o/w the DR library is in the process and it's not clear
             * what's going on)
             */
#ifndef X64
            if (x64_in_wow64) {
#    ifdef DEBUG
                bool ok =
#    endif
                    free_library_64(dll);
                ASSERT(ok);
            } else
#endif
                FreeLibrary(dll);
#ifdef DEBUG
            display_error("Error getting " PRODUCT_NAME " functions\n");
#endif
            return false;
        }
        VERBOSE_MESSAGE("about to inject dynamorio");
#ifndef X64
        if (x64_in_wow64)
            res = switch_modes_and_call(init_func, NULL, NULL, NULL);
        else
#endif
            res = (*init_func)();
        VERBOSE_MESSAGE("dynamorio_app_init() returned %d\n", res);
#ifndef X64
        if (x64_in_wow64)
            switch_modes_and_call(take_over_func, NULL, NULL, NULL);
        else
#endif
            (*take_over_func)();
        VERBOSE_MESSAGE("inside " PRODUCT_NAME " now\n");
    }
    return true;
}
Ejemplo n.º 30
0
int
_tmain(int argc, TCHAR *targv[])
{
    char **argv;
    char *process = NULL;
    char *dr_root = NULL;
    char *drheapstat_root = NULL;
    char default_dr_root[MAXIMUM_PATH];
    char default_drheapstat_root[MAXIMUM_PATH];
    char client_path[MAXIMUM_PATH];

    char client_ops[MAX_DR_CMDLINE];
    size_t cliops_sofar = 0; /* for BUFPRINT to client_ops */
    char dr_ops[MAX_DR_CMDLINE];
    size_t drops_sofar = 0; /* for BUFPRINT to dr_ops */
    ssize_t len; /* shared by all BUFPRINT */

    char logdir[MAXIMUM_PATH];
    bool have_logdir = false;
    bool use_root_for_logdir;

#ifdef WINDOWS
    char *pidfile = NULL;
#endif
#ifndef MACOS /* XXX i#1286: implement nudge on MacOS */
    process_id_t nudge_pid = 0;
#endif
    bool use_dr_debug = false;
    bool use_drheapstat_debug = false;

    char *app_name;
    char full_app_name[MAXIMUM_PATH];
    char **app_argv;

    int errcode;
    void *inject_data;
    int i;
    char *c;
    char buf[MAXIMUM_PATH];
    process_id_t pid;
    bool exit0 = false;
    bool dr_logdir_specified = false;
    bool doubledash_present = false;

    drfront_status_t sc;
    bool res;
#ifndef X64
    bool is64, is32;
#endif

    dr_standalone_init();

#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\n", sc);
#endif
    /* Default root: we assume this exe is <root>/bin/drheapstat.exe */
    get_full_path(argv[0], buf, BUFFER_SIZE_ELEMENTS(buf));
    c = buf + strlen(buf) - 1;
    while (*c != DIRSEP && *c != ALT_DIRSEP && c > buf)
        c--;
    _snprintf(c+1, BUFFER_SIZE_ELEMENTS(buf) - (c+1-buf), "../dynamorio");
    NULL_TERMINATE_BUFFER(buf);
    get_absolute_path(buf, default_dr_root, BUFFER_SIZE_ELEMENTS(default_dr_root));
    NULL_TERMINATE_BUFFER(default_dr_root);
    dr_root = default_dr_root;

    /* assuming we're in bin/ (mainly due to CPack NSIS limitations) */
    _snprintf(c+1, BUFFER_SIZE_ELEMENTS(buf) - (c+1-buf), "..");
    NULL_TERMINATE_BUFFER(buf);
    get_absolute_path(buf, default_drheapstat_root,
                      BUFFER_SIZE_ELEMENTS(default_drheapstat_root));
    NULL_TERMINATE_BUFFER(default_drheapstat_root);
    drheapstat_root = default_drheapstat_root;
    string_replace_character(drheapstat_root, ALT_DIRSEP, DIRSEP); /* canonicalize */

    BUFPRINT(dr_ops, BUFFER_SIZE_ELEMENTS(dr_ops),
             drops_sofar, len, "%s ", DEFAULT_DR_OPS);

    client_ops[0] = '\0';

    /* default logdir */
    if (drfront_appdata_logdir(drheapstat_root, "Dr. Heapstat", &use_root_for_logdir,
                               logdir, BUFFER_SIZE_ELEMENTS(logdir)) == DRFRONT_SUCCESS
        && !use_root_for_logdir) {
        if ((dr_create_dir(logdir) || dr_directory_exists(logdir)) &&
            file_is_writable(logdir))
            have_logdir = true;
    }
    if (!have_logdir) {
        _snprintf(logdir, BUFFER_SIZE_ELEMENTS(logdir), "%s%cdrheapstat%clogs",
                  drheapstat_root, DIRSEP, DIRSEP);
        NULL_TERMINATE_BUFFER(logdir);
        if (!file_is_writable(logdir)) {
            _snprintf(logdir, BUFFER_SIZE_ELEMENTS(logdir), "%s%clogs",
                      drheapstat_root, DIRSEP);
            NULL_TERMINATE_BUFFER(logdir);
            if (file_is_writable(logdir))
                have_logdir = true;
        } else
            have_logdir = true;
    } else
        have_logdir = true;
    if (!have_logdir) {
        /* try logs in cur dir */
        get_absolute_path("./logs", logdir, BUFFER_SIZE_ELEMENTS(logdir));
        NULL_TERMINATE_BUFFER(logdir);
        if (!file_is_writable(logdir)) {
            /* try cur dir */
            get_absolute_path(".", logdir, BUFFER_SIZE_ELEMENTS(logdir));
            NULL_TERMINATE_BUFFER(logdir);
        }
    }

    /* parse command line */
    /* FIXME PR 487993: use optionsx.h to construct this parsing code */
    for (i=1; i<argc; i++) {
        if (strcmp(argv[i], "--") == 0) {
            doubledash_present = true;
            break;
        }
    }
    for (i=1; i<argc; i++) {
        /* note that we pass unknown args to client, until -- */
        if (strcmp(argv[i], "--") == 0) {
            i++;
            break;
        }
        /* drag-and-drop does not include "--" so we try to identify the app.
         * we explicitly parse -logdir and -suppress, and all the other
         * client ops that take args take numbers so this should be safe.
         */
        else if (argv[i][0] != '-' && !doubledash_present && ends_in_exe(argv[i])) {
            /* leave i alone: this is the app itself */
            break;
        }
        else if (strcmp(argv[i], "-v") == 0) {
            verbose = true;
            continue;
        }
        else if (strcmp(argv[i], "-h") == 0 ||
                 strcmp(argv[i], "--help") == 0) {
            print_usage(true/*full*/);
            exit(0);
        }
        else if (strcmp(argv[i], "-dr_debug") == 0) {
            use_dr_debug = true;
            continue;
        }
        else if (strcmp(argv[i], "-dr_release") == 0) {
            use_dr_debug = false;
            continue;
        }
        else if (strcmp(argv[i], "-debug") == 0) {
            use_drheapstat_debug = true;
            continue;
        }
        else if (strcmp(argv[i], "-release") == 0) {
            use_drheapstat_debug = false;
            continue;
        }
        else if (!strcmp(argv[i], "-version")) {
#if defined(BUILD_NUMBER) && defined(VERSION_NUMBER)
          printf("Dr. Heapstat version %s -- build %d\n", VERSION_STRING, BUILD_NUMBER);
#elif defined(BUILD_NUMBER)
          printf("Dr. Heapstat custom build %d -- %s\n", BUILD_NUMBER, __DATE__);
#elif defined(VERSION_NUMBER)
          printf("Dr. Heapstat version %s -- custom build %s, %s\n",
                 VERSION_STRING, __DATE__, __TIME__);
#else
          printf("Dr. Heapstat custom build -- %s, %s\n", __DATE__, __TIME__);
#endif
          exit(0);
        }
        else if (strcmp(argv[i], "-dr") == 0) {
            if (i >= argc - 1)
                usage("invalid arguments");
            dr_root = argv[++i];
        }
        else if (strcmp(argv[i], "-drheapstat") == 0) {
            if (i >= argc - 1)
                usage("invalid arguments");
            drheapstat_root = argv[++i];
        }
        else if (strcmp(argv[i], "-follow_children") == 0 ||
                 strcmp(argv[i], "-no_follow_children") == 0) {
            BUFPRINT(dr_ops, BUFFER_SIZE_ELEMENTS(dr_ops),
                     drops_sofar, len, "%s ", argv[i]);
        }
#ifndef MACOS /* XXX i#1286: implement nudge on MacOS */
        else if (strcmp(argv[i], "-nudge") == 0) {
            if (i >= argc - 1)
                usage("invalid arguments");
            nudge_pid = strtoul(argv[++i], NULL, 10);
        }
#endif
        else if (strcmp(argv[i], "-dr_ops") == 0) {
            if (i >= argc - 1)
                usage("invalid arguments");
            /* Slight risk of some other option containing "-logdir " but
             * -dr_ops is really only be used by Dr. Memory developers anyway.
             */
            if (strstr(argv[i+1], "-logdir ") != NULL)
                dr_logdir_specified = true;
            BUFPRINT(dr_ops, BUFFER_SIZE_ELEMENTS(dr_ops),
                     drops_sofar, len, "%s ", argv[++i]);
        }
#ifdef WINDOWS
        else if (strcmp(argv[i], "-pid_file") == 0) {
            if (i >= argc - 1)
                usage("invalid arguments");
            pidfile = argv[++i];
        }
#endif
        else if (strcmp(argv[i], "-logdir") == 0) {
            if (i >= argc - 1)
                usage("invalid arguments");
            /* make absolute */
            get_absolute_path(argv[++i], logdir, BUFFER_SIZE_ELEMENTS(logdir));
            NULL_TERMINATE_BUFFER(logdir);
            /* added to client ops below */
        }
        else if (strcmp(argv[i], "-exit0") == 0) {
            exit0 = true;
        } /* FIXME i#1653: add support for options the old perl frontend supported */
        else {
            /* pass to client */
            BUFPRINT(client_ops, BUFFER_SIZE_ELEMENTS(client_ops),
                     cliops_sofar, len, "`%s` ", argv[i]);
        }
    }

#ifndef MACOS /* XXX i#1286: implement nudge on MacOS */
    if (nudge_pid != 0) {
        if (i < argc)
            usage("%s", "-nudge does not take an app to run");
        /* could also complain about other client or app specific ops */
        res = dr_nudge_pid(nudge_pid, CLIENT_ID, NUDGE_LEAK_SCAN, INFINITE);
        if (res != DR_SUCCESS) {
            fatal("error nudging %d%s", nudge_pid,
                  (res == DR_NUDGE_PID_NOT_INJECTED) ? ": no such Dr. Heapstat process"
                  : "");
            assert(false); /* shouldn't get here */
        }
        exit(0);
    }
#endif

    if (i >= argc)
        usage("%s", "no app specified");
    app_name = argv[i];
    get_full_path(app_name, full_app_name, BUFFER_SIZE_ELEMENTS(full_app_name));
    if (full_app_name[0] != '\0')
        app_name = full_app_name;
    info("targeting application: \"%s\"", app_name);

    /* XXX: See drmemory/frontend.c for notes about our argv processing. */
    app_argv = &argv[i];
    if (verbose) {
        int j;
        c = buf;
        for (j = 0; app_argv[j] != NULL; j++) {
            c += _snprintf(c, BUFFER_SIZE_ELEMENTS(buf) - (c - buf),
                           "\"%s\" ", app_argv[j]);
        }
        NULL_TERMINATE_BUFFER(buf);
        assert(c - buf < BUFFER_SIZE_ELEMENTS(buf));
        info("app cmdline: %s", buf);
    }

#ifndef X64
    /* XXX: See drmemory/frontend.c for notes about 64-bit support. */
    if (drfront_is_64bit_app(app_name, &is64, &is32) == DRFRONT_SUCCESS &&
        is64 && !is32) {
        fatal("This Dr. Heapstat release does not support 64-bit applications.");
        goto error; /* actually won't get here */
    }
#endif

    if (!file_is_readable(dr_root)) {
        fatal("invalid -dr %s", dr_root);
        goto error; /* actually won't get here */
    }
    if (dr_root != default_dr_root) {
        /* Workaround for DRi#1082 where DR root path can't have ".." */
        get_absolute_path(dr_root, default_dr_root,
                          BUFFER_SIZE_ELEMENTS(default_dr_root));
        NULL_TERMINATE_BUFFER(default_dr_root);
        dr_root = default_dr_root;
    }
    _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf),
              "%s/"LIB_ARCH"/%s/%s", dr_root,
              use_dr_debug ? "debug" : "release", DR_LIB_NAME);
    NULL_TERMINATE_BUFFER(buf);
    if (!file_is_readable(buf)) {
        /* support debug build w/ integrated debug DR build and so no release */
        if (!use_dr_debug) {
            _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf),
                      "%s/"LIB_ARCH"/%s/%s", dr_root, "debug", DR_LIB_NAME);
            NULL_TERMINATE_BUFFER(buf);
            if (!file_is_readable(buf)) {
                fatal("cannot find DynamoRIO library %s", buf);
                goto error; /* actually won't get here */
            }
            warn("using debug DynamoRIO since release not found");
            use_dr_debug = true;
        }
    }

    /* once we have 64-bit we'll need to address the NSIS "bin/" requirement */
    _snprintf(client_path, BUFFER_SIZE_ELEMENTS(client_path),
              "%s%c"BIN_ARCH"%c%s%c%s", drheapstat_root, DIRSEP, DIRSEP,
              use_drheapstat_debug ? "debug" : "release", DIRSEP, DRHEAPSTAT_LIB_NAME);
    NULL_TERMINATE_BUFFER(client_path);
    if (!file_is_readable(client_path)) {
        if (!use_drheapstat_debug) {
            _snprintf(client_path, BUFFER_SIZE_ELEMENTS(client_path),
                      "%s%c"BIN_ARCH"%c%s%c%s", drheapstat_root,
                      DIRSEP, DIRSEP, "debug", DIRSEP, DRHEAPSTAT_LIB_NAME);
            NULL_TERMINATE_BUFFER(client_path);
            if (!file_is_readable(client_path)) {
                fatal("invalid -drheapstat_root: cannot find %s", client_path);
                goto error; /* actually won't get here */
            }
            /* try to avoid warning for devs running from build dir */
            _snprintf(buf, BUFFER_SIZE_ELEMENTS(client_path),
                      "%s%cCMakeCache.txt", drheapstat_root, DIRSEP);
            NULL_TERMINATE_BUFFER(buf);
            if (!file_is_readable(buf))
                warn("using debug Dr. Heapstat since release not found");
            use_drheapstat_debug = true;
        }
    }

    drfront_string_replace_character(logdir, ALT_DIRSEP, DIRSEP); /* canonicalize */
    if (!file_is_writable(logdir)) {
        fatal("invalid -logdir: cannot find/write %s", logdir);
        goto error; /* actually won't get here */
    }
    info("logdir is \"%s\"", logdir);
    BUFPRINT(client_ops, BUFFER_SIZE_ELEMENTS(client_ops),
             cliops_sofar, len, "-logdir `%s` ", logdir);

    /* Put DR logs inside drheapstat logdir */
    if (!dr_logdir_specified) {
        _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s%cdynamorio", logdir, DIRSEP);
        NULL_TERMINATE_BUFFER(buf);
        if (!dr_directory_exists(buf)) {
            if (!dr_create_dir(buf)) {
                /* check again in case of a race */
                if (!dr_directory_exists(buf)) {
                    fatal("cannot create %s", buf);
                    goto error; /* actually won't get here */
                }
            }
        }
        BUFPRINT(dr_ops, BUFFER_SIZE_ELEMENTS(dr_ops),
                 drops_sofar, len, "-logdir `%s` ", buf);
    }

#ifdef UNIX
    errcode = dr_inject_prepare_to_exec(app_name, (const char **)app_argv, &inject_data);
#else
    errcode = dr_inject_process_create(app_name, (const char **)app_argv, &inject_data);
#endif
    /* Mismatch is just a warning */
    if (errcode != 0 && errcode != WARN_IMAGE_MACHINE_TYPE_MISMATCH_EXE) {
#ifdef WINDOWS
        int sofar =
#endif
            _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);
        }
        NULL_TERMINATE_BUFFER(buf);
#endif
        fatal("%s", buf);
        goto error; /* actually won't get here */
    }

    pid = dr_inject_get_process_id(inject_data);
#ifdef WINDOWS
    if (pidfile != NULL)
        write_pid_to_file(pidfile, pid);
#endif

    process = dr_inject_get_image_name(inject_data);
    /* we don't care if this app is already registered for DR b/c our
     * this-pid config will override
     */
    info("configuring %s pid=%d dr_ops=\"%s\"", process, pid, dr_ops);
    if (dr_register_process(process, pid,
                            false/*local*/, dr_root,  DR_MODE_CODE_MANIPULATION,
                            use_dr_debug, DR_PLATFORM_DEFAULT, dr_ops) != DR_SUCCESS) {
        fatal("failed to register DynamoRIO configuration");
        goto error; /* actually won't get here */
    }
    info("configuring client \"%s\" ops=\"%s\"", client_path, client_ops);
    if (dr_register_client(process, pid,
                           false/*local*/, DR_PLATFORM_DEFAULT, CLIENT_ID,
                           0, client_path, client_ops) != DR_SUCCESS) {
        fatal("failed to register DynamoRIO client configuration");
        goto error; /* actually won't get here */
    }
    if (!dr_inject_process_inject(inject_data, false/*!force*/, NULL)) {
        fatal("unable to inject");
        goto error; /* actually won't get here */
    }
    dr_inject_process_run(inject_data);
#ifdef UNIX
    fatal("Failed to exec application");
#else
    info("waiting for app to exit...");
    errcode = WaitForSingleObject(dr_inject_get_process_handle(inject_data), INFINITE);
    if (errcode != WAIT_OBJECT_0)
        info("failed to wait for app: %d\n", errcode);
#endif
    errcode = dr_inject_process_exit(inject_data, false/*don't kill process*/);
    goto cleanup;
 error:
    dr_inject_process_exit(inject_data, false);
    errcode = 1;
 cleanup:
    sc = drfront_cleanup_args(argv, argc);
    if (sc != DRFRONT_SUCCESS)
        fatal("drfront_cleanup_args failed: %d\n", sc);
    return (exit0 ? 0 : errcode);
}