diff --git a/api/docs/deployment.dox b/api/docs/deployment.dox index 11ddd030607..d0db96715dc 100644 --- a/api/docs/deployment.dox +++ b/api/docs/deployment.dox @@ -214,6 +214,12 @@ bbsize sample client using the following configure-and-run command: bin32/drrun.exe -c samples/bin32/bbsize.dll -- notepad \endcode +Alternatively, you can first run \c notepad.exe, and then use \c drrun +to attach to it: +\code +bin32/drrun.exe -attach -c samples/bin32/bbsize.dll +\endcode + To use system-wide injection, allowing for an application to be run under DynamoRIO regardless of how it is invoked, configure the application first (-syswide_on requires administrative privileges): @@ -307,6 +313,13 @@ under DynamoRIO with the bbsize sample client: \code % bin32/drrun -c samples/bin32/libbbsize.so -- ls \endcode + +Alternatively, you can first run the target, and then use \c drrun +to attach to it: +\code +% bin32/drrun -attach -c samples/bin32/libbbsize.so +\endcode + Run \c drrun with no options to get a list of the options and environment variable shortcuts it supports. To disable following across child execve calls, use the \ref op_children "-no_follow_children" runtime diff --git a/api/docs/release.dox b/api/docs/release.dox index ee2d4985e97..2a82cca39dd 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -238,6 +238,7 @@ Further non-compatibility-affecting changes include: - Added \p drstatecmp Extension which provides mechanisms to enable systematic and exhaustive machine state comparisons across instrumentation sequences. - Added drmodtrack_lookup_segment(). + - Added a new drrun option \p -attach for attaching to a running process. **************************************************
@@ -1754,6 +1755,11 @@ I/O and nudges. through the Windows-On-Windows or WOW64 emulator so system call and indirect call processing clients must be aware of #instr_is_wow64_syscall(). + - On all versions of Windows, attaching DynamoRIO to an already-running + process can result in loss of control if the attach point is in the + middle of an operating system event callback. From the callback return + point until the next system call hook, no instructions will be observed + by a client. \anchor limits_64bit - This release of DynamoRIO supports running 64-bit Windows applications, using the 64-bit DynamoRIO build, on diff --git a/core/dynamo.c b/core/dynamo.c index ce5e1a70560..ac8fcfe8d9f 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)) + os_thread_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..15304acc7b0 100644 --- a/core/lib/dr_inject.h +++ b/core/lib/dr_inject.h @@ -102,6 +102,24 @@ 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. + * \param[out] app_name Pointer to the name of the target process. + * Only valid until dr_inject_process_exit. + * \return Returns 0 on success. On failure, returns a system error code.` + */ +int +dr_inject_process_attach(process_id_t pid, void **data, char **app_name); +#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/inject.c b/core/win32/inject.c index 10fefbc28e6..12491356922 100644 --- a/core/win32/inject.c +++ b/core/win32/inject.c @@ -1139,41 +1139,6 @@ generate_switch_mode_jmp_to_hook(HANDLE phandle, byte *local_code_buf, } #endif -static uint64 -find_remote_ntdll_base(HANDLE phandle, bool find64bit) -{ - MEMORY_BASIC_INFORMATION64 mbi; - uint64 got; - NTSTATUS res; - uint64 addr = 0; - char name[MAXIMUM_PATH]; - do { - res = remote_query_virtual_memory_maybe64(phandle, addr, &mbi, sizeof(mbi), &got); - if (got != sizeof(mbi) || !NT_SUCCESS(res)) - break; -#if VERBOSE - print_file(STDERR, "0x%I64x-0x%I64x type=0x%x state=0x%x\n", mbi.BaseAddress, - mbi.BaseAddress + mbi.RegionSize, mbi.Type, mbi.State); -#endif - if (mbi.Type == MEM_IMAGE && mbi.BaseAddress == mbi.AllocationBase) { - bool is_64; - if (get_remote_dll_short_name(phandle, mbi.BaseAddress, name, - BUFFER_SIZE_ELEMENTS(name), &is_64)) { -#if VERBOSE - print_file(STDERR, "found |%s| @ 0x%I64x 64=%d\n", name, mbi.BaseAddress, - is_64); -#endif - if (strcmp(name, "ntdll.dll") == 0 && BOOLS_MATCH(find64bit, is_64)) - return mbi.BaseAddress; - } - } - if (addr + mbi.RegionSize < addr) - break; - addr += mbi.RegionSize; - } while (true); - return 0; -} - static uint64 inject_gencode_mapped_helper(HANDLE phandle, char *dynamo_path, uint64 hook_location, byte hook_buf[EARLY_INJECT_HOOK_SIZE], byte *map, @@ -1235,7 +1200,7 @@ inject_gencode_mapped_helper(HANDLE phandle, char *dynamo_path, uint64 hook_loca /* see below on why it's easier to point at args in memory */ args.dr_base = (uint64)map; - args.ntdll_base = find_remote_ntdll_base(phandle, target_64); + args.ntdll_base = find_remote_dll_base(phandle, target_64, "ntdll.dll"); if (args.ntdll_base == 0) goto error; args.tofree_base = remote_code_buf; @@ -1572,7 +1537,7 @@ inject_into_new_process(HANDLE phandle, HANDLE thandle, char *dynamo_path, bool } if (hook_location == 0) { bool target_64 = !x86_code IF_X64(|| DYNAMO_OPTION(inject_x64)); - uint64 ntdll_base = find_remote_ntdll_base(phandle, target_64); + uint64 ntdll_base = find_remote_dll_base(phandle, target_64, "ntdll.dll"); uint64 thread_start = get_remote_proc_address(phandle, ntdll_base, "RtlUserThreadStart"); if (thread_start != 0) diff --git a/core/win32/injector.c b/core/win32/injector.c index 25b5c6a88f9..7b6cd6db9fa 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,116 @@ dr_inject_process_create(const char *app_name, const char **argv, void **data OU return errcode; } +static int +create_attach_thread(HANDLE process_handle IN, PHANDLE thread_handle OUT, PDWORD tid OUT) +{ + uint64 kernel32; + uint64 sleep_address; + bool target_is_32; + + *thread_handle = NULL; + *tid = 0; + + target_is_32 = is_32bit_process(process_handle); + kernel32 = find_remote_dll_base(process_handle, !target_is_32, "kernel32.dll"); + if (kernel32 == 0) { + return ERROR_INVALID_PARAMETER; + } + + sleep_address = get_remote_proc_address(process_handle, kernel32, "SleepEx"); + if (sleep_address == 0) { + return ERROR_INVALID_PARAMETER; + } + + *thread_handle = CreateRemoteThread(process_handle, NULL, 0, + (LPTHREAD_START_ROUTINE)(SIZE_T)sleep_address, + (LPVOID)(SIZE_T)INFINITE, CREATE_SUSPENDED, tid); + if (*thread_handle == NULL) { + return GetLastError(); + } + + return ERROR_SUCCESS; +} + +DYNAMORIO_EXPORT +int +dr_inject_process_attach(process_id_t pid, void **data OUT, char **app_name OUT) +{ + dr_inject_info_t *info = HeapAlloc(GetProcessHeap(), 0, sizeof(*info)); + memset(info, 0, sizeof(*info)); + bool received_initial_debug_event = false; + DEBUG_EVENT dbgevt = { 0 }; + wchar_t exe_path[MAX_PATH]; + DWORD exe_path_size = MAX_PATH; + wchar_t *exe_name; + HANDLE process_handle; + int res; + + *data = info; + + if (DebugActiveProcess((DWORD)pid) == false) { + return GetLastError(); + } + + DebugSetProcessKillOnExit(false); + + info->using_debugger_injection = false; + info->attached = true; + + 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); + + info->pi.dwProcessId = dbgevt.dwProcessId; + + DuplicateHandle(GetCurrentProcess(), dbgevt.u.CreateProcessInfo.hProcess, + GetCurrentProcess(), &info->pi.hProcess, 0, FALSE, + DUPLICATE_SAME_ACCESS); + + process_handle = info->pi.hProcess; + + /* XXX i#725: Attach does not begin as long as the injected thread is blocking. + * To overcome it, We create a new thread in the target process that will live + * as long as the target lives, and inject into it. + * For better transparency we should exit the thread immediately after injection. + * Would require changing termination assumptions in win32/syscall.c. + */ + res = create_attach_thread(process_handle, &info->pi.hThread, &info->pi.dwThreadId); + if (res != ERROR_SUCCESS) { + return res; + } + + BOOL(__stdcall * query_full_process_image_name_w) + (HANDLE, DWORD, LPWSTR, PDWORD) = (BOOL(__stdcall *)(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) { + return GetLastError(); + } + + exe_name = wcsrchr(exe_path, '\\'); + if (exe_name == NULL) { + return ERROR_INVALID_PARAMETER; + } + + wchar_to_char(info->image_name, BUFFER_SIZE_ELEMENTS(info->image_name), exe_name, + wcslen(exe_name) * sizeof(wchar_t)); + + char_to_tchar(info->image_name, info->wimage_name, + BUFFER_SIZE_ELEMENTS(info->image_name)); + + *app_name = info->image_name; + + return ERROR_SUCCESS; +} + DYNAMORIO_EXPORT bool dr_inject_use_late_injection(void *data) @@ -954,6 +1065,10 @@ bool dr_inject_process_run(void *data) { dr_inject_info_t *info = (dr_inject_info_t *)data; + if (info->attached == true) { + /* detach the debugger */ + DebugActiveProcessStop(info->pi.dwProcessId); + } /* resume the suspended app process so its main thread can run */ ResumeThread(info->pi.hThread); close_handle(info->pi.hThread); diff --git a/core/win32/module_shared.c b/core/win32/module_shared.c index 1774c8218de..20dba05ec21 100644 --- a/core/win32/module_shared.c +++ b/core/win32/module_shared.c @@ -1326,6 +1326,48 @@ read_remote_maybe64(HANDLE process, uint64 addr, size_t bufsz, void *buf) num_read == bufsz; } +uint64 +find_remote_dll_base(HANDLE phandle, bool find64bit, char *dll_name) +{ + MEMORY_BASIC_INFORMATION64 mbi; + uint64 got; + NTSTATUS res; + uint64 addr = 0; + char name[MAXIMUM_PATH]; + do { + res = remote_query_virtual_memory_maybe64(phandle, addr, &mbi, sizeof(mbi), &got); + if (got != sizeof(mbi) || !NT_SUCCESS(res)) + break; +# if VERBOSE + print_file(STDERR, "0x%I64x-0x%I64x type=0x%x state=0x%x\n", mbi.BaseAddress, + mbi.BaseAddress + mbi.RegionSize, mbi.Type, mbi.State); +# endif + if (mbi.Type == MEM_IMAGE && mbi.BaseAddress == mbi.AllocationBase) { + bool is_64; + if (get_remote_dll_short_name(phandle, mbi.BaseAddress, name, + BUFFER_SIZE_ELEMENTS(name), &is_64)) { +# if VERBOSE + print_file(STDERR, "found |%s| @ 0x%I64x 64=%d\n", name, mbi.BaseAddress, + is_64); +# endif + if (_strcmpi(name, dll_name) == 0 && BOOLS_MATCH(find64bit, is_64)) + return mbi.BaseAddress; + } + } + if (addr + mbi.RegionSize < addr) + break; + /* XXX - this check is needed because otherwise, for 32-bit targets on a 64 + * bit machine, this loop doesn't return if the dll is not loaded. + * When addr passes 0x800000000000 remote_query_virtual_memory_maybe64 + * returns the previous mbi (ending at 0x7FFFFFFF0000). + * For now just return if the addr is not inside the mbi region. */ + if (mbi.BaseAddress + mbi.RegionSize < addr) + break; + addr += mbi.RegionSize; + } while (true); + return 0; +} + /* Handles 32-bit or 64-bit remote processes. * Ignores forwarders and ordinals. */ 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..4bf4a696e25 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); @@ -2218,6 +2221,9 @@ remote_protect_virtual_memory_64(HANDLE process, uint64 base, size_t size, uint uint *old_prot); #endif /* !X64 */ +uint64 +find_remote_dll_base(HANDLE phandle, bool find64bit, char *dll_name); + uint64 get_remote_proc_address(HANDLE process, uint64 remote_base, const char *name); diff --git a/core/win32/os.c b/core/win32/os.c index 0bdbb5cdf6a..5d4c50e4a8c 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/suite/runsuite_wrapper.pl b/suite/runsuite_wrapper.pl index cd3cf7e0264..2fc92ce143a 100755 --- a/suite/runsuite_wrapper.pl +++ b/suite/runsuite_wrapper.pl @@ -220,6 +220,7 @@ 'code_api|client.drwrap-test-detach' => 1, # i#4616 'code_api|client.cbr4' => 1, # i#4792 'code_api|win32.hookerfirst' => 1, # i#4870 + 'code_api|client.attach_test' => 1, # i#725 # These are from earlier runs on Appveyor: 'code_api|security-common.retnonexisting' => 1, 'code_api|security-win32.gbop-test' => 1, # i#2972 @@ -254,6 +255,7 @@ 'code_api|tool.histogram.offline' => 1, # i#4621 'code_api|tool.drcacheoff.burst_static' => 1, # i#4486 'code_api|tool.drcacheoff.burst_replace' => 1, # i#4486 + 'code_api|client.attach_test' => 1, # i#725 # i#4617: These need build-and-test to build # the 32-bit test app in our separate 64-bit job. 'code_api|win32.mixedmode_late' => 1, # i#4617 diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index 2fd24c2f332..31a747bc655 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -1308,9 +1308,16 @@ function(torun test key source native standalone_dr dr_ops exe_ops added_out) # We always run infloop. get_target_path_for_execution(app_path linux.infloop "${location_suffix}") else () - # It's easier to run our own infloop, which prints so we know when it's up, - # than to run notepad or sthg. - get_target_path_for_execution(app_path win32.infloop "${location_suffix}") + if ("${runall}" MATCHES "") + # For attach we use attachee, which is identical to infloop, aside for writing + # a starting message to the output file. This allows us to detect when it is + # up and running and we can start the attach process. + get_target_path_for_execution(app_path win32.attachee "${location_suffix}") + else () + # It's easier to run our own infloop, which prints so we know when it's up, + # than to run notepad or sthg. + get_target_path_for_execution(app_path win32.infloop "${location_suffix}") + endif () endif () # Swap from drrun to run_in_bg to run the test in the background and print the pid. # XXX i#1874: add support for running on Android @@ -1346,6 +1353,14 @@ function(torun test key source native standalone_dr dr_ops exe_ops added_out) string(REGEX MATCHALL "]+" nudge_arg "${runall}") string(REGEX REPLACE "") + # move params to nudge + string(REGEX MATCH ";-quiet;(.*);--" nudge_arg "${rundr}") + string(REGEX REPLACE ";--" "" nudge_arg "${nudge_arg}") + string(PREPEND nudge_arg "") + string(REGEX REPLACE ";" "@" nudge_arg "${nudge_arg}") + # clear client from dr command and run native + string(REGEX REPLACE ";-quiet;(.*);--" ";-no_inject;--;" rundr "${rundr}") endif () set(cmd_with_at ${rundr} ${app_path} ${exe_ops}) # we pass intra-arg spaces via @@ and inter-arg via @ and ; via ! @@ -2390,6 +2405,7 @@ if (UNIX) use_DynamoRIO_extension(client.emulation_api_simple.dll drmgr) endif () else (UNIX) + tobuild_ci(client.attach_test client-interface/attach_test.runall "" "" "") tobuild_ci(client.events client-interface/events.c "" "" "${events_appdll_path}") tobuild_ci(client.events_cpp client-interface/events_cpp.cpp @@ -4268,6 +4284,7 @@ if (UNIX) endif () else (UNIX) add_exe(win32.infloop win32/infloop.c) + add_exe(win32.attachee win32/attachee.c) if (PROGRAM_SHEPHERDING) # too flaky across platforms so we limit to security mode only: not too useful # for other than testing our ASLR anyway diff --git a/suite/tests/client-interface/attach_test.dll.c b/suite/tests/client-interface/attach_test.dll.c new file mode 100644 index 00000000000..31811c0f96d --- /dev/null +++ b/suite/tests/client-interface/attach_test.dll.c @@ -0,0 +1,85 @@ +/* ********************************************************** + * 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 "dr_api.h" +#include "client_tools.h" +#include "Windows.h" + +static thread_id_t injection_tid; +static bool first_thread = true; + +static void +dr_exit(void) +{ + dr_fprintf(STDERR, "done\n"); +} + +static void +dr_thread_init(void *drcontext) +{ + thread_id_t tid = dr_get_thread_id(drcontext); + if (tid != injection_tid && first_thread) { + first_thread = false; + dr_fprintf(STDERR, "thread init\n"); + } +} + +static bool +dr_exception_event(void *drcontext, dr_exception_t *excpt) +{ + thread_id_t tid = dr_get_thread_id(drcontext); + dr_fprintf(STDERR, "exception in thread %p\ninjection thread %p\n", tid, + injection_tid); + + dr_fprintf(STDERR, "ExceptionCode=%08x\n", excpt->record->ExceptionCode); + dr_fprintf(STDERR, "ExceptionFlags=%08x\n", excpt->record->ExceptionFlags); + dr_fprintf(STDERR, "ExceptionAddress=%p\n", excpt->record->ExceptionAddress); + dr_fprintf(STDERR, "parameters:\n"); + for (DWORD i = 0; i < excpt->record->NumberParameters; i++) { + dr_fprintf(STDERR, "parameters[%ld]:%p\n", i, + excpt->record->ExceptionInformation[i]); + } + + return true; +} + +DR_EXPORT +void +dr_init(client_id_t id) +{ + void *drcontext = dr_get_current_drcontext(); + injection_tid = dr_get_thread_id(drcontext); + dr_register_exit_event(dr_exit); + dr_register_thread_init_event(dr_thread_init); + dr_register_exception_event(dr_exception_event); + dr_fprintf(STDERR, "thank you for testing attach\n"); +} diff --git a/suite/tests/client-interface/attach_test.runall b/suite/tests/client-interface/attach_test.runall new file mode 100644 index 00000000000..23b651e0b70 --- /dev/null +++ b/suite/tests/client-interface/attach_test.runall @@ -0,0 +1 @@ + diff --git a/suite/tests/client-interface/attach_test.template b/suite/tests/client-interface/attach_test.template new file mode 100644 index 00000000000..f498fd896b4 --- /dev/null +++ b/suite/tests/client-interface/attach_test.template @@ -0,0 +1,7 @@ +starting attachee +thank you for testing attach +thread init +#ifdef WINDOWS +MessageBox closed +#endif +done diff --git a/suite/tests/runall.cmake b/suite/tests/runall.cmake index 9705564ba76..815ae399913 100644 --- a/suite/tests/runall.cmake +++ b/suite/tests/runall.cmake @@ -103,7 +103,7 @@ function (do_sleep ms) else () # XXX: ping's units are secs. For now we always do 1 sec. # We could try to use cygwin bash or perl. - execute_process(COMMAND ${PING} 127.0.0.1 -n 1 OUTPUT_QUIET) + execute_process(COMMAND ${PING} 127.0.0.1 -n 2 OUTPUT_QUIET) endif () endfunction (do_sleep) @@ -153,6 +153,16 @@ if (pidfile) endif () endwhile () file(READ "${pidfile}" pid) + set(iters 0) + while ("${pid}" STREQUAL "") + do_sleep(0.1) + math(EXPR iters "${iters}+1") + if (${iters} GREATER ${MAX_ITERS}) + kill_background_process(ON) + message(FATAL_ERROR "Timed out waiting for ${pidfile} content") + endif () + file(READ "${pidfile}" pid) + endwhile () string(REGEX REPLACE "\n" "" pid ${pid}) endif () @@ -185,6 +195,21 @@ if ("${nudge}" MATCHES "") if (NOT "${maps}" MATCHES "\\.dpc\n") set(fail_msg "no .dpc files found in ${maps}: not using pcaches!") endif () +elseif ("${nudge}" MATCHES "") + set(nudge_cmd run_in_bg) + string(REGEX REPLACE "" "${toolbindir}/drrun@-attach@${pid}@-takeover_sleep@-takeovers@1000" nudge "${nudge}") + string(REGEX REPLACE "@" ";" nudge "${nudge}") + execute_process(COMMAND "${toolbindir}/${nudge_cmd}" ${nudge} + RESULT_VARIABLE nudge_result + ERROR_VARIABLE nudge_err + OUTPUT_VARIABLE nudge_out + ) + # combine out and err + set(nudge_err "${nudge_out}${nudge_err}") + if (nudge_result) + kill_background_process(ON) + message(FATAL_ERROR "*** ${nudge_cmd} failed (${nudge_result}): ${nudge_err}***\n") + endif (nudge_result) else () # nudgeunix and drconfig have different syntax: if (WIN32) @@ -223,6 +248,29 @@ if ("${orig_nudge}" MATCHES "-client") message(FATAL_ERROR "Timed out waiting for more output") endif () endwhile() +elseif ("${orig_nudge}" MATCHES "") + # wait until attached + set(iters 0) + while (NOT "${output}" MATCHES "attach\n") + do_sleep(0.1) + file(READ "${out}" output) + math(EXPR iters "${iters}+1") + if (${iters} GREATER ${MAX_ITERS}) + kill_background_process(ON) + message(FATAL_ERROR "Timed out waiting for attach") + endif () + endwhile() + # wait until thread init + set(iters 0) + while (NOT "${output}" MATCHES "thread init\n") + do_sleep(0.1) + file(READ "${out}" output) + math(EXPR iters "${iters}+1") + if (${iters} GREATER ${MAX_ITERS}) + kill_background_process(ON) + message(FATAL_ERROR "Timed out waiting for attach") + endif () + endwhile() else () # for reset or other DR tests there won't be further output # so we have to guess how long to wait. diff --git a/suite/tests/win32/attachee.c b/suite/tests/win32/attachee.c new file mode 100644 index 00000000000..41a3bc83893 --- /dev/null +++ b/suite/tests/win32/attachee.c @@ -0,0 +1,62 @@ +/* ********************************************************** + * Copyright (c) 2018-2019 Google, 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 Google, 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. + */ + +/* An app that announces when it's up, and stays up long enough for testing + * attach. */ +#include "tools.h" +#include + +/* Timeout to avoid leaving stale processes in case something goes wrong. */ +static VOID CALLBACK +TimerProc(HWND hwnd, UINT msg, UINT_PTR id, DWORD time) +{ + print("timed out\n"); + ExitProcess(1); +} + +int +main(int argc, const char *argv[]) +{ + /* We put the pid into the title so that tools/closewnd can target it + * uniquely when run in a parallel test suite. + * runall.cmake assumes this precise title. + */ + char title[64]; + _snprintf_s(title, BUFFER_SIZE_BYTES(title), BUFFER_SIZE_ELEMENTS(title), + "Infloop pid=%d", GetProcessId(GetCurrentProcess())); + SetTimer(NULL, 0, 180 * 1000 /*3 mins*/, TimerProc); + + print("starting attachee\n"); + MessageBoxA(NULL, "DynamoRIO test: will be auto-closed", title, MB_OK); + print("MessageBox closed\n"); + return 0; +} diff --git a/tools/drdeploy.c b/tools/drdeploy.c index 6f5c6423090..b2c276847fa 100644 --- a/tools/drdeploy.c +++ b/tools/drdeploy.c @@ -309,6 +309,18 @@ 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" + " 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 +1277,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 +1285,24 @@ _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"); + 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 # 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, &app_name); + } else +# endif /* WINDOWS */ { errcode = dr_inject_process_create(app_name, app_argv, &inject_data); info("created child with pid " PIDFMT " for %s",