blob: c269863281cbffe3ccd1a34b25e257f64ea8e7f6 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2014 Google, Inc. All rights reserved.
* Copyright (c) 2007 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.
*/
/* Tests detaching from threads in a variety of states. */
#include <windows.h>
#include "tools.h"
#define PROC_NAME "detach_test.exe"
#define MAX_COUNT 10
static HWND hwnd = NULL;
static BOOL thread_ready = FALSE;
void CALLBACK SendAsyncProc(HWND hwnd, UINT uMsg, ULONG *dwData, LRESULT lResult);
void do_test(int count);
int do_busy_work(int c)
{
int i, t;
for (i = 0, t = 0; i < c; i++)
t = (i * c) - t;
if (t != c*((c+1)/2))
print("Failure %d != %d\n", t, c*((c+1)/2));
return t;
}
/* detach from in cache thread */
static BOOL in_busy_work = FALSE;
static BOOL exit_busy_work = FALSE;
DWORD WINAPI
ThreadProcBusyWork(LPVOID param)
{
print("Starting busy work\n");
in_busy_work = TRUE;
while (!exit_busy_work)
do_busy_work(20);
in_busy_work = FALSE;
exit_busy_work = FALSE;
print("Done busy working\n");
return 0;
}
/* detach from actively building thread */
static BOOL in_busy_build = FALSE;
static BOOL exit_busy_build = FALSE;
#define BUF_LEN 0x1000
DWORD WINAPI
ThreadProcBusyBuild(LPVOID param)
{
char *buf = VirtualAlloc(NULL, BUF_LEN, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
/* use selfmod to keep building */
copy_to_buf(buf, BUF_LEN, NULL, CODE_SELF_MOD, COPY_NORMAL);
print("Starting busy build\n");
in_busy_build = TRUE;
while (!exit_busy_build) {
/* don't do more than sandbox2ro_threshold iters (20) to avoid case 9908
* triggered resets (xref 10036 too) leading to hangs from detach at same time
* (case 8492). We're only trying to test code creation here anyways.
* Using 2 different values so there's a real change with the write. */
test(buf, 4);
test(buf, 5);
}
VirtualFree(buf, 0, MEM_RELEASE);
in_busy_build = FALSE;
exit_busy_build = FALSE;
print("Done busy building\n");
return 0;
}
/* see win32/tls.c test for alt. method of starting a detach, this way is preferable
* since we may at some point disallow the process from detaching itself */
void detach()
{
char buf[2*MAX_PATH] = "\"";
char *tools = getenv("DYNAMORIO_WINTOOLS");
int size;
STARTUPINFO sinfo = {sizeof(sinfo), NULL, "", 0, 0, 0, 0, 0, 0, 0, 0,
STARTF_USESTDHANDLES, 0, 0, NULL,
GetStdHandle(STD_INPUT_HANDLE),
GetStdHandle(STD_OUTPUT_HANDLE),
GetStdHandle(STD_ERROR_HANDLE)};
PROCESS_INFORMATION pinfo;
strncat(buf, tools == NULL ? "" : tools, BUFFER_ROOM_LEFT(buf));
NULL_TERMINATE_BUFFER(buf);
strncat(buf, "\\DRcontrol.exe\" -detachexe "PROC_NAME, BUFFER_ROOM_LEFT(buf));
NULL_TERMINATE_BUFFER(buf);
print("Detaching\n");
if (CreateProcess(NULL, buf, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo)) {
/* This thread will be detached at non-dr intercepted syscall (the wait below)
* with multiple stacked callbacks (from SendAsyncProc). */
WaitForSingleObject(pinfo.hProcess, INFINITE);
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
} else {
print("Detach Failed!\n");
}
}
static BOOL did_send_callback[MAX_COUNT];
static BOOL action_detach = FALSE;
static BOOL action_exit = FALSE;
void CALLBACK SendAsyncProc(HWND hwnd, UINT uMsg, ULONG *dwData, LRESULT lResult) {
int count = (int)dwData;
did_send_callback[count] = TRUE;
if (count > 0) {
do_test(count - 1);
} else {
if (action_detach) {
HANDLE ht_busy_build, ht_busy_work;
DWORD tid;
ht_busy_work = CreateThread(NULL, 0, &ThreadProcBusyWork, NULL, 0, &tid);
while (!in_busy_work)
Sleep(10);
ht_busy_build = CreateThread(NULL, 0, &ThreadProcBusyBuild, NULL, 0, &tid);
while (!in_busy_build)
Sleep(10);
detach();
print("Detach finished\n");
exit_busy_build = TRUE;
WaitForSingleObject(ht_busy_build, INFINITE);
exit_busy_work = TRUE;
WaitForSingleObject(ht_busy_work, INFINITE);
CloseHandle(ht_busy_build);
CloseHandle(ht_busy_work);
} else if (action_exit) {
print("Exiting with stacked callbacks\n");
ExitThread(0);
}
}
}
/* Some website claimed that EnumWindows was done by a kernel callback, but it's not
* (on XP at least). SendMessageCallback does, however, use a kernel callback for its
* callback (interestingly not just any alertable system call will do, has to be
* a message system call for the callback to be delivered). So we nest those to build
* up a callback stack. This routine will end up being recursively called count times
* building up count stacked callbacks. */
#define MAX_SLEEP 30000
void do_test(int count)
{
MSG msg;
int total_slept = 0;
did_send_callback[count] = FALSE;
if (!SendMessageCallback(hwnd, WM_NULL, 0, 0, &SendAsyncProc, count)) {
print("SendMsg failed.\n");
return;
}
while (!did_send_callback[count] && total_slept < MAX_SLEEP) {
Sleep(100);
total_slept += 100;
/* FIXME - all callbacks will have the same after address, need to mix in a
* GetMessage or some such so can verify order of callback stack. */
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
}
if (total_slept >= MAX_SLEEP) {
print("Callback never delivered.\n");
}
}
DWORD WINAPI
ThreadProcDoTest(LPVOID param)
{
do_test((int)param);
print("Finished test (Not Reached)\n");
}
/* detach from self suspended thread (also covers thread at dr intercepted syscall) */
DWORD WINAPI
ThreadProcSelfSuspend(LPVOID param)
{
SuspendThread(GetCurrentThread());
print("SuspendSelf resumed, exiting\n");
return 0;
}
LRESULT CALLBACK
wnd_callback(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI
window_func(void * arg)
{
MSG msg;
char *winName = "foobar";
WNDCLASS wndclass = {0, wnd_callback, 0, 0, NULL/* WinMain hwnd would be here */,
NULL, NULL, NULL, NULL, winName};
if (!RegisterClass(&wndclass)) {
print("Unable to create window class\n");
return 0;
}
hwnd = CreateWindow(winName, winName, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, NULL/* WinMain hwnd would be here */,
NULL);
if (hwnd == NULL) {
print("Error creating window\n");
return 0;
}
/* deliberately not calling ShowWindow */
thread_ready = true;
while (true) {
if (GetMessage(&msg, NULL, 0, 0)) {
/* Messages not auto-sent to callbacks are processed here */
TranslateMessage(&msg); /* convert virtual-key msgs to character msgs */
DispatchMessage(&msg);
}
}
return msg.wParam;
}
int
main()
{
HANDLE ht_selfsuspend, ht_exit, ht_window;
DWORD tid, res;
INIT();
print("creating window\n");
ht_window = CreateThread(NULL, 0, window_func, NULL, 0, &tid);
if (ht_window == NULL) {
print("Error creating window thread\n");
return -1;
}
while (!thread_ready)
Sleep(20);
print("detach_callback start\n");
ht_selfsuspend = CreateThread(NULL, 0, &ThreadProcSelfSuspend, NULL, 0, &tid);
if (ht_selfsuspend == NULL) {
print("Error creating self-suspend thread\n");
return -1;
}
/* wait for thread to suspend itself */
res = 0;
while (res == 0) {
res = SuspendThread(ht_selfsuspend);
if (res == 0) {
/* Thread might not yet have gotten around to suspending itself */
ResumeThread(ht_selfsuspend);
/* short sleep to wait */
Sleep(20);
}
}
do_test(2);
print("finished first callback test\n");
action_exit = TRUE;
ht_exit = CreateThread(NULL, 0, &ThreadProcDoTest, (void *)2, 0, &tid);
if (ht_exit == NULL) {
print("Error creating exit thread\n");
return -1;
}
WaitForSingleObject(ht_exit, INFINITE);
CloseHandle(ht_exit);
print("finished exit test\n");
action_exit = FALSE;
action_detach = TRUE;
do_test(2);
print("finished detach test\n");
action_detach = FALSE;
/* we are now detached */
/* just a little extra work to make sure everything looks ok natively */
do_test(1);
print("finished second callback test\n");
/* verify selfsuspended thread detached okay */
ResumeThread(ht_selfsuspend);
ResumeThread(ht_selfsuspend);
WaitForSingleObject(ht_selfsuspend, INFINITE);
CloseHandle(ht_selfsuspend);
print("detach_callback done\n");
return 0;
}