Skip to content

Commit

Permalink
i#725: Add an automated test of detach on Windows (DynamoRIO#6221)
Browse files Browse the repository at this point in the history
This patch adds an automated test of detach on Windows platform. The
test start a target program and attach to the process firstly, then use
the drconfig front-end to detach from the process.
As shown in the .template file, the detach mechanism lets the target
process be detached before stopping it.

Issue: [DynamoRIO#725](DynamoRIO#725)

---------

Co-authored-by: Derek Bruening <[email protected]>
  • Loading branch information
2 people authored and ivankyluk committed Jul 28, 2023
1 parent 0d3d48e commit 1305f11
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 8 deletions.
18 changes: 17 additions & 1 deletion suite/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,12 @@ function(torun test key source native standalone_dr dr_ops exe_ops added_out pas
set(exe_ops "${exe_ops};-block")
endif ()
else ()
if ("${runall}" MATCHES "<attach>")
if ("${runall}" MATCHES "<attach>" OR "${runall}" MATCHES "<detach>")
# For detach we use attachee to attach firstly, and "-detach" option is used
# for differentiating between attach and detach.
if ("${runall}" MATCHES "<detach>")
set(exe_ops "${exe_ops};-detach")
endif ()
# For attach we use attachee, which is identical to infloop, aside for writing
# a starting message to the output file. This allows us to detect when it is
# up and running and we can start the attach process.
Expand Down Expand Up @@ -1386,6 +1391,14 @@ function(torun test key source native standalone_dr dr_ops exe_ops added_out pas
string(REGEX REPLACE ";" "@" nudge_arg "${nudge_arg}")
# clear client from dr command and run native
string(REGEX REPLACE ";-quiet;(.*);--" ";-no_inject;--;" rundr "${rundr}")
elseif ("${runall}" MATCHES "<detach>")
# Move params to nudge.
string(REGEX MATCH ";-quiet;(.*);--" nudge_arg "${rundr}")
string(REGEX REPLACE ";--" "" nudge_arg "${nudge_arg}")
string(PREPEND nudge_arg "<detach>")
string(REGEX REPLACE ";" "@" nudge_arg "${nudge_arg}")
# Clear client from dr command and run native.
string(REGEX REPLACE ";-quiet;(.*);--" ";-no_inject;--;" rundr "${rundr}")
endif ()
set(cmd_with_at ${rundr} ${app_path} ${exe_ops})
# we pass intra-arg spaces via @@ and inter-arg via @ and ; via !
Expand Down Expand Up @@ -2456,6 +2469,9 @@ endif (ANNOTATIONS AND NOT ARM)

if (NOT ANDROID) # TODO i#38: Port test to Android.
tobuild_ci(client.attach_test client-interface/attach_test.runall "" "" "")
if (WIN32)
tobuild_ci(client.detach_test client-interface/detach_test.runall "" "" "")
endif (WIN32)
if (UNIX)
# Test attaching during a blocking syscall.
torunonly_ci(client.attach_blocking linux.infloop client.attach_test.dll
Expand Down
114 changes: 114 additions & 0 deletions suite/tests/client-interface/detach_test.dll.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* **********************************************************
* Copyright (c) 2021-2022 Google, Inc. All rights reserved.
* Copyright (c) 2008-2010 VMware, Inc. All rights reserved.
* **********************************************************/

/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/

#include "dr_api.h"
#include "client_tools.h"
#ifdef WINDOWS
# include <windows.h>
#endif

static thread_id_t injection_tid;
static bool first_thread = true;

static void
dr_exit(void)
{
dr_fprintf(STDERR, "done\n");
}

static void
dr_thread_init(void *drcontext)
{
thread_id_t tid = dr_get_thread_id(drcontext);
#ifdef WINDOWS
/* On Windows there is an additional thread used for attach injection.
* XXX i#725: We should remove it or hide it, and not rely on it here.
*/
if (tid != injection_tid && first_thread) {
first_thread = false;
dr_fprintf(STDERR, "thread init\n");
}
#else
dr_fprintf(STDERR, "thread init\n");
#endif
}

#ifdef WINDOWS
static bool
dr_exception_event(void *drcontext, dr_exception_t *excpt)
{
thread_id_t tid = dr_get_thread_id(drcontext);
dr_fprintf(STDERR, "exception in thread %p\ninjection thread %p\n", tid,
injection_tid);

dr_fprintf(STDERR, "ExceptionCode=%08x\n", excpt->record->ExceptionCode);
dr_fprintf(STDERR, "ExceptionFlags=%08x\n", excpt->record->ExceptionFlags);
dr_fprintf(STDERR, "ExceptionAddress=%p\n", excpt->record->ExceptionAddress);
dr_fprintf(STDERR, "parameters:\n");
for (DWORD i = 0; i < excpt->record->NumberParameters; i++) {
dr_fprintf(STDERR, "parameters[%ld]:%p\n", i,
excpt->record->ExceptionInformation[i]);
}

return true;
}
#endif

static void
event_post_attach(void)
{
dr_fprintf(STDERR, "attach\n");
}

static void
event_pre_detach(void)
{
dr_fprintf(STDERR, "detach\n");
}

DR_EXPORT
void
dr_init(client_id_t id)
{
void *drcontext = dr_get_current_drcontext();
injection_tid = dr_get_thread_id(drcontext);
dr_register_exit_event(dr_exit);
dr_register_thread_init_event(dr_thread_init);
dr_register_pre_detach_event(event_pre_detach);
#ifdef WINDOWS
dr_register_exception_event(dr_exception_event);
#endif
if (!dr_register_post_attach_event(event_post_attach))
dr_fprintf(STDERR, "Failed to register post-attach event");
dr_fprintf(STDERR, "thank you for testing detach\n");
}
1 change: 1 addition & 0 deletions suite/tests/client-interface/detach_test.runall
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<detach>
6 changes: 6 additions & 0 deletions suite/tests/client-interface/detach_test.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
starting attachee
thank you for testing detach
attach
thread init
detach
done
50 changes: 44 additions & 6 deletions suite/tests/runall.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,23 @@ elseif ("${nudge}" MATCHES "<attach>")
kill_background_process(ON)
message(FATAL_ERROR "*** ${nudge_cmd} failed (${nudge_result}): ${nudge_err}***\n")
endif (nudge_result)
elseif ("${nudge}" MATCHES "<detach>")
set(nudge_cmd run_in_bg)
string(REGEX REPLACE "<detach>"
"${toolbindir}/drrun@-attach@${pid}@-takeover_sleep@-takeovers@1000"
nudge "${nudge}")
string(REGEX REPLACE "@" ";" nudge "${nudge}")
execute_process(COMMAND "${toolbindir}/${nudge_cmd}" ${nudge}
RESULT_VARIABLE nudge_result
ERROR_VARIABLE nudge_err
OUTPUT_VARIABLE nudge_out
)
# Combine out and err.
set(nudge_err "${nudge_out}${nudge_err}")
if (nudge_result)
kill_background_process(ON)
message(FATAL_ERROR "*** ${nudge_cmd} failed (${nudge_result}): ${nudge_err}***\n")
endif (nudge_result)
else ()
# drnudgeunix and drconfig have different syntax:
if (WIN32)
Expand Down Expand Up @@ -250,8 +267,8 @@ if ("${orig_nudge}" MATCHES "-client")
message(FATAL_ERROR "Timed out waiting for more output")
endif ()
endwhile()
elseif ("${orig_nudge}" MATCHES "<attach>")
# wait until attached
elseif ("${orig_nudge}" MATCHES "<attach>" OR "${orig_nudge}" MATCHES "<detach>")
# Wait until attached.
set(iters 0)
while (NOT "${output}" MATCHES "attach\n")
do_sleep(0.1)
Expand All @@ -262,7 +279,7 @@ elseif ("${orig_nudge}" MATCHES "<attach>")
message(FATAL_ERROR "Timed out waiting for attach")
endif ()
endwhile()
# wait until thread init
# Wait until thread init.
set(iters 0)
while (NOT "${output}" MATCHES "thread init\n")
do_sleep(0.1)
Expand All @@ -280,6 +297,28 @@ else ()
do_sleep(0.5)
endif ()

if ("${orig_nudge}" MATCHES "<detach>")
execute_process(COMMAND "${toolbindir}/drconfig.exe" "-detach" ${pid}
RESULT_VARIABLE detach_result
ERROR_VARIABLE detach_err
OUTPUT_VARIABLE detach_out)
set(detach_err "${detach_out}${detach_err}")
if (detach_result)
message(FATAL_ERROR "*** detach failed (${detach_result}): ${detach_err}***\n")
endif (detach_result)
# Wait until detach.
set(iters 0)
while (NOT "${output}" MATCHES "detach\n")
do_sleep(0.1)
file(READ "${out}" output)
math(EXPR iters "${iters}+1")
if (${iters} GREATER ${MAX_ITERS})
kill_background_process(ON)
message(FATAL_ERROR "Timed out waiting for attach")
endif ()
endwhile()
endif()

kill_background_process(OFF)

if (NOT "${fail_msg}" STREQUAL "")
Expand All @@ -306,11 +345,10 @@ endwhile()
string(REGEX REPLACE "[ \n]+$" "" output "${output}")
message("${output}")

# Sometimes infloop keeps running: FIXME: figure out why.
if (UNIX)
# sometimes infloop keeps running: FIXME: figure out why
execute_process(COMMAND "${KILL}" -9 ${pid} ERROR_QUIET OUTPUT_QUIET)
# we can't run pkill b/c there are other tests running infloop (i#1341)
else ()
# We could run "${toolbindir}/DRkill.exe" -pid ${pid} but we shouldn't need to
# as the app itself has a timeout.
execute_process(COMMAND "${toolbindir}/DRkill.exe" -pid ${pid} ERROR_QUIET OUTPUT_QUIET)
endif ()
14 changes: 13 additions & 1 deletion suite/tests/win32/attachee.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ TimerProc(HWND hwnd, UINT msg, UINT_PTR id, DWORD time)
int
main(int argc, const char *argv[])
{
int arg_offs = 1;
bool for_detach = false;
while (arg_offs < argc && argv[arg_offs][0] == '-') {
if (strcmp(argv[arg_offs], "-detach") == 0) {
for_detach = true;
arg_offs++;
} else
return 1;
}

/* We put the pid into the title so that tools/closewnd can target it
* uniquely when run in a parallel test suite.
* runall.cmake assumes this precise title.
Expand All @@ -57,6 +67,8 @@ main(int argc, const char *argv[])

print("starting attachee\n");
MessageBoxA(NULL, "DynamoRIO test: will be auto-closed", title, MB_OK);
print("MessageBox closed\n");
if (!for_detach) {
print("MessageBox closed\n");
}
return 0;
}

0 comments on commit 1305f11

Please sign in to comment.