// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved. #if LC_VERSION == 1 // BEGIN EPIC MOD //#include PCH_INCLUDE // END EPIC MOD #include "LC_Process.h" #include "LC_Memory.h" #include "LC_PointerUtil.h" #include "LC_VirtualMemory.h" #include "LC_WindowsInternalFunctions.h" #include "LC_Thread.h" // BEGIN EPIC MOD #include "LC_Logging.h" #include #include "Containers/Map.h" #include "Containers/UnrealString.h" // END EPIC MOD // BEGIN EPIC MODS #pragma warning(push) #pragma warning(disable:6011) // warning C6011: Dereferencing NULL pointer 'processInfo'. #pragma warning(disable:6335) // warning C6335: Leaking process information handle 'context->pi.hProcess'. // END EPIC MODS namespace Process { struct Context { uint32_t flags; HANDLE pipeReadEnd; PROCESS_INFORMATION processInformation; Thread::Handle drainStdoutThread; std::wstring stdoutData; }; } void* Process::Current::GetBase(void) { return ::GetModuleHandle(NULL); } Process::Handle Process::Current::GetHandle(void) { return Process::Handle(::GetCurrentProcess()); } Process::Id Process::Current::GetId(void) { return Process::Id(::GetCurrentProcessId()); } Filesystem::Path Process::Current::GetImagePath(void) { wchar_t filename[Filesystem::Path::CAPACITY] = { '\0' }; ::GetModuleFileNameW(NULL, filename, Filesystem::Path::CAPACITY); return Filesystem::Path(filename); } Filesystem::Path Process::Current::GetWorkingDirectory(void) { wchar_t workingDirectory[Filesystem::Path::CAPACITY] = { '\0' }; ::GetCurrentDirectoryW(Filesystem::Path::CAPACITY, workingDirectory); return Filesystem::Path(workingDirectory); } std::wstring Process::Current::GetCommandLine(void) { return std::wstring(::GetCommandLineW()); } Process::Id Process::GetId(const Context* context) { return Process::Id(context->processInformation.dwProcessId); } Process::Handle Process::GetHandle(const Context* context) { return Process::Handle(context->processInformation.hProcess); } std::wstring Process::GetStdOutData(const Context* context) { return context->stdoutData; } namespace { static Thread::ReturnValue DrainPipe(Process::Context* context) { std::vector stdoutData; stdoutData.reserve(1024u); char buffer[1024u]; for (;;) { DWORD bytesRead = 0u; if (!::ReadFile(context->pipeReadEnd, buffer, sizeof(buffer) - 1u, &bytesRead, NULL)) { // error while trying to read from the pipe, process has probably ended and closed its end of the pipe const DWORD error = ::GetLastError(); if (error == ERROR_BROKEN_PIPE) { // this is expected break; } LC_ERROR_USER("Error 0x%X while reading from pipe", error); break; } stdoutData.insert(stdoutData.end(), buffer, buffer + bytesRead); } // convert stdout data to UTF16 if (stdoutData.size() > 0u) { // cl.exe and link.exe write to stdout using the OEM codepage const int sizeNeeded = ::MultiByteToWideChar(CP_OEMCP, 0, stdoutData.data(), static_cast(stdoutData.size()), NULL, 0); wchar_t* strTo = new wchar_t[static_cast(sizeNeeded)]; ::MultiByteToWideChar(CP_OEMCP, 0, stdoutData.data(), static_cast(stdoutData.size()), strTo, sizeNeeded); context->stdoutData.assign(strTo, static_cast(sizeNeeded)); delete[] strTo; } return Thread::ReturnValue(0u); } } Process::Context* Process::Spawn(const wchar_t* exePath, const wchar_t* workingDirectory, const wchar_t* commandLine, const void* environmentBlock, uint32_t flags) { Context* context = new Context { flags, nullptr, ::PROCESS_INFORMATION {}, Thread::Handle(nullptr), {} }; ::SECURITY_ATTRIBUTES saAttr; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); // BEGIN EPIC MOD saAttr.bInheritHandle = Windows::TRUE; // END EPIC MOD saAttr.lpSecurityDescriptor = NULL; ::STARTUPINFOW startupInfo = {}; startupInfo.cb = sizeof(startupInfo); // BEGIN EPIC MOD Windows::HANDLE hProcessStdOutRead = NULL; Windows::HANDLE hProcessStdOutWrite = NULL; Windows::HANDLE hProcessStdErrWrite = NULL; // END EPIC MOD if (flags & SpawnFlags::REDIRECT_STDOUT) { // create a STD_OUT pipe for the child process if (!::CreatePipe(&hProcessStdOutRead, &hProcessStdOutWrite, &saAttr, 0)) { LC_ERROR_USER("Cannot create stdout pipe. Error: 0x%X", ::GetLastError()); delete context; return nullptr; } // create a duplicate of the STD_OUT write handle for the STD_ERR write handle. this is necessary in case the child // application closes one of its STD output handles. // BEGIN EPIC MOD if (!::DuplicateHandle(::GetCurrentProcess(), hProcessStdOutWrite, ::GetCurrentProcess(), &hProcessStdErrWrite, 0, Windows::TRUE, DUPLICATE_SAME_ACCESS)) // END EPIC MOD { LC_ERROR_USER("Cannot duplicate stdout pipe. Error: 0x%X", ::GetLastError()); ::CloseHandle(hProcessStdOutRead); ::CloseHandle(hProcessStdOutWrite); delete context; return nullptr; } // the spawned process will output data into the write-end of the pipe, and our process will read from the // read-end. because pipes can only do some buffering, we need to ensure that pipes never get clogged, otherwise // the spawned process could block due to the pipe being full. // therefore, we also create a new thread that continuously reads data from the pipe on our end. context->pipeReadEnd = hProcessStdOutRead; context->drainStdoutThread = Thread::CreateFromFunction("DrainStdoutPipe", 16u * 1024u, &DrainPipe, context); startupInfo.hStdOutput = hProcessStdOutWrite; startupInfo.hStdError = hProcessStdErrWrite; startupInfo.dwFlags = STARTF_USESTDHANDLES; } wchar_t* commandLineBuffer = nullptr; if (commandLine) { commandLineBuffer = new wchar_t[32768]; ::wcscpy_s(commandLineBuffer, 32768u, commandLine); } LC_LOG_DEV("%s", "Spawning process:"); { LC_LOG_INDENT_DEV; LC_LOG_DEV("Executable: %S", exePath); LC_LOG_DEV("Command line: %S", commandLineBuffer ? commandLineBuffer : L"none"); LC_LOG_DEV("Working directory: %S", workingDirectory ? workingDirectory : L"none"); LC_LOG_DEV("Custom environment block: %S", environmentBlock ? L"yes" : L"no"); LC_LOG_DEV("Flags: %u", flags); } DWORD creationFlags = CREATE_UNICODE_ENVIRONMENT; if (flags & SpawnFlags::NO_WINDOW) { creationFlags |= CREATE_NO_WINDOW; } if (flags & SpawnFlags::SUSPENDED) { creationFlags |= CREATE_SUSPENDED; } // the environment block is not written to by CreateProcess, so it is safe to const_cast (it's a Win32 API mistake) // BEGIN EPIC MOD const BOOL success = ::CreateProcessW(exePath, commandLineBuffer, NULL, NULL, Windows::TRUE, creationFlags, const_cast(environmentBlock), workingDirectory, &startupInfo, &context->processInformation); // END EPIC MOD if (success == 0) { LC_ERROR_USER("Could not spawn process %S. Error: %d", exePath, ::GetLastError()); } delete[] commandLineBuffer; if (flags & SpawnFlags::REDIRECT_STDOUT) { // we don't need those ends of the pipe ::CloseHandle(hProcessStdOutWrite); ::CloseHandle(hProcessStdErrWrite); } return context; } void Process::Destroy(Context*& context) { ::CloseHandle(context->processInformation.hProcess); ::CloseHandle(context->processInformation.hThread); memory::DeleteAndNull(context); } void Process::ResumeMainThread(Context* context) { ::ResumeThread(context->processInformation.hThread); } unsigned int Process::Wait(Context* context) { // wait until process terminates ::WaitForSingleObject(context->processInformation.hProcess, INFINITE); if (context->flags & SpawnFlags::REDIRECT_STDOUT) { // wait until all data is drained from the pipe Thread::Join(context->drainStdoutThread); Thread::Close(context->drainStdoutThread); // close remaining pipe handles ::CloseHandle(context->pipeReadEnd); } DWORD exitCode = 0xFFFFFFFFu; ::GetExitCodeProcess(context->processInformation.hProcess, &exitCode); return exitCode; } unsigned int Process::Wait(Handle handle) { // wait until process terminates ::WaitForSingleObject(+handle, INFINITE); DWORD exitCode = 0xFFFFFFFFu; ::GetExitCodeProcess(+handle, &exitCode); return exitCode; } void Process::Terminate(Context* context) { Terminate(Process::Handle(context->processInformation.hProcess)); } void Process::Terminate(Handle handle) { ::TerminateProcess(+handle, 0u); // termination is asynchronous, wait until the process is really gone ::WaitForSingleObject(+handle, INFINITE); } Process::Handle Process::Open(Id processId) { // BEGIN EPIC MOD return Process::Handle(::OpenProcess(PROCESS_ALL_ACCESS, Windows::FALSE, +processId)); // END EPIC MOD } void Process::Close(Handle& handle) { ::CloseHandle(+handle); handle = INVALID_HANDLE; } void Process::Suspend(Context* context) { WindowsInternals::NtSuspendProcess(context->processInformation.hProcess); } void Process::Suspend(Handle handle) { WindowsInternals::NtSuspendProcess(+handle); } void Process::Resume(Context* context) { WindowsInternals::NtResumeProcess(context->processInformation.hProcess); } void Process::Resume(Handle handle) { WindowsInternals::NtResumeProcess(+handle); } bool Process::IsActive(Handle handle) { DWORD exitCode = 0u; const BOOL success = ::GetExitCodeProcess(+handle, &exitCode); if ((success != 0) && (exitCode == STILL_ACTIVE)) { return true; } // either the function has failed (because the process terminated unexpectedly) or the exit code // signals that the process exited already. return false; } bool Process::IsWoW64(Handle handle) { // a WoW64 process has a PEB32 instead of a real PEB. // if we get a meaningful pointer to this PEB32, the process is running under WoW64. ULONG_PTR peb32 = 0u; WindowsInternals::NtQueryInformationProcess(+handle, WindowsInternals::ProcessWow64Information, &peb32, sizeof(ULONG_PTR), NULL); return (peb32 != 0u); } void Process::ReadProcessMemory(Handle handle, const void* srcAddress, void* destBuffer, size_t size) { WindowsInternals::NtReadVirtualMemory(+handle, const_cast(srcAddress), destBuffer, static_cast(size), NULL); } void Process::WriteProcessMemory(Handle handle, void* destAddress, const void* srcBuffer, size_t size) { DWORD oldProtect = 0u; ::VirtualProtectEx(+handle, destAddress, size, PAGE_READWRITE, &oldProtect); { // instead of the regular WriteProcessMemory function, we use an undocumented function directly. // this is because Windows 10 introduced a performance regression that causes WriteProcessMemory to be 100 times slower (!) // than in previous versions of Windows. // this bug was reported here: // https://developercommunity.visualstudio.com/content/problem/228061/writeprocessmemory-slowdown-on-windows-10.html WindowsInternals::NtWriteVirtualMemory(+handle, destAddress, const_cast(srcBuffer), static_cast(size), NULL); } ::VirtualProtectEx(+handle, destAddress, size, oldProtect, &oldProtect); } void* Process::ScanMemoryRange(Handle handle, const void* lowerBound, const void* upperBound, size_t size, size_t alignment) { for (const void* scan = lowerBound; /* nothing */; /* nothing */) { // align address to be scanned scan = pointer::AlignTop(scan, alignment); if (pointer::Offset(scan, size) >= upperBound) { // outside of range to scan LC_ERROR_DEV("Could not find memory range that fits 0x%X bytes with alignment 0x%X in range from 0x%p to 0x%p (scan: 0x%p)", size, alignment, lowerBound, upperBound, scan); return nullptr; } else if (scan < lowerBound) { // outside of range (possible wrap-around) LC_ERROR_DEV("Could not find memory range that fits 0x%X bytes with alignment 0x%X in range from 0x%p to 0x%p (scan: 0x%p)", size, alignment, lowerBound, upperBound, scan); return nullptr; } ::MEMORY_BASIC_INFORMATION memoryInfo = {}; ::VirtualQueryEx(+handle, scan, &memoryInfo, sizeof(::MEMORY_BASIC_INFORMATION)); if ((memoryInfo.RegionSize >= size) && (memoryInfo.State == MEM_FREE)) { return memoryInfo.BaseAddress; } // keep on searching scan = pointer::Offset(memoryInfo.BaseAddress, memoryInfo.RegionSize); } } uint32_t Process::ConvertPageProtectionToExecutableProtection(uint32_t protection) { // cut off PAGE_GUARD, PAGE_NOCACHE, PAGE_WRITECOMBINE, and PAGE_REVERT_TO_FILE_MAP const uint32_t extraBits = protection & 0xFFFFFF00u; const uint32_t pageProtection = protection & 0x000000FFu; switch (pageProtection) { case PAGE_NOACCESS: case PAGE_READONLY: case PAGE_READWRITE: case PAGE_WRITECOPY: return (pageProtection << 4u) | extraBits; case PAGE_EXECUTE: case PAGE_EXECUTE_READ: case PAGE_EXECUTE_READWRITE: case PAGE_EXECUTE_WRITECOPY: default: return protection; } } void Process::MakePagesExecutable(Handle handle, void* address, size_t size) { const uint32_t pageSize = VirtualMemory::GetPageSize(); const void* endOfRegion = pointer::Offset(address, size); for (const void* scan = address; /* nothing */; /* nothing */) { ::MEMORY_BASIC_INFORMATION memoryInfo = {}; const SIZE_T bytesInBuffer = ::VirtualQueryEx(+handle, scan, &memoryInfo, sizeof(::MEMORY_BASIC_INFORMATION)); if (bytesInBuffer == 0u) { // could not query the protection, bail out break; } const uint32_t executableProtection = ConvertPageProtectionToExecutableProtection(memoryInfo.Protect); if (executableProtection != memoryInfo.Protect) { // change this page into an executable one DWORD oldProtection = 0u; ::VirtualProtectEx(+handle, memoryInfo.BaseAddress, pageSize, executableProtection, &oldProtection); } const void* endOfThisRegion = pointer::Offset(memoryInfo.BaseAddress, pageSize); if (endOfThisRegion >= endOfRegion) { // we are done break; } // keep on walking pages scan = endOfThisRegion; } } void Process::FlushInstructionCache(Handle handle, void* address, size_t size) { ::FlushInstructionCache(+handle, address, size); } Process::Environment Process::CreateEnvironment(Handle handle) { const void* processEnvironment = nullptr; // EPIC BEGIN MOD SIZE_T processEnvironmentSize = 0; // EPIC END MOD const bool isWow64 = IsWoW64(handle); if (!isWow64) { // this is either a 32-bit process running on 32-bit Windows, or a 64-bit process running on 64-bit Windows. // the environment can be retrieved directly from the process' PEB and process parameters. WindowsInternals::NT_PROCESS_BASIC_INFORMATION pbi = {}; WindowsInternals::NtQueryInformationProcess(+handle, WindowsInternals::ProcessBasicInformation, &pbi, sizeof(pbi), NULL); const WindowsInternals::NT_PEB peb = ReadProcessMemory(handle, pbi.PebBaseAddress); const WindowsInternals::RTL_USER_PROCESS_PARAMETERS parameters = ReadProcessMemory(handle, peb.ProcessParameters); processEnvironment = parameters.Environment; // EPIC BEGIN MOD processEnvironmentSize = parameters.EnvironmentSize; // EPIC END MOD } else { // this is a process running under WoW64. // we must get the environment from the PEB32 of the process, rather than the "real" PEB. ULONG_PTR peb32Wow64 = 0u; WindowsInternals::NtQueryInformationProcess(+handle, WindowsInternals::ProcessWow64Information, &peb32Wow64, sizeof(ULONG_PTR), NULL); const WindowsInternals::NT_PEB32 peb32 = ReadProcessMemory(handle, pointer::FromInteger(peb32Wow64)); const WindowsInternals::RTL_USER_PROCESS_PARAMETERS32 parameters32 = ReadProcessMemory(handle, pointer::FromInteger(peb32.ProcessParameters32)); processEnvironment = pointer::FromInteger(parameters32.Environment); // EPIC BEGIN MOD processEnvironmentSize = parameters32.EnvironmentSize; // EPIC END MOD } if (!processEnvironment) { return Environment { 0u, nullptr }; } // EPIC BEGIN MOD Environment environment = { processEnvironmentSize, ::malloc(processEnvironmentSize) }; // EPIC END MOD ReadProcessMemory(handle, processEnvironment, environment.data, environment.size); return environment; } Process::Environment Process::CreateEnvironment(Context* context) { return CreateEnvironment(Process::Handle(context->processInformation.hProcess)); } void Process::DestroyEnvironment(Environment& environment) { ::free(environment.data); environment.data = nullptr; environment.size = 0u; } // BEGIN EPIC MOD std::vector Process::EnumerateThreads(Id processId) // END EPIC MOD { // BEGIN EPIC MOD std::vector threadIds; // END EPIC MOD threadIds.reserve(256u); // 2MB should be enough for getting the process information, even on systems with high load ULONG bufferSize = 2048u * 1024u; void* processSnapshot = nullptr; WindowsInternals::NTSTATUS status = 0; do { processSnapshot = ::malloc(bufferSize); // try getting a process snapshot into the provided buffer // BEGIN EPIC MOD status = WindowsInternals::NtQuerySystemInformation.ExecNoResultCheck(WindowsInternals::SystemProcessInformation, processSnapshot, bufferSize, NULL); // END EPIC MOD if (status == STATUS_INFO_LENGTH_MISMATCH) { // buffer is too small, try again ::free(processSnapshot); processSnapshot = nullptr; bufferSize *= 2u; } else if (status < 0) { // something went wrong // BEGIN EPIC MOD - PVS is having problems dealing with + here. WindowsInternals::NtQuerySystemInformation.CheckResult(status); // write the error LC_ERROR_USER("Cannot enumerate threads in process (PID: %d)", static_cast(processId)); // END EPIC MOD ::free(processSnapshot); return threadIds; } } while (status == STATUS_INFO_LENGTH_MISMATCH); // find the process information for the given process ID { WindowsInternals::NT_SYSTEM_PROCESS_INFORMATION* processInfo = static_cast(processSnapshot); while (processInfo != nullptr) { if (processInfo->UniqueProcessId == reinterpret_cast(static_cast(+processId))) { // we found the process we're looking for break; } if (processInfo->NextEntryOffset == 0u) { // we couldn't find our process // BEGIN EPIC MOD - PVS is having problems dealing with + here. LC_ERROR_USER("Cannot enumerate threads, process not found (PID: %d)", static_cast(processId)); // END EPIC MOD ::free(processSnapshot); return threadIds; } else { // walk to the next process info processInfo = pointer::Offset(processInfo, processInfo->NextEntryOffset); } } // record all threads belonging to the given process if (processInfo) { for (ULONG i = 0u; i < processInfo->NumberOfThreads; ++i) { const DWORD threadId = static_cast(reinterpret_cast(processInfo->Threads[i].ClientId.UniqueThread)); threadIds.push_back(Thread::Id(threadId)); } } } ::free(processSnapshot); return threadIds; } // BEGIN EPIC MOD std::vector Process::EnumerateModules(Handle handle) // END EPIC MOD { // 1024 modules should be enough for most processes // BEGIN EPIC MOD std::vector modules; // END EPIC MOD modules.reserve(1024u); WindowsInternals::NT_PROCESS_BASIC_INFORMATION pbi = {}; WindowsInternals::NtQueryInformationProcess(+handle, WindowsInternals::ProcessBasicInformation, &pbi, sizeof(pbi), NULL); const WindowsInternals::NT_PEB processPEB = ReadProcessMemory(handle, pbi.PebBaseAddress); const WindowsInternals::NT_PEB_LDR_DATA loaderData = ReadProcessMemory(handle, processPEB.Ldr); ::LIST_ENTRY* listHeader = loaderData.InLoadOrderModuleList.Flink; ::LIST_ENTRY* currentNode = listHeader; do { const WindowsInternals::NT_LDR_DATA_TABLE_ENTRY entry = ReadProcessMemory(handle, currentNode); wchar_t fullDllName[Filesystem::Path::CAPACITY] = { '\0' }; // certain modules don't have a name and DLL base, skip those if ((entry.DllBase != nullptr) && (entry.FullDllName.Length > 0) && (entry.FullDllName.Buffer != nullptr)) { ReadProcessMemory(handle, entry.FullDllName.Buffer, fullDllName, entry.FullDllName.Length); modules.emplace_back(Module { Filesystem::Path(fullDllName), entry.DllBase, entry.SizeOfImage }); } currentNode = entry.InLoadOrderLinks.Flink; if (currentNode == nullptr) { break; } } while (listHeader != currentNode); return modules; } Filesystem::Path Process::GetImagePath(Handle handle) { DWORD charCount = Filesystem::Path::CAPACITY; wchar_t processName[Filesystem::Path::CAPACITY] = { '\0' }; ::QueryFullProcessImageName(+handle, 0u, processName, &charCount); return Filesystem::Path(processName); } uint32_t Process::GetModuleSize(Handle handle, void* moduleBase) { ::MODULEINFO info = {}; ::GetModuleInformation(+handle, static_cast(moduleBase), &info, sizeof(::MODULEINFO)); return info.SizeOfImage; } // BEGIN EPIC MOD - Allow passing environment block for linker Process::Environment* Process::CreateEnvironmentFromMap(const TMap& Pairs) { std::vector environmentData; for (const TPair& Pair : Pairs) { FString Variable = FString::Printf(TEXT("%s=%s"), *Pair.Key, *Pair.Value); environmentData.insert(environmentData.end(), *Variable, *Variable + (Variable.Len() + 1)); } environmentData.push_back('\0'); Environment* environment = new Environment; environment->size = environmentData.size(); environment->data = ::malloc(environmentData.size() * sizeof(wchar_t)); if (environment->data != nullptr) { memcpy(environment->data, environmentData.data(), environmentData.size() * sizeof(wchar_t)); } return environment; } // END EPIC MOD void Process::DumpMemory(Handle handle, const void* address, size_t size) { uint8_t* memory = new uint8_t[size]; ReadProcessMemory(handle, address, memory, size); LC_LOG_DEV("%s", "Raw data:"); LC_LOG_INDENT_DEV; for (size_t i = 0u; i < size; ++i) { LC_LOG_DEV("0x%02X", memory[i]); } delete[] memory; } // BEGIN EPIC MODS #pragma warning(pop) // END EPIC MODS #endif // LC_VERSION