-
Notifications
You must be signed in to change notification settings - Fork 562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement attach for both windows and linux #3328
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -544,6 +544,20 @@ dr_inject_prepare_to_exec(const char *exe, const char **argv, void **data OUT) | |
return errcode; | ||
} | ||
|
||
DR_EXPORT | ||
int | ||
dr_inject_prepare_to_attach(process_id_t pid, const char *appname, void **data OUT) | ||
{ | ||
dr_inject_info_t *info = create_inject_info(appname, NULL); | ||
int errcode = 0; | ||
*data = info; | ||
info->pid = pid; | ||
info->pipe_fd = 0; /* No pipe. */ | ||
info->exec_self = false; | ||
info->method = INJECT_PTRACE; | ||
return errcode; | ||
} | ||
|
||
DR_EXPORT | ||
bool | ||
dr_inject_prepare_to_ptrace(void *data) | ||
|
@@ -1372,6 +1386,20 @@ detach_and_exec_gdb(process_id_t pid, const char *library_path) | |
ASSERT(false && "failed to exec gdb?"); | ||
} | ||
|
||
/* singlestep traced process | ||
*/ | ||
bool | ||
ptrace_singlestep(process_id_t pid) | ||
{ | ||
if (our_ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) < 0) | ||
return false; | ||
|
||
if (!wait_until_signal(pid, SIGTRAP)) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
bool | ||
inject_ptrace(dr_inject_info_t *info, const char *library_path) | ||
{ | ||
|
@@ -1406,6 +1434,12 @@ inject_ptrace(dr_inject_info_t *info, const char *library_path) | |
return false; | ||
if (!continue_until_break(info->pid)) | ||
return false; | ||
} else { | ||
/* We are attached to target process, singlestep to make sure not returning from | ||
* blocked syscall. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please elaborate in the comment: why does it matter if the app was at a "blocked" syscall or just at any syscall (e.g., inside SYS_getpid)? Why do we need to single-step in such cases? Will the kernel auto-adjust the PC for us for an auto-restart syscall? |
||
*/ | ||
if (!ptrace_singlestep(info->pid)) | ||
return false; | ||
} | ||
|
||
/* Open libdynamorio.so as readonly in the child. */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,7 @@ | |
#include "../globals.h" | ||
#define WIN32_LEAN_AND_MEAN | ||
#include <windows.h> | ||
#include <Psapi.h> | ||
#include <commdlg.h> | ||
#include <imagehlp.h> | ||
#include <stdio.h> | ||
|
@@ -235,6 +236,7 @@ tchar_to_char(const TCHAR *wstr, OUT char *buf, size_t buflen /*# elements*/) | |
typedef struct _dr_inject_info_t { | ||
PROCESS_INFORMATION pi; | ||
bool using_debugger_injection; | ||
bool attached; | ||
TCHAR wimage_name[MAXIMUM_PATH]; | ||
/* We need something to point at for dr_inject_get_image_name so we just | ||
* keep a utf8 buffer as well. | ||
|
@@ -754,6 +756,56 @@ append_app_arg_and_space(char *buf, size_t bufsz, size_t *sofar, const char *arg | |
} | ||
} | ||
|
||
DYNAMORIO_EXPORT | ||
int | ||
dr_inject_process_attach(process_id_t pid, void **data OUT) | ||
{ | ||
dr_inject_info_t *info = HeapAlloc(GetProcessHeap(), 0, sizeof(*info)); | ||
memset(info, 0, sizeof(*info)); | ||
int errcode = ERROR_SUCCESS; | ||
if (DebugActiveProcess((DWORD)pid)) { | ||
info->using_debugger_injection = false; | ||
info->attached = true; | ||
DEBUG_EVENT dbgevt = { 0 }; | ||
for (;;) { | ||
dbgevt.dwProcessId = (DWORD)pid; | ||
WaitForDebugEvent(&dbgevt, INFINITE); | ||
ContinueDebugEvent(dbgevt.dwProcessId, dbgevt.dwThreadId, DBG_CONTINUE); | ||
|
||
if (dbgevt.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { | ||
break; | ||
} | ||
} | ||
char szExePath[MAX_PATH]; | ||
summershrimp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
char *pExeName = NULL; | ||
summershrimp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
GetModuleFileNameExA(dbgevt.u.CreateProcessInfo.hProcess, NULL, szExePath, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use the ExW version to properly handle non-ascii chars |
||
MAX_PATH); | ||
pExeName = strrchr(szExePath, '\\'); | ||
if (pExeName == NULL) { | ||
return ERROR_INVALID_PARAMETER; | ||
} | ||
|
||
strcpy(info->image_name, pExeName + 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. strcpy is not allowed: use strncpy and NULL_TERMINATE_BUFFER |
||
char_to_tchar(info->image_name, info->wimage_name, | ||
BUFFER_SIZE_ELEMENTS(info->wimage_name)); | ||
|
||
info->pi.dwProcessId = dbgevt.dwProcessId; | ||
info->pi.dwThreadId = dbgevt.dwThreadId; | ||
|
||
DuplicateHandle(GetCurrentProcess(), dbgevt.u.CreateProcessInfo.hProcess, | ||
GetCurrentProcess(), &info->pi.hProcess, 0, FALSE, | ||
DUPLICATE_SAME_ACCESS); | ||
|
||
DuplicateHandle(GetCurrentProcess(), dbgevt.u.CreateProcessInfo.hThread, | ||
GetCurrentProcess(), &info->pi.hThread, 0, FALSE, | ||
DUPLICATE_SAME_ACCESS); | ||
} else { | ||
errcode = GetLastError(); | ||
} | ||
*data = info; | ||
return errcode; | ||
} | ||
|
||
/* Returns 0 on success. | ||
* On failure, returns a Windows API error code. | ||
*/ | ||
|
@@ -937,9 +989,14 @@ bool | |
dr_inject_process_run(void *data) | ||
{ | ||
dr_inject_info_t *info = (dr_inject_info_t *)data; | ||
/* resume the suspended app process so its main thread can run */ | ||
ResumeThread(info->pi.hThread); | ||
close_handle(info->pi.hThread); | ||
if (info->attached) { | ||
/* detach the debugger */ | ||
DebugActiveProcessStop(info->pi.dwProcessId); | ||
} else { | ||
/* resume the suspended app process so its main thread can run */ | ||
ResumeThread(info->pi.hThread); | ||
close_handle(info->pi.hThread); | ||
} | ||
|
||
return true; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -286,10 +286,10 @@ const char *options_list_str = | |
" -early Requests early injection (the default).\n" | ||
" -late Requests late injection.\n" | ||
# endif | ||
" -attach <pid> Attach to the process with the given pid. Pass 0\n" | ||
" for pid to launch and inject into a new process.\n" | ||
" -logdir <dir> Logfiles will be stored in this directory.\n" | ||
# endif | ||
" -attach <pid> Attach to the process with the given pid. Pass 0\n" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The big problem with attach on Windows is that if we attach when a thread is inside a Windows callback we will lose control when it goes back into the kernel, since there is no way to know where it will resume. We have some syscall hooks to try and regain control but there will at least be a window where that thread is native. We should document this: maybe label Windows attach support as "experimental" or describe this issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the main documentation ("page_deploy" in intro.dox) should talk about attaching as a supported feature? It should be listed in the changelog in release.dox for sure as a major feature. |
||
" for pid to launch and inject into a new process.\n" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why anyone would pass 0? If you don't want to attach to an existing process you just wouldn't pass -attach. I would remove that sentence and make 0 an invalid argument. |
||
" -use_dll <dll> Inject given dll instead of configured DR dll.\n" | ||
" -force Inject regardless of configuration.\n" | ||
" -exit0 Return a 0 exit code instead of the app's exit code.\n" | ||
|
@@ -1035,6 +1035,7 @@ _tmain(int argc, TCHAR *targv[]) | |
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; | ||
|
@@ -1170,23 +1171,25 @@ _tmain(int argc, TCHAR *targv[]) | |
} else if (strcmp(argv[i], "-no_wait") == 0) { | ||
limit = -1; | ||
continue; | ||
} | ||
# ifdef UNIX | ||
else if (strcmp(argv[i], "-use_ptrace") == 0) { | ||
/* Undocumented option for using ptrace on a fresh process. */ | ||
use_ptrace = true; | ||
continue; | ||
} else if (strcmp(argv[i], "-attach") == 0) { | ||
const char *pid_str = argv[++i]; | ||
process_id_t pid = strtoul(pid_str, NULL, 10); | ||
if (pid == ULONG_MAX) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would make 0 invalid as well |
||
usage(false, "-attach expects an integer pid"); | ||
if (pid != 0) | ||
usage(false, "attaching to running processes is not yet implemented"); | ||
attach_pid = pid; | ||
limit = -1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is your reasoning for not allowing a time limit on attaching? Maybe it would be less commonly used but is there a reason to disallow it? |
||
# ifdef UNIX | ||
use_ptrace = true; | ||
# endif | ||
/* FIXME: use pid below to attach. */ | ||
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 */ | ||
|
@@ -1442,7 +1445,20 @@ _tmain(int argc, TCHAR *targv[]) | |
/* Support no app if the tool has its own frontend, under the assumption | ||
* it may have post-processing or other features. | ||
*/ | ||
if (i < argc || native_tool[0] == '\0') { | ||
# ifdef UNIX | ||
if (attach_pid != 0) { | ||
char *exe = malloc(PATH_MAX); | ||
char *exe_str = malloc(PATH_MAX); | ||
ssize_t size; | ||
sprintf(exe_str, "/proc/%u/exe", attach_pid); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sprintf is not allowed: use _snprintf and NULL_TERMINATE_BUFFER |
||
size = readlink(exe_str, exe, PATH_MAX); | ||
exe[size] = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check the return value of readlink: this will overflow when size==-1! |
||
app_name = strdup(exe); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Memory leak: never freed. |
||
free(exe); | ||
free(exe_str); | ||
} | ||
# endif /* UNIX */ | ||
if (attach_pid == 0 && (i < argc || native_tool[0] == '\0')) { | ||
# endif | ||
if (i >= argc) | ||
usage(false, "%s", "no app specified"); | ||
|
@@ -1644,8 +1660,14 @@ _tmain(int argc, TCHAR *targv[]) | |
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) { | ||
errcode = dr_inject_prepare_to_attach(attach_pid, app_name, &inject_data); | ||
} else | ||
# endif /* UNIX */ | ||
# elif defined(WINDOWS) | ||
if (attach_pid != 0) { | ||
errcode = dr_inject_process_attach(attach_pid, &inject_data); | ||
} else | ||
# endif /* WINDOWS */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to write some simple tests of these new attach features? It will take a little more work than adding a regular test because you'll need to launch something in the background. See client.nudge_test and how it has a .runall file and uses run_in_bg to run the infloop app in the background. If you'd prefer to make adding a test a separate CL that would be fine. |
||
{ | ||
errcode = dr_inject_process_create(app_name, app_argv, &inject_data); | ||
info("created child with pid %d for %s", dr_inject_get_process_id(inject_data), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This psapi dependence should not be added here since this is linked into the core DR library. We do not want the core DR library to have extra dependences: it will fail to function with early injection and will hit transparency issues in general. Please see the transparency slides in the DR tutorial and http://dynamorio.org/docs/transparency.html#sec_trans_resource. Please only link this with non-core targets like drinjectlib.