From 33813aabe54a8f174788bd62128267e00df1131b Mon Sep 17 00:00:00 2001 From: orbp Date: Thu, 2 Sep 2021 14:25:53 +0300 Subject: [PATCH] i#725: add attach for windows --- core/dynamo.c | 7 ++-- core/lib/dr_inject.h | 16 ++++++++ core/optionsx.h | 10 +++++ core/win32/injector.c | 87 +++++++++++++++++++++++++++++++++++++++++-- core/win32/ntdll.c | 15 ++++++++ core/win32/ntdll.h | 3 ++ core/win32/os.c | 9 +++++ tools/drdeploy.c | 35 ++++++++++++++--- 8 files changed, 171 insertions(+), 11 deletions(-) diff --git a/core/dynamo.c b/core/dynamo.c index ce5e1a70560..c75cb4fc105 100644 --- a/core/dynamo.c +++ b/core/dynamo.c @@ -2880,8 +2880,6 @@ dynamo_thread_not_under_dynamo(dcontext_t *dcontext) #endif } -#define MAX_TAKE_OVER_ATTEMPTS 8 - /* Mark this thread as under DR, and take over other threads in the current process. */ void @@ -2892,6 +2890,7 @@ dynamorio_take_over_threads(dcontext_t *dcontext) */ bool found_threads; uint attempts = 0; + uint max_takeover_attempts = DYNAMO_OPTION(takeover_attempts); os_process_under_dynamorio_initiate(dcontext); /* We can start this thread now that we've set up process-wide actions such @@ -2912,7 +2911,9 @@ dynamorio_take_over_threads(dcontext_t *dcontext) attempts++; if (found_threads && !bb_lock_start) bb_lock_start = true; - } while (found_threads && attempts < MAX_TAKE_OVER_ATTEMPTS); + if (DYNAMO_OPTION(sleep_between_takeovers)) + dr_sleep(1); + } while (found_threads && attempts < max_takeover_attempts); os_process_under_dynamorio_complete(dcontext); /* End the barrier to new threads. */ signal_event(dr_attach_finished); diff --git a/core/lib/dr_inject.h b/core/lib/dr_inject.h index c2d556d4644..7177812f173 100644 --- a/core/lib/dr_inject.h +++ b/core/lib/dr_inject.h @@ -102,6 +102,22 @@ DR_EXPORT int dr_inject_process_create(const char *app_name, const char **app_cmdline, void **data); +#ifdef WINDOWS +DR_EXPORT +/** + * Attach to an existing process. + * + * \param[in] pid PID for process to attach. + * + * \param[out] data An opaque pointer that should be passed to + * subsequent dr_inject_* routines to refer to + * this process. + * \return Returns 0 on success. On failure, returns a system error code.` + */ +int +dr_inject_process_attach(process_id_t pid, void **data); +#endif + #ifdef UNIX DR_EXPORT diff --git a/core/optionsx.h b/core/optionsx.h index bad8ff6981b..91dfd203eac 100644 --- a/core/optionsx.h +++ b/core/optionsx.h @@ -2633,6 +2633,16 @@ OPTION_COMMAND(bool, native_exec_opt, false, "native_exec_opt", "optimize control flow transition between native and non-native modules", STATIC, OP_PCACHE_GLOBAL) +#ifdef WINDOWS +OPTION_DEFAULT(bool, skip_terminating_threads, false, + "do not takeover threads that are terminating") +#endif + +OPTION_DEFAULT(bool, sleep_between_takeovers, false, + "sleep between takeover attempts to allow threads to exit syscalls") + +OPTION_DEFAULT(uint, takeover_attempts, 8, "number of takeover attempts") + /* vestiges from our previous life as a dynamic optimizer */ OPTION_DEFAULT_INTERNAL(bool, inline_calls, true, "inline calls in traces") diff --git a/core/win32/injector.c b/core/win32/injector.c index 25b5c6a88f9..a72971b9f54 100644 --- a/core/win32/injector.c +++ b/core/win32/injector.c @@ -231,6 +231,7 @@ typedef struct _dr_inject_info_t { PROCESS_INFORMATION pi; bool using_debugger_injection; bool using_thread_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. @@ -843,6 +844,81 @@ dr_inject_process_create(const char *app_name, const char **argv, void **data OU return errcode; } +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; + bool received_initial_debug_event = false; + + if (DebugActiveProcess((DWORD)pid)) { + DebugSetProcessKillOnExit(false); + + info->using_debugger_injection = false; + info->attached = true; + + DEBUG_EVENT dbgevt = { 0 }; + do { + dbgevt.dwProcessId = (DWORD)pid; + WaitForDebugEvent(&dbgevt, INFINITE); + ContinueDebugEvent(dbgevt.dwProcessId, dbgevt.dwThreadId, DBG_CONTINUE); + + if (dbgevt.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { + received_initial_debug_event = true; + } + } while (received_initial_debug_event == false); + + wchar_t exe_path[MAX_PATH]; + DWORD exe_path_size = MAX_PATH; + + HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid); + if (process_handle != NULL) { + BOOL(*query_full_process_image_name_w) + (HANDLE, DWORD, LPWSTR, PDWORD) = + (BOOL(*)(HANDLE, DWORD, LPWSTR, PDWORD))(GetProcAddress( + GetModuleHandle(TEXT("Kernel32")), "QueryFullProcessImageNameW")); + + if (query_full_process_image_name_w(process_handle, 0, exe_path, + &exe_path_size) != 0) { + wchar_t *exe_name = wcsrchr(exe_path, '\\'); + if (exe_name != NULL) { + wcsncpy(info->wimage_name, exe_name + 1, + BUFFER_SIZE_ELEMENTS(info->wimage_name)); + NULL_TERMINATE_BUFFER(info->wimage_name); + + tchar_to_char(info->wimage_name, info->image_name, + BUFFER_SIZE_ELEMENTS(info->image_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 = ERROR_INVALID_PARAMETER; + } + } else { + errcode = GetLastError(); + } + } else { + errcode = GetLastError(); + } + } else { + errcode = GetLastError(); + } + *data = info; + return errcode; +} + DYNAMORIO_EXPORT bool dr_inject_use_late_injection(void *data) @@ -954,9 +1030,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 == true) { + /* 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; } diff --git a/core/win32/ntdll.c b/core/win32/ntdll.c index 44ed563f9a9..d28a27b3e3f 100644 --- a/core/win32/ntdll.c +++ b/core/win32/ntdll.c @@ -2383,6 +2383,21 @@ nt_set_context(HANDLE hthread, CONTEXT *cxt) return NT_SYSCALL(SetContextThread, hthread, cxt); } +bool +nt_is_thread_terminating(HANDLE hthread) +{ + ULONG previous_suspend_count; + NTSTATUS res; + GET_RAW_SYSCALL(SuspendThread, IN HANDLE ThreadHandle, + OUT PULONG PreviousSuspendCount OPTIONAL); + res = NT_SYSCALL(SuspendThread, hthread, &previous_suspend_count); + if (NT_SUCCESS(res)) { + nt_thread_resume(hthread, (int *)&previous_suspend_count); + } + + return res == STATUS_THREAD_IS_TERMINATING; +} + bool nt_thread_suspend(HANDLE hthread, int *previous_suspend_count) { diff --git a/core/win32/ntdll.h b/core/win32/ntdll.h index dad832b5bf1..1935b53a190 100644 --- a/core/win32/ntdll.h +++ b/core/win32/ntdll.h @@ -1436,6 +1436,9 @@ nt_get_context(HANDLE hthread, CONTEXT *cxt); NTSTATUS nt_set_context(HANDLE hthread, CONTEXT *cxt); +bool +nt_is_thread_terminating(HANDLE hthread); + bool nt_thread_suspend(HANDLE hthread, int *previous_suspend_count); diff --git a/core/win32/os.c b/core/win32/os.c index 0bdbb5cdf6a..ad4a99f34aa 100644 --- a/core/win32/os.c +++ b/core/win32/os.c @@ -2497,6 +2497,15 @@ static bool os_take_over_thread(dcontext_t *dcontext, HANDLE hthread, thread_id_t tid, bool suspended) { bool success = true; + + if (DYNAMO_OPTION(skip_terminating_threads)) { + if (nt_is_thread_terminating(hthread)) { + // takeover fails when attaching and trying to takeover terminating threads + // luckily, we don't really need to take over them + return success; + } + } + DWORD cxt_flags = CONTEXT_DR_STATE; size_t bufsz = nt_get_context_size(cxt_flags); char *buf = (char *)heap_alloc(dcontext, bufsz HEAPACCT(ACCT_THREAD_MGT)); diff --git a/tools/drdeploy.c b/tools/drdeploy.c index 6f5c6423090..79869005bdd 100644 --- a/tools/drdeploy.c +++ b/tools/drdeploy.c @@ -309,6 +309,19 @@ const char *options_list_str = " Attaching to a process will force blocking system calls\n" " to fail with EINTR.\n" # endif +# endif +# ifdef WINDOWS + " -attach (Experimental)\n" + " Must be used with -late.\n" + " Attach to the process with the given pid.\n" + " If attach to a process which is in middle of blocking\n" + " system call, attach 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 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 Inject given dll instead of configured DR dll.\n" " -force Inject regardless of configuration.\n" @@ -1265,7 +1278,6 @@ _tmain(int argc, TCHAR *targv[]) } # endif else if (strcmp(argv[i], "-attach") == 0) { -# ifdef UNIX const char *pid_str = argv[++i]; process_id_t pid = strtoul(pid_str, NULL, 10); if (pid == ULONG_MAX) @@ -1274,14 +1286,23 @@ _tmain(int argc, TCHAR *targv[]) usage(false, "attach to invalid pid"); } attach_pid = pid; -# endif # ifdef UNIX use_ptrace = true; # endif # ifdef WINDOWS - usage(false, "attach in Windows not yet implemented"); + 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 # ifdef X86 @@ -1568,7 +1589,6 @@ _tmain(int argc, TCHAR *targv[]) _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)); -# endif /* UNIX */ if (size > 0) { if (size < BUFFER_SIZE_ELEMENTS(exe)) exe[size] = '\0'; @@ -1577,6 +1597,7 @@ _tmain(int argc, TCHAR *targv[]) } 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 @@ -1789,7 +1810,11 @@ _tmain(int argc, TCHAR *targv[]) errcode = dr_inject_prepare_to_attach(attach_pid, app_name, wait_syscall, &inject_data); } else -# endif /* UNIX */ +# elif defined(WINDOWS) + if (attach_pid != 0) { + errcode = dr_inject_process_attach(attach_pid, &inject_data); + } else +# endif /* WINDOWS */ { errcode = dr_inject_process_create(app_name, app_argv, &inject_data); info("created child with pid " PIDFMT " for %s",