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.
503 lines
16 KiB
503 lines
16 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
|
|
* system calls that get/set context of a suspended thread.
|
|
*/
|
|
|
|
#define _WIN32_WINNT 0x0400 // Needed for SignalObjectAndWait()
|
|
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <windows.h>
|
|
|
|
using std::string;
|
|
using std::cerr;
|
|
using std::endl;
|
|
using std::endl;
|
|
typedef unsigned __int8 UINT8 ;
|
|
typedef unsigned __int16 UINT16;
|
|
typedef unsigned __int32 UINT32;
|
|
typedef unsigned __int64 UINT64;
|
|
|
|
// 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;
|
|
|
|
// Pointer to the NtTerminateThread function.
|
|
typedef LONG __stdcall NtTerminateThread_T(HANDLE threadHandle, LONG exitStatus);
|
|
NtTerminateThread_T * fpNtTerminateThread;
|
|
|
|
//==========================================================================
|
|
// Printing utilities
|
|
//==========================================================================
|
|
string UnitTestName("suspend_context_win");
|
|
string FunctionTestName;
|
|
|
|
static void StartFunctionTest(const string & functionTestName)
|
|
{
|
|
if (FunctionTestName != "")
|
|
{
|
|
cerr << UnitTestName << "[" << FunctionTestName << "] Success" << endl;
|
|
}
|
|
FunctionTestName = functionTestName;
|
|
}
|
|
|
|
static void SkipFunctionTest(const string & msg)
|
|
{
|
|
cerr << UnitTestName << "[" << FunctionTestName << "] Skip: " << msg << endl;
|
|
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);
|
|
}
|
|
|
|
//==========================================================================
|
|
// Context manipulation utilities
|
|
//==========================================================================
|
|
#if defined(TARGET_IA32)
|
|
|
|
// IA-32 CONTEXT keeps XMM registers in the ExtendedRegisters[] array. This array
|
|
// has the FXSAVE structure.
|
|
struct FXSAVE
|
|
{
|
|
UINT16 _fcw;
|
|
UINT16 _fsw;
|
|
UINT8 _ftw;
|
|
UINT8 _pad1;
|
|
UINT16 _fop;
|
|
UINT32 _fpuip;
|
|
UINT16 _cs;
|
|
UINT16 _pad2;
|
|
UINT32 _fpudp;
|
|
UINT16 _ds;
|
|
UINT16 _pad3;
|
|
UINT32 _mxcsr;
|
|
UINT32 _mxcsrmask;
|
|
UINT8 _st[8 * 16];
|
|
UINT8 _xmm[8 * 16];
|
|
UINT8 _pad4[56 * 4];
|
|
};
|
|
|
|
const size_t XmmRegsOffset = (offsetof(CONTEXT, ExtendedRegisters) + offsetof(FXSAVE, _xmm[0]));
|
|
const size_t XmmRegSize = 16;
|
|
const size_t NumXmmRegs = 8;
|
|
const DWORD XMM_CONTEXT_FLAG = CONTEXT_EXTENDED_REGISTERS;
|
|
|
|
#elif defined(TARGET_IA32E)
|
|
|
|
const size_t XmmRegsOffset = offsetof(CONTEXT, FltSave.XmmRegisters);
|
|
const size_t XmmRegSize = 16;
|
|
const size_t NumXmmRegs = 16;
|
|
const DWORD XMM_CONTEXT_FLAG = CONTEXT_FLOATING_POINT;
|
|
|
|
#endif
|
|
|
|
/*!
|
|
* Return TRUE if XMM registers in specified contexts are identical.
|
|
*/
|
|
static BOOL CompareXmmState(PCONTEXT pContext1, PCONTEXT pContext2)
|
|
{
|
|
return (memcmp((unsigned char *)(pContext1) + XmmRegsOffset,
|
|
(unsigned char *)(pContext2) + XmmRegsOffset,
|
|
NumXmmRegs*XmmRegSize) == 0);
|
|
}
|
|
|
|
|
|
/*!
|
|
* Suspend specified threads and exchange their contexts.
|
|
*/
|
|
void ExchangeContexts(HANDLE hThread0, HANDLE hThread1)
|
|
{
|
|
BOOL bStatus;
|
|
DWORD suspendCount;
|
|
|
|
CONTEXT ctxt0;
|
|
CONTEXT ctxt1;
|
|
ctxt0.ContextFlags = CONTEXT_ALL;
|
|
ctxt1.ContextFlags = CONTEXT_ALL;
|
|
|
|
suspendCount = SuspendThread(hThread0);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread(0) failed");}
|
|
suspendCount = SuspendThread(hThread1);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread(1) failed");}
|
|
|
|
bStatus = GetThreadContext(hThread0, &ctxt0);
|
|
if (bStatus == 0) {Abort("GetThreadContext(0) failed");}
|
|
bStatus = GetThreadContext(hThread1, &ctxt1);
|
|
if (bStatus == 0) {Abort("GetThreadContext(1) failed");}
|
|
|
|
bStatus = SetThreadContext(hThread0, &ctxt1);
|
|
if (bStatus == 0) {Abort("SetThreadContext(0) failed");}
|
|
bStatus = SetThreadContext(hThread1, &ctxt0);
|
|
if (bStatus == 0) {Abort("SetThreadContext(1) failed");}
|
|
}
|
|
|
|
//==========================================================================
|
|
// Test A)
|
|
//==========================================================================
|
|
volatile DWORD PhaseOfThreadA = 0;
|
|
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadA(void * arg)
|
|
{
|
|
SignalObjectAndWait(StartEvent, AllowStartEvent, INFINITE, FALSE);
|
|
|
|
volatile DWORD * pTid = (volatile DWORD *)arg;
|
|
|
|
{
|
|
DWORD tid = GetCurrentThreadId();
|
|
*pTid = tid;
|
|
}
|
|
|
|
while (PhaseOfThreadA != 1) {}
|
|
|
|
{
|
|
DWORD tid = GetCurrentThreadId();
|
|
*pTid = tid;
|
|
}
|
|
|
|
while (PhaseOfThreadA != 2) {}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
// Function test B)
|
|
//==========================================================================
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadB(void * pContext)
|
|
{
|
|
SignalObjectAndWait(StartEvent, AllowStartEvent, INFINITE, FALSE);
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
// Function test C)
|
|
//==========================================================================
|
|
/*!
|
|
* Call NtTerminateThread for the current thread.
|
|
*/
|
|
static void TerminateThisThread()
|
|
{
|
|
//exit with zero status
|
|
fpNtTerminateThread((HANDLE)(LONG_PTR)(-2), 0);
|
|
}
|
|
|
|
/*!
|
|
* The thread start routine.
|
|
*/
|
|
static DWORD WINAPI ThreadC(void * )
|
|
{
|
|
SignalObjectAndWait(StartEvent, AllowStartEvent, INFINITE, FALSE);
|
|
//exit with non-zero status
|
|
return 1;
|
|
}
|
|
|
|
/*!
|
|
* Given a handle to a thread suspended in the ThreadC function.
|
|
* Change the context: IP register = TerminateThisThread entry.
|
|
* Resume the thread and check the thread exit status.
|
|
* @return 0 - the thread exited with an expected status.
|
|
* 1 - the thread exited with an unexpected status.
|
|
* -1 - the test can not be performed in this configuration.
|
|
*/
|
|
static int TerminateThreadCAndCheckStatus(HANDLE hThread)
|
|
{
|
|
int result = 0;
|
|
BOOL bStatus;
|
|
DWORD dwStatus;
|
|
DWORD suspendCount;
|
|
CONTEXT ctxt;
|
|
ctxt.ContextFlags = CONTEXT_CONTROL;
|
|
|
|
bStatus = GetThreadContext(hThread, &ctxt);
|
|
if (bStatus == 0) {Abort("GetThreadContext(0) failed");}
|
|
|
|
// Set IP = TerminateThisThread
|
|
#if defined(TARGET_IA32)
|
|
ctxt.Eip = reinterpret_cast<DWORD>(TerminateThisThread);
|
|
#elif defined(TARGET_IA32E)
|
|
// We do not want to change Rsp of the suspended system call.
|
|
// It is not guarantied that system will accept this change.
|
|
// On the other hand, we need Rsp properly aligned to execute
|
|
// TerminateThisThread. So, we check alignment and it is not
|
|
// good for us, just skip the test.
|
|
if (ctxt.Rsp % 16 == 8)
|
|
{
|
|
ctxt.Rip = reinterpret_cast<DWORD64>(TerminateThisThread);
|
|
}
|
|
else
|
|
{
|
|
result = -1;
|
|
}
|
|
#endif
|
|
|
|
bStatus = SetThreadContext(hThread, &ctxt);
|
|
if (bStatus == 0) {Abort("SetThreadContext failed");}
|
|
|
|
// Start the thread. It should exit with the zero status.
|
|
suspendCount = ResumeThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread failed");}
|
|
|
|
// Wait for the thread exit and check the exit code.
|
|
dwStatus = WaitForSingleObject(hThread, 60000);
|
|
if (dwStatus != WAIT_OBJECT_0) {Abort("The thread did not exit");}
|
|
|
|
if (result != -1)
|
|
{
|
|
DWORD exitCode = 1;
|
|
bStatus = GetExitCodeThread(hThread,&exitCode);
|
|
if (bStatus == 0) {Abort("GetExitCodeThread failed");}
|
|
|
|
result = ((exitCode == 0) ? 0 : 1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/*!
|
|
* 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);
|
|
|
|
fpNtTerminateThread = (NtTerminateThread_T *)GetProcAddress(
|
|
GetModuleHandle("ntdll.dll"),
|
|
"NtTerminateThread");
|
|
if (fpNtTerminateThread == 0) {Abort("NtTerminateThread function is not found");}
|
|
|
|
//============================================================================
|
|
// A) Verify that Pin correctly gets/sets context for threads suspended in the
|
|
// code cache.
|
|
//============================================================================
|
|
StartFunctionTest("A");
|
|
|
|
// Create two threads each of which assigns its own thread ID to the specified
|
|
// variable: targ[0] = tid[0] and targ[1] = tid[1]. Suspend threads on entry
|
|
// and exchange thread contexts. After both threads write their IDs, suspend
|
|
// threads and exchange their contexts again. When threads exit, their IDs
|
|
// should appear in opposite order: targ[0] = tid[1] and targ[1] = tid[0].
|
|
|
|
{
|
|
HANDLE hThread[2];
|
|
DWORD tid[2];
|
|
volatile DWORD targ[2] = {0, 0};
|
|
|
|
ResetEvent(AllowStartEvent);
|
|
|
|
hThread[0] = CreateThread(0, 0, ThreadA, (void *)&(targ[0]), 0, 0);
|
|
if (hThread[0] == 0) {Abort("CreateThread[0] failed");}
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
|
|
hThread[1] = CreateThread(0, 0, ThreadA, (void *)&(targ[1]), 0, 0);
|
|
if (hThread[1] == 0) {Abort("CreateThread[1] failed");}
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
|
|
// Start the threads
|
|
SetEvent(AllowStartEvent);
|
|
|
|
// Wait until both threads pass their IDs
|
|
while ((targ[0] == 0) || (targ[1] == 0)) {Sleep(5);}
|
|
tid[0] = targ[0];
|
|
tid[1] = targ[1];
|
|
|
|
// Suspend threads and switch their contexts
|
|
ExchangeContexts(hThread[0], hThread[1]);
|
|
|
|
targ[0] = targ[1] = 0;
|
|
PhaseOfThreadA = 1;
|
|
|
|
// Resume threads and wait until they pass IDs again
|
|
suspendCount = ResumeThread(hThread[0]);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread(0) failed");}
|
|
suspendCount = ResumeThread(hThread[1]);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread(1) failed");}
|
|
|
|
while ((targ[0] == 0) || (targ[1] == 0)) {Sleep(5);}
|
|
|
|
// Suspend threads and switch their contexts back. This is needed
|
|
// because the thread exit procedure may check consistency of the
|
|
// stack register and the TEB stack bounds.
|
|
ExchangeContexts(hThread[0], hThread[1]);
|
|
|
|
// Allow both threads to exit
|
|
PhaseOfThreadA = 2;
|
|
|
|
// Resume threads to let them exit
|
|
suspendCount = ResumeThread(hThread[0]);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread(0) failed");}
|
|
suspendCount = ResumeThread(hThread[1]);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread(1) failed");}
|
|
|
|
dwStatus = WaitForMultipleObjects(2, hThread, TRUE, 60000);
|
|
if (dwStatus == WAIT_TIMEOUT)
|
|
{
|
|
Abort("At least one thread did not exit");
|
|
}
|
|
else if (dwStatus == WAIT_FAILED)
|
|
{
|
|
Abort("WaitForMultipleObjects failed");
|
|
}
|
|
|
|
CloseHandle(hThread[0]);
|
|
CloseHandle(hThread[1]);
|
|
|
|
// Check to see that <targ> and <tid> arrays have reverse order
|
|
if ((targ [0] != tid[1]) || (targ [1] != tid[0]))
|
|
{
|
|
Abort("Context exchange failed");
|
|
}
|
|
}
|
|
|
|
#ifndef SKIP_XMM_TEST_CASE
|
|
//============================================================================
|
|
// B) Verify that Pin correctly reads/writes XMM state of threads suspended in
|
|
// a system call.
|
|
//============================================================================
|
|
StartFunctionTest("B");
|
|
|
|
// Suspend a thread in a system call and set some predefined XMM state for
|
|
// the suspended thread. Read the context and check the state of XMM registers.
|
|
{
|
|
// The "predefined" XMM state
|
|
const size_t xmmStateSize = XmmRegSize*NumXmmRegs;
|
|
char xmmState[xmmStateSize];
|
|
memset(xmmState, 1, xmmStateSize);
|
|
|
|
CONTEXT ctxt, ctxt1, ctxt2;
|
|
|
|
// Create a thread and wait until it hangs in a system call.
|
|
ResetEvent(AllowStartEvent);
|
|
hThread = CreateThread(0, 0, ThreadB, 0, 0, 0);
|
|
if (hThread == 0) {Abort("CreateThread failed");}
|
|
WaitForSingleObject(StartEvent, INFINITE);
|
|
|
|
// Suspend the thread in a system call.
|
|
suspendCount = SuspendThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread failed");}
|
|
if (suspendCount != 0) {Abort("Unexpected suspend count in SuspendThread");}
|
|
|
|
// Read the original XMM state
|
|
ctxt.ContextFlags = XMM_CONTEXT_FLAG;
|
|
bStatus = GetThreadContext(hThread, &ctxt);
|
|
if (bStatus == 0) {Abort("GetThreadContext failed");}
|
|
|
|
// Set the "predefined" state for XMM registers
|
|
ctxt1 = ctxt;
|
|
memcpy((char *)(&ctxt1) + XmmRegsOffset, xmmState, xmmStateSize);
|
|
bStatus = SetThreadContext(hThread, &ctxt1);
|
|
if (bStatus == 0) {Abort("SetThreadContext failed");}
|
|
|
|
// Read and check the state for XMM registers
|
|
ctxt2.ContextFlags = XMM_CONTEXT_FLAG;
|
|
bStatus = GetThreadContext(hThread, &ctxt2);
|
|
if (bStatus == 0) {Abort("GetThreadContext failed");}
|
|
if (!CompareXmmState(&ctxt1, &ctxt2))
|
|
{
|
|
Abort("Mismatch in the XMM state");
|
|
}
|
|
|
|
// Restore the original XMM state
|
|
bStatus = SetThreadContext(hThread, &ctxt);
|
|
if (bStatus == 0) {Abort("SetThreadContext failed");}
|
|
|
|
// Let the thread exit
|
|
SetEvent(AllowStartEvent);
|
|
suspendCount = ResumeThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("ResumeThread failed");}
|
|
dwStatus = WaitForSingleObject(hThread, 60000);
|
|
if (dwStatus != WAIT_OBJECT_0) {Abort("The thread did not exit");}
|
|
|
|
CloseHandle(hThread);
|
|
}
|
|
#endif
|
|
|
|
//============================================================================
|
|
// C) Verify that Pin correctly changes IP register for threads suspended in the
|
|
// a system call.
|
|
//============================================================================
|
|
StartFunctionTest("C");
|
|
|
|
// Create a thread that blocks itself in a system call. Suspend the thread and
|
|
// change its IP register, so after resumption the thread executes a procedure
|
|
// (TerminateThisThread) that terminates the thread. If context change
|
|
// succeeded, the exit status of the thread should be zero. Otherwise, in case
|
|
// of failure the thread returns with a non-zero status.
|
|
|
|
{
|
|
ResetEvent(AllowStartEvent);
|
|
hThread = CreateThread(0, 0, ThreadC, 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 in a system call.
|
|
suspendCount = SuspendThread(hThread);
|
|
if (suspendCount == DWORD(-1)) {Abort("SuspendThread failed");}
|
|
if (suspendCount != 0) {Abort("Unexpected suspend count in SuspendThread");}
|
|
SetEvent(AllowStartEvent);
|
|
|
|
int result = TerminateThreadCAndCheckStatus(hThread);
|
|
if (result == 1) {Abort("Unexpected thread exit code");}
|
|
if (result == -1) {SkipFunctionTest("The test can not be performed in this configuration");}
|
|
}
|
|
|
|
//============================================================================
|
|
// D) Similar to C), but instead of suspension in a system call, create the
|
|
// thread in the suspended state (initially suspended).
|
|
//============================================================================
|
|
StartFunctionTest("D");
|
|
|
|
{
|
|
ResetEvent(AllowStartEvent);
|
|
hThread = CreateThread(0, 0, ThreadC, 0, CREATE_SUSPENDED, 0);
|
|
if (hThread == 0) {Abort("CreateThread failed");}
|
|
|
|
int result = TerminateThreadCAndCheckStatus(hThread);
|
|
if (result == 1) {Abort("Unexpected thread exit code");}
|
|
if (result == -1) {SkipFunctionTest("The test can not be performed in this configuration");}
|
|
}
|
|
|
|
//============================================================================
|
|
ExitUnitTest();
|
|
|
|
return 0;
|
|
}
|