Where to put your code
When you use the functions to load and unload Thread Validator from your service, it is important that you put the function calls in the correct place in your software.
The correct place to put them is in a 'balanced' location, such that you would expect no memory leaks to occur between the load and the unload function call, assuming the service was working correctly.
Typically, this means that Thread Validator is:
•loaded as the first action in the service_main() function
•unloaded just before the service control manager is informed of the stopped status
The source code shown below shows an example service_main() function used in a service, demonstrating where to load and unload Thread Validator.
The long comment covers problems with the way services are stopped and what may be displayed in a debugger if this happens.
The code is extracted from service\service.cpp, part of the full example of an NT service, client and a utility for controlling whether the service uses Thread Validator.
void serviceCallback(void *userParam)
{
// just tell the Service Control Manager that we are still busy
// in this example userParam is not used
static DWORD dwCheckPoint = 1;
ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ssStatus.dwServiceSpecificExitCode = 0;
ssStatus.dwControlsAccepted = 0;
ssStatus.dwCurrentState = dwCurrentState;
ssStatus.dwWin32ExitCode = dwWin32ExitCode;
ssStatus.dwWaitHint = dwWaitHint;
ssStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the service control manager.
return SetServiceStatus(sshStatusHandle, &ssStatus);
}
//-NAME---------------------------------
// service_main
//.DESCRIPTION..........................
//
// Initializes the service, then calls the function to do the work.
// This function is typically where you will load and unload Thread Validator
//
//.PARAMETERS...........................
//
// dwArgc - number of command line arguments
// lpszArgv - array of command line arguments
//
//.RETURN.CODES.........................
//--------------------------------------
static CRITICAL_SECTION tvExampleCritSect;
void WINAPI service_main(DWORD dwArgc,
LPTSTR *lpszArgv)
{
if (bLogging)
{
svlTVStub_setLogFileName(SZLOGFILENAME);
svlTVStub_deleteLogFile();
}
InitializeCriticalSection(&tvExampleCritSect);
InitializeCriticalSection(&testCritSect);
// register our service control handler:
sshStatusHandle = RegisterServiceCtrlHandler(TEXT(SZSERVICENAME), service_ctrl);
if (sshStatusHandle != 0)
{
DWORD dwErr = 0;
// **TV_EXAMPLE** start
if (bThreadValidator)
{
// load Thread Validator (but if monitoring a 32 bit service with Thread Validator x64 use svlTVStub_LoadThreadValidator6432())
if (bLogging)
{
svlTVStub_writeToLogFileW(_T("About to load Thread Validator\r\n"));
}
SVL_SERVICE_ERROR errCode;
#ifdef IS6432
// x86 with x64 GUI
errCode = svlTVStub_LoadThreadValidator6432();
#else //#ifdef IS6432
// x86 with x86 GUI
// x64 with x64 GUI
errCode = svlTVStub_LoadThreadValidator();
#endif //#ifdef IS6432
if (bLogging)
{
if (errCode != SVL_OK)
{
DWORD lastError;
lastError = GetLastError();
svlTVStub_writeToLogFileW(_T("Thread Validator load failed. \r\n"));
svlTVStub_writeToLogFileLastError(lastError);
svlTVStub_writeToLogFile(errCode);
svlTVStub_dumpPathToLogFile();
}
else
{
svlTVStub_writeToLogFileW(_T("Thread Validator load success. \r\n"));
}
}
// setup a service callback so that the Service Control Manager knows the service
// is starting up even if instrumentation takes longer than 10 seconds (which it will
// for a non-trivial application)
if (bLogging)
svlTVStub_writeToLogFileW(_T("Setting service callback Thread Validator\r\n"));
errCode = svlTVStub_SetServiceCallback(serviceCallback, // the callback
NULL); // some user data (we don't have any, so set NULL)
if (bLogging)
{
if (errCode != SVL_OK)
{
svlTVStub_writeToLogFileW(_T("Setting service callback failed. \r\n"));
svlTVStub_writeToLogFile(errCode);
}
svlTVStub_writeToLogFileW(_T("Starting Thread Validator\r\n"));
}
errCode = svlTVStub_StartThreadValidator();
if (bLogging)
{
if (errCode != SVL_OK)
{
DWORD lastError;
lastError = GetLastError();
svlTVStub_writeToLogFileW(_T("Starting Thread Validator failed. \r\n"));
svlTVStub_writeToLogFileLastError(lastError);
svlTVStub_writeToLogFile(errCode);
}
svlTVStub_writeToLogFileW(_T("Finished loading Thread Validator\r\n"));
}
}
else
{
if (bLogging)
svlTVStub_writeToLogFileW(_T("Not using Thread Validator, DLL will not be loaded\r\n"));
}
// **TV_EXAMPLE** end
// SERVICE_STATUS members that don't change in example
ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ssStatus.dwServiceSpecificExitCode = 0;
// report the status to the service control manager.
if (ReportStatusToSCMgr(SERVICE_START_PENDING, // service state
NO_ERROR, // exit code
3000)) // wait hint
{
// deliberately enter a critical section so that we can see that with Thread Validator
EnterCriticalSection(&tvExampleCritSect);
// do work
dwErr = ServiceStart(dwArgc, lpszArgv);
// finished doing work
LeaveCriticalSection(&tvExampleCritSect);
}
// **TV_EXAMPLE** start
if (bThreadValidator)
{
// unload Thread Validator here
// IMPORTANT.
// Because of the way services work, you can find that this thread which is trying to gracefully unload
// ThreadValidator is ripped from under you by the operating system. This prevents Thread Validator from
// removing all its hooks successfully. If Thread Validator does not remove all of its hooks successfully
// because this happens, then you may get a crash when the service stops.
//
// An alternative fix is to spawn another thread which then unloads Thread Validator.
// See the code for ServiceStop() for comments relating to this.
//
// A callstack for such a crash is shown below. If you see this type of crash you need to put you code to
// unload Thread Validator somewhere else. The stack trace may be different, but a fundamental point is the
// code calling through doexit(), exit() and ExitProcess()
//
//NTDLL! 77f64e70()
//SVLTHREADVALIDATORSTUB!
//MSVCRT! 78001436()
//MSVCRT! 7800578c()
//DBGHELP! 6d55da25()
//DBGHELP! 6d55de83()
//DBGHELP! 6d53705d()
//DBGHELP! 6d51cc69()
//DBGHELP! 6d51f6e8()
//DBGHELP! 6d524ebf()
//DBGHELP! 6d52a7b0()
//DBGHELP! 6d52b00a()
//DBGHELP! 6d526487()
//DBGHELP! 6d5264d7()
//DBGHELP! 6d5264f7()
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//MSVCRT! 78001436()
//MSVCRT! 780057db()
//KERNEL32! 77f19fdb()
//SVLTHREADVALIDATORSTUB! ExitProcess hook
//doexit(int 0x00000000, int 0x00000000, int 0x00000000) line 392
//exit(int 0x00000000) line 279 + 13 bytes
//mainCRTStartup() line 345
//KERNEL32! 77f1b9ea()
svlTVStub_UnloadThreadValidator();
}
// **TV_EXAMPLE** end
// try to report the stopped status to the service control manager.
(VOID)ReportStatusToSCMgr(SERVICE_STOPPED, dwErr, 0);
}
DeleteCriticalSection(&testCritSect);
DeleteCriticalSection(&tvExampleCritSect);
return;
}
|
|
void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv)
{
// load Thread Validator here
svlTVStub_LoadThreadValidator();
// register our service control handler:
sshStatusHandle = RegisterServiceCtrlHandler(TEXT(SZSERVICENAME), service_ctrl);
// SERVICE_STATUS members that don't change in example
ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ssStatus.dwServiceSpecificExitCode = 0;
// report the status to the service control manager.
if (!ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000))
{
if (sshStatusHandle)
ReportStatusToSCMgr(SERVICE_STOPPED, dwErr, 0);
}
// do the work of the service
doWork(dwArgc, lpszArgv);
// unload Thread Validator here
// IMPORTANT.
// Because of the way services work, you can find that this thread which is trying to gracefully unload
// ThreadValidator is ripped from under you by the operating system. This prevents Thread Validator from
// removing all its hooks successfully. If Thread Validator does not remove all of its hooks successfully
// because this happens, then you may get a crash when the service stops.
//
// A callstack for such a crash is shown below. If you see this type of crash you need to put your code to
// unload Thread Validator somewhere else. The stack trace may be different, but a fundamental point is the
// code calling through doexit(), exit() and ExitProcess()
//
//NTDLL! 77f64e70()
//SVLTHREADVALIDATORSTUB!
//MSVCRT! 78001436()
//MSVCRT! 7800578c()
//DBGHELP! 6d55da25()
//DBGHELP! 6d55de83()
//DBGHELP! 6d53705d()
//DBGHELP! 6d51cc69()
//DBGHELP! 6d51f6e8()
//DBGHELP! 6d524ebf()
//DBGHELP! 6d52a7b0()
//DBGHELP! 6d52b00a()
//DBGHELP! 6d526487()
//DBGHELP! 6d5264d7()
//DBGHELP! 6d5264f7()
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//SVLTHREADVALIDATORSTUB!
//MSVCRT! 78001436()
//MSVCRT! 780057db()
//KERNEL32! 77f19fdb()
//SVLTHREADVALIDATORSTUB! ExitProcess hook
//doexit(int 0x00000000, int 0x00000000, int 0x00000000) line 392
//exit(int 0x00000000) line 279 + 13 bytes
//mainCRTStartup() line 345
//KERNEL32! 77f1b9ea()
svlTVStub_UnloadThreadValidator();
// try to report the stopped status to the service control manager.
if (sshStatusHandle)
ReportStatusToSCMgr(SERVICE_STOPPED, dwErr, 0);
// tried putting the call to svlTVStub_UnloadThreadValidator(); here but often the thread
// was pulled from under it by the operating system
return;
}