| /* ********************************************************** |
| * Copyright (c) 2010-2014 Google, Inc. All rights reserved. |
| * Copyright (c) 2000-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. |
| */ |
| |
| /* Copyright (c) 2003-2007 Determina Corp. */ |
| /* Copyright (c) 2001-2003 Massachusetts Institute of Technology */ |
| /* Copyright (c) 2000-2001 Hewlett-Packard Company */ |
| |
| /* |
| * os.c - win32 specific routines |
| */ |
| |
| #include "../globals.h" |
| #include "../fragment.h" |
| #include "../fcache.h" |
| #include "ntdll.h" |
| #include "os_private.h" |
| #include "../nudge.h" |
| #include "../moduledb.h" |
| #include "../hotpatch.h" |
| #ifdef DEBUG |
| # include "../vmareas.h" |
| #endif |
| #include "../dispatch.h" |
| #include "instrument.h" /* is_in_client_lib() */ |
| |
| #include <windows.h> |
| #include <stddef.h> /* for offsetof */ |
| |
| #include "events.h" /* event log messages */ |
| #include "aslr.h" |
| #include "../synch.h" |
| #include "../perscache.h" |
| #include "../native_exec.h" |
| |
| #ifdef NOT_DYNAMORIO_CORE_PROPER |
| # undef ASSERT |
| # undef ASSERT_NOT_IMPLEMENTED |
| # undef ASSERT_NOT_TESTED |
| # undef ASSERT_CURIOSITY_ONCE |
| # define ASSERT(x) /* nothing */ |
| # define ASSERT_NOT_IMPLEMENTED(x) /* nothing */ |
| # define ASSERT_NOT_TESTED(x) /* nothing */ |
| # define ASSERT_CURIOSITY_ONCE(x) /* nothing */ |
| # undef LOG |
| # define LOG(x, ...) /* nothing */ |
| #else /* !NOT_DYNAMORIO_CORE_PROPER: around most of file, to exclude preload */ |
| |
| #ifdef DEBUG |
| DECLARE_CXTSWPROT_VAR(static mutex_t snapshot_lock, INIT_LOCK_FREE(snapshot_lock)); |
| #endif |
| |
| DECLARE_CXTSWPROT_VAR(static mutex_t dump_core_lock, INIT_LOCK_FREE(dump_core_lock)); |
| DECLARE_CXTSWPROT_VAR(static mutex_t debugbox_lock, INIT_LOCK_FREE(debugbox_lock)); |
| |
| /* PR 601533: cleanup_and_terminate must release the initstack_mutex |
| * prior to its final syscall, yet the wow64 syscall writes to the |
| * stack b/c it makes a call, so we have a race that can lead to a |
| * hang or worse. we do not expect the syscall to return, so we can |
| * use a global single-entry stack (the wow64 layer swaps to a |
| * different stack: presumably for alignment and other reasons). |
| * We also use this for non-wow64, except on win8 wow64 where we need |
| * a per-thread stack and we use the TEB. |
| * We do leave room to store the 2 args to NtTerminateProcess for win8 wow64 |
| * in case we can't get the target thread's TEB. |
| */ |
| #define WOW64_SYSCALL_SETUP_SIZE 3*XSP_SZ /* 2 args + retaddr of call to win8 wrapper */ |
| /* 1 for call + 1 extra + setup */ |
| #define WOW64_SYSCALL_STACK_SIZE 2*XSP_SZ + (WOW64_SYSCALL_SETUP_SIZE) |
| DECLARE_NEVERPROT_VAR(static byte wow64_syscall_stack_array[WOW64_SYSCALL_STACK_SIZE], |
| {0}); |
| /* We point it several stack slots in for win8 setup */ |
| const byte *wow64_syscall_stack = |
| &wow64_syscall_stack_array[WOW64_SYSCALL_STACK_SIZE - WOW64_SYSCALL_SETUP_SIZE]; |
| |
| /* globals */ |
| bool intercept_asynch = false; |
| bool intercept_callbacks = false; |
| /* we store here to enable TEB.ClientId.ProcessHandle as a spill slot */ |
| process_id_t win32_pid = 0; |
| /* we store here to enable TEB.ProcessEnvironmentBlock as a spill slot */ |
| void *peb_ptr; |
| static int os_version; |
| static uint os_service_pack_major; |
| static uint os_service_pack_minor; |
| static const char *os_name; |
| app_pc vsyscall_page_start = NULL; |
| /* pc kernel will claim app is at while in syscall */ |
| app_pc vsyscall_after_syscall = NULL; |
| /* pc of the end of the syscall instr itself */ |
| app_pc vsyscall_syscall_end_pc = NULL; |
| /* atomic variable to prevent multiple threads from trying to detach at the same time */ |
| DECLARE_CXTSWPROT_VAR(static volatile int dynamo_detaching_flag, LOCK_FREE_STATE); |
| |
| #ifdef PROFILE_RDTSC |
| uint kilo_hertz; /* cpu clock speed */ |
| #endif |
| |
| #define HEAP_INITIAL_SIZE 1024*1024 |
| |
| /* pc values delimiting dynamo dll image */ |
| app_pc dynamo_dll_start = NULL; |
| app_pc dynamo_dll_end = NULL; /* open-ended */ |
| |
| /* needed for randomizing DR library location */ |
| static app_pc dynamo_dll_preferred_base = NULL; |
| |
| /* thread-local storage slots */ |
| enum {TLS_UNINITIALIZED = (ushort) 0U}; |
| static ushort tls_local_state_offs = TLS_UNINITIALIZED; |
| /* we keep this cached for easy asm access */ |
| static ushort tls_dcontext_offs = TLS_UNINITIALIZED; |
| |
| /* used for early inject */ |
| app_pc parent_early_inject_address = NULL; /* dynamo.c fills in */ |
| /* note that this is the early inject location we'll use for child processes |
| * dr_early_injected_location is the location (if any) that the current |
| * process was injected at */ |
| static int early_inject_location = INJECT_LOCATION_Invalid; |
| static app_pc early_inject_address = NULL; |
| static app_pc ldrpLoadDll_address_not_NT = NULL; |
| static app_pc ldrpLoadDll_address_NT = NULL; |
| static app_pc ldrpLoadImportModule_address = NULL; |
| dcontext_t *early_inject_load_helper_dcontext = NULL; |
| |
| static char cwd[MAXIMUM_PATH]; |
| |
| /* forwards */ |
| static void |
| get_system_basic_info(void); |
| static bool |
| is_child_in_thin_client(HANDLE process_handle); |
| static const char* |
| get_process_SID_string(void); |
| static const PSID |
| get_Everyone_SID(void); |
| static const PSID |
| get_process_owner_SID(void); |
| |
| static size_t |
| get_allocation_size_ex(HANDLE process, byte *pc, byte **base_pc); |
| |
| static void |
| os_take_over_init(void); |
| |
| static void |
| os_take_over_exit(void); |
| |
| bool |
| os_user_directory_supports_ownership(void); |
| |
| /* Safely gets the target of the call (assumed to be direct) to the nth stack |
| * frame (i.e. the entry point to that function), returns NULL on failure. |
| * NOTE - Be aware this routine may be called by DllMain before dr is |
| * initialized (before even syscalls_init, though note that safe_read should |
| * be fine as will just use the nt wrapper). */ |
| static app_pc |
| get_nth_stack_frames_call_target(int num_frames, reg_t *ebp) |
| { |
| reg_t *cur_ebp = ebp; |
| reg_t next_frame[2]; |
| int i; |
| |
| /* walk up the stack */ |
| for (i = 0; i < num_frames; i++) { |
| if (!safe_read(cur_ebp, sizeof(next_frame), next_frame)) |
| break; |
| cur_ebp = (reg_t *)next_frame[0]; |
| } |
| |
| if (i == num_frames) { |
| /* success walking frames, return address should be the after |
| * call address of the call that targeted this frame */ |
| /* FIXME - would be nice to get this with decode_cti, but dr might |
| * not even be initialized yet and this is safer */ |
| byte buf[5]; /* sizeof call rel32 */ |
| if (safe_read((byte *)(next_frame[1] - sizeof(buf)), sizeof(buf), &buf) && |
| buf[0] == CALL_REL32_OPCODE) { |
| app_pc return_point = (app_pc)next_frame[1]; |
| return (return_point + *(int *)&buf[1]); |
| } |
| } |
| return NULL; |
| } |
| |
| /* Should be called from NtMapViewOfSection interception with *base |
| * pointing to the just mapped region. */ |
| void |
| check_for_ldrpLoadImportModule(byte *base, uint *ebp) |
| { |
| MEMORY_BASIC_INFORMATION mbi; |
| if (query_virtual_memory(base, &mbi, sizeof(mbi)) == sizeof(mbi) |
| && mbi.Type == MEM_IMAGE && is_readable_pe_base(base)) { |
| /* loaded a module, check name */ |
| const char *name; |
| bool match = false; |
| |
| name = get_dll_short_name(base); /* we only need pe name */ |
| if (name != NULL) { |
| LOG(GLOBAL, LOG_TOP, 1, |
| "early_inject hit mapview of image %s\n", name); |
| string_option_read_lock(); |
| /* we control both the pe_name and the option value so use |
| * strcmp (vs. strcasecmp), just to keep things tight */ |
| match = |
| strcmp(DYNAMO_OPTION(early_inject_helper_name), |
| name) == 0; |
| string_option_read_unlock(); |
| } |
| if (match) { |
| /* Found it. We expect the stack to look like this |
| * (in NtMapViewOfSection) |
| * ntdll!LdrpMapDll |
| * ntdll!LdrpLoadImportModule (what we want) |
| * After that don't really care (is one of the |
| * Ldrp*ImportDescriptor* routines. So we walk the |
| * stack back and get the desired address. |
| * FIXME - would be nice if we had some way to double |
| * check this address, could try to decode and check against |
| * the versions we've seen. |
| * Note that NtMapViewOfSection in all its various platform forms |
| * (i.e. int, vsyscall, KiFastSystemCall etc.) doesn't set up a |
| * new frame (nor do its callees) so will always be depth 2 |
| */ |
| #define STACK_DEPTH_LdrpLoadImportModule 2 |
| ldrpLoadImportModule_address = |
| get_nth_stack_frames_call_target(STACK_DEPTH_LdrpLoadImportModule, |
| (reg_t *)ebp); |
| LOG(GLOBAL, LOG_TOP, 1, |
| "early_inject found address "PFX" for LdrpLoadImportModule\n", |
| ldrpLoadImportModule_address); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| ** DllMain Routines |
| ** |
| **/ |
| |
| #ifdef INTERNAL |
| /* we have interp not inline calls to this routine */ |
| void |
| DllMainThreadAttach() |
| { |
| if (INTERNAL_OPTION(noasynch) && dynamo_initialized && !dynamo_exited) { |
| /* we normally intercept thread creation in callback.c, but with |
| * noasynch, we do it here (which is later, but better than nothing) |
| */ |
| LOG(GLOBAL, LOG_TOP|LOG_THREADS, 1, |
| "DllMain: initializing new thread "TIDFMT"\n", get_thread_id()); |
| dynamo_thread_init(NULL, NULL _IF_CLIENT_INTERFACE(false)); |
| } |
| } |
| #endif |
| |
| /* Hand-made DO_ONCE since DllMain is executed prior to DR init */ |
| DECLARE_FREQPROT_VAR(static bool do_once_DllMain, false); |
| |
| /* DLL entry point |
| * N.B.: dynamo interprets this routine! |
| */ |
| /* get_nth_stack_frames_call_target() assumes that DllMain has a frame pointer |
| * so we cannot optimize it (i#566) |
| */ |
| START_DO_NOT_OPTIMIZE |
| bool WINAPI /* get link warning 4216 if export via APIENTRY */ |
| DllMain(HANDLE hModule, DWORD reason_for_call, LPVOID Reserved) |
| { |
| switch (reason_for_call) { |
| case DLL_PROCESS_ATTACH: |
| /* case 8097: with -no_hide, DllMain will be called a second time |
| * after all the statically-bound dlls are loaded (the loader |
| * blindly calls all the init routines regardless of whether a dll |
| * was explicitly loaded and already had its init routine called). |
| * We make that 2nd time a nop via a custom DO_ONCE (since default |
| * DO_ONCE will try to unprotect .data, but we're pre-init). |
| */ |
| if (!do_once_DllMain) { |
| byte *cur_ebp; |
| do_once_DllMain = true; |
| ASSERT(!dynamo_initialized); |
| ASSERT(ldrpLoadDll_address_NT == NULL); |
| ASSERT(ldrpLoadDll_address_not_NT == NULL); |
| /* Carefully walk stack to find address of LdrpLoadDll. */ |
| /* Remember dr isn't initialized yet, no need to worry about |
| * protect from app etc., but also can't check options. */ |
| GET_FRAME_PTR(cur_ebp); |
| /* For non early_inject (late follow children, preinject) expect |
| * stack to look like (for win2k and higher) |
| * here (our DllMain) |
| * ntdll!LdrpCallInitRoutine |
| * ntdll!LdrpRunInitializeRoutines |
| * ntdll!LdrpLoadDll |
| * ntdll!LdrLoadDll |
| * For NT is the same only doesn't have ntdll!LdrpCallInitRoutine. |
| * |
| * That's as far we care, after that is likely to be shimeng.dll |
| * or kernel32 (possibly someone else?) depending on how we were |
| * injected. For -early_inject, ntdll!LdrGetProcedureAddress is |
| * usually the root of the call to our DLLMain (likely something |
| * to do with load vs. init order at process startup? FIXME |
| * understand better, is there a flag we can send to have this |
| * called on load?), but in that case we use the address passed to |
| * us by the parent. */ |
| #define STACK_DEPTH_LdrpLoadDll_NT 3 |
| #define STACK_DEPTH_LdrpLoadDll 4 |
| /* Since dr isn't initialized yet we can't call get_os_version() |
| * so just grab both possible LdrpLoadDll addresses (NT and non NT) |
| * and we'll sort it out later in early_inject_init(). */ |
| ldrpLoadDll_address_NT = |
| get_nth_stack_frames_call_target(STACK_DEPTH_LdrpLoadDll_NT, |
| (reg_t *)cur_ebp); |
| ldrpLoadDll_address_not_NT = |
| get_nth_stack_frames_call_target(STACK_DEPTH_LdrpLoadDll, |
| (reg_t *)cur_ebp); |
| /* FIXME - would be nice to have extra verification here, |
| * but after this frame there are too many possibilites (many |
| * of which are unexported) so is hard to find something we |
| * can check. */ |
| } else |
| ASSERT(dynamo_initialized); |
| break; |
| #ifdef INTERNAL |
| case DLL_THREAD_ATTACH: |
| DllMainThreadAttach(); |
| break; |
| #endif |
| /* we don't care about DLL_PROCESS_DETACH or DLL_THREAD_DETACH */ |
| } |
| return true; |
| } |
| END_DO_NOT_OPTIMIZE |
| |
| #ifdef WINDOWS_PC_SAMPLE |
| static profile_t *global_profile = NULL; |
| static profile_t *dynamo_dll_profile = NULL; |
| static profile_t *ntdll_profile = NULL; |
| file_t profile_file = INVALID_FILE; |
| DECLARE_CXTSWPROT_VAR(mutex_t profile_dump_lock, INIT_LOCK_FREE(profile_dump_lock)); |
| |
| static void |
| get_dll_bounds(wchar_t *name, app_pc *start, app_pc *end) |
| { |
| module_handle_t dllh; |
| size_t len; |
| PBYTE pb; |
| MEMORY_BASIC_INFORMATION mbi; |
| |
| dllh = get_module_handle(name); |
| ASSERT(dllh != NULL); |
| pb = (PBYTE) dllh; |
| /* FIXME: we should just call get_allocation_size() */ |
| len = query_virtual_memory(pb, &mbi, sizeof(mbi)); |
| ASSERT(len == sizeof(mbi)); |
| ASSERT(mbi.State != MEM_FREE); |
| *start = (app_pc) mbi.AllocationBase; |
| do { |
| if (mbi.State == MEM_FREE || (app_pc) mbi.AllocationBase != *start) |
| break; |
| if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize)) |
| break; |
| pb += mbi.RegionSize; |
| } while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi)); |
| *end = (app_pc) pb; |
| } |
| |
| static void |
| init_global_profiles() |
| { |
| app_pc start, end; |
| |
| /* create the profile file */ |
| /* if logging is on create in log directory, else use base directory */ |
| DOLOG(1, LOG_ALL, { |
| char buf[MAX_PATH]; |
| uint size = BUFFER_SIZE_ELEMENTS(buf); |
| if (get_log_dir(PROCESS_DIR, buf, &size)) { |
| NULL_TERMINATE_BUFFER(buf); |
| strncat(buf, "\\profile", BUFFER_SIZE_ELEMENTS(buf) - strlen(buf)); |
| NULL_TERMINATE_BUFFER(buf); |
| profile_file = os_open(buf, OS_OPEN_REQUIRE_NEW|OS_OPEN_WRITE); |
| LOG(GLOBAL, LOG_PROFILE, 1, "Profile file is \"%s\"\n", buf); |
| } |
| }); |
| if (profile_file == INVALID_FILE) { |
| get_unique_logfile(".profile", NULL, 0, false, &profile_file); |
| } |
| DOLOG(1, LOG_PROFILE, { |
| if (profile_file == INVALID_FILE) |
| LOG(GLOBAL, LOG_PROFILE, 1, "Failed to create profile file\n"); |
| }); |
| ASSERT(profile_file != INVALID_FILE); |
| |
| /* Case 7533: put basic run info in profile file. */ |
| print_version_and_app_info(profile_file); |
| |
| /* set the interval, don't assert success, on my desktop anything less than |
| * 1221 gets set to 1221 on laptop was different minimum value, at least |
| * appears that os sets it close as possible to the requested (starting |
| * value was 39021 for me) */ |
| LOG(GLOBAL, LOG_PROFILE, 1, "Profile interval was %d, setting to %d,", |
| nt_query_profile_interval(), dynamo_options.prof_pcs_freq); |
| nt_set_profile_interval(dynamo_options.prof_pcs_freq); |
| LOG(GLOBAL, LOG_PROFILE, 1, " is now %d (units of 100 nanoseconds)\n", |
| nt_query_profile_interval()); |
| print_file(profile_file, "Interval %d\n\n", nt_query_profile_interval()); |
| |
| /* create profiles */ |
| /* Default shift of 30 gives 4 buckets for the global profile, |
| * allows us to separate kernel and user space (even in the case |
| * of 3GB user space). Note if invalid range given we default to |
| * 30, so we always have a global profile to use as denominator |
| * later. |
| */ |
| global_profile = create_profile(UNIVERSAL_REGION_BASE, UNIVERSAL_REGION_END, |
| DYNAMO_OPTION(prof_pcs_global), NULL); |
| if (dynamo_options.prof_pcs_DR >= 2 && dynamo_options.prof_pcs_DR <= 32) { |
| get_dll_bounds(L_DYNAMORIO_LIBRARY_NAME, &start, &end); |
| dynamo_dll_profile = create_profile(start, end, |
| dynamo_options.prof_pcs_DR, NULL); |
| } |
| if (dynamo_options.prof_pcs_ntdll >= 2 && |
| dynamo_options.prof_pcs_ntdll <= 32) { |
| get_dll_bounds(L"ntdll.dll", &start, &end); |
| ntdll_profile = create_profile(start, end, |
| dynamo_options.prof_pcs_ntdll, NULL); |
| } |
| |
| /* start profiles */ |
| start_profile(global_profile); |
| if (dynamo_dll_profile) |
| start_profile(dynamo_dll_profile); |
| if (ntdll_profile) |
| start_profile(ntdll_profile); |
| } |
| |
| static void |
| dump_dll_profile(profile_t *profile, uint global_sum, char *dll_name) |
| { |
| uint dll_sum; |
| uint top=0, bottom=0; |
| |
| dll_sum = sum_profile(profile); |
| if (global_sum > 0) |
| divide_uint64_print(dll_sum, global_sum, true, 2, &top, &bottom); |
| print_file(profile_file, |
| "\nDumping %s profile\n%d hits out of %d, %u.%.2u%%\n", |
| dll_name, dll_sum, global_sum, top, bottom); |
| LOG(GLOBAL, LOG_PROFILE, 1, |
| "%s profile had %d hits out of %d total, %u.%.2u%%\n", |
| dll_name, dll_sum, global_sum, top, bottom); |
| dump_profile(profile_file, profile); |
| free_profile(profile); |
| } |
| |
| static void |
| exit_global_profiles() |
| { |
| int global_sum; |
| |
| if (dynamo_dll_profile) |
| stop_profile(dynamo_dll_profile); |
| if (ntdll_profile) |
| stop_profile(ntdll_profile); |
| stop_profile(global_profile); |
| |
| global_sum = sum_profile(global_profile); |
| |
| /* we expect to be the last thread at this point. |
| FIXME: we can remove the mutex_lock/unlock then */ |
| mutex_lock(&profile_dump_lock); |
| if (dynamo_dll_profile) |
| dump_dll_profile(dynamo_dll_profile, global_sum, "dynamorio.dll"); |
| if (ntdll_profile) |
| dump_dll_profile(ntdll_profile, global_sum, "ntdll.dll"); |
| |
| print_file(profile_file, "\nDumping global profile\n%d hits\n", global_sum); |
| dump_profile(profile_file, global_profile); |
| mutex_unlock(&profile_dump_lock); |
| LOG(GLOBAL, LOG_PROFILE, 1, |
| "\nDumping global profile\n%d hits\n", global_sum); |
| DOLOG(1, LOG_PROFILE, dump_profile(GLOBAL, global_profile);); |
| free_profile(global_profile); |
| |
| DELETE_LOCK(profile_dump_lock); |
| } |
| #endif |
| |
| /** |
| ** |
| ****************************************************************************/ |
| |
| static uint |
| get_context_xstate_flag(void) |
| { |
| /* i#437: AVX is supported on Windows 7 SP1 and Windows Server 2008 R2 SP1 |
| * win7sp1+ both should be 0x40. |
| */ |
| if (YMM_ENABLED()) { |
| /* YMM_ENABLED indicates both OS and processor support (i#1278) |
| * but we expect OS support only on Win7 SP1+. |
| * XXX: what about the WINDOWS Server 2008 R2? |
| */ |
| ASSERT_CURIOSITY(os_version >= WINDOWS_VERSION_8 || |
| (os_version == WINDOWS_VERSION_7 && |
| os_service_pack_major >= 1)); |
| return (IF_X64_ELSE(CONTEXT_AMD64, CONTEXT_i386) | 0x40L); |
| } |
| return IF_X64_ELSE((CONTEXT_AMD64 | 0x20L), (CONTEXT_i386 | 0x40L)); |
| } |
| |
| /* FIXME: Right now error reporting will work here, but once we have our |
| * error reporting syscalls going through wrappers and requiring this |
| * init routine, we'll have to have a fallback here that dynamically |
| * determines the syscalls and finishes init, and then reports the error. |
| * We may never be able to report errors for the non-NT OS family. |
| * N.B.: this is too early for LOGs so don't do any -- any errors reported |
| * will not die, they will simply skip LOG. |
| * N.B.: this is prior to eventlog_init(), but then we've been reporting |
| * usage errors prior to that for a long time now anyway. |
| */ |
| bool |
| windows_version_init() |
| { |
| PEB *peb = get_own_peb(); |
| |
| /* choose appropriate syscall array (the syscall numbers change from |
| * one version of windows to the next! |
| * they may even change at different patch levels) |
| */ |
| syscalls = NULL; |
| |
| DOCHECK(1, { check_syscall_array_sizes(); }); |
| |
| /* In at least 2K, XP, XP64, Vista, and Win7, the service pack is |
| * stored in peb->OSCSDVersion, major in the top byte: |
| */ |
| os_service_pack_major = (peb->OSCSDVersion & 0xff00) >> 8; |
| os_service_pack_minor = (peb->OSCSDVersion & 0xff); |
| |
| if (peb->OSPlatformId == VER_PLATFORM_WIN32_NT) { |
| /* WinNT or descendents */ |
| if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 3) { |
| if (module_is_64bit(get_ntdll_base())) { |
| syscalls = (int *) windows_81_x64_syscalls; |
| os_name = "Microsoft Windows 8.1 x64"; |
| } else if (is_wow64_process(NT_CURRENT_PROCESS)) { |
| syscalls = (int *) windows_81_wow64_syscalls; |
| os_name = "Microsoft Windows 8.1 x64"; |
| } else { |
| syscalls = (int *) windows_81_x86_syscalls; |
| os_name = "Microsoft Windows 8.1"; |
| } |
| os_version = WINDOWS_VERSION_8_1; |
| } |
| else if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 2) { |
| if (module_is_64bit(get_ntdll_base())) { |
| syscalls = (int *) windows_8_x64_syscalls; |
| os_name = "Microsoft Windows 8 x64"; |
| } else if (is_wow64_process(NT_CURRENT_PROCESS)) { |
| syscalls = (int *) windows_8_wow64_syscalls; |
| os_name = "Microsoft Windows 8 x64"; |
| } else { |
| syscalls = (int *) windows_8_x86_syscalls; |
| os_name = "Microsoft Windows 8"; |
| } |
| os_version = WINDOWS_VERSION_8; |
| } else if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 1) { |
| module_handle_t ntdllh = get_ntdll_base(); |
| /* i#437: ymm/avx is supported after Win-7 SP1 */ |
| if (os_service_pack_major >= 1) { |
| /* Sanity check on our SP ver retrieval */ |
| ASSERT(get_proc_address(ntdllh, "RtlCopyContext") != NULL); |
| if (module_is_64bit(get_ntdll_base()) || |
| is_wow64_process(NT_CURRENT_PROCESS)) { |
| syscalls = (int *) windows_7_x64_syscalls; |
| os_name = "Microsoft Windows 7 x64 SP1"; |
| } else { |
| syscalls = (int *) windows_7_syscalls; |
| os_name = "Microsoft Windows 7 SP1"; |
| } |
| } else { |
| ASSERT(get_proc_address(ntdllh, "RtlCopyContext") == NULL); |
| if (module_is_64bit(get_ntdll_base()) || |
| is_wow64_process(NT_CURRENT_PROCESS)) { |
| syscalls = (int *) windows_7_x64_syscalls; |
| os_name = "Microsoft Windows 7 x64 SP0"; |
| } else { |
| syscalls = (int *) windows_7_syscalls; |
| os_name = "Microsoft Windows 7 SP0"; |
| } |
| } |
| os_version = WINDOWS_VERSION_7; |
| } else if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 0) { |
| module_handle_t ntdllh = get_ntdll_base(); |
| if (os_service_pack_major >= 1) { |
| /* Vista system call number differ between service packs, we use |
| * the existence of NtReplacePartitionUnit as a sanity check |
| * for sp1 - see PR 246402. They also differ for |
| * 32-bit vs 64-bit/wow64. |
| */ |
| ASSERT(get_proc_address(ntdllh, "NtReplacePartitionUnit") != NULL); |
| if (module_is_64bit(get_ntdll_base()) || |
| is_wow64_process(NT_CURRENT_PROCESS)) { |
| syscalls = (int *) windows_vista_sp1_x64_syscalls; |
| os_name = "Microsoft Windows Vista x64 SP1"; |
| } else { |
| syscalls = (int *) windows_vista_sp1_syscalls; |
| os_name = "Microsoft Windows Vista SP1"; |
| } |
| } else { |
| ASSERT(get_proc_address(ntdllh, "NtReplacePartitionUnit") == NULL); |
| if (module_is_64bit(get_ntdll_base()) || |
| is_wow64_process(NT_CURRENT_PROCESS)) { |
| syscalls = (int *) windows_vista_sp0_x64_syscalls; |
| os_name = "Microsoft Windows Vista x64 SP0"; |
| } else { |
| syscalls = (int *) windows_vista_sp0_syscalls; |
| os_name = "Microsoft Windows Vista SP0"; |
| } |
| } |
| os_version = WINDOWS_VERSION_VISTA; |
| } else if (peb->OSMajorVersion == 5 && peb->OSMinorVersion == 2) { |
| /* Version 5.2 can mean 32- or 64-bit 2003, or 64-bit XP */ |
| /* Assumption: get_ntll_base makes no system calls */ |
| if (module_is_64bit(get_ntdll_base()) || |
| is_wow64_process(NT_CURRENT_PROCESS)) { |
| /* We expect x64 2003 and x64 XP to have the same system call |
| * numbers but that has not been verified. System call numbers |
| * remain the same even under WOW64 (ignoring the extra WOW |
| * system calls, anyway). We do not split the version for WOW |
| * as most users do not care to distinguish; those that do must |
| * use a separate is_wow64_process() check. |
| */ |
| syscalls = (int *) windows_XP_x64_syscalls; |
| /* We don't yet have need to split the version enum */ |
| os_version = WINDOWS_VERSION_2003; |
| os_name = "Microsoft Windows x64 XP/2003"; |
| } else { |
| syscalls = (int *) windows_2003_syscalls; |
| os_version = WINDOWS_VERSION_2003; |
| os_name = "Microsoft Windows 2003"; |
| } |
| } else if (peb->OSMajorVersion == 5 && peb->OSMinorVersion == 1) { |
| syscalls = (int *) windows_XP_syscalls; |
| os_version = WINDOWS_VERSION_XP; |
| os_name = "Microsoft Windows XP"; |
| } else if (peb->OSMajorVersion == 5 && peb->OSMinorVersion == 0) { |
| syscalls = (int *) windows_2000_syscalls; |
| os_version = WINDOWS_VERSION_2000; |
| os_name = "Microsoft Windows 2000"; |
| } else if (peb->OSMajorVersion == 4) { |
| module_handle_t ntdllh = get_ntdll_base(); |
| os_version = WINDOWS_VERSION_NT; |
| /* NT4 syscalls differ among service packs. |
| * Rather than reading the registry to find the service pack we |
| * directly check which system calls are there. We don't just |
| * check the number of the last syscall in our list b/c we want to |
| * avoid issues w/ hookers. |
| * We rely on these observations: |
| * SP3: + Nt{Read,Write}FileScatter |
| * SP4: - NtW32Call |
| */ |
| if (get_proc_address(ntdllh, "NtW32Call") != NULL) { |
| /* < SP4 */ |
| /* we don't know whether SP1 and SP2 fall in line w/ SP0 or w/ SP3, |
| * or possibly are different from both, but we don't support them |
| */ |
| if (get_proc_address(ntdllh, "NtReadFileScatter") != NULL) { |
| /* > SP0 */ |
| syscalls = (int *) windows_NT_sp3_syscalls; |
| os_name = "Microsoft Windows NT SP3"; |
| } else { |
| /* < SP3 */ |
| syscalls = (int *) windows_NT_sp0_syscalls; |
| os_name = "Microsoft Windows NT SP0"; |
| } |
| } else { |
| syscalls = (int *) windows_NT_sp4_syscalls; |
| os_name = "Microsoft Windows NT SP4, 5, 6, or 6a"; |
| } |
| } else { |
| SYSLOG_INTERNAL_ERROR("Unknown Windows NT-family version: major=%d, minor=%d\n", |
| peb->OSMajorVersion, peb->OSMinorVersion); |
| if (standalone_library) |
| return false; /* let app handle it */ |
| os_name = "Unrecognized Windows NT-family version"; |
| FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(), |
| get_application_pid(), PRODUCT_NAME, os_name); |
| } |
| } else if (peb->OSPlatformId == VER_PLATFORM_WIN32_WINDOWS) { |
| /* Win95 or Win98 */ |
| uint ver_high = (peb->OSBuildNumber >> 8) & 0xff; |
| uint ver_low = peb->OSBuildNumber & 0xff; |
| if (standalone_library) |
| return false; /* let app handle it */ |
| if (ver_low >= 90 || ver_high >= 5) |
| os_name = "Windows ME"; |
| else if (ver_low >= 10 && ver_low < 90) |
| os_name = "Windows 98"; |
| else if (ver_low < 5) |
| os_name = "Windows 3.1 / WfWg"; |
| else if (ver_low < 10) |
| os_name = "Windows 98"; |
| else |
| os_name = "this unknown version of Windows"; |
| FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(), |
| get_application_pid(), PRODUCT_NAME, os_name); |
| } else { |
| if (standalone_library) |
| return false; /* let app handle it */ |
| os_name = "Win32s"; |
| /* Win32S on Windows 3.1 */ |
| FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(), |
| get_application_pid(), PRODUCT_NAME, os_name); |
| } |
| return true; |
| } |
| |
| /* Note that assigning a process to a Job is done only after it has |
| * been created - with ZwAssignProcessToJobObject(), and we may start |
| * before or after that has been done. |
| */ |
| static void |
| print_mem_quota() |
| { |
| QUOTA_LIMITS qlimits; |
| NTSTATUS res = get_process_mem_quota(NT_CURRENT_PROCESS, &qlimits); |
| if (!NT_SUCCESS(res)) { |
| ASSERT(false && "print_mem_quota"); |
| return; |
| } |
| LOG(GLOBAL, LOG_TOP, 1, "Process Memory Limits:\n"); |
| LOG(GLOBAL, LOG_TOP, 1, "\t Paged pool limit: %6d KB\n", |
| qlimits.PagedPoolLimit/1024); |
| LOG(GLOBAL, LOG_TOP, 1, "\t Non Paged pool limit: %6d KB\n", |
| qlimits.NonPagedPoolLimit/1024); |
| LOG(GLOBAL, LOG_TOP, 1, "\t Minimum working set size: %6d KB\n", |
| qlimits.MinimumWorkingSetSize/1024); |
| LOG(GLOBAL, LOG_TOP, 1, "\t Maximum working set size: %6d KB\n", |
| qlimits.MaximumWorkingSetSize/1024); |
| |
| /* 4GB for unlimited */ |
| LOG(GLOBAL, LOG_TOP, 1, "\t Pagefile limit: %6d KB\n", |
| qlimits.PagefileLimit/1024); |
| /* TimeLimit not supported on Win2k, but WSRM (Windows System |
| * Resource Manager) can definitely set, so expected to be |
| * supported on Win2003. Time in 100ns units. |
| */ |
| LOG(GLOBAL, LOG_TOP, 1, "\t TimeLimit: 0x%.8x%.8x\n", |
| qlimits.TimeLimit.HighPart, qlimits.TimeLimit.LowPart); |
| } |
| |
| /* os-specific initializations */ |
| void |
| os_init(void) |
| { |
| PEB *peb = get_own_peb(); |
| uint alignment = 0; |
| uint offs; |
| int res; |
| DEBUG_DECLARE(bool ok;) |
| |
| if (dynamo_options.max_supported_os_version < |
| peb->OSMajorVersion * 10 + peb->OSMinorVersion) { |
| SYSLOG(SYSLOG_WARNING, UNSUPPORTED_OS_VERSION, 3, |
| get_application_name(), get_application_pid(), os_name); |
| } |
| |
| /* make sure we create the message box title string before we are |
| * multi-threaded and is no longer safe to do so on demand, this also |
| * takes care of initializing the static buffer get_appilication_name |
| * and get_application_pid */ |
| debugbox_setup_title(); |
| |
| win32_pid = get_process_id(); |
| LOG(GLOBAL, LOG_TOP, 1, "Process id: %d\n", win32_pid); |
| peb_ptr = (void *) get_own_peb(); |
| LOG(GLOBAL, LOG_TOP, 1, "PEB: "PFX"\n", peb_ptr); |
| ASSERT((PEB *)peb_ptr == get_own_teb()->ProcessEnvironmentBlock); |
| #ifndef X64 |
| /* We no longer rely on peb64 being adjacent to peb for i#816 but |
| * let's print it nonetheless |
| */ |
| DOLOG(1, LOG_TOP, { |
| if (is_wow64_process(NT_CURRENT_PROCESS)) { |
| ptr_uint_t peb64 = (ptr_uint_t) get_own_x64_peb(); |
| LOG(GLOBAL, LOG_TOP, 1, "x64 PEB: "PFX"\n", peb64); |
| } |
| }); |
| #endif |
| |
| /* match enums in os_exports.h with TEB definition from ntdll.h */ |
| ASSERT(EXCEPTION_LIST_TIB_OFFSET == offsetof(TEB, ExceptionList)); |
| ASSERT(TOP_STACK_TIB_OFFSET == offsetof(TEB, StackBase)); |
| ASSERT(BASE_STACK_TIB_OFFSET == offsetof(TEB, StackLimit)); |
| ASSERT(FIBER_DATA_TIB_OFFSET == offsetof(TEB, FiberData)); |
| ASSERT(SELF_TIB_OFFSET == offsetof(TEB, Self)); |
| ASSERT(TID_TIB_OFFSET == offsetof(TEB, ClientId) + |
| offsetof(CLIENT_ID, UniqueThread)); |
| ASSERT(PID_TIB_OFFSET == offsetof(TEB, ClientId) + |
| offsetof(CLIENT_ID, UniqueProcess)); |
| ASSERT(ERRNO_TIB_OFFSET == offsetof(TEB, LastErrorValue)); |
| ASSERT(WOW64_TIB_OFFSET == offsetof(TEB, WOW32Reserved)); |
| ASSERT(PEB_TIB_OFFSET == offsetof(TEB, ProcessEnvironmentBlock)); |
| |
| /* windows_version_init should have already been called */ |
| ASSERT(syscalls != NULL); |
| LOG(GLOBAL, LOG_TOP, 1, "Running on %s == %d SP%d.%d\n", |
| os_name, os_version, os_service_pack_major, os_service_pack_minor); |
| |
| /* i#437, i#1278: get the context_xstate after proc_init() sets proc_avx_enabled() */ |
| context_xstate = get_context_xstate_flag(); |
| |
| ntdll_init(); |
| callback_init(); |
| |
| eventlog_init(); /* os dependent and currently Windows specific */ |
| |
| if (os_version >= WINDOWS_VERSION_XP) { |
| /* FIXME: bootstrapping problem where we see 0x7ffe0300 before we see |
| * the 1st sysenter...solution for now is to hardcode initial values so |
| * we pass the 1st PROGRAM_SHEPHERDING code origins test, then re-set these once |
| * we see the 1st syscall. |
| */ |
| /* on XP service pack 2 the syscall enter and exit stubs are Ki |
| * routines in ntdll.dll FIXME : as a hack for now will leave |
| * page_start as 0 (as it would be for 2000, since region is |
| * executable no need for the code origins exception) and |
| * after syscall to the appropriate value, this means will still |
| * execute the return natively (as in xp/03) for simplicity even |
| * though we could intercept it much more easily than before since |
| * the ki routines are aligned (less concern about enough space for the |
| * interception stub, nicely exported for us etc.) |
| */ |
| /* initializing so get_module_handle should be safe, FIXME */ |
| module_handle_t ntdllh = get_ntdll_base(); |
| app_pc return_point = (app_pc) get_proc_address(ntdllh, |
| "KiFastSystemCallRet"); |
| if (return_point != NULL) { |
| app_pc syscall_pc = (app_pc) get_proc_address(ntdllh, |
| "KiFastSystemCall"); |
| vsyscall_after_syscall = (app_pc) return_point; |
| /* we'll re-set this once we see the 1st syscall, but we set an |
| * initial value to what it should be for go-native scenarios |
| * where we may not see the 1st syscall (DrMem i#1235). |
| */ |
| if (syscall_pc != NULL) |
| vsyscall_syscall_end_pc = syscall_pc + SYSENTER_LENGTH; |
| else |
| vsyscall_syscall_end_pc = NULL; /* wait until 1st one */ |
| } else { |
| /* FIXME : if INT syscalls are being used then this opens up a |
| * security hole for the followin page */ |
| vsyscall_page_start = VSYSCALL_PAGE_START_BOOTSTRAP_VALUE; |
| vsyscall_after_syscall = VSYSCALL_AFTER_SYSCALL_BOOTSTRAP_VALUE; |
| vsyscall_syscall_end_pc = vsyscall_after_syscall; |
| } |
| } |
| |
| /* TLS alignment use either preferred on processor, or hardcoded option value */ |
| if (DYNAMO_OPTION(tls_align) == 0) { |
| IF_X64(ASSERT_TRUNCATE(alignment, uint, proc_get_cache_line_size())); |
| alignment = (uint) proc_get_cache_line_size(); |
| } else { |
| alignment = DYNAMO_OPTION(tls_align); |
| } |
| /* case 3701 about performance gains, |
| * and case 6670 about TLS conflict in SQL2005 */ |
| |
| /* FIXME: could control which entry should be cache aligned, but |
| * we should be able to restructure the state to ensure first |
| * entry is indeed important. Should make sure we choose same |
| * position in both release and debug, see local_state_t.stats. |
| */ |
| |
| /* allocate thread-private storage */ |
| res = tls_calloc(false/*no synch required*/, &offs, TLS_NUM_SLOTS, |
| alignment); |
| |
| DODEBUG({ |
| /* FIXME: elevate failure here to a release-build syslog? */ |
| if (!res) |
| SYSLOG_INTERNAL_ERROR("Cannot allocate %d tls slots at %d alignment", TLS_NUM_SLOTS, |
| alignment); |
| }); |
| |
| /* retry with no alignment on failure */ |
| if (!res) { |
| alignment = 0; |
| ASSERT_NOT_TESTED(); |
| |
| /* allocate thread-private storage with no alignment */ |
| res = tls_calloc(false/*no synch required*/, &offs, TLS_NUM_SLOTS, |
| alignment); |
| |
| /* report even in release build that we really can't grab in TLS64 */ |
| if (!res) { |
| ASSERT_NOT_TESTED(); |
| SYSLOG_INTERNAL_ERROR("Cannot allocate %d tls slots at %d alignment", TLS_NUM_SLOTS, |
| alignment); |
| |
| report_dynamorio_problem(NULL, DUMPCORE_INTERNAL_EXCEPTION, NULL, NULL, |
| "Unrecoverable error on TLS allocation", |
| NULL, NULL, NULL); |
| } |
| } |
| |
| ASSERT(res); |
| ASSERT(offs != TLS_UNINITIALIZED); |
| ASSERT_TRUNCATE(tls_local_state_offs, ushort, offs); |
| tls_local_state_offs = (ushort) offs; |
| LOG(GLOBAL, LOG_TOP, 1, "%d TLS slots are @ %s:0x%x\n", |
| TLS_NUM_SLOTS, IF_X64_ELSE("gs", "fs"), tls_local_state_offs); |
| ASSERT_CURIOSITY(proc_is_cache_aligned(get_local_state()) || |
| DYNAMO_OPTION(tls_align != 0)); |
| if (IF_UNIT_TEST_ELSE(true, !standalone_library)) { |
| tls_dcontext_offs = os_tls_offset(TLS_DCONTEXT_SLOT); |
| ASSERT(tls_dcontext_offs != TLS_UNINITIALIZED); |
| } |
| |
| DOLOG(1, LOG_VMAREAS, { print_modules(GLOBAL, DUMP_NOT_XML); }); |
| DOLOG(2, LOG_TOP, { print_mem_quota(); }); |
| |
| #ifdef WINDOWS_PC_SAMPLE |
| if (dynamo_options.profile_pcs) |
| init_global_profiles(); |
| #endif |
| |
| #ifdef PROFILE_RDTSC |
| if (dynamo_options.profile_times) { |
| ASSERT_NOT_TESTED(); |
| kilo_hertz = get_timer_frequency(); |
| LOG(GLOBAL, LOG_TOP|LOG_STATS, 1, "CPU MHz is %d\n", kilo_hertz/1000); |
| } |
| #endif |
| |
| if (!dr_early_injected && !dr_earliest_injected) |
| inject_init(); |
| |
| get_dynamorio_library_path(); |
| /* just to preserve side effects. If not done yet in eventlog, |
| * path needs to be be preserved before hiding from module list. |
| */ |
| |
| aslr_init(); |
| |
| /* ensure static cache buffers are primed, both for .data protection purposes and |
| * because it may not be safe to get this information later */ |
| get_own_qualified_name(); |
| get_own_unqualified_name(); |
| get_own_short_qualified_name(); |
| get_own_short_unqualified_name(); |
| get_application_name(); |
| get_application_short_name(); |
| get_application_short_unqualified_name(); |
| get_process_primary_SID(); |
| get_process_SID_string(); |
| get_process_owner_SID(); |
| get_Everyone_SID(); |
| |
| /* avoid later .data-unprotection calls */ |
| get_dynamorio_dll_preferred_base(); |
| get_image_entry(); |
| get_application_base(); |
| get_application_end(); |
| get_system_basic_info(); |
| if (!standalone_library) |
| os_user_directory_supports_ownership(); |
| is_wow64_process(NT_CURRENT_PROCESS); |
| is_in_ntdll(get_ntdll_base()); |
| |
| os_take_over_init(); |
| |
| /* i#298: cache cur dir at init time, when safer to read it. |
| * We just don't support later changes to cur dir. |
| */ |
| DEBUG_DECLARE(ok =) |
| os_get_current_dir(cwd, BUFFER_SIZE_ELEMENTS(cwd)); |
| } |
| |
| static void |
| print_mem_stats() |
| { |
| VM_COUNTERS mem; |
| bool ok = get_process_mem_stats(NT_CURRENT_PROCESS, &mem); |
| ASSERT(ok); |
| LOG(GLOBAL, LOG_TOP, 1, "Process Memory Statistics:\n"); |
| LOG(GLOBAL, LOG_TOP, 1, "\tPeak virtual size: %6d KB\n", |
| mem.PeakVirtualSize/1024); |
| LOG(GLOBAL, LOG_TOP, 1, "\tPeak working set size: %6d KB\n", |
| mem.PeakWorkingSetSize/1024); |
| LOG(GLOBAL, LOG_TOP, 1, "\tPeak paged pool usage: %6d KB\n", |
| mem.QuotaPeakPagedPoolUsage/1024); |
| LOG(GLOBAL, LOG_TOP, 1, "\tPeak non-paged pool usage: %6d KB\n", |
| mem.QuotaPeakNonPagedPoolUsage/1024); |
| LOG(GLOBAL, LOG_TOP, 1, "\tPeak pagefile usage: %6d KB\n", |
| mem.PeakPagefileUsage/1024); |
| } |
| |
| /* os-specific atexit cleanup |
| * note that this is called even on the fast exit release path so don't add |
| * uneccesary cleanup without ifdef DEBUG, but be careful with ifdef DEBUG's |
| * also as Detach wants to leave nothing from us behind |
| * Called by dynamo_shared_exit() and the fast path in dynamo_process_exit(). |
| */ |
| void |
| os_fast_exit(void) |
| { |
| /* make sure we never see an .exe that does all its work in |
| * DllMain()'s -- unlikely, even .NET apps have an image entry |
| * just to jump to mscoree |
| * |
| * The curiosity is relaxed for thin_client and hotp_only; if nothing else |
| * in the core is has run into this, then reached_image_entry doesn't have |
| * to be set for thin_client & hotp_only. TODO: put in the image entry |
| * hook or not? |
| * |
| * The curiosity is also relaxed if we enter DR using the API |
| */ |
| ASSERT_CURIOSITY(reached_image_entry_yet() || |
| RUNNING_WITHOUT_CODE_CACHE() |
| IF_APP_EXPORTS( || dr_api_entry) |
| /* Clients can go native. XXX: add var for whether client did? */ |
| IF_CLIENT_INTERFACE |
| (|| !IS_INTERNAL_STRING_OPTION_EMPTY(client_lib))); |
| |
| DOLOG(1, LOG_TOP, { print_mem_quota(); }); |
| DOLOG(1, LOG_TOP, { |
| print_mem_stats(); |
| }); |
| |
| #ifdef WINDOWS_PC_SAMPLE |
| if (dynamo_options.profile_pcs) { |
| exit_global_profiles(); |
| /* check to see if we are using the fast exit path |
| * if so dump profiles that were skipped */ |
| # ifndef DEBUG |
| if (dynamo_detaching_flag == LOCK_FREE_STATE) { |
| /* fast exit path, get remaining ungathered profile data */ |
| if (dynamo_options.prof_pcs_gencode >= 2 && |
| dynamo_options.prof_pcs_gencode <= 32) { |
| thread_record_t **threads; |
| int num, i; |
| /* get surviving threads */ |
| arch_profile_exit(); |
| mutex_lock(&thread_initexit_lock); |
| get_list_of_threads(&threads, &num); |
| for (i = 0; i < num; i++) { |
| arch_thread_profile_exit(threads[i]->dcontext); |
| } |
| global_heap_free(threads, num*sizeof(thread_record_t*) |
| HEAPACCT(ACCT_THREAD_MGT)); |
| mutex_unlock(&thread_initexit_lock); |
| } |
| if (dynamo_options.prof_pcs_fcache >= 2 && |
| dynamo_options.prof_pcs_fcache <= 32) { |
| /* note that fcache_exit() is called before os_fast_exit(), |
| * we are here on fast exit path in which case fcache_exit() |
| * is not called |
| */ |
| fcache_profile_exit(); |
| } |
| if (dynamo_options.prof_pcs_stubs >= 2 && |
| dynamo_options.prof_pcs_stubs <= 32) { |
| special_heap_profile_exit(); |
| } |
| } |
| # endif |
| print_file(profile_file, "\nFinished dumping all profile info\n"); |
| close_file(profile_file); |
| } |
| #endif |
| |
| eventlog_fast_exit(); |
| |
| #ifdef DEBUG |
| module_info_exit(); |
| DELETE_LOCK(snapshot_lock); |
| #endif |
| |
| /* case 10338: we don't free TLS on the fast path, in case there |
| * are other active threads: we don't want to synchall on exit so |
| * we let other threads run and try not to crash them until |
| * the process is terminated. |
| */ |
| |
| DELETE_LOCK(dump_core_lock); |
| DELETE_LOCK(debugbox_lock); |
| |
| callback_exit(); |
| ntdll_exit(); |
| } |
| |
| /* os-specific atexit cleanup since Detach wants to leave nothing from |
| * us behind. In addition any debug cleanup should only be DODEBUG. |
| * Called by dynamo_shared_exit(). |
| * Note it is expected to be called _after_ os_fast_exit(). |
| */ |
| void |
| os_slow_exit(void) |
| { |
| /* free and zero thread-private storage (case 10338: slow path only) */ |
| DEBUG_DECLARE(int res = ) |
| tls_cfree(true/*need to synch*/, (uint) tls_local_state_offs, |
| TLS_NUM_SLOTS); |
| ASSERT(res); |
| |
| aslr_exit(); |
| eventlog_slow_exit(); |
| os_take_over_exit(); |
| } |
| |
| |
| /* Win8 WOW64 does not point edx at the param base so we must |
| * put the args on the actual stack. We could have multiple threads |
| * writing to these same slots so we use the TEB which should be dead |
| * (unless the syscall fails and the app continues: which we do not handle). |
| * Xref i#565. |
| */ |
| /* Pass INVALID_HANDLE_VALUE for process exit */ |
| byte * |
| os_terminate_wow64_stack(HANDLE thread_handle) |
| { |
| #ifdef X64 |
| return (byte *) wow64_syscall_stack; |
| #else |
| if (syscall_uses_edx_param_base()) |
| return (byte *) wow64_syscall_stack; |
| else { |
| TEB *teb; |
| if (thread_handle == INVALID_HANDLE_VALUE) |
| teb = get_own_teb(); |
| else |
| teb = get_teb(thread_handle); |
| if (teb == NULL) /* app may have passed bogus handle */ |
| return (byte *) wow64_syscall_stack; |
| /* We use our scratch slots in the TEB. We need room for syscall |
| * call*'s retaddr below and 2 args for os_terminate_wow64_write_args() |
| * above, so we take our own xbx slot, which has xax below and xcx+xdx |
| * above. We do not have the extra safety slot that wow64_syscall_stack |
| * has, but that's not necessary, and if the wow64 wrapper wrote to it |
| * it would just be writing to an app slot that's likely unused (b/c DR |
| * takes TLS slots from the end). |
| * |
| * XXX: it would be cleaner to not write to this until we're done |
| * cleaning up private libraries, which examine the TEB. |
| * Then we could use any part of the TEB. |
| * |
| * XXX: we rely here on os_slow_exit()'s tls_cfree() not zeroing out |
| * our TLS slots during cleanup (i#1156). |
| */ |
| return (byte *)teb + os_tls_offset(TLS_XBX_SLOT); |
| } |
| #endif |
| } |
| |
| /* Only takes action when edx is not the param base */ |
| void |
| os_terminate_wow64_write_args(bool exit_process, HANDLE proc_or_thread_handle, |
| int exit_status) |
| { |
| #ifndef X64 |
| if (!syscall_uses_edx_param_base()) { |
| byte *xsp = os_terminate_wow64_stack(exit_process ? INVALID_HANDLE_VALUE : |
| proc_or_thread_handle); |
| ASSERT(ALIGNED(xsp, sizeof(reg_t))); /* => atomic writes */ |
| /* skip a slot (natively it's the retaddr from the call to the wrapper) */ |
| *(((reg_t*)xsp)+1) = (reg_t) proc_or_thread_handle; |
| *(((reg_t*)xsp)+2) = (reg_t) exit_status; |
| } |
| #endif |
| } |
| |
| /* FIXME: what are good values here? */ |
| #define KILL_PROC_EXIT_STATUS -1 |
| #define KILL_THREAD_EXIT_STATUS -1 |
| |
| /* custom_code only honored if exit_process == true */ |
| static byte * |
| os_terminate_static_arguments(bool exit_process, bool custom_code, int exit_code) |
| { |
| byte *arguments; |
| |
| /* arguments for NtTerminate{Process,Thread} */ |
| typedef struct _terminate_args_t { |
| union { |
| const byte *debug_code; |
| byte pad_bytes[SYSCALL_PARAM_MAX_OFFSET]; |
| } padding; |
| struct { |
| IN HANDLE ProcessOrThreadHandle; |
| IN NTSTATUS ExitStatus; |
| } args; |
| } terminate_args_t; |
| /* It is not safe to use app stack and hope application will work. |
| * We need to stick the arguments for NtTerminate* in a place that |
| * doesn't exacerbate the problem - esp may have been in attacker's |
| * hands - so we place args in const static (read only) dr memory. |
| */ |
| /* To facilitate detecting syscall failure for SYSENTER, we set a |
| * retaddr at edx (two extra slots there) as esp will be set to edx |
| * by the kernel at the return from the sysenter. The kernel then sends |
| * control to a native ret which targets the debug infinite loop. |
| * (DEBUG only). |
| */ |
| static const terminate_args_t term_thread_args = { |
| IF_DEBUG_ELSE_0((byte *)debug_infinite_loop), /* 0 -> NULL for release */ |
| {NT_CURRENT_THREAD, KILL_THREAD_EXIT_STATUS} |
| }; |
| static const terminate_args_t term_proc_args = { |
| IF_DEBUG_ELSE_0((byte *)debug_infinite_loop), /* 0 -> NULL for release */ |
| {NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS} |
| }; |
| /* special sygate froms (non-const) */ |
| static terminate_args_t sygate_term_thread_args = { |
| 0, /* will be set to sysenter_ret_address */ |
| {NT_CURRENT_THREAD, KILL_THREAD_EXIT_STATUS} |
| }; |
| static terminate_args_t sygate_term_proc_args = { |
| 0, /* will be set to sysenter_ret_address */ |
| {NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS} |
| }; |
| /* for variable exit code */ |
| static terminate_args_t custom_term_proc_args = { |
| IF_DEBUG_ELSE_0((byte *)debug_infinite_loop), /* 0 -> NULL for release */ |
| {NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS} |
| }; |
| |
| /* for LOG statement just pick proc vs. thread here, will adjust for |
| * offset below */ |
| if (exit_process) { |
| if (custom_code) { |
| SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); |
| ATOMIC_4BYTE_WRITE((byte *)&custom_term_proc_args.args.ExitStatus, |
| exit_code, false); |
| SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); |
| arguments = (byte *)&custom_term_proc_args; |
| } else if (DYNAMO_OPTION(sygate_sysenter) && |
| get_syscall_method() == SYSCALL_METHOD_SYSENTER) { |
| byte *tgt = (byte *)&sygate_term_proc_args; |
| /* Note we overwrite every time we use this, but is ATOMIC and |
| * always with the same value so is ok */ |
| SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); |
| ATOMIC_ADDR_WRITE(tgt, sysenter_ret_address, false); |
| DODEBUG({ |
| ATOMIC_ADDR_WRITE(tgt+sizeof(sysenter_ret_address), |
| (byte *)debug_infinite_loop, false);}); |
| SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); |
| arguments = (byte *)&sygate_term_proc_args; |
| } else |
| arguments = (byte *)&term_proc_args; |
| } else { |
| if (DYNAMO_OPTION(sygate_sysenter) && |
| get_syscall_method() == SYSCALL_METHOD_SYSENTER) { |
| byte *tgt = (byte *)&sygate_term_thread_args; |
| SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); |
| ATOMIC_ADDR_WRITE(tgt, sysenter_ret_address, false); |
| DODEBUG({ |
| tgt += sizeof(sysenter_ret_address); |
| ATOMIC_ADDR_WRITE(tgt, (byte *)debug_infinite_loop, false);}); |
| SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); |
| arguments = (byte *)&sygate_term_thread_args; |
| } else |
| arguments = (byte *)&term_thread_args; |
| } |
| |
| LOG(THREAD_GET, LOG_SYSCALLS, 2, |
| "Placing terminate arguments tombstone at "PFX" offset=0x%x\n", |
| arguments, SYSCALL_PARAM_OFFSET()); |
| |
| os_terminate_wow64_write_args |
| (exit_process, |
| ((terminate_args_t*)arguments)->args.ProcessOrThreadHandle, |
| ((terminate_args_t*)arguments)->args.ExitStatus); |
| |
| arguments += offsetof(terminate_args_t, args) - SYSCALL_PARAM_OFFSET(); |
| return arguments; |
| } |
| |
| /* dcontext is not needed for TERMINATE_PROCESS, so can pass NULL in |
| */ |
| void |
| os_terminate_common(dcontext_t *dcontext, terminate_flags_t terminate_type, |
| bool custom_code, int exit_code) |
| { |
| HANDLE currentThreadOrProcess = NT_CURRENT_PROCESS; |
| bool exit_process = true; |
| |
| ASSERT(TEST(TERMINATE_PROCESS, terminate_type) != /* xor */ |
| TEST(TERMINATE_THREAD, terminate_type)); |
| |
| /* We could be holding the bb_building_lock at this point -- if we cleanup, |
| * we will get a rank order violation with all_threads_synch_lock. if we |
| * unlock the bb lock, we risk an error about the non-owning thread |
| * releasing the lock. |
| * Our solution is for the caller to release it when possible -- |
| * on an attack we know if we hold it or not. But for other callers |
| * they do not know who holds it...for now we do nothing, none of them |
| * terminate just a thread, so the process is going down anyway, and it's |
| * just a debug build assertion :) |
| */ |
| |
| /* clean up may be dangerous - just terminate */ |
| if (terminate_type == TERMINATE_PROCESS) { |
| /* skip synchronizing dynamic options, is risky and caller has almost |
| * certainly already done so for a syslog */ |
| if (TESTANY(DETACH_ON_TERMINATE|DETACH_ON_TERMINATE_NO_CLEAN, |
| DYNAMO_OPTION(internal_detach_mask))) { |
| /* FIXME : if we run into stack problems we could reset the stack |
| * here though caller has likely alredy gone as deep as detach |
| * will since almost everyone SYSLOG's before calling this */ |
| detach_helper(TEST(DETACH_ON_TERMINATE_NO_CLEAN, |
| DYNAMO_OPTION(internal_detach_mask)) ? |
| DETACH_BAD_STATE_NO_CLEANUP : DETACH_BAD_STATE); |
| /* skip option synch, make this as safe as possible */ |
| SYSLOG_INTERNAL_NO_OPTION_SYNCH(SYSLOG_WARNING, |
| "detach on terminate failed or already started by another thread, killing thread "TIDFMT"\n", |
| get_thread_id()); |
| /* if we get here, either we recursed or someone is already trying |
| * to detach, just kill this thread so progress is made we don't |
| * have anything better to do with it */ |
| /* skip cleanup, our state is likely messed up and we'd just like |
| * to get out alive, also avoids recursion problems, see caveat at |
| * remove_thread below */ |
| terminate_type = TERMINATE_THREAD; |
| } else { |
| config_exit(); /* delete .1config file */ |
| nt_terminate_process(currentThreadOrProcess, KILL_PROC_EXIT_STATUS); |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| /* CHECK: Can a process disallow PROCESS_TERMINATE or THREAD_TERMINATE |
| access even to itself? |
| */ |
| if (TEST(TERMINATE_THREAD, terminate_type)) { |
| exit_process = (!IS_CLIENT_THREAD(dcontext) && |
| is_last_app_thread() && !dynamo_exited); |
| if (!exit_process) { |
| currentThreadOrProcess = NT_CURRENT_THREAD; |
| } |
| } |
| |
| STATS_INC(num_threads_killed); |
| if (TEST(TERMINATE_CLEANUP, terminate_type)) { |
| byte *arguments = os_terminate_static_arguments(exit_process, |
| custom_code, exit_code); |
| |
| /* Make sure debug loop pointer is in expected place since this makes |
| * assumptions about offsets. We don't use the debug loop pointer for |
| * int2e/syscall/wow64 system calls (since they return to the invocation |
| * and can be handled there). For SYSENTER the SYSCALL_PARAM_OFFSET should |
| * match up with arguments such that arguments is pointing to debugme */ |
| ASSERT(does_syscall_ret_to_callsite() || |
| *(byte **)arguments == (byte *)&debug_infinite_loop || |
| (DYNAMO_OPTION(sygate_sysenter) && |
| *(((byte **)arguments)+1) == (byte *)&debug_infinite_loop)); |
| |
| STATS_INC(num_threads_killed_cleanly); |
| |
| /* we enter from several different places, so rewind until top-level kstat */ |
| KSTOP_REWIND_UNTIL(thread_measured); |
| |
| /* now we issue a syscall by number */ |
| /* we can't use issue_system_call_for_app because it relies on |
| * dstack that we should release */ |
| /* FIXME: what happens now if we get some callbacks that are still on |
| * their way? Shouldn't happen since Terminate* are believed to be |
| * non-alertable. */ |
| /* FIXME: we only want the last part of cleanup_and_terminate */ |
| ASSERT(dcontext != NULL); |
| cleanup_and_terminate |
| (dcontext, |
| syscalls[exit_process ? SYS_TerminateProcess : SYS_TerminateThread], |
| (ptr_uint_t) |
| IF_X64_ELSE((exit_process ? NT_CURRENT_PROCESS : NT_CURRENT_THREAD), |
| arguments), |
| (ptr_uint_t) |
| IF_X64_ELSE((exit_process ? |
| (custom_code ? exit_code : KILL_PROC_EXIT_STATUS) : |
| KILL_THREAD_EXIT_STATUS), |
| arguments /* no 2nd arg, just a filler */), |
| exit_process, 0, 0); |
| } else { |
| /* may have decided to terminate process */ |
| if (exit_process) { |
| config_exit(); /* delete .1config file */ |
| nt_terminate_process(currentThreadOrProcess, KILL_PROC_EXIT_STATUS); |
| ASSERT_NOT_REACHED(); |
| } else { |
| /* FIXME: this is now very dangerous - we even leave our own state */ |
| /* we should at least remove this thread from the all threads list |
| * to avoid synchronizing issues, though we are running the risk of |
| * an infinite loop with a failure in this function and detach on |
| * failure */ |
| if (all_threads != NULL) |
| remove_thread(NULL, get_thread_id()); |
| nt_terminate_thread(currentThreadOrProcess, KILL_THREAD_EXIT_STATUS); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* CHECK: who is supposed to clean up the thread's stack? |
| ZwFreeVirtualMemory can be called by another thread |
| waiting on the thread object, hopefully someone will do it */ |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void |
| os_terminate_with_code(dcontext_t *dcontext, terminate_flags_t terminate_type, |
| int exit_code) |
| { |
| os_terminate_common(dcontext, terminate_type, true, exit_code); |
| } |
| |
| void |
| os_terminate(dcontext_t *dcontext, terminate_flags_t terminate_type) |
| { |
| os_terminate_common(dcontext, terminate_type, false, 0); |
| } |
| |
| void |
| os_tls_init() |
| { |
| /* everything was done in os_init, even TEB TLS slots are initialized to 0 for us */ |
| } |
| |
| void |
| os_tls_exit(local_state_t *local_state, bool other_thread) |
| { |
| /* not needed for windows, everything is done is os_slow_exit including zeroing |
| * the freed TEB tls slots */ |
| } |
| |
| #ifdef CLIENT_INTERFACE |
| /* Allocates num_slots tls slots aligned with alignment align */ |
| bool |
| os_tls_calloc(OUT uint *offset, uint num_slots, uint alignment) |
| { |
| bool need_synch = !dynamo_initialized; |
| return (bool) tls_calloc(need_synch, offset, num_slots, alignment); |
| } |
| |
| bool |
| os_tls_cfree(uint offset, uint num_slots) |
| { |
| return (bool) tls_cfree(true, offset, num_slots); |
| } |
| #endif |
| |
| void |
| os_thread_init(dcontext_t *dcontext) |
| { |
| NTSTATUS res; |
| DEBUG_DECLARE(bool ok;) |
| os_thread_data_t *ostd = (os_thread_data_t *) |
| heap_alloc(dcontext, sizeof(os_thread_data_t) HEAPACCT(ACCT_OTHER)); |
| dcontext->os_field = (void *) ostd; |
| /* init ostd fields here */ |
| ostd->stack_base = NULL; |
| ostd->stack_top = NULL; |
| ostd->teb_stack_no_longer_valid = false; |
| DEBUG_DECLARE(ok = )get_stack_bounds(dcontext, NULL, NULL); |
| ASSERT(ok); |
| |
| /* case 8721: save the win32 start address and print it in the ldmp */ |
| res = query_win32_start_addr(NT_CURRENT_THREAD, &dcontext->win32_start_addr); |
| if (!NT_SUCCESS(res)) { |
| ASSERT(false && "failed to obtain win32 start address"); |
| dcontext->win32_start_addr = (app_pc)0; |
| } else { |
| LOG(THREAD, LOG_THREADS, 2, "win32 start addr is "PFX"\n", |
| dcontext->win32_start_addr); |
| } |
| aslr_thread_init(dcontext); |
| } |
| |
| void |
| os_thread_exit(dcontext_t *dcontext, bool other_thread) |
| { |
| os_thread_data_t *ostd = (os_thread_data_t *) dcontext->os_field; |
| aslr_thread_exit(dcontext); |
| #ifdef DEBUG |
| /* for non-debug we do fast exit path and don't free local heap */ |
| /* clean up ostd fields here */ |
| heap_free(dcontext, ostd, sizeof(os_thread_data_t) HEAPACCT(ACCT_OTHER)); |
| #endif |
| } |
| |
| void |
| os_thread_stack_exit(dcontext_t *dcontext) |
| { |
| os_thread_data_t *ostd = (os_thread_data_t *) dcontext->os_field; |
| ASSERT_OWN_MUTEX(true, &thread_initexit_lock); |
| /* see case 3768: a thread's stack is not de-allocated by this process, |
| * so we remove its stack from our executable region here |
| * ref also case 5518 where it is sometimes freed in process, we watch for |
| * that and set stack_base to NULL |
| * note: thin_client doesn't have executable or aslr areas, so this is moot. |
| */ |
| if (DYNAMO_OPTION(thin_client)) |
| return; |
| if (IS_CLIENT_THREAD(dcontext)) { |
| /* dstack is the only stack */ |
| return; |
| } |
| |
| if (ostd->stack_base != NULL) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "os_thread_stack_exit : removing "PFX" - "PFX"\n", |
| ostd->stack_base, ostd->stack_top); |
| |
| ASSERT(ostd->stack_top != NULL); |
| DOCHECK(1, { |
| /* ASSERT that os region matches region stored in ostd */ |
| byte *alloc_base; |
| size_t size = get_allocation_size(ostd->stack_base, &alloc_base); |
| /* Xref case 5877, this assert can fire if the exiting thread has already |
| * exited (resulting in freed stack) before we clean it up. This could be do |
| * to using THREAD_SYNCH_TERMINATED_AND_CLEANED with a synch_with* routine |
| * (no current uses) or a race with detach resuming a translated thread |
| * before cleaning it up. The detach race is harmless so we allow it. */ |
| ASSERT(doing_detach || |
| ((size == (size_t) ALIGN_FORWARD |
| (ostd->stack_top - (ptr_int_t)ostd->stack_base, PAGE_SIZE) || |
| /* PR 252008: for WOW64 nudges we allocate an extra page. */ |
| (size == PAGE_SIZE + (size_t)(ostd->stack_top - ostd->stack_base) && |
| is_wow64_process(NT_CURRENT_PROCESS) && |
| dcontext->nudge_target != NULL)) && |
| ostd->stack_base == alloc_base)); |
| }); |
| /* believe <= win2k frees the stack in process, would like to check |
| * that but we run into problems with stacks that are never freed |
| * (TerminateThread, threads killed by TerminateProcess 0, last thread |
| * calling TerminateProcess, etc.) FIXME figure out way to add an |
| * assert_curiosity */ |
| /* make sure we use our dcontext (dcontext could belong to another thread |
| * from other_thread_exit) since flushing will end up using this dcontext |
| * for synchronization purposes */ |
| /* do not flush if at process exit since already cleaned up fragment |
| * info (for PR 536058) |
| */ |
| if (!dynamo_exited) { |
| app_memory_deallocation(get_thread_private_dcontext(), ostd->stack_base, |
| ostd->stack_top - ostd->stack_base, |
| true /* own thread_initexit_lock */, |
| false /* not image */); |
| } |
| if (TEST(ASLR_HEAP_FILL, DYNAMO_OPTION(aslr))) { |
| size_t stack_reserved_size = ostd->stack_top - ostd->stack_base; |
| /* verified above with get_allocation_size() this is not only the committed portion */ |
| aslr_pre_process_free_virtual_memory(dcontext, ostd->stack_base, |
| stack_reserved_size); |
| } |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_VMAREAS, 1, |
| "os_thread_stack_exit : Thread's os stack has alread been freed\n"); |
| /* believe >= XP free the stack out of process */ |
| ASSERT(ostd->stack_top == NULL); |
| ASSERT_CURIOSITY(get_os_version() <= WINDOWS_VERSION_2000); |
| } |
| } |
| |
| void |
| os_thread_under_dynamo(dcontext_t *dcontext) |
| { |
| /* add cur thread to callback list */ |
| ASSERT_MESSAGE(CHKLVL_ASSERTS+1/*expensive*/, "can only act on executing thread", |
| dcontext == get_thread_private_dcontext()); |
| set_asynch_interception(get_thread_id(), true); |
| } |
| |
| void |
| os_thread_not_under_dynamo(dcontext_t *dcontext) |
| { |
| /* remove cur thread from callback list */ |
| ASSERT_MESSAGE(CHKLVL_ASSERTS+1/*expensive*/, "can only act on executing thread", |
| dcontext == get_thread_private_dcontext()); |
| set_asynch_interception(get_thread_id(), false); |
| } |
| |
| /*************************************************************************** |
| * THREAD TAKEOVER |
| */ |
| |
| /* Data passed to a thread for its own initialization */ |
| typedef struct _takeover_data_t { |
| app_pc continuation_pc; |
| bool in_progress; |
| thread_id_t tid; |
| #ifndef X64 |
| /* For WOW64 we sometimes need to modify the x64 state: */ |
| HANDLE thread_handle; |
| CONTEXT_64 *cxt64; |
| byte *cxt64_alloc; |
| /* We assume these will never be 0 and use that as a sentinel */ |
| ptr_uint_t memval_stack; |
| ptr_uint_t memval_r14; |
| #endif |
| } takeover_data_t; |
| |
| /* List of threads */ |
| typedef struct _thread_list_t { |
| HANDLE handle; |
| thread_id_t tid; /* may not be known, in which case INVALID_THREAD_ID */ |
| void *user_data; /* set to NULL initially */ |
| } thread_list_t; |
| |
| /* Stored in thread_list_t.user_data */ |
| enum { |
| TAKEOVER_NEW = 0, /* must match initial NULL */ |
| TAKEOVER_TRIED, |
| TAKEOVER_SUCCESS, |
| }; |
| |
| /* Our set of a thread's context is not always visible until the thread is |
| * scheduled. Thus to avoid memory leaks we need global storage that lasts |
| * across calls to os_take_over_all_unknown_threads(). |
| * We also use the table to ensure we (eventually) free any takeover_data_t for |
| * a thread that never gets scheduled. |
| * A final use is for cases where our set context doesn't seem to take |
| * effect except for eip. |
| * We do not hold the table lock while accessing table payloads because |
| * we rely on an invariant that only the owning thread can free its own |
| * data, or another thread during synchall. |
| */ |
| static generic_table_t *takeover_table; |
| #define INIT_HTABLE_SIZE_TAKEOVER 6 /* should remain small */ |
| #define INVALID_PAYLOAD ((void *)(ptr_int_t)-2) /* NULL and -1 are used by table */ |
| |
| static void |
| takeover_table_entry_free(void *e) |
| { |
| takeover_data_t *data = (takeover_data_t *) e; |
| if (e == INVALID_PAYLOAD) |
| return; |
| #ifndef X64 |
| if (data->thread_handle != NULL) |
| close_handle(data->thread_handle); |
| if (data->cxt64_alloc != NULL) { |
| global_heap_free(data->cxt64_alloc, MAX_CONTEXT_64_SIZE |
| HEAPACCT(ACCT_THREAD_MGT)); |
| } |
| #endif |
| global_heap_free(data, sizeof(*data) HEAPACCT(ACCT_THREAD_MGT)); |
| } |
| |
| static void |
| os_take_over_init(void) |
| { |
| takeover_table = generic_hash_create(GLOBAL_DCONTEXT, INIT_HTABLE_SIZE_TAKEOVER, |
| 80 /* load factor: not perf-critical */, |
| HASHTABLE_SHARED | HASHTABLE_PERSISTENT, |
| takeover_table_entry_free |
| _IF_DEBUG("takeover table")); |
| } |
| |
| /* Only called on slow exit */ |
| static void |
| os_take_over_exit(void) |
| { |
| generic_hash_destroy(GLOBAL_DCONTEXT, takeover_table); |
| } |
| |
| /* We need to distinguish a thread intercepted via APC hook but that is in ntdll |
| * code (e.g., waiting for a lock) so we mark threads during init prior to being |
| * added to the main thread table |
| */ |
| void |
| os_take_over_mark_thread(thread_id_t tid) |
| { |
| TABLE_RWLOCK(takeover_table, write, lock); |
| if (generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, tid) == NULL) |
| generic_hash_add(GLOBAL_DCONTEXT, takeover_table, tid, INVALID_PAYLOAD); |
| TABLE_RWLOCK(takeover_table, write, unlock); |
| } |
| |
| void |
| os_take_over_unmark_thread(thread_id_t tid) |
| { |
| TABLE_RWLOCK(takeover_table, write, lock); |
| if (generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, tid) == INVALID_PAYLOAD) |
| generic_hash_remove(GLOBAL_DCONTEXT, takeover_table, tid); |
| TABLE_RWLOCK(takeover_table, write, unlock); |
| } |
| |
| /* Returns an array of num_threads_out thread_list_t entries allocated on the |
| * global protected heap with HEAPACCT(ACCT_THREAD_MGT). |
| * Each HANDLE should be closed prior to freeing the array. |
| */ |
| static thread_list_t * |
| os_list_threads(uint *num_threads_out) |
| { |
| HANDLE hthread; |
| thread_list_t *threads = NULL; |
| NTSTATUS res = nt_thread_iterator_next |
| (NT_CURRENT_PROCESS, NULL, &hthread, THREAD_ALL_ACCESS); |
| ASSERT(num_threads_out != NULL); |
| if (NT_SUCCESS(res)) { |
| uint num_threads = 0; |
| uint num_alloc = 16; |
| threads = global_heap_alloc(num_alloc*sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT)); |
| do { |
| if (num_threads == num_alloc) { |
| uint new_alloc = num_alloc * 2; |
| threads = global_heap_realloc(threads, num_alloc, new_alloc, |
| sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT)); |
| num_alloc = new_alloc; |
| } |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "%s: thread "TIDFMT" handle="PFX"\n", __FUNCTION__, num_threads, hthread); |
| threads[num_threads].handle = hthread; |
| threads[num_threads].tid = INVALID_THREAD_ID; |
| threads[num_threads].user_data = NULL; |
| num_threads++; |
| res = nt_thread_iterator_next |
| (NT_CURRENT_PROCESS, hthread, &hthread, THREAD_ALL_ACCESS); |
| } while (NT_SUCCESS(res)); |
| *num_threads_out = num_threads; |
| threads = global_heap_realloc(threads, num_alloc, num_threads, |
| sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT)); |
| } else { |
| SYSTEM_PROCESSES *sp; |
| uint sysinfo_size; |
| byte *sysinfo; |
| sysinfo = get_system_processes(&sysinfo_size); |
| sp = (SYSTEM_PROCESSES *) sysinfo; |
| while (sysinfo != NULL) { |
| if (is_pid_me((process_id_t)sp->ProcessId)) { |
| uint i; |
| threads = global_heap_alloc(sp->ThreadCount*sizeof(*threads) |
| HEAPACCT(ACCT_THREAD_MGT)); |
| for (i = 0; i < sp->ThreadCount; i++) { |
| thread_id_t tid = (thread_id_t) sp->Threads[i].ClientId.UniqueThread; |
| /* sanity checks (xref i#1220) */ |
| ASSERT(get_process_id() == (process_id_t) |
| sp->Threads[i].ClientId.UniqueProcess); |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "%s: thread "TIDFMT" UniqueThread="PFX"\n", __FUNCTION__, i, tid); |
| threads[i].handle = thread_handle_from_id(tid); |
| ASSERT(threads[i].handle != INVALID_HANDLE_VALUE); |
| threads[i].tid = tid; |
| threads[i].user_data = NULL; |
| } |
| *num_threads_out = sp->ThreadCount; |
| break; |
| } |
| if (sp->NextEntryDelta == 0) |
| break; |
| sp = (SYSTEM_PROCESSES *) (((byte *)sp) + sp->NextEntryDelta); |
| } |
| global_heap_free(sysinfo, sysinfo_size HEAPACCT(ACCT_OTHER)); |
| } |
| return threads; |
| } |
| |
| /* Removes the entry for the executing thread from the table and frees data */ |
| static void |
| thread_attach_remove_from_table(takeover_data_t *data) |
| { |
| TABLE_RWLOCK(takeover_table, write, lock); |
| /* this will free data */ |
| generic_hash_remove(GLOBAL_DCONTEXT, takeover_table, data->tid); |
| TABLE_RWLOCK(takeover_table, write, unlock); |
| } |
| |
| /* Restores memory and the x64 context */ |
| static void |
| thread_attach_restore_full_state(takeover_data_t *data) |
| { |
| #ifndef X64 |
| if (data->cxt64 != NULL) { |
| if (data->memval_stack != 0) { |
| if (!safe_write((void *)(ptr_uint_t)data->cxt64->Rsp, |
| sizeof(data->memval_stack), &data->memval_stack)) { |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "%s: failed to restore *Rsp "PFX"\n", __FUNCTION__, |
| data->cxt64->Rsp); |
| } |
| } |
| if (data->memval_r14 != 0) { |
| if (!safe_write((void *)(ptr_uint_t)data->cxt64->R14, |
| sizeof(data->memval_r14), &data->memval_r14)) { |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "%s: failed to restore *R14 "PFX"\n", __FUNCTION__, |
| data->cxt64->R14); |
| } |
| } |
| if (!thread_set_context_64(data->thread_handle, data->cxt64)) { |
| LOG(GLOBAL, LOG_THREADS, 1, "%s: failed to set x64 context\n", __FUNCTION__); |
| } |
| } |
| #endif |
| } |
| |
| void |
| thread_attach_translate(dcontext_t *dcontext, priv_mcontext_t *mc INOUT, |
| bool restore_memory) |
| { |
| takeover_data_t *data; |
| TABLE_RWLOCK(takeover_table, read, lock); |
| data = (takeover_data_t *) |
| generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, |
| (ptr_uint_t)dcontext->owning_thread); |
| TABLE_RWLOCK(takeover_table, read, unlock); |
| if (data != NULL && data != INVALID_PAYLOAD) { |
| mc->pc = data->continuation_pc; |
| if (restore_memory) |
| thread_attach_restore_full_state(data); |
| } else |
| ASSERT_NOT_REACHED(); /* translating a non-native thread! */ |
| } |
| |
| void |
| thread_attach_context_revert(CONTEXT *cxt INOUT) |
| { |
| takeover_data_t *data; |
| TABLE_RWLOCK(takeover_table, read, lock); |
| data = (takeover_data_t *) |
| generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, (ptr_uint_t)get_thread_id()); |
| TABLE_RWLOCK(takeover_table, read, unlock); |
| if (data != NULL && data != INVALID_PAYLOAD) { |
| cxt->CXT_XIP = (ptr_uint_t) data->continuation_pc; |
| thread_attach_restore_full_state(data); |
| thread_attach_remove_from_table(data); |
| } else |
| ASSERT_NOT_REACHED(); /* translating a non-native thread! */ |
| } |
| |
| void |
| thread_attach_exit(dcontext_t *dcontext, priv_mcontext_t *mc) |
| { |
| ASSERT(mc->pc == (app_pc) thread_attach_takeover); |
| TABLE_RWLOCK(takeover_table, write, lock); |
| generic_hash_remove(GLOBAL_DCONTEXT, takeover_table, |
| (ptr_uint_t) dcontext->owning_thread); |
| TABLE_RWLOCK(takeover_table, write, unlock); |
| } |
| |
| #ifndef X64 |
| /* i#1141: problems with NtGetContextThread and NtSetContextThread on WOW64 |
| * |
| * For wow64, when a thread is in the wow64 layer, 32-bit NtGetContextThread |
| * goes and reads from the CONTEXT32 (aka WOW64_CONTEXT) stored in userspace |
| * (TEB64->TlsSlots[1]) by the wow64 layer and returns that. Similary, |
| * NtSetContextThread goes and writes that stored CONTEXT32. |
| * |
| * If a target suspended thread is in the process of saving (on entry to wow64 |
| * layer 64-bit mode), NtGetContextThread will return an incorrect context; |
| * and if the thread is in the process of restoring (on exit back to 32-bit |
| * mode), NtSetContextThread will have some of its values overwritten once the |
| * thread resumes. |
| * |
| * My solution is to get the x64 CONTEXT64, pattern-match the entry and exit, |
| * and set the appropriate registers or memory. Unfortunately this is fragile |
| * with respect to the exact code sequences in |
| * wow64cpu!CpupReturnFromSimulatedCode and wow64cpu!CpuSimulate changing in |
| * the future. |
| * |
| * As part of this I also changed the takeover to not store the context at |
| * suspend time and instead only change Eip then, capturing the context when |
| * the thread resumes. This requires an assume-nothing routine, which |
| * requires initstack: but these takeover points shouldn't be perf-critical. |
| * This really simplifies the wow64 entry/exit corner cases. |
| */ |
| static void |
| os_take_over_wow64_extra(takeover_data_t *data, HANDLE hthread, thread_id_t tid, |
| CONTEXT *cxt32) |
| { |
| CONTEXT_64 *cxt64; |
| bool changed_x64_cxt = false; |
| app_pc takeover = thread_attach_takeover; |
| byte * buf; |
| # ifdef DEBUG |
| /* Match the wow64 syscall call*: |
| * 7d8513eb 64ff15c0000000 call dword ptr fs:[000000c0] |
| */ |
| static const byte WOW64_SYSCALL_CALL[] = {0x64, 0xff, 0x15, 0xc0, 0x00, 0x00, 0x00}; |
| # endif |
| /* The WOW64_CONTEXT.Eip won't be correct in two spots: right before it's |
| * saved, and right after it's restored. |
| * It's saved here: |
| * wow64cpu!CpupReturnFromSimulatedCode: |
| * 00000000`78b83c2c 67448b0424 mov r8d,dword ptr [esp] |
| * 00000000`78b83c31 458985bc000000 mov dword ptr [r13+0BCh],r8d |
| * 00000000`78b83c38 83c404 add esp,4 |
| * 00000000`78b83c3b 4189a5c8000000 mov dword ptr [r13+0C8h],esp |
| * And restored here: |
| * wow64cpu!CpuSimulate+0x161: |
| * 00000000`74ff2711 458b8dbc000000 mov r9d,dword ptr [r13+0BCh] |
| * 00000000`74ff2718 45890e mov dword ptr [r14],r9d |
| * 00000000`74ff271b 41ff2e jmp fword ptr [r14] |
| * We have to change either [esp], r8d, r9d, or [r14]. |
| */ |
| /* We include the subsequent instr for 12 to avoid matching elsewhere in wow64 code */ |
| static const byte WOW64_ENTER_INST12[] = {0x67, 0x44, 0x8b, 0x04, 0x24, |
| 0x45, 0x89, 0x85, 0xbc, 0x00, 0x00, 0x00}; |
| static const byte WOW64_ENTER_INST2[] = {0x45, 0x89, 0x85, 0xbc, 0x00, 0x00, 0x00}; |
| static const byte WOW64_EXIT_INST12[] = {0x45, 0x89, 0x0e, 0x41, 0xff, 0x2e}; |
| static const byte WOW64_EXIT_INST2[] = {0x41, 0xff, 0x2e}; |
| |
| if (!is_wow64_process(NT_CURRENT_PROCESS)) |
| return; |
| |
| /* WOW64 context setting is fragile: we need the raw x64 context as well. |
| * We can't easily use nt_initialize_context so we manually set the flags. |
| */ |
| buf = (byte *) global_heap_alloc(MAX_CONTEXT_64_SIZE HEAPACCT(ACCT_THREAD_MGT)); |
| cxt64 = (CONTEXT_64 *) ALIGN_FORWARD(buf, 0x10); |
| cxt64->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; |
| if (!thread_get_context_64(hthread, cxt64)) { |
| LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to get x64 cxt for thread "TIDFMT"\n", tid); |
| ASSERT_NOT_REACHED(); |
| global_heap_free(buf, MAX_CONTEXT_64_SIZE HEAPACCT(ACCT_THREAD_MGT)); |
| return; |
| } |
| LOG(GLOBAL, LOG_THREADS, 2, |
| "x64 context for thread "TIDFMT": xip is "HEX64_FORMAT_STRING |
| ", xsp="HEX64_FORMAT_STRING, tid, cxt64->Rip, cxt64->Rsp); |
| if (cxt64->SegCs == CS32_SELECTOR) { |
| /* In x86 mode, so not inside the wow64 layer. Context setting should |
| * work fine. |
| */ |
| global_heap_free(buf, MAX_CONTEXT_64_SIZE HEAPACCT(ACCT_THREAD_MGT)); |
| return; |
| } |
| /* Could be in ntdll or user32 or anywhere a syscall is made, so we don't |
| * assert is_in_ntdll, but we do check that it's the wow64 syscall call*: |
| */ |
| ASSERT_CURIOSITY(memcmp(data->continuation_pc - sizeof(WOW64_SYSCALL_CALL), |
| WOW64_SYSCALL_CALL, sizeof(WOW64_SYSCALL_CALL)) == 0); |
| |
| /* Corner case #1: 1st instr on entry where retaddr is in [esp] */ |
| if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST12, |
| sizeof(WOW64_ENTER_INST12)) == 0) { |
| if (safe_read((void *)(ptr_uint_t)cxt64->Rsp, sizeof(data->memval_stack), |
| &data->memval_stack) && |
| safe_write((void *)(ptr_uint_t)cxt64->Rsp, sizeof(takeover), &takeover)) { |
| changed_x64_cxt = true; |
| LOG(GLOBAL, LOG_THREADS, 2, |
| "\ttid %d @ wow64 enter1 => changed [esp]\n", tid); |
| } else { |
| data->memval_stack = 0; |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "\ttid %d @ wow64 enter1, but FAILED to change [esp]\n", tid); |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| /* Corner case #2: 2nd instr in entry where retaddr is in r8d */ |
| else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST2, |
| sizeof(WOW64_ENTER_INST2)) == 0) { |
| uint64 orig_r8 = cxt64->R8; |
| cxt64->R8 = (DWORD64)(ptr_uint_t) takeover; |
| if (thread_set_context_64(hthread, cxt64)) { |
| changed_x64_cxt = true; |
| LOG(GLOBAL, LOG_THREADS, 2, |
| "\ttid %d @ wow64 enter2 => changed r8d\n", tid); |
| } else { |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "\ttid %d @ wow64 enter2, but FAILED to change r8d\n", tid); |
| ASSERT_NOT_REACHED(); |
| } |
| /* Restore so we can use cxt64 to revert if necessary */ |
| cxt64->R8 = orig_r8; |
| } |
| /* Corner case #3: 2nd-to-last instr in exit where retaddr is in r9d */ |
| else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT_INST12, |
| sizeof(WOW64_EXIT_INST12)) == 0) { |
| uint64 orig_r9 = cxt64->R9; |
| cxt64->R9 = (DWORD64)(ptr_uint_t) takeover; |
| if (thread_set_context_64(hthread, cxt64)) { |
| changed_x64_cxt = true; |
| LOG(GLOBAL, LOG_THREADS, 2, |
| "\ttid %d @ wow64 exit1 => changed r9d\n", tid); |
| } else { |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "\ttid %d @ wow64 exit1, but FAILED to change r9d\n", tid); |
| ASSERT_NOT_REACHED(); |
| } |
| /* Restore so we can use cxt64 to revert if necessary */ |
| cxt64->R9 = orig_r9; |
| } |
| /* Corner case #4: last instr in exit where we already copied retaddr to [r14] */ |
| else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT_INST2, |
| sizeof(WOW64_EXIT_INST2)) == 0) { |
| if (safe_read((void *)(ptr_uint_t)cxt64->R14, sizeof(data->memval_r14), |
| &data->memval_r14) && |
| safe_write((void *)(ptr_uint_t)cxt64->R14, sizeof(takeover), &takeover)) { |
| changed_x64_cxt = true; |
| LOG(GLOBAL, LOG_THREADS, 2, |
| "\ttid %d @ wow64 exit2 => changed [r14]\n", tid); |
| } else { |
| data->memval_r14 = 0; |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "\ttid %d @ wow64 exit2, but FAILED to change *r14\n", tid); |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| if (changed_x64_cxt) { |
| /* We'll need the handle in case we have to revert/restore the x64 context. |
| * We shouldn't have to undo any of these changes on a successful |
| * takeover. |
| */ |
| duplicate_handle(NT_CURRENT_PROCESS, hthread, NT_CURRENT_PROCESS, |
| &data->thread_handle, 0, 0, |
| DUPLICATE_SAME_ACCESS|DUPLICATE_SAME_ATTRIBUTES); |
| data->cxt64 = cxt64; |
| data->cxt64_alloc = buf; |
| } else { |
| global_heap_free(buf, MAX_CONTEXT_64_SIZE HEAPACCT(ACCT_THREAD_MGT)); |
| } |
| } |
| #endif |
| |
| /* On success, returns true and leaves thread suspended. */ |
| static bool |
| os_take_over_thread(dcontext_t *dcontext, HANDLE hthread, thread_id_t tid, bool suspended) |
| { |
| bool success = true; |
| char buf[MAX_CONTEXT_SIZE]; |
| CONTEXT *cxt = nt_initialize_context(buf, CONTEXT_DR_STATE); |
| ASSERT(tid == thread_id_from_handle(hthread)); |
| if ((suspended || nt_thread_suspend(hthread, NULL)) && |
| NT_SUCCESS(nt_get_context(hthread, cxt))) { |
| /* Rather than try to emulate clone handling by putting this |
| * on the stack and thus risking transparency violations, we |
| * just allocate it on our heap and put it into a hashtable. |
| * |
| * Originally I tried storing the context here, pointing at it in a |
| * register, and swapping to dstack now, for a simpler takeover routine: |
| * but the state changes between here and the takeover routine, |
| * resulting in corrupted registers. Thus, we have the takeover routine |
| * assume nothing and capture the context once the thread gets there. |
| * Then our only problem is the eip setting not sticking, meaning we |
| * don't take over at all. |
| */ |
| NTSTATUS res; |
| takeover_data_t *data; |
| void *already_taken_over; |
| /* i#1443: avoid self-interp on threads that are waiting at our hook |
| * for DR to initialize. We have to check two things: first, whether |
| * the context is in DR; second, whether flagged (to cover the thread |
| * being in ntdll or vsyscall). |
| */ |
| if (is_in_dynamo_dll((app_pc)cxt->CXT_XIP) || |
| new_thread_is_waiting_for_dr_init(tid, (app_pc)cxt->CXT_XIP)) { |
| LOG(GLOBAL, LOG_THREADS, 1, "\tthread "TIDFMT" is already waiting\n", tid); |
| return true; /* it's waiting for us to take it over */ |
| } |
| /* Avoid double-takeover. |
| * N.B.: is_dynamo_address() on xip and xsp is not sufficient as |
| * a newly set context may not show up until the thread is scheduled. |
| * We still want to check them to catch threads created after |
| * our APC hook was in place. |
| */ |
| TABLE_RWLOCK(takeover_table, read, lock); |
| already_taken_over = |
| generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, (ptr_uint_t)tid); |
| TABLE_RWLOCK(takeover_table, read, unlock); |
| if (already_taken_over != NULL || |
| is_dynamo_address((byte *)cxt->CXT_XIP)) { |
| /* Thread was never scheduled on last takeover, or has not |
| * yet added itself to main thread table. |
| */ |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "\tthread "TIDFMT" partially taken over already; pc="PFX"\n", |
| tid, cxt->CXT_XIP); |
| if (already_taken_over != NULL && already_taken_over != INVALID_PAYLOAD && |
| !is_dynamo_address((byte *)cxt->CXT_XIP) && |
| /* Rule out thread initializing but currently in ntdll */ |
| !((takeover_data_t *)already_taken_over)->in_progress && |
| cxt->CXT_XIP != (ptr_uint_t) thread_attach_takeover) { |
| /* XXX: I see cases where my setcontext succeeds, immediate getcontext |
| * confirms, and then later the thread's context is back to native |
| * and we never take it over! So we detect here and try again. |
| * See also comment above. |
| */ |
| data = (takeover_data_t *) already_taken_over; |
| LOG(GLOBAL, LOG_THREADS, 1, "\tthread "TIDFMT" reverted!", tid); |
| /* Now that i#1141 is fixed this shouldn't happen: we'd like to |
| * know if it does. |
| */ |
| ASSERT_CURIOSITY(false && "thread takeover context reverted!"); |
| } else |
| return true; |
| } else { |
| thread_record_t *tr = thread_lookup(tid); |
| data = (takeover_data_t *) |
| global_heap_alloc(sizeof(*data) HEAPACCT(ACCT_THREAD_MGT)); |
| } |
| LOG(GLOBAL, LOG_THREADS, 1, "thread "TIDFMT" context:\n", tid); |
| memset(data, 0, sizeof(*data)); |
| data->tid = tid; |
| data->continuation_pc = (app_pc) cxt->CXT_XIP; |
| cxt->CXT_XIP = (ptr_uint_t) thread_attach_takeover; |
| #ifndef X64 |
| os_take_over_wow64_extra(data, hthread, tid, cxt); |
| #endif |
| /* See comments above: not safe to change any other regs here */ |
| ASSERT(TESTALL(CONTEXT_DR_STATE, cxt->ContextFlags)); |
| res = nt_set_context(hthread, cxt); |
| if (!NT_SUCCESS(res)) { |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "\tfailed to set context for thread "TIDFMT" with error %d\n", tid, res); |
| success = false; |
| global_heap_free(data, sizeof(*data) HEAPACCT(ACCT_THREAD_MGT)); |
| if (!nt_thread_resume(hthread, NULL)) { |
| LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to resume thread "TIDFMT"\n", tid); |
| ASSERT_NOT_REACHED(); |
| } |
| } else { |
| if (already_taken_over == NULL) { |
| TABLE_RWLOCK(takeover_table, write, lock); |
| generic_hash_add(GLOBAL_DCONTEXT, takeover_table, tid, data); |
| TABLE_RWLOCK(takeover_table, write, unlock); |
| } |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "\tset context for thread "TIDFMT"; old xip="PFX", xsp="PFX", data="PFX"\n", |
| tid, data->continuation_pc, cxt->CXT_XSP, data); |
| /* leave thread suspended */ |
| } |
| } else { |
| LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to suspend/query thread "TIDFMT"\n", tid); |
| success = false; |
| } |
| return success; |
| } |
| |
| bool |
| os_thread_take_over_suspended_native(dcontext_t *dcontext) |
| { |
| thread_record_t *tr = dcontext->thread_record; |
| if (!is_thread_currently_native(tr)) |
| return false; |
| /* If the app voluntarily stopped, wait for it to ask to start again */ |
| if (dcontext->currently_stopped) |
| return false; |
| /* In case of failure (xref all the issues with setting the context), we |
| * use this to signal syscall_while_native() to take this thread |
| * over if it makes it to one of our syscall hooks. |
| * The thread will still be considered is_thread_currently_native(). |
| */ |
| tr->retakeover = true; |
| return os_take_over_thread(dcontext, tr->handle, tr->id, true/*suspended*/); |
| } |
| |
| bool |
| os_take_over_all_unknown_threads(dcontext_t *dcontext) |
| { |
| uint i, iters; |
| const uint MAX_ITERS = 16; |
| uint num_threads = 0; |
| thread_list_t *threads = NULL; |
| bool took_over_all = true, found_new_threads = true; |
| /* ensure user_data starts out how we think it does */ |
| ASSERT(TAKEOVER_NEW == (ptr_uint_t) NULL); |
| |
| mutex_lock(&thread_initexit_lock); |
| |
| /* Need to iterate until no new threads, w/ an escape valve of max iters. |
| * This ends up looking similar to synch_with_all_threads(), though it has |
| * some key differences, making it non-trivial to share code. |
| * We need to do at least 2 iters no matter what, but dr_app_start or |
| * external attach should be considered heavyweight events in any case. |
| */ |
| for (iters = 0; found_new_threads && iters < MAX_ITERS; iters++) { |
| uint num_new_threads, j; |
| thread_list_t *new_threads = os_list_threads(&num_new_threads); |
| LOG(GLOBAL, LOG_THREADS, 1, "TAKEOVER: iteration %d\n", iters); |
| if (new_threads == NULL) { |
| took_over_all = false; |
| break; |
| } |
| found_new_threads = false; |
| for (i = 0; i < num_new_threads; i++) { |
| if (new_threads[i].tid == INVALID_THREAD_ID) |
| new_threads[i].tid = thread_id_from_handle(new_threads[i].handle); |
| } |
| if (threads != NULL) { |
| /* Copy user_data over. Yeah, nested loop: but hashtable seems overkill. */ |
| for (i = 0; i < num_threads; i++) { |
| for (j = 0; j < num_new_threads; j++) { |
| if (new_threads[j].tid == threads[i].tid) |
| new_threads[j].user_data = threads[i].user_data; |
| } |
| if ((ptr_uint_t)threads[i].user_data == TAKEOVER_SUCCESS) |
| close_handle(threads[i].handle); |
| } |
| global_heap_free(threads, num_threads*sizeof(*threads) |
| HEAPACCT(ACCT_THREAD_MGT)); |
| } |
| threads = new_threads; |
| num_threads = num_new_threads; |
| for (i = 0; i < num_threads; i++) { |
| thread_record_t *tr; |
| if ((ptr_uint_t)threads[i].user_data == TAKEOVER_NEW) { |
| found_new_threads = true; |
| threads[i].user_data = (void *)(ptr_uint_t) TAKEOVER_TRIED; |
| tr = thread_lookup(threads[i].tid); |
| if (tr == NULL) { /* not already under our control */ |
| /* cur thread is assumed to be under DR */ |
| ASSERT(threads[i].tid != get_thread_id()); |
| LOG(GLOBAL, LOG_THREADS, 1, "TAKEOVER: taking over thread "TIDFMT"\n", |
| threads[i].tid); |
| if (os_take_over_thread(dcontext, threads[i].handle, |
| threads[i].tid, false/*!suspended*/)) { |
| threads[i].user_data = (void *)(ptr_uint_t) TAKEOVER_SUCCESS; |
| } else { |
| took_over_all = false; |
| /* We want to know when this happens. We might expect |
| * it with injected logon/logoff threads: let's see. |
| */ |
| ASSERT_CURIOSITY(false && "failed to take over a thread!"); |
| } |
| } |
| } |
| if ((ptr_uint_t)threads[i].user_data != TAKEOVER_SUCCESS) |
| close_handle(threads[i].handle); |
| } |
| } |
| /* Potential risk of a thread from an earlier list somehow not showing up on |
| * the final list: but shouldn't happen unless the thread is destroyed in |
| * which case it's ok to never resume it. |
| */ |
| for (i = 0; i < num_threads; i++) { |
| if ((ptr_uint_t)threads[i].user_data == TAKEOVER_SUCCESS) { |
| if (!nt_thread_resume(threads[i].handle, NULL)) { |
| LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to resume thread "TIDFMT"\n", |
| threads[i].tid); |
| took_over_all = false; |
| ASSERT_NOT_REACHED(); |
| } |
| close_handle(threads[i].handle); |
| } |
| } |
| global_heap_free(threads, num_threads*sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT)); |
| if (iters == MAX_ITERS) { |
| LOG(GLOBAL, LOG_THREADS, 1, "TAKEOVER: hit max iters %d\n", iters); |
| took_over_all = false; |
| } |
| |
| mutex_unlock(&thread_initexit_lock); |
| return !took_over_all; |
| } |
| |
| /* Previously-unknown thread is redirected here to initialize itself. */ |
| void |
| thread_attach_setup(priv_mcontext_t *mc) |
| { |
| dcontext_t *dcontext; |
| takeover_data_t *data; |
| int rc; |
| ENTERING_DR(); |
| |
| TABLE_RWLOCK(takeover_table, write, lock); |
| data = (takeover_data_t *) |
| generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, (ptr_uint_t)get_thread_id()); |
| TABLE_RWLOCK(takeover_table, write, unlock); |
| if (data == NULL || data == INVALID_PAYLOAD) { |
| ASSERT_NOT_REACHED(); |
| /* in release better to let thread run native than to crash */ |
| EXITING_DR(); |
| return; |
| } |
| /* Preclude double takeover if we become suspended while in ntdll */ |
| data->in_progress = true; |
| |
| rc = dynamo_thread_init(NULL, mc _IF_CLIENT_INTERFACE(false)); |
| /* We don't assert that rc!=-1 b/c we are used to take over a |
| * native_exec thread, which is already initialized. |
| */ |
| dcontext = get_thread_private_dcontext(); |
| ASSERT(dcontext != NULL); |
| dynamo_thread_under_dynamo(dcontext); |
| /* clear retakeover field, if we came from os_thread_take_over_suspended_native() */ |
| dcontext->thread_record->retakeover = false; |
| /* A native_exec_syscalls hook on NtCallbackReturn could have left the |
| * at_syscall flag set, so make sure to clear it. |
| */ |
| set_at_syscall(dcontext, false); |
| |
| LOG(GLOBAL, LOG_THREADS, 1, |
| "TAKEOVER: thread "TIDFMT", start pc "PFX"\n", get_thread_id(), data->continuation_pc); |
| |
| ASSERT(os_using_app_state(dcontext)); |
| |
| dcontext->next_tag = data->continuation_pc; |
| *get_mcontext(dcontext) = *mc; |
| |
| thread_attach_remove_from_table(data); |
| data = NULL; |
| |
| transfer_to_dispatch(dcontext, get_mcontext(dcontext), false/*!full_DR_state*/); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /*************************************************************************** |
| * CLIENT THREADS |
| */ |
| |
| #ifdef CLIENT_SIDELINE /* PR 222812: tied to sideline usage */ |
| /* i#41/PR 222812: client threads |
| * * thread must have dcontext since many API routines require one and we |
| * don't expose GLOBAL_DCONTEXT (xref PR 243008, PR 216936, PR 536058) |
| * * reversed the old design of not using dstack (partly b/c want dcontext) |
| * and avoiding needing a temp stack by just creating dstack up front, |
| * like is done on linux. dstack should be big enough for client threads |
| * (xref PR 202669) |
| * * reversed the old design of explicit dr_terminate_client_thread(): now |
| * the thread is auto-terminated and stack cleaned up on return from run |
| * function |
| */ |
| /* FIXME PR 210591: transparency issues: |
| * 1) All dlls will be notifed of thread creation by DLL_THREAD_ATTACH |
| * => this is now solved by not running the Ldr code: intercept_new_thread() |
| * just comes straight here |
| * 2) The thread will show up in the list of threads accessed by |
| * NtQuerySystemInformation's SystemProcessesAndThreadsInformation structure. |
| * 3) check_sole_thread() |
| * 4) Vista+'s NtGetNextThread and NtGetNextProcess |
| * (which I am assuming expose the iterator interface of |
| * PsGetNextProcessThread, should check) |
| */ |
| |
| void |
| client_thread_target(void *param) |
| { |
| /* Thread was initialized in intercept_new_thread() */ |
| dcontext_t *dcontext = get_thread_private_dcontext(); |
| /* We stored the func and args at base of dstack and param points at them */ |
| void **arg_buf = (void **) param; |
| void (*func)(void *param) = (void (*)(void*)) convert_data_to_function(arg_buf[0]); |
| void *arg = arg_buf[1]; |
| byte *dstack = dcontext->dstack; |
| ASSERT(IS_CLIENT_THREAD(dcontext)); |
| LOG(THREAD, LOG_ALL, 1, "\n***** CLIENT THREAD %d *****\n\n", |
| get_thread_id()); |
| LOG(THREAD, LOG_ALL, 1, "func="PFX", arg="PFX"\n", func, arg); |
| |
| (*func)(arg); |
| |
| LOG(THREAD, LOG_ALL, 1, "\n***** CLIENT THREAD %d EXITING *****\n\n", |
| get_thread_id()); |
| os_terminate(dcontext, TERMINATE_THREAD|TERMINATE_CLEANUP); |
| } |
| |
| DR_API bool |
| dr_create_client_thread(void (*func)(void *param), void *arg) |
| { |
| dcontext_t *dcontext = get_thread_private_dcontext(); |
| byte *dstack = stack_alloc(DYNAMORIO_STACK_SIZE); |
| HANDLE hthread; |
| bool res; |
| thread_id_t tid; |
| void *arg_buf[2]; |
| LOG(THREAD, LOG_ASYNCH, 1, |
| "dr_create_client_thread: dstack for new thread is "PFX"\n", dstack); |
| |
| pre_second_thread(); |
| |
| /* We store the func and args at base of dstack for client_thread_target */ |
| arg_buf[0] = (void *) func; |
| arg_buf[1] = arg; |
| |
| /* FIXME PR 225714: does this work on Vista? */ |
| hthread = create_thread_have_stack(NT_CURRENT_PROCESS, IF_X64_ELSE(true, false), |
| (void *)client_thread_target, |
| NULL, arg_buf, BUFFER_SIZE_BYTES(arg_buf), |
| dstack, DYNAMORIO_STACK_SIZE, false, &tid); |
| CLIENT_ASSERT(hthread != INVALID_HANDLE_VALUE, "error creating thread"); |
| if (hthread == INVALID_HANDLE_VALUE) { |
| stack_free(dstack, DYNAMORIO_STACK_SIZE); |
| return false; |
| } |
| /* FIXME: what about all of our check_sole_thread() checks? */ |
| res = close_handle(hthread); |
| CLIENT_ASSERT(res, "error closing thread handle"); |
| return res; |
| } |
| #endif CLIENT_SIDELINE /* PR 222812: tied to sideline usage */ |
| |
| int |
| get_os_version() |
| { |
| return os_version; |
| } |
| |
| void |
| get_os_version_ex(int *version OUT, uint *service_pack_major OUT, |
| uint *service_pack_minor OUT) |
| { |
| if (version != NULL) |
| *version = os_version; |
| if (service_pack_major != NULL) |
| *service_pack_major = os_service_pack_major; |
| if (service_pack_minor != NULL) |
| *service_pack_minor = os_service_pack_minor; |
| } |
| |
| bool |
| is_in_dynamo_dll(app_pc pc) |
| { |
| ASSERT(dynamo_dll_start != NULL && dynamo_dll_end != NULL); |
| return (pc >= dynamo_dll_start && pc < dynamo_dll_end); |
| } |
| |
| static char * |
| mem_state_string(uint state) |
| { |
| switch (state) { |
| case 0: return "none"; |
| case MEM_COMMIT: return "COMMIT"; |
| case MEM_FREE: return "FREE"; |
| case MEM_RESERVE: return "RESERVE"; |
| } |
| return "(error)"; |
| } |
| |
| static char * |
| mem_type_string(uint type) |
| { |
| switch (type) { |
| case 0: return "none"; |
| case MEM_IMAGE: return "IMAGE"; |
| case MEM_MAPPED: return "MAPPED"; |
| case MEM_PRIVATE: return "PRIVATE"; |
| } |
| return "(error)"; |
| } |
| |
| char * |
| prot_string(uint prot) |
| { |
| uint ignore_extras = prot & ~PAGE_PROTECTION_QUALIFIERS; |
| switch (ignore_extras) { |
| case PAGE_NOACCESS: return "----"; |
| case PAGE_READONLY: return "r---"; |
| case PAGE_READWRITE: return "rw--"; |
| case PAGE_WRITECOPY: return "rw-c"; |
| case PAGE_EXECUTE: return "--x-"; |
| case PAGE_EXECUTE_READ: return "r-x-"; |
| case PAGE_EXECUTE_READWRITE: return "rwx-"; |
| case PAGE_EXECUTE_WRITECOPY: return "rwxc"; |
| } |
| return "(error)"; |
| } |
| |
| static bool |
| prot_is_readable(uint prot) |
| { |
| prot &= ~PAGE_PROTECTION_QUALIFIERS; |
| /* FIXME: consider just E to be unreadable? |
| * do not do exclusions, sometimes prot == 0 or something |
| */ |
| switch (prot) { |
| case PAGE_READONLY: |
| case PAGE_READWRITE: |
| case PAGE_WRITECOPY: |
| case PAGE_EXECUTE: |
| case PAGE_EXECUTE_READ: |
| case PAGE_EXECUTE_READWRITE: |
| case PAGE_EXECUTE_WRITECOPY: return true; |
| } |
| return false; |
| } |
| |
| bool |
| prot_is_writable(uint prot) |
| { |
| prot &= ~PAGE_PROTECTION_QUALIFIERS; |
| return (prot == PAGE_READWRITE || prot == PAGE_WRITECOPY || |
| prot == PAGE_EXECUTE_READWRITE || prot == PAGE_EXECUTE_WRITECOPY); |
| } |
| |
| bool |
| prot_is_executable(uint prot) |
| { |
| prot &= ~PAGE_PROTECTION_QUALIFIERS; |
| return (prot == PAGE_EXECUTE || prot == PAGE_EXECUTE_READ || |
| prot == PAGE_EXECUTE_READWRITE || prot == PAGE_EXECUTE_WRITECOPY); |
| } |
| |
| /* true when page hasn't been written to */ |
| bool |
| prot_is_copyonwrite(uint prot) |
| { |
| prot &= ~PAGE_PROTECTION_QUALIFIERS; |
| /* although really providing an enumeration, the known PAGE_ |
| * values use separate bit flags. We use TESTANY in case new |
| * PAGE_PROTECTION_QUALIFIERS show up. |
| */ |
| return TESTANY(PAGE_WRITECOPY|PAGE_EXECUTE_WRITECOPY, prot); |
| } |
| |
| /* true when page is a guard page and hasn't been touched */ |
| bool |
| prot_is_guard(uint prot) |
| { |
| return TEST(PAGE_GUARD, prot); |
| } |
| |
| /* translate platform independent protection bits to native flags */ |
| int |
| memprot_to_osprot(uint prot) |
| { |
| uint os_prot = 0; |
| if (TEST(MEMPROT_EXEC, prot)) { |
| if (!TEST(MEMPROT_READ, prot)) { |
| ASSERT(!TEST(MEMPROT_WRITE, prot)); |
| os_prot = PAGE_EXECUTE; |
| } else if (TEST(MEMPROT_WRITE, prot)) |
| os_prot = PAGE_EXECUTE_READWRITE; |
| else |
| os_prot = PAGE_EXECUTE_READ; |
| } else if (TEST(MEMPROT_READ, prot)) { |
| if (TEST(MEMPROT_WRITE, prot)) |
| os_prot = PAGE_READWRITE; |
| else |
| os_prot = PAGE_READONLY; |
| } else |
| os_prot = PAGE_NOACCESS; |
| if (TEST(MEMPROT_GUARD, prot)) |
| os_prot |= PAGE_GUARD; |
| return os_prot; |
| } |
| |
| /* translate native flags to platform independent protection bits */ |
| int |
| osprot_to_memprot(uint prot) |
| { |
| uint mem_prot = 0; |
| if (prot_is_readable(prot)) |
| mem_prot |= MEMPROT_READ; |
| if (prot_is_writable(prot)) |
| mem_prot |= MEMPROT_WRITE; |
| if (prot_is_executable(prot)) |
| mem_prot |= MEMPROT_EXEC; |
| if (prot_is_guard(prot)) |
| mem_prot |= MEMPROT_GUARD; |
| return mem_prot; |
| } |
| |
| int |
| osprot_add_writecopy(uint prot) |
| { |
| int pr = prot & ~PAGE_PROTECTION_QUALIFIERS; |
| switch (pr) { |
| case PAGE_READWRITE: return (prot & (~pr)) | PAGE_WRITECOPY; |
| case PAGE_EXECUTE_READWRITE: return (prot & (~pr)) | PAGE_EXECUTE_WRITECOPY; |
| default: ASSERT_NOT_REACHED(); |
| } |
| return prot; |
| } |
| |
| /* does not change prot if it doesn't already have read access */ |
| static uint |
| osprot_add_write(uint prot) |
| { |
| int pr = prot & ~PAGE_PROTECTION_QUALIFIERS; |
| switch (pr) { |
| case PAGE_READONLY: return (prot & (~pr)) | PAGE_READWRITE; |
| case PAGE_EXECUTE_READ: return (prot & (~pr)) | PAGE_EXECUTE_READWRITE; |
| } |
| return prot; |
| } |
| |
| /* returns osprot flags preserving all native protection flags except |
| * for RWX, which are replaced according to memprot */ |
| uint |
| osprot_replace_memprot(uint old_osprot, uint memprot) |
| { |
| uint old_qualifiers = |
| old_osprot & PAGE_PROTECTION_QUALIFIERS; |
| uint new_osprot = memprot_to_osprot(memprot); |
| |
| /* preserve any old WRITECOPY 'flag' if page hasn't been touched */ |
| if (prot_is_copyonwrite(old_osprot) && prot_is_writable(new_osprot)) |
| new_osprot = osprot_add_writecopy(new_osprot); |
| new_osprot |= old_qualifiers; |
| return new_osprot; |
| } |
| |
| void |
| dump_mbi(file_t file, MEMORY_BASIC_INFORMATION *mbi, bool dump_xml) |
| { |
| print_file(file, dump_xml ? |
| "\t\tBaseAddress= \""PFX"\"\n" |
| "\t\tAllocationBase= \""PFX"\"\n" |
| "\t\tAllocationProtect= \"0x%08x %s\"\n" |
| "\t\tRegionSize= \"0x%08x\"\n" |
| "\t\tState= \"0x%08x %s\"\n" |
| "\t\tProtect= \"0x%08x %s\"\n" |
| "\t\tType= \"0x%08x %s\"\n" |
| : |
| "BaseAddress: "PFX"\n" |
| "AllocationBase: "PFX"\n" |
| "AllocationProtect: 0x%08x %s\n" |
| "RegionSize: 0x%08x\n" |
| "State: 0x%08x %s\n" |
| "Protect: 0x%08x %s\n" |
| "Type: 0x%08x %s\n", |
| mbi->BaseAddress, |
| mbi->AllocationBase, |
| mbi->AllocationProtect, prot_string(mbi->AllocationProtect), |
| mbi->RegionSize, |
| mbi->State, mem_state_string(mbi->State), |
| mbi->Protect, prot_string(mbi->Protect), |
| mbi->Type, mem_type_string(mbi->Type)); |
| } |
| |
| void |
| dump_mbi_addr(file_t file, app_pc target, bool dump_xml) |
| { |
| MEMORY_BASIC_INFORMATION mbi; |
| size_t len; |
| len = query_virtual_memory(target, &mbi, sizeof(mbi)); |
| if (len == sizeof(mbi)) |
| dump_mbi(file, &mbi, dump_xml); |
| else { |
| if (dump_xml) { |
| print_file(file, "<-- Unable to dump mbi for addr "PFX"\n -->", |
| target); |
| } else { |
| print_file(file, "Unable to dump mbi for addr "PFX"\n", target); |
| } |
| } |
| } |
| |
| /* FIXME: |
| * We need to be able to distinguish our own pid from that of a child |
| * process. We observe that after CreateProcess a child has pid of 0 (as |
| * determined by process_id_from_handle, calling NtQueryInformationProcess). |
| * For our current injection methods pid is always set when we take over, |
| * but for future early-injection methods what if the pid is still 0 when |
| * we start executing in the process' context? |
| */ |
| bool |
| is_pid_me(process_id_t pid) |
| { |
| return (pid == get_process_id()); |
| } |
| |
| bool |
| is_phandle_me(HANDLE phandle) |
| { |
| /* make the common case of NT_CURRENT_PROCESS faster */ |
| if (phandle == NT_CURRENT_PROCESS) { |
| return true; |
| } else { |
| /* we know of no way to detect whether two handles point to the same object, |
| * so we go to pid |
| */ |
| process_id_t pid = process_id_from_handle(phandle); |
| return is_pid_me(pid); |
| } |
| } |
| |
| /* used only in get_dynamorio_library_path() but file level namespace |
| * so it is easily available to windbg scripts */ |
| static char dynamorio_library_path[MAXIMUM_PATH]; |
| |
| /* get full path to our own library, (cached), used for forking and message file name */ |
| char* |
| get_dynamorio_library_path() |
| { |
| /* This operation could be dangerous, so it is still better that we do it |
| * once at startup when there is a single thread only |
| */ |
| if (!dynamorio_library_path[0]) { /* not cached */ |
| /* get_module_name can take any pc in the dll, |
| * so we simply take the address of this function |
| * instead of using get_module_handle to find the base |
| */ |
| app_pc pb = (app_pc)&get_dynamorio_library_path; |
| |
| /* here's where we set the library path */ |
| ASSERT(!dr_earliest_injected); /* should be already set for earliest */ |
| get_module_name(pb, dynamorio_library_path, MAXIMUM_PATH); |
| } |
| return dynamorio_library_path; |
| } |
| |
| /* based on a process handle to a process that is not yet running, |
| * verify whether we should be taking control over it */ |
| /* if target process should be injected into returns true, and |
| * inject_settings is set if non-NULL */ |
| bool |
| should_inject_into_process(dcontext_t *dcontext, HANDLE process_handle, |
| int *rununder_mask, /* OPTIONAL OUT */ |
| inject_setting_mask_t *inject_settings /* OPTIONAL OUT */) |
| { |
| bool inject = false; |
| synchronize_dynamic_options(); |
| if (DYNAMO_OPTION(follow_children) || DYNAMO_OPTION(follow_explicit_children) || |
| DYNAMO_OPTION(follow_systemwide)) { |
| |
| inject_setting_mask_t should_inject = |
| systemwide_should_inject(process_handle, rununder_mask); |
| |
| if (DYNAMO_OPTION(follow_systemwide) && TEST(INJECT_TRUE, should_inject)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "\tconfigured child should be injected\n"); |
| inject = true; |
| } |
| |
| if (!inject && DYNAMO_OPTION(follow_explicit_children) && |
| TESTALL(INJECT_EXPLICIT|INJECT_TRUE, should_inject)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "\texplicit child should be injected\n"); |
| inject = true; |
| } |
| |
| if (!inject && DYNAMO_OPTION(follow_children)) { |
| inject = true; /* -follow_children defaults to inject */ |
| |
| /* check if child should be excluded from running under dr */ |
| if (TEST(INJECT_EXCLUDED, should_inject)) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "\tchild is excluded, not injecting\n"); |
| inject = false; |
| } |
| |
| /* check if we should leave injection to preinjector */ |
| if (TEST(INJECT_TRUE, should_inject) && systemwide_inject_enabled() && |
| !TEST(INJECT_EXPLICIT, should_inject)) { |
| ASSERT(!DYNAMO_OPTION(follow_systemwide)); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "\tletting preinjector inject into child\n"); |
| inject = false; |
| } |
| |
| DODEBUG({ |
| if (inject) |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "\tnon-excluded, non-preinjected child should be injected\n"); |
| }); |
| } |
| if (inject) { |
| ASSERT(!TEST(INJECT_EXCLUDED, should_inject)); |
| if (inject_settings != NULL) |
| *inject_settings = should_inject; |
| } |
| } |
| DODEBUG({ |
| if (inject) { |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, |
| "\tinjecting into child process\n"); |
| |
| } else { |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, "\tletting child execute natively " |
| "(may still get injected by systemwide injector!)\n"); |
| } |
| }); |
| return inject; |
| } |
| |
| /* cxt may be NULL if -inject_at_create_process */ |
| static int |
| inject_into_process(dcontext_t *dcontext, HANDLE process_handle, CONTEXT *cxt, |
| inject_setting_mask_t should_inject) |
| { |
| /* Here in fact we don't want to have the default argument override |
| mechanism take place. If an app specific AUTOINJECT value is |
| provided, then we should of course use it. However, if no |
| specific one is given we should not use the global default when |
| follow_children. For follow_explicit_children it is actually OK |
| to use the global default value, it will be the GUI's |
| responsibility to set both the parent and child if it is desired |
| to have them use the same library. |
| */ |
| char library_path_buf[MAXIMUM_PATH]; |
| char *library = library_path_buf; |
| bool res; |
| |
| int err = get_process_parameter(process_handle, PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), |
| library_path_buf, sizeof(library_path_buf)); |
| |
| /* If there is no app-specific subkey, then we should check in what mode are we injecting */ |
| /* If we are in fact in follow_children - meaning all children are followed, |
| and there is no app specific option then we should use the parent library, |
| unless the child is in fact explicit in which case we just use the global library. |
| */ |
| |
| switch (err) { |
| case GET_PARAMETER_SUCCESS: |
| break; |
| case GET_PARAMETER_NOAPPSPECIFIC: |
| /* We got the global key's library, use parent's library instead if the only |
| * reason we're injecting is -follow_children (i.e. reading RUNUNDER gave us |
| * !INJECT_TRUE). */ |
| if (!TEST(INJECT_TRUE, should_inject)) { |
| ASSERT(DYNAMO_OPTION(follow_children)); |
| library = get_dynamorio_library_path(); |
| } |
| break; |
| case GET_PARAMETER_BUF_TOO_SMALL: |
| case GET_PARAMETER_FAILURE: |
| library = get_dynamorio_library_path(); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, "\tinjecting %s into child process\n", library); |
| |
| if (DYNAMO_OPTION(aslr_dr) && |
| /* case 8749 - can't aslr dr for thin_clients */ |
| process_handle != NULL && !is_child_in_thin_client(process_handle)) { |
| aslr_force_dynamorio_rebase(process_handle); |
| } |
| |
| /* Can't early inject 32-bit DR into a wow64 process as there is no |
| * ntdll32.dll at early inject point, so thread injection only. PR 215423. |
| * This is only true for xp64/2003. It happens to work on vista+ because |
| * it turns out ntdll32 is mapped in by the kernel. (xref i#381) |
| */ |
| if (DYNAMO_OPTION(early_inject) && |
| (get_os_version() >= WINDOWS_VERSION_VISTA || |
| !is_wow64_process(process_handle))) { |
| ASSERT(early_inject_address != NULL || |
| !INJECT_LOCATION_IS_LDR(early_inject_location)); |
| /* FIXME if early_inject_address == NULL then early_inject_init failed |
| * to find the correct address to use. Don't expect that to happen, |
| * but if it does could fall back to late injection (though we can't |
| * be sure that would work, i.e. early thread process for ex.) or |
| * do a SYSLOG error. */ |
| res = inject_into_new_process(process_handle, library, |
| DYNAMO_OPTION(early_inject_map), |
| early_inject_location, |
| early_inject_address); |
| } else { |
| ASSERT(cxt != NULL); |
| res = inject_into_thread(process_handle, cxt, NULL, library); |
| } |
| |
| if (!res) { |
| SYSLOG_INTERNAL_ERROR("ERROR: injection from pid=%d of %s into child " |
| "process %d failed", get_process_id(), library, |
| process_id_from_handle(process_handle)); |
| /* FIXME i#49: this can happen for a 64-bit child of a 32-bit parent */ |
| ASSERT_CURIOSITY(false && "injection into child failed: 32 to 64?"); |
| return false; /* for compilation correctness and release builds */ |
| } |
| return true; |
| } |
| |
| bool |
| is_first_thread_in_new_process(HANDLE process_handle, CONTEXT *cxt) |
| { |
| /* ASSUMPTION: based on what I've seen, on win2k a new process has |
| * pid 0 until its first thread is created. This is not true on XP |
| * so we also check if the argument value is the PEB address |
| * (which it should be if it is the first thread in the process, |
| * according to inside win2k). This is a slight risk of double |
| * or late injection if someone creates a remote thread that |
| * happens to have an argument that equals the address of PEB. |
| * Better would be able to tell from Eip if it is pointing at the |
| * kernel32 thread start thunk or the kernel32 process start thunk, |
| * or to check if the number of threads in the process equals 0, |
| * but no easy way to do either here. FIXME |
| */ |
| process_id_t pid = process_id_from_handle(process_handle); |
| if (pid == 0) |
| return true; |
| if (!is_pid_me(pid)) { |
| ptr_uint_t peb = (ptr_uint_t) get_peb(process_handle); |
| if (cxt->THREAD_START_ARG == peb) |
| return true; |
| else if (is_wow64_process(process_handle) && |
| get_os_version() >= WINDOWS_VERSION_VISTA) { |
| /* i#816: for wow64 process PEB query will be x64 while thread addr |
| * will be the x86 PEB. On Vista and Win7 the x86 PEB seems to |
| * always be one page below but we don't want to rely on that, and |
| * it doesn't hold on Win8. Instead we ensure the start addr is |
| * a one-page alloc whose first 3 fields match the x64 PEB: |
| * boolean flags, Mutant, and ImageBaseAddress. |
| */ |
| int64 peb64[3]; |
| int peb32[3]; |
| byte *base = NULL; |
| size_t sz = get_allocation_size_ex |
| (process_handle, (byte *)cxt->THREAD_START_ARG, &base); |
| LOG(THREAD_GET, LOG_SYSCALLS|LOG_THREADS, 2, |
| "%s: pid="PIFX" vs me="PIFX", arg="PFX" vs peb="PFX"\n", |
| __FUNCTION__, pid, get_process_id(), cxt->THREAD_START_ARG, peb); |
| if (sz != PAGE_SIZE || base != (byte *)cxt->THREAD_START_ARG) |
| return false; |
| if (!nt_read_virtual_memory(process_handle, (const void *) peb, |
| peb64, sizeof(peb64), &sz) || |
| sz != sizeof(peb64) || |
| !nt_read_virtual_memory(process_handle, |
| (const void *) cxt->THREAD_START_ARG, |
| peb32, sizeof(peb32), &sz) || |
| sz != sizeof(peb32)) |
| return false; |
| LOG(THREAD_GET, LOG_SYSCALLS|LOG_THREADS, 2, |
| "%s: peb64 "PIFX","PIFX","PIFX" vs peb32 "PIFX","PIFX","PIFX"\n", |
| __FUNCTION__, peb64[0], peb64[1], peb64[2], peb32[0], peb32[1], peb32[2]); |
| if (peb64[0] == peb32[0] && peb64[1] == peb32[1] && peb64[2] == peb32[2]) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* Depending on registry and options maybe inject into child process with |
| * handle process_handle. Called by SYS_CreateThread in pre_system_call (in |
| * which case cxt is non-NULL) and by CreateProcess[Ex] in post_system_call (in |
| * which case cxt is NULL). */ |
| bool |
| maybe_inject_into_process(dcontext_t *dcontext, HANDLE process_handle, |
| CONTEXT *cxt) |
| { |
| /* if inject_at_create_process becomes dynamic, need to move this check below |
| * the synchronize dynamic options */ |
| /* FIXME - can't read process parameters, at process create time is NULL |
| * value in peb field except in Vista. Could pass it in. */ |
| /* Can't early inject 32-bit DR into a wow64 process as there is no |
| * ntdll32.dll at early inject point, so thread injection only. PR 215423. |
| * This is only true for xp64/2003. It happens to work on vista+ because |
| * it turns out ntdll32 is mapped in by the kernel. (xref i#381) |
| */ |
| bool injected = false; |
| if ((cxt == NULL && (DYNAMO_OPTION(inject_at_create_process) || |
| (get_os_version() >= WINDOWS_VERSION_VISTA && |
| DYNAMO_OPTION(vista_inject_at_create_process))) |
| && (!is_wow64_process(process_handle) || |
| get_os_version() >= WINDOWS_VERSION_VISTA)) || |
| (cxt != NULL && is_first_thread_in_new_process(process_handle, cxt))) { |
| int rununder_mask; |
| inject_setting_mask_t should_inject; |
| /* Creating a new process & at potential inject point */ |
| DEBUG_DECLARE(process_id_t pid = process_id_from_handle(process_handle);) |
| DOLOG(3, LOG_SYSCALLS|LOG_THREADS, { |
| SYSLOG_INTERNAL_INFO("found a fork: pid %d", pid); |
| }); |
| LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, "found a fork: pid %d\n", pid); |
| |
| if (should_inject_into_process(dcontext, process_handle, |
| &rununder_mask, &should_inject)) { |
| injected = true; /* attempted, at least */ |
| ASSERT(cxt != NULL || DYNAMO_OPTION(early_inject)); |
| /* FIXME : if not -early_inject, we are going to read and write |
| * to cxt, which may be unsafe */ |
| if (inject_into_process(dcontext, process_handle, cxt, |
| should_inject)) { |
| check_for_run_once(process_handle, rununder_mask); |
| } |
| } |
| } |
| return injected; |
| } |
| |
| /* For case 8749: can't aslr dr for thin_client because cygwin apps will die. */ |
| static bool |
| is_child_in_thin_client(HANDLE process_handle) |
| { |
| bool res; |
| const options_t *opts; |
| |
| /* Shouldn't be using this for the current process. */ |
| ASSERT(process_handle != NT_CURRENT_PROCESS && |
| process_handle != NT_CURRENT_THREAD && process_handle != NULL); |
| |
| opts = get_process_options(process_handle); |
| ASSERT_OWN_READWRITE_LOCK(true, &options_lock); |
| ASSERT(opts != NULL); |
| |
| /* In this case the option is used only for preventing aslr_dr, so be safe |
| * if you can't read it and say yes which will prevent aslr dr. Note: this |
| * isn't the secure option, which is to say no, so that we alsr dr. |
| * Interesting tradeoff; choosing safety as this scenario is rare in which |
| * case first goal is to do no harm. |
| */ |
| if (opts == NULL) { |
| res = false; |
| } else { |
| res = opts->thin_client; |
| } |
| write_unlock(&options_lock); |
| return res; |
| } |
| |
| app_pc |
| get_dynamorio_dll_start() { |
| if (dynamo_dll_start == NULL) |
| dynamo_dll_start = get_allocation_base((app_pc) get_dynamorio_dll_start); |
| return dynamo_dll_start; |
| } |
| |
| app_pc |
| get_dynamorio_dll_preferred_base(void) |
| { |
| if (dynamo_dll_preferred_base == NULL) { |
| dynamo_dll_preferred_base = get_module_preferred_base(get_dynamorio_dll_start()); |
| ASSERT(dynamo_dll_preferred_base != NULL); |
| } |
| return dynamo_dll_preferred_base; |
| } |
| |
| static app_pc highest_user_address = (app_pc)(ptr_uint_t) |
| IF_X64_ELSE(0x000007fffffeffffLL, 0x7ffeffff); |
| /* 0x7ffeffff on 2GB:2GB default */ |
| /* or 0xbffeffff with /3GB in boot.ini, */ |
| /* /userva switch may also change the actual value seen */ |
| |
| static void |
| |