From 1bc12ba2b3bc2c4fbe8fad4797cf33266eb13f4b Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Thu, 23 Jul 2020 00:34:43 +0100 Subject: Streamline startup sequence * Clean up cRoot & main * Move some OS-specifics into OSSupport --- src/OSSupport/CMakeLists.txt | 2 + src/OSSupport/MiniDumpWriter.h | 139 +++++++++++++++++++++++++++++++++ src/OSSupport/StartAsService.h | 173 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 src/OSSupport/MiniDumpWriter.h create mode 100644 src/OSSupport/StartAsService.h (limited to 'src/OSSupport') diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 1b853c202..42ce6934d 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -28,12 +28,14 @@ target_sources( HostnameLookup.h IPLookup.h IsThread.h + MiniDumpWriter.h Network.h NetworkLookup.h NetworkSingleton.h Queue.h ServerHandleImpl.h StackTrace.h + StartAsService.h TCPLinkImpl.h UDPEndpointImpl.h WinStackWalker.h diff --git a/src/OSSupport/MiniDumpWriter.h b/src/OSSupport/MiniDumpWriter.h new file mode 100644 index 000000000..e5cd1a0ac --- /dev/null +++ b/src/OSSupport/MiniDumpWriter.h @@ -0,0 +1,139 @@ + +#pragma once + + + + + +/** Flags to control minidump contents on supported platforms. */ +enum class MiniDumpFlags +{ + WithDataSegments, + WithFullMemory +}; + + + + + +#if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER) // 32-bit Windows app compiled in MSVC + +#include + + + + + +/** Windows 32-bit stuff: +When the server crashes, create a "dump file" containing the callstack of each thread and some variables; +let the user send us that crash file for analysis */ +class MiniDumpWriter +{ + typedef BOOL(WINAPI *pMiniDumpWriteDump)( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + +public: + + MiniDumpWriter() + { + // Magic code to produce dump-files on Windows if the server crashes: + + m_DbgHelp = LoadLibrary(L"DBGHELP.DLL"); + if (m_DbgHelp == INVALID_HANDLE_VALUE) + { + return; + } + + s_WriteMiniDump = (pMiniDumpWriteDump)GetProcAddress(m_DbgHelp, "MiniDumpWriteDump"); + if (s_WriteMiniDump != nullptr) + { + ASSERT(swprintf(s_DumpFileName, ARRAYCOUNT(s_DumpFileName), L"crash_mcs_%x.dmp", GetCurrentProcessId()) > 0); + SetUnhandledExceptionFilter(LastChanceExceptionFilter); + } + + // End of dump-file magic + } + + void AddDumpFlags(const MiniDumpFlags a_Flags) + { + switch (a_Flags) + { + case MiniDumpFlags::WithDataSegments: + { + s_DumpFlags = static_cast(s_DumpFlags | MINIDUMP_TYPE::MiniDumpWithDataSegs); + break; + } + case MiniDumpFlags::WithFullMemory: + { + s_DumpFlags = static_cast(s_DumpFlags | MINIDUMP_TYPE::MiniDumpWithFullMemory); + break; + } + } + } + + ~MiniDumpWriter() + { + FreeLibrary(m_DbgHelp); + } + +private: + + /** This function gets called just before the "program executed an illegal instruction and will be terminated" or similar. + Its purpose is to create the crashdump using the dbghlp DLLs */ + static LONG WINAPI LastChanceExceptionFilter(__in struct _EXCEPTION_POINTERS * a_ExceptionInfo) + { + char * newStack = &s_ExceptionStack[sizeof(s_ExceptionStack) - 1]; + char * oldStack; + + // Use the substitute stack: + // This code is the reason why we don't support 64-bit (yet) + _asm + { + mov oldStack, esp + mov esp, newStack + } + + MINIDUMP_EXCEPTION_INFORMATION ExcInformation; + ExcInformation.ThreadId = GetCurrentThreadId(); + ExcInformation.ExceptionPointers = a_ExceptionInfo; + ExcInformation.ClientPointers = 0; + + // Write the dump file: + HANDLE dumpFile = CreateFile(s_DumpFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + s_WriteMiniDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, s_DumpFlags, (a_ExceptionInfo) ? &ExcInformation : nullptr, nullptr, nullptr); + CloseHandle(dumpFile); + + // Revert to old stack: + _asm + { + mov esp, oldStack + } + + return 0; + } + + HINSTANCE m_DbgHelp; + + static inline pMiniDumpWriteDump s_WriteMiniDump; // The function in dbghlp DLL that creates dump files + static inline wchar_t s_DumpFileName[MAX_PATH]; // Filename of the dump file; hes to be created before the dump handler kicks in + static inline char s_ExceptionStack[128 * 1024]; // Substitute stack, just in case the handler kicks in because of "insufficient stack space" + static inline MINIDUMP_TYPE s_DumpFlags = MiniDumpNormal; // By default dump only the stack and some helpers +}; + +#else + +struct MiniDumpWriter +{ + void AddDumpFlags(const MiniDumpFlags) + { + } +}; + +#endif diff --git a/src/OSSupport/StartAsService.h b/src/OSSupport/StartAsService.h new file mode 100644 index 000000000..472184367 --- /dev/null +++ b/src/OSSupport/StartAsService.h @@ -0,0 +1,173 @@ + +#pragma once + + + + + +#ifdef _WIN32 + +#include + + + + + +class cStartAsService +{ +public: + + /** Make a Windows service. */ + template + static bool MakeIntoService() + { + SERVICE_TABLE_ENTRY ServiceTable[] = + { + { g_ServiceName, (LPSERVICE_MAIN_FUNCTION)serviceMain }, + { nullptr, nullptr } + }; + + if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) + { + throw std::system_error(GetLastError(), std::system_category()); + } + + return true; + } + +private: + + /** Set the internal status of the service */ + static void serviceSetState(DWORD acceptedControls, DWORD newState, DWORD exitCode) + { + SERVICE_STATUS serviceStatus = {}; + serviceStatus.dwCheckPoint = 0; + serviceStatus.dwControlsAccepted = acceptedControls; + serviceStatus.dwCurrentState = newState; + serviceStatus.dwServiceSpecificExitCode = 0; + serviceStatus.dwServiceType = SERVICE_WIN32; + serviceStatus.dwWaitHint = 0; + serviceStatus.dwWin32ExitCode = exitCode; + + if (SetServiceStatus(g_StatusHandle, &serviceStatus) == FALSE) + { + LOGERROR("SetServiceStatus() failed\n"); + } + } + + /** Handle stop events from the Service Control Manager */ + static void WINAPI serviceCtrlHandler(DWORD CtrlCode) + { + if (CtrlCode == SERVICE_CONTROL_STOP) + { + std::raise(SIGINT); + serviceSetState(0, SERVICE_STOP_PENDING, 0); + } + } + + /* Startup logic for running as a service */ + template + static void WINAPI serviceMain(DWORD argc, TCHAR *argv[]) + { + wchar_t applicationFilename[MAX_PATH]; + wchar_t applicationDirectory[MAX_PATH]; + + // Get this binary's file path: + if (GetModuleFileName(nullptr, applicationFilename, std::size(applicationFilename)) == 0) + { + serviceSetState(0, SERVICE_STOPPED, GetLastError()); + return; + } + + const auto LastComponent = wcsrchr(applicationFilename, L'\\'); + if (LastComponent == nullptr) + { + serviceSetState(0, SERVICE_STOPPED, E_UNEXPECTED); + return; + } + + const auto LengthToLastComponent = LastComponent - applicationFilename; + + // Strip off the filename, keep only the path: + wcsncpy(applicationDirectory, applicationFilename, LengthToLastComponent); + applicationDirectory[LengthToLastComponent] = L'\0'; // Make sure new path is null terminated + + // Services are run by the SCM, and inherit its working directory - usually System32. + // Set the working directory to the same location as the binary. + if (SetCurrentDirectory(applicationDirectory) == FALSE) + { + serviceSetState(0, SERVICE_STOPPED, GetLastError()); + return; + } + + + g_StatusHandle = RegisterServiceCtrlHandler(g_ServiceName, serviceCtrlHandler); + if (g_StatusHandle == nullptr) + { + OutputDebugStringA("RegisterServiceCtrlHandler() failed\n"); + serviceSetState(0, SERVICE_STOPPED, GetLastError()); + return; + } + + serviceSetState(SERVICE_ACCEPT_STOP, SERVICE_RUNNING, 0); + + char MultibyteArgV0[MAX_PATH]; + char * MultibyteArgV[] = { MultibyteArgV0 }; + + const auto OutputSize = std::size(MultibyteArgV0); + const auto TranslateResult = wcstombs(MultibyteArgV0, argv[0], OutputSize); + + if (TranslateResult == static_cast(-1)) + { + // Translation failed entirely (!): + MultibyteArgV0[0] = '\0'; + } + else if (TranslateResult == OutputSize) + { + // Output too small: + MultibyteArgV0[OutputSize - 1] = '\0'; + } + + const auto Result = MainFunction(1, MultibyteArgV, true); + const auto Return = (Result == EXIT_SUCCESS) ? S_OK : E_FAIL; + + serviceSetState(0, SERVICE_STOPPED, Return); + } + + static inline SERVICE_STATUS_HANDLE g_StatusHandle = nullptr; + static inline HANDLE g_ServiceThread = INVALID_HANDLE_VALUE; + static inline wchar_t g_ServiceName[] = L"Cuberite"; +}; + +#else + +struct cStartAsService +{ + /** Make a UNIX daemon. */ + template + static bool MakeIntoService() + { + pid_t pid = fork(); + + // fork() returns a negative value on error. + if (pid < 0) + { + throw std::system_error(errno, std::system_category()); + } + + // Check if we are the parent or child process. Parent stops here. + if (pid > 0) + { + return true; + } + + // Child process now goes quiet, running in the background. + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + return false; + } +}; + +#endif -- cgit v1.2.3