You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
17 KiB
496 lines
17 KiB
/*
|
|
* Copyright 2002-2019 Intel Corporation.
|
|
*
|
|
* This software is provided to you as Sample Source Code as defined in the accompanying
|
|
* End User License Agreement for the Intel(R) Software Development Products ("Agreement")
|
|
* section 1.L.
|
|
*
|
|
* This software and the related documents are provided as is, with no express or implied
|
|
* warranties, other than those that are expressly stated in the License.
|
|
*/
|
|
|
|
/*! @file
|
|
* This test application verifies that Pin on Windows correctly handles
|
|
* thread suspension and thread termination system calls. The application must
|
|
* be run with the "suspend_win" tool.
|
|
*/
|
|
|
|
#define _WIN32_WINNT 0x0400 // Needed for SignalObjectAndWait()
|
|
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <windows.h>
|
|
using std::string;
|
|
using std::cerr;
|
|
using std::endl;
|
|
// Auto-reset event. It is signaled when a thread is about to start.
|
|
HANDLE StartEvent;
|
|
|
|
// Manual-reset event. It is signaled when a thread is allowed to start.
|
|
HANDLE AllowStartEvent;
|
|
|
|
//==========================================================================
|
|
// Printing utilities
|
|
//==========================================================================
|
|
string UnitTestName("suspend_win");
|
|
string FunctionTestName;
|
|
|
|
static void StartFunctionTest(const string & functionTestName)
|
|
{
|
|
if (FunctionTestName != "")
|
|
{
|
|
cerr << UnitTestName << "[" << FunctionTestName << "] Success" << endl;
|
|
}
|
|
FunctionTestName = functionTestName;
|
|
}
|
|
|
|
static void ExitUnitTest()
|
|
{
|
|
if (FunctionTestName != "")
|
|
{
|
|
cerr << UnitTestName << "[" << FunctionTestName << "] Success" << endl;
|
|
}
|
|
cerr << UnitTestName << " : Completed successfully" << endl;
|
|
exit(0);
|
|
}
|
|
|
|
static void Abort(const string & msg)
|
|
{
|
|
cerr << UnitTestName << "[" << FunctionTestName << "] Failure: " << msg << endl;
|
|
exit(1);
|
|
}
|
|
|
|
//==========================================================================
|
|
// Check the tool presence
|
|
//==========================================================================
|
|
typedef int (*FP_IsToolPresent)(volatile int *);
|
|
extern "C" __declspec(dllexport) int IsToolPresent(volatile int *);
|
|
volatile FP_IsToolPresent fpIsToolPresent = IsToolPresent;
|
|
volatile int IsTool = 0;
|
|
|
|
/*!
|
|
* The tool intercepts this routine and sets <isTool> to 1.
|
|
*/
|
|
static int IsToolPresent(volatile int * pIsTool)
|
|
{
|
|
return *pIsTool;
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
// Function test A)
|
|
//==========================================================================
|
|
typedef void (*FP_SleepInTool)(volatile int *);
|
|
extern "C" __declspec(dllexport) void SleepInTool(volatile int *);
|
|
volatile FP_SleepInTool fpSleepInTool = SleepInTool;
|
|
volatile int InTool = 0;
|
|
|
|
/*!
|
|
* The tool intercepts this routine, sets <InTool> to 1 and sleeps some time
|
|
* on entry to this function.
|
|
*/
|
|
static void SleepInTool(volatile int * pInTool)
|
|
{
|
|
*pInTool = 0;
|
|
}
|
|
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadA(void * )
|
|
{
|
|
SetEvent(StartEvent);
|
|
while (TRUE)
|
|
{
|
|
// Call this dummy function through a volatile pointer to ensure the
|
|
// compiler doesn't inline it.
|
|
fpSleepInTool(&InTool);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
// Function test C)
|
|
//==========================================================================
|
|
extern "C" __declspec(dllexport) void DoFlush();
|
|
|
|
typedef void (*FP_CheckFlush)(volatile int *);
|
|
extern "C" __declspec(dllexport) void CheckFlush(volatile int *);
|
|
volatile FP_CheckFlush fpCheckFlush = CheckFlush;
|
|
|
|
volatile int InLoop = 0;
|
|
volatile int CodeCacheFlushed = 0;
|
|
|
|
void DoFlush()
|
|
{
|
|
// The tool intercepts this routine to flush the code cache.
|
|
}
|
|
|
|
void CheckFlush(volatile int * )
|
|
{
|
|
// The tool intercepts this routine and sets <CodeCacheFlushed> to 1 if
|
|
// the code cache was flushed.
|
|
}
|
|
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadC(void * )
|
|
{
|
|
SetEvent(StartEvent);
|
|
while (TRUE)
|
|
{
|
|
InLoop = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
// Function test F)
|
|
//==========================================================================
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadF(void * )
|
|
{
|
|
SignalObjectAndWait(StartEvent, AllowStartEvent, INFINITE, FALSE);
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
// Function test G)
|
|
//==========================================================================
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadG(void * )
|
|
{
|
|
SetEvent(StartEvent);
|
|
SuspendThread(GetCurrentThread());
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
// Function test M)
|
|
//==========================================================================
|
|
volatile LONG ThreadMEntryCount = 0;
|
|
volatile LONG ThreadMExitCount = 0;
|
|
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadM(void * pTargetThread)
|
|
{
|
|
|
|
SignalObjectAndWait(StartEvent, AllowStartEvent, INFINITE, FALSE);
|
|
|
|
const char * err = 0;
|
|
HANDLE hTargetThread = *(HANDLE *)pTargetThread;
|
|
|
|
// Wait for other ThreadM to enter the loop
|
|
if (InterlockedIncrement(&ThreadMEntryCount) != 2)
|
|
{
|
|
while (ThreadMEntryCount != 2) {;}
|
|
}
|
|
|
|
for (int i =0; i< 3; i++)
|
|
{
|
|
DWORD suspendCount;
|
|
suspendCount = SuspendThread(hTargetThread);
|
|
if (suspendCount == DWORD(-1)) {err = "SuspendThread failed"; break;}
|
|
if (suspendCount != 0) {err = "Unexpected suspend count in SuspendThread"; break;}
|
|
|
|
suspendCount = ResumeThread(hTargetThread);
|
|
if (suspendCount == DWORD(-1)) {err = "ResumeThread failed"; break;}
|
|
if (suspendCount != 1) {err = "Unexpected suspend count in ResumeThread"; break;}
|
|
}
|
|
|
|
// Wait for other ThreadM to complete the loop
|
|
if (InterlockedIncrement(&ThreadMExitCount) != 2)
|
|
{
|
|
while (ThreadMExitCount != 2) {;}
|
|
}
|
|
|
|
if (err != 0) {Abort(err);}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*!
|
|
* The main procedure of the application.
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
BOOL bStatus;
|
|
DWORD dwStatus;
|
|
HANDLE hThread;
|
|
DWORD suspendCount;
|
|
|
|
StartEvent = CreateEvent(0, FALSE, FALSE, 0);
|
|
AllowStartEvent = CreateEvent(0, TRUE, FALSE, 0);
|
|
|
|
// Check to see if the tool is present
|
|
IsToolPresent(&IsTool);
|
|
|
|
//============================================================================
|
|
// A) Verify that Pin does not suspend threads in analysis routines
|
|
//============================================================================
|
|
StartFunctionTest("A");
|
|
|
|
// Create a thread that calls to a dummy function in an infinite loop.
|
|
// The function is instrumented by the tool, that sleeps some time on each
|
|
// function call. Try to suspend the thread while it is sleeping in the tool.
|
|
// Pin should postpone suspension till the thread returns from the tool's
|
|
// routine.
|
|
hThread = CreateThread(0, 0, ThreadA, 0, 0, 0);
|
|
if (hThread == 0) {Abort("CreateThread failed");}
|
|
|
|
// Wait until the new thread starts running
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
// Wait until the thread enters the sleeping routine in the tool and then
|
|
// suspend the thread.
|
|
|
|
while (IsTool && (InTool == 0))
|
|
{
|
|
Sleep(0);
|
|
}
|
|
|
|
suspendCount = SuspendThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread failed");}
|
|
if (suspendCount != 0) {Abort("Unexpected suspend count in SuspendThread");}
|
|
if (InTool != 0) {Abort("The thread is suspended in the tool");}
|
|
|
|
// Resume the thread and check the suspension count
|
|
suspendCount = ResumeThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread failed");}
|
|
if (suspendCount != 1) {Abort("Unexpected suspend count in ResumeThread");}
|
|
}
|
|
|
|
//============================================================================
|
|
// B) Verify that Pin does not terminate threads in analysis routines
|
|
//============================================================================
|
|
StartFunctionTest("B");
|
|
|
|
while (IsTool && (InTool == 0))
|
|
{
|
|
Sleep(0);
|
|
}
|
|
bStatus = TerminateThread(hThread, 0);
|
|
if (bStatus == 0) {Abort("TerminateThread failed");}
|
|
if (InTool != 0) {Abort("The thread is terminated in the tool");}
|
|
|
|
// Wait some time before checking the thread state. TerminateThread() kills
|
|
// threads asynchronously.
|
|
dwStatus = WaitForSingleObject(hThread, 5000);
|
|
if (dwStatus != WAIT_OBJECT_0) {Abort("The thread is not terminated");}
|
|
|
|
CloseHandle(hThread);
|
|
|
|
//============================================================================
|
|
// C) Verify that Pin does not suspend threads in the code cache and the
|
|
// CC flush is possible when a thread is suspended.
|
|
//============================================================================
|
|
StartFunctionTest("C");
|
|
|
|
// Create a thread that loops infinitely in the code cache and suspend
|
|
// the thread somewhere in the middle.
|
|
// Cause the tool to flush the code cache and then check if the flush happened.
|
|
// Pin pokes the thread out of the code cache before suspension and removes the
|
|
// thread from generation counts. This should allow CC flush during the thread
|
|
// suspension.
|
|
|
|
hThread = CreateThread(0, 0, ThreadC, 0, 0, 0);
|
|
if (hThread == 0) {Abort("CreateThread failed");}
|
|
|
|
// Wait until the new thread enters the loop
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
while (!InLoop)
|
|
{
|
|
Sleep(0);
|
|
}
|
|
|
|
// Suspend the thread
|
|
suspendCount = SuspendThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread failed");}
|
|
if (suspendCount != 0) {Abort("Unexpected suspend count in SuspendThread");}
|
|
|
|
// Run for (at most) three iterations to ensure steady state - no more jitting of the DoFlush function.
|
|
// The tool will remove instrumentation only after reaching steady state.
|
|
// The first two calls may be jitted, but by the third we expect the tool to remove instrumentation.
|
|
// The next (fourth at most) call will cause the code to be rejitted due to the cache flush and that will set
|
|
// CodeCacheFlushed. Eventually, the cache will only be flushed once.
|
|
for (unsigned int i = 0; i < 4; ++i)
|
|
{
|
|
DoFlush();
|
|
|
|
// Call this dummy function through a volatile pointer to ensure the compiler doesn't inline it.
|
|
fpCheckFlush(&CodeCacheFlushed);
|
|
if (CodeCacheFlushed) break;
|
|
}
|
|
if (IsTool && (CodeCacheFlushed == 0)) {Abort("The code cache was not flushed");}
|
|
|
|
//============================================================================
|
|
// D) Verify that Pin correctly terminates suspended threads
|
|
//============================================================================
|
|
StartFunctionTest("D");
|
|
|
|
bStatus = TerminateThread(hThread, 0);
|
|
if (bStatus == 0) {Abort("TerminateThread failed");}
|
|
|
|
// Wait some time before checking the thread state. TerminateThread() kills
|
|
// threads asynchronously.
|
|
dwStatus = WaitForSingleObject(hThread, 5000);
|
|
if (dwStatus != WAIT_OBJECT_0) {Abort("The thread is not terminated");}
|
|
|
|
CloseHandle(hThread);
|
|
|
|
//============================================================================
|
|
// E) Verify that Pin correctly suspends and terminates threads that are not
|
|
// yet started.
|
|
//============================================================================
|
|
StartFunctionTest("E");
|
|
|
|
// Create a thread in the suspended state
|
|
hThread = CreateThread(0, 0, ThreadC, 0, CREATE_SUSPENDED, 0);
|
|
if (hThread == 0) {Abort("CreateThread failed");}
|
|
|
|
// Suspend the thread one more time and check the suspension count
|
|
suspendCount = SuspendThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread failed");}
|
|
if (suspendCount != 1) {Abort("Unexpected suspend count in SuspendThread");}
|
|
|
|
// Terminate the thread that has not run yet
|
|
bStatus = TerminateThread(hThread, 0);
|
|
if (bStatus == 0) {Abort("TerminateThread failed");}
|
|
|
|
dwStatus = WaitForSingleObject(hThread, 5000);
|
|
if (dwStatus != WAIT_OBJECT_0) {Abort("The thread is not terminated");}
|
|
|
|
CloseHandle(hThread);
|
|
|
|
//============================================================================
|
|
// F) Verify that Pin correctly suspends and terminates threads that are
|
|
// blocked in a system call.
|
|
//============================================================================
|
|
StartFunctionTest("F");
|
|
|
|
// Create a thread that blocks itself in a system call. Suspend and terminate
|
|
// the blocked thread.
|
|
|
|
ResetEvent(AllowStartEvent);
|
|
hThread = CreateThread(0, 0, ThreadF, 0, 0, 0);
|
|
if (hThread == 0) {Abort("CreateThread failed");}
|
|
|
|
// Wait until the new thread hangs in a system call
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
|
|
// Suspend the thread
|
|
suspendCount = SuspendThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread failed");}
|
|
if (suspendCount != 0) {Abort("Unexpected suspend count in SuspendThread");}
|
|
|
|
// Resume the thread. It is still blocked on AllowStartEvent!
|
|
suspendCount = ResumeThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread failed");}
|
|
if (suspendCount != 1) {Abort("Unexpected suspend count in ResumeThread");}
|
|
|
|
// Terminate the thread that is blocked in a system call
|
|
bStatus = TerminateThread(hThread, 0);
|
|
if (bStatus == 0) {Abort("TerminateThread failed");}
|
|
|
|
dwStatus = WaitForSingleObject(hThread, 5000);
|
|
if (dwStatus != WAIT_OBJECT_0) {Abort("The thread is not terminated");}
|
|
|
|
CloseHandle(hThread);
|
|
|
|
//============================================================================
|
|
// G) Verify that Pin correctly handles the thread self-suspension.
|
|
//============================================================================
|
|
StartFunctionTest("G");
|
|
|
|
// Create a thread and wait until it suspends itself. Then, resume the thread.
|
|
hThread = CreateThread(0, 0, ThreadG, 0, 0, 0);
|
|
if (hThread == 0) {Abort("CreateThread failed");}
|
|
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
while (TRUE)
|
|
{
|
|
Sleep(5);
|
|
suspendCount = SuspendThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread failed");}
|
|
if (suspendCount != 0)
|
|
{
|
|
if (suspendCount != 1) {Abort("Unexpected suspend count in SuspendThread");}
|
|
break;
|
|
}
|
|
|
|
suspendCount = ResumeThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread failed");}
|
|
}
|
|
suspendCount = ResumeThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread#1 failed");}
|
|
if (suspendCount != 2) {Abort("Unexpected suspend count in ResumeThread");}
|
|
|
|
suspendCount = ResumeThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread#2 failed");}
|
|
if (suspendCount != 1) {Abort("Unexpected suspend count in ResumeThread");}
|
|
|
|
dwStatus = WaitForSingleObject(hThread, 60000);
|
|
if (dwStatus != WAIT_OBJECT_0) {Abort("The thread is not resumed");}
|
|
|
|
CloseHandle(hThread);
|
|
|
|
//============================================================================
|
|
// M) Verify that mutual suspension does not cause deadlock.
|
|
//============================================================================
|
|
|
|
// Apparently, this test works fine under Pin but causes deadlock when
|
|
// executed natively.
|
|
// It seems that Windows kernel suspends both threads if they try to suspend
|
|
// each other and issue SuspendThread system calls simultaneously.
|
|
// Leave this test for internal Pin debugging only.
|
|
|
|
if ((argc == 2) && (string(argv[1]) == "-dbg"))
|
|
{
|
|
StartFunctionTest("M");
|
|
// Create two threads that simultaneously attempt to suspend/resume each
|
|
// other. Both threads should exit successfully if not deadlocked.
|
|
|
|
HANDLE hThread[2];
|
|
ResetEvent(AllowStartEvent);
|
|
|
|
hThread[0] = CreateThread(0, 0, ThreadM, &(hThread[1]), 0, 0);
|
|
if (hThread[0] == 0) {Abort("CreateThread[0] failed");}
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
|
|
hThread[1] = CreateThread(0, 0, ThreadM, &(hThread[0]), 0, 0);
|
|
if (hThread[1] == 0) {Abort("CreateThread[1] failed");}
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
|
|
// Start the threads
|
|
SetEvent(AllowStartEvent);
|
|
|
|
// Wait 1 minute or until both threads exit
|
|
dwStatus = WaitForMultipleObjects(2, hThread, TRUE, 60000);
|
|
if (dwStatus == WAIT_TIMEOUT)
|
|
{
|
|
Abort("The threads appear to be deadlocked");
|
|
}
|
|
else if (dwStatus == WAIT_FAILED)
|
|
{
|
|
Abort("WaitForMultipleObjects failed");
|
|
}
|
|
|
|
CloseHandle(hThread[0]);
|
|
CloseHandle(hThread[1]);
|
|
}
|
|
|
|
//============================================================================
|
|
ExitUnitTest();
|
|
|
|
return 0;
|
|
}
|