blob: cf8136a0caae776f8356b567e1faf8b8309e4b69 [file] [log] [blame]
/* **********************************************************
* 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;
}