Skip to content

Commit

Permalink
part of i#725: Windows attach: internal attach
Browse files Browse the repository at this point in the history
Added the basics for internally-triggered attach (via start/stop API)
on Windows.  Many of these components would also be used for external
attach or re-attach.

+ Get the list of threads using NtQuerySystemInformation
  SystemProcessesAndThreadsInformation for pre-Vista and NtGetNextThread
  for Vista+.

+ Take over each unknown thread by storing its suspended context
  into data stored on the DR heap and then setting its context
  to an asm routine that calls DR code and passes in the data.

+ Work around various cases where the context seems to get reverted
  or changed.

+ Handle takeover during init APC, detected on NtContinue, by
  backing out the attach takeover and doing a regular DR takeover.

+ Updated api/startstop.c to test internal attach, except the
  setcontext is being reverted in certain cases, so we can't yet enable
  that part of the test as a success criterion.

There are some issues with the context being set not sticking, but this is
a good starting point.

SVN-Revision: 2032
  • Loading branch information
derekbruening committed Mar 28, 2013
1 parent 203eb32 commit 9c95ba5
Show file tree
Hide file tree
Showing 12 changed files with 587 additions and 28 deletions.
43 changes: 39 additions & 4 deletions core/dynamo.c
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,14 @@ dynamo_thread_init(byte *dstack_in, priv_mcontext_t *mc
/* note that ENTERING_DR is assumed to have already happened: in apc handler
* for win32, in new_thread_setup for linux, in main init for 1st thread
*/
#if defined(WINDOWS) && defined(DR_APP_EXPORTS)
/* We need to identify a thread we intercepted in its APC when we
* take over all threads on dr_app_start(). Stack and pc checks aren't
* simple b/c it can be in ntdll waiting on a lock.
*/
if (dr_api_entry)
os_take_over_mark_thread(get_thread_id());
#endif

/* Try to handle externally injected threads */
if (dynamo_initialized && !bb_lock_start)
Expand Down Expand Up @@ -2061,6 +2069,10 @@ dynamo_thread_init(byte *dstack_in, priv_mcontext_t *mc

if (is_thread_initialized()) {
mutex_unlock(&thread_initexit_lock);
#if defined(WINDOWS) && defined(DR_APP_EXPORTS)
if (dr_api_entry)
os_take_over_unmark_thread(get_thread_id());
#endif
return -1;
}

Expand Down Expand Up @@ -2096,6 +2108,11 @@ dynamo_thread_init(byte *dstack_in, priv_mcontext_t *mc
*/
add_thread(IF_WINDOWS_ELSE(NT_CURRENT_THREAD, get_process_id()), get_thread_id(),
under_dynamo_control, dcontext);
#if defined(WINDOWS) && defined(DR_APP_EXPORTS)
/* Now that the thread is in the main thread table we don't need to remember it */
if (dr_api_entry)
os_take_over_unmark_thread(get_thread_id());
#endif

LOG(GLOBAL, LOG_TOP|LOG_THREADS, 1,
"\ndynamo_thread_init: %d thread(s) now, dcontext="PFX", #=%d, id=%d, pid=%d\n\n",
Expand Down Expand Up @@ -2493,6 +2510,11 @@ dynamo_thread_stack_free_and_exit(byte *stack)
DR_APP_API int
dr_app_setup(void)
{
/* FIXME: we either have to disallow the client calling this with
* more than one thread running, or we have to suspend all the threads.
* We should share the suspend-and-takeover loop (and for dr_app_setup_and_start
* share the takeover portion) from dr_app_start().
*/
dr_api_entry = true;
return dynamorio_app_init();
}
Expand Down Expand Up @@ -2528,6 +2550,8 @@ void
dr_app_start_helper(priv_mcontext_t *mc)
{
apicheck(dynamo_initialized, PRODUCT_NAME" not initialized");
LOG(GLOBAL, LOG_TOP, 1, "dr_app_start in thread %d", get_thread_id());

if (!INTERNAL_OPTION(nullcalls)) {
/* Adjust the app stack to account for the return address + alignment.
* See dr_app_start in x86.asm.
Expand Down Expand Up @@ -2937,7 +2961,14 @@ check_should_be_protected(uint sec)
/* FIXME: even checking get_num_threads()==1 is still racy as a thread could
* exit, and it's not worth grabbing thread_initexit_lock here..
*/
if (threads_ever_count == 1)
if (threads_ever_count == 1
#ifdef DR_APP_EXPORTS
/* For start/stop, can be other threads running around so we bail on
* perfect protection
*/
&& !dr_api_entry
#endif
)
return false;
/* FIXME: no count of threads in DR or anything so can't conclude much
* Just return true and hope developer looks at datasec_not_prot stats.
Expand Down Expand Up @@ -3055,8 +3086,11 @@ data_section_exit(void)
/* There can't have been that many races.
* A failure to re-protect should result in a ton of dispatch
* entrances w/ .data unprot, so should show up here.
* However, an app with threads that are initializing in DR and thus
* unprotected .data while other threads are running new code (such as
* on attach) can easily rack up hundreds of unprot cache entrances.
*/
ASSERT_CURIOSITY(GLOBAL_STAT(datasec_not_prot) < 100);
ASSERT_CURIOSITY(GLOBAL_STAT(datasec_not_prot) < 5000);
});
for (i=0; i<DATASEC_NUM; i++)
DELETE_LOCK(datasec_lock[i]);
Expand Down Expand Up @@ -3110,8 +3144,9 @@ protect_data_section(uint sec, bool writable)
}
LOG(TEST(DATASEC_SELFPROT[sec], SELFPROT_ON_CXT_SWITCH) ? THREAD_GET : GLOBAL,
LOG_VMAREAS, TEST(DATASEC_SELFPROT[sec], SELFPROT_ON_CXT_SWITCH) ? 3U : 2U,
"protect_data_section: %s %s %s %d\n",
DATASEC_WRITABLE(sec) == 1 ? "changing" : "nop",
"protect_data_section: thread %d %s (recur %d, stat %d) %s %s %d\n",
get_thread_id(), DATASEC_WRITABLE(sec) == 1 ? "changing" : "nop",
DATASEC_WRITABLE(sec), GLOBAL_STAT(datasec_not_prot),
DATASEC_NAMES[sec], writable ? "rw" : "r", DATASEC_WRITABLE(sec));
if (!writable) {
ASSERT(DATASEC_WRITABLE(sec) > 0);
Expand Down
8 changes: 8 additions & 0 deletions core/win32/callback.c
Original file line number Diff line number Diff line change
Expand Up @@ -3637,6 +3637,14 @@ intercept_nt_continue(CONTEXT *cxt, int flag)
* like signals do for better performance?
*/
cxt->CXT_XIP = (ptr_uint_t) nt_continue_dynamo_start;
} else if (cxt->CXT_XIP == (ptr_uint_t) thread_attach_takeover) {
/* We set the context of this thread before it was done with its init
* APC: so we need to undo our takeover changes and take over
* normally here.
*/
thread_attach_context_revert(cxt);
dcontext->asynch_target = (app_pc) cxt->CXT_XIP;
cxt->CXT_XIP = (ptr_uint_t) nt_continue_dynamo_start;
} else {
/* No explanation for this one! */
SYSLOG_INTERNAL_ERROR("ERROR: intercept_nt_continue: xip="PFX
Expand Down
8 changes: 4 additions & 4 deletions core/win32/diagnost.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2011 Google, Inc. All rights reserved.
* Copyright (c) 2011-2012 Google, Inc. All rights reserved.
* Copyright (c) 2003-2009 VMware, Inc. All rights reserved.
* **********************************************************/

Expand Down Expand Up @@ -829,8 +829,8 @@ report_current_process(IN file_t diagnostics_file, IN PSYSTEM_PROCESSES sp,
* calling function can cast the buffer to PSYSTEM_PROCESSES for
* each process chained by the NextEntryDelta field.
*/
static byte *
get_system_processes(IN file_t diagnostics_file, OUT uint *info_bytes_needed)
byte *
get_system_processes(OUT uint *info_bytes_needed)
{
NTSTATUS result;
byte *process_info;
Expand Down Expand Up @@ -874,7 +874,7 @@ report_processes(IN file_t diagnostics_file, IN security_violation_t violation_t
/* We use byte * for process_info because
* SystemProcessesAndThreadsInformation is variable,
* each entry is cast to a SYSTEM_PROCESSES prior to access */
process_info = get_system_processes(diagnostics_file, &info_bytes_needed);
process_info = get_system_processes(&info_bytes_needed);

if (process_info != NULL) {
/* Initialize to first process */
Expand Down
49 changes: 49 additions & 0 deletions core/win32/ntdll.c
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,17 @@ ntdll_RtlGetExtendedContextLength_t ntdll_RtlGetExtendedContextLength = NULL;
ntdll_RtlInitializeExtendedContext_t ntdll_RtlInitializeExtendedContext = NULL;
ntdll_RtlLocateLegacyContext_t ntdll_RtlLocateLegacyContext = NULL;

#if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
/* Nt* routines that are not available on all versions of Windows */
typedef NTSTATUS (WINAPI *NtGetNextThread_t)(__in HANDLE ProcessHandle,
__in HANDLE ThreadHandle,
__in ACCESS_MASK DesiredAccess,
__in ULONG HandleAttributes,
__in ULONG Flags,
__out PHANDLE NewThreadHandle);
NtGetNextThread_t NtGetNextThread;
#endif

/***************************************************************************
* Implementation
*/
Expand Down Expand Up @@ -586,6 +597,11 @@ nt_get_context_extended_functions(app_pc base)
}
}

static void
nt_init_dynamic_syscall_wrappers(app_pc base)
{
NtGetNextThread = (NtGetNextThread_t) get_proc_address(base, "NtGetNextThread");
}
#endif /* !NOT_DYNAMORIO_CORE_PROPER */

void
Expand All @@ -596,6 +612,7 @@ ntdll_init()
*/
ASSERT(offsetof(TEB, TlsSlots) == TEB_TLS64_OFFSET);
#if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
nt_init_dynamic_syscall_wrappers((app_pc)get_ntdll_base());
nt_get_context_extended_functions((app_pc)get_ntdll_base());
#endif
}
Expand Down Expand Up @@ -844,6 +861,27 @@ process_handle_from_id(process_id_t pid)
return h;
}

#if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
HANDLE
thread_handle_from_id(thread_id_t tid)
{
NTSTATUS res;
HANDLE h;
OBJECT_ATTRIBUTES oa;
CLIENT_ID cid;
InitializeObjectAttributes(&oa, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL);
memset(&cid, 0, sizeof(cid));
cid.UniqueThread = (HANDLE) tid;
res = nt_raw_OpenThread(&h, THREAD_ALL_ACCESS, &oa, &cid);
if (!NT_SUCCESS(res)) {
NTPRINT("nt_open_thread failed: %x\n", res);
}
if (!NT_SUCCESS(res))
return INVALID_HANDLE_VALUE;
else
return h;
}
#endif

/* PEB:
* for a running thread this is stored at fs:[30h]
Expand Down Expand Up @@ -2006,6 +2044,17 @@ nt_thread_resume(HANDLE hthread, int *previous_suspend_count)
return NT_SUCCESS(res);
}

#if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
NTSTATUS
nt_thread_iterator_next(HANDLE hprocess, HANDLE cur_thread, HANDLE *next_thread,
ACCESS_MASK access)
{
if (NtGetNextThread == NULL)
return STATUS_NOT_IMPLEMENTED;
return NtGetNextThread(hprocess, cur_thread, access, 0, 0, next_thread);
}
#endif

bool
nt_terminate_thread(HANDLE hthread, NTSTATUS exit_code)
{
Expand Down
13 changes: 13 additions & 0 deletions core/win32/ntdll.h
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,9 @@ process_id_from_thread_handle(HANDLE h);
HANDLE
process_handle_from_id(process_id_t pid);

HANDLE
thread_handle_from_id(thread_id_t tid);

PEB *
get_peb(HANDLE h);

Expand Down Expand Up @@ -1093,6 +1096,13 @@ nt_thread_suspend(HANDLE hthread, int *previous_suspend_count);
bool
nt_thread_resume(HANDLE hthread, int *previous_suspend_count);

#if !defined(NOT_DYNAMORIO_CORE_PROPER) && !defined(NOT_DYNAMORIO_CORE)
/* Returns STATUS_NOT_IMPLEMENTED on pre-Vista */
NTSTATUS
nt_thread_iterator_next(HANDLE hprocess, HANDLE cur_thread, HANDLE *next_thread,
ACCESS_MASK access);
#endif

bool
nt_terminate_thread(HANDLE hthread, NTSTATUS exit_code);

Expand Down Expand Up @@ -1370,6 +1380,9 @@ query_full_attributes_file(PCWSTR filename,

/* From NTSTATUS.H -- this shouldn't change, but you never know... */
/* DDK2003SP1/3790.1830/inc/wnet/ntstatus.h */

#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L)

#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)

/* The requested operation is not implemented. */
Expand Down
Loading

0 comments on commit 9c95ba5

Please sign in to comment.