summaryrefslogtreecommitdiffstats
path: root/mozglue/tests/TestNativeNt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/tests/TestNativeNt.cpp')
-rw-r--r--mozglue/tests/TestNativeNt.cpp606
1 files changed, 606 insertions, 0 deletions
diff --git a/mozglue/tests/TestNativeNt.cpp b/mozglue/tests/TestNativeNt.cpp
new file mode 100644
index 0000000000..d4f671e33c
--- /dev/null
+++ b/mozglue/tests/TestNativeNt.cpp
@@ -0,0 +1,606 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsEnumProcessModules.h"
+
+#include <stdio.h>
+#include <windows.h>
+#include <strsafe.h>
+
+const wchar_t kNormal[] = L"Foo.dll";
+const wchar_t kHex12[] = L"Foo.ABCDEF012345.dll";
+const wchar_t kHex15[] = L"ABCDEF012345678.dll";
+const wchar_t kHex16[] = L"ABCDEF0123456789.dll";
+const wchar_t kHex17[] = L"ABCDEF0123456789a.dll";
+const wchar_t kHex24[] = L"ABCDEF0123456789cdabef98.dll";
+const wchar_t kHex8[] = L"01234567.dll";
+const wchar_t kNonHex12[] = L"Foo.ABCDEFG12345.dll";
+const wchar_t kHex13[] = L"Foo.ABCDEF0123456.dll";
+const wchar_t kHex11[] = L"Foo.ABCDEF01234.dll";
+const wchar_t kPrefixedHex16[] = L"Pabcdef0123456789.dll";
+const uint32_t kTlsDataValue = 1234;
+static MOZ_THREAD_LOCAL(uint32_t) sTlsData;
+
+// Need non-inline functions to bypass compiler optimization that the thread
+// local storage pointer is cached in a register before accessing a thread-local
+// variable. See bug 1803322 for a motivating example.
+MOZ_NEVER_INLINE uint32_t getTlsData() { return sTlsData.get(); }
+MOZ_NEVER_INLINE void setTlsData(uint32_t x) { sTlsData.set(x); }
+
+const char kFailFmt[] =
+ "TEST-FAILED | NativeNt | %s(%s) should have returned %s but did not\n";
+
+#define RUN_TEST(fn, varName, expected) \
+ if (fn(varName) == !expected) { \
+ printf(kFailFmt, #fn, #varName, #expected); \
+ return 1; \
+ }
+
+#define EXPECT_FAIL(fn, varName) RUN_TEST(fn, varName, false)
+
+#define EXPECT_SUCCESS(fn, varName) RUN_TEST(fn, varName, true)
+
+using namespace mozilla;
+using namespace mozilla::nt;
+
+bool TestVirtualQuery(HANDLE aProcess, LPCVOID aAddress) {
+ MEMORY_BASIC_INFORMATION info1 = {}, info2 = {};
+ SIZE_T result1 = ::VirtualQueryEx(aProcess, aAddress, &info1, sizeof(info1)),
+ result2 = mozilla::nt::VirtualQueryEx(aProcess, aAddress, &info2,
+ sizeof(info2));
+ if (result1 != result2) {
+ printf("TEST-FAILED | NativeNt | The returned values mismatch\n");
+ return false;
+ }
+
+ if (!result1) {
+ // Both APIs failed.
+ return true;
+ }
+
+ if (memcmp(&info1, &info2, result1) != 0) {
+ printf("TEST-FAILED | NativeNt | The returned structures mismatch\n");
+ return false;
+ }
+
+ return true;
+}
+
+// This class copies the self executable file to the %temp%\<outer>\<inner>
+// folder. The length of its path is longer than MAX_PATH.
+class LongNameModule {
+ wchar_t mOuterDirBuffer[MAX_PATH];
+ wchar_t mInnerDirBuffer[MAX_PATH * 2];
+ wchar_t mTargetFileBuffer[MAX_PATH * 2];
+
+ const wchar_t* mOuterDir;
+ const wchar_t* mInnerDir;
+ const wchar_t* mTargetFile;
+
+ public:
+ explicit LongNameModule(const wchar_t* aNewLeafNameAfterCopy)
+ : mOuterDir(nullptr), mInnerDir(nullptr), mTargetFile(nullptr) {
+ const wchar_t kFolderName160Chars[] =
+ L"0123456789ABCDEF0123456789ABCDEF"
+ L"0123456789ABCDEF0123456789ABCDEF"
+ L"0123456789ABCDEF0123456789ABCDEF"
+ L"0123456789ABCDEF0123456789ABCDEF"
+ L"0123456789ABCDEF0123456789ABCDEF";
+ UniquePtr<wchar_t[]> thisExe = GetFullBinaryPath();
+ if (!thisExe) {
+ return;
+ }
+
+ // If the buffer is too small, GetTempPathW returns the required
+ // length including a null character, while on a successful case
+ // it returns the number of copied characters which does not include
+ // a null character. This means len == MAX_PATH should never happen
+ // and len > MAX_PATH means GetTempPathW failed.
+ wchar_t tempDir[MAX_PATH];
+ DWORD len = ::GetTempPathW(MAX_PATH, tempDir);
+ if (!len || len >= MAX_PATH) {
+ return;
+ }
+
+ if (FAILED(::StringCbPrintfW(mOuterDirBuffer, sizeof(mOuterDirBuffer),
+ L"\\\\?\\%s%s", tempDir,
+ kFolderName160Chars)) ||
+ !::CreateDirectoryW(mOuterDirBuffer, nullptr)) {
+ return;
+ }
+ mOuterDir = mOuterDirBuffer;
+
+ if (FAILED(::StringCbPrintfW(mInnerDirBuffer, sizeof(mInnerDirBuffer),
+ L"\\\\?\\%s%s\\%s", tempDir,
+ kFolderName160Chars, kFolderName160Chars)) ||
+ !::CreateDirectoryW(mInnerDirBuffer, nullptr)) {
+ return;
+ }
+ mInnerDir = mInnerDirBuffer;
+
+ if (FAILED(::StringCbPrintfW(mTargetFileBuffer, sizeof(mTargetFileBuffer),
+ L"\\\\?\\%s%s\\%s\\%s", tempDir,
+ kFolderName160Chars, kFolderName160Chars,
+ aNewLeafNameAfterCopy)) ||
+ !::CopyFileW(thisExe.get(), mTargetFileBuffer,
+ /*bFailIfExists*/ TRUE)) {
+ return;
+ }
+ mTargetFile = mTargetFileBuffer;
+ }
+
+ ~LongNameModule() {
+ if (mTargetFile) {
+ ::DeleteFileW(mTargetFile);
+ }
+ if (mInnerDir) {
+ ::RemoveDirectoryW(mInnerDir);
+ }
+ if (mOuterDir) {
+ ::RemoveDirectoryW(mOuterDir);
+ }
+ }
+
+ operator const wchar_t*() const { return mTargetFile; }
+};
+
+// Make sure module info retrieved from nt::PEHeaders is the same as one
+// retrieved from GetModuleInformation API.
+bool CompareModuleInfo(HMODULE aModuleForApi, HMODULE aModuleForPEHeader) {
+ MODULEINFO moduleInfo;
+ if (!::GetModuleInformation(::GetCurrentProcess(), aModuleForApi, &moduleInfo,
+ sizeof(moduleInfo))) {
+ printf("TEST-FAILED | NativeNt | GetModuleInformation failed - %08lx\n",
+ ::GetLastError());
+ return false;
+ }
+
+ PEHeaders headers(aModuleForPEHeader);
+ if (!headers) {
+ printf("TEST-FAILED | NativeNt | Failed to instantiate PEHeaders\n");
+ return false;
+ }
+
+ Maybe<Range<const uint8_t>> bounds = headers.GetBounds();
+ if (!bounds) {
+ printf("TEST-FAILED | NativeNt | PEHeaders::GetBounds failed\n");
+ return false;
+ }
+
+ if (bounds->length() != moduleInfo.SizeOfImage) {
+ printf("TEST-FAILED | NativeNt | SizeOfImage does not match\n");
+ return false;
+ }
+
+ // GetModuleInformation sets EntryPoint to 0 for executables
+ // except the running self.
+ static const HMODULE sSelf = ::GetModuleHandleW(nullptr);
+ if (aModuleForApi != sSelf &&
+ !(headers.GetFileCharacteristics() & IMAGE_FILE_DLL)) {
+ if (moduleInfo.EntryPoint) {
+ printf(
+ "TEST-FAIL | NativeNt | "
+ "GetModuleInformation returned a non-zero entrypoint "
+ "for an executable\n");
+ return false;
+ }
+
+ // Cannot verify PEHeaders::GetEntryPoint.
+ return true;
+ }
+
+ // For a module whose entrypoint is 0 (e.g. ntdll.dll or win32u.dll),
+ // MODULEINFO::EntryPoint is set to 0, while PEHeaders::GetEntryPoint
+ // returns the imagebase (RVA=0).
+ intptr_t rvaEntryPoint =
+ moduleInfo.EntryPoint
+ ? reinterpret_cast<uintptr_t>(moduleInfo.EntryPoint) -
+ reinterpret_cast<uintptr_t>(moduleInfo.lpBaseOfDll)
+ : 0;
+ if (rvaEntryPoint < 0) {
+ printf("TEST-FAILED | NativeNt | MODULEINFO is invalid\n");
+ return false;
+ }
+
+ if (headers.RVAToPtr<FARPROC>(rvaEntryPoint) != headers.GetEntryPoint()) {
+ printf("TEST-FAILED | NativeNt | Entrypoint does not match\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool TestModuleInfo() {
+ UNICODE_STRING newLeafName;
+ ::RtlInitUnicodeString(&newLeafName,
+ L"\u672D\u5E4C\u5473\u564C.\u30E9\u30FC\u30E1\u30F3");
+
+ LongNameModule longNameModule(newLeafName.Buffer);
+ if (!longNameModule) {
+ printf(
+ "TEST-FAILED | NativeNt | "
+ "Failed to copy the executable to a long directory path\n");
+ return 1;
+ }
+
+ {
+ nsModuleHandle module(::LoadLibraryW(longNameModule));
+
+ bool detectedTarget = false;
+ bool passedAllModules = true;
+ auto moduleCallback = [&](const wchar_t* aModulePath, HMODULE aModule) {
+ UNICODE_STRING modulePath, moduleName;
+ ::RtlInitUnicodeString(&modulePath, aModulePath);
+ GetLeafName(&moduleName, &modulePath);
+ if (::RtlEqualUnicodeString(&moduleName, &newLeafName,
+ /*aCaseInsensitive*/ TRUE)) {
+ detectedTarget = true;
+ }
+
+ if (!CompareModuleInfo(aModule, aModule)) {
+ passedAllModules = false;
+ }
+ };
+
+ if (!mozilla::EnumerateProcessModules(moduleCallback)) {
+ printf("TEST-FAILED | NativeNt | EnumerateProcessModules failed\n");
+ return false;
+ }
+
+ if (!detectedTarget) {
+ printf(
+ "TEST-FAILED | NativeNt | "
+ "EnumerateProcessModules missed the target file\n");
+ return false;
+ }
+
+ if (!passedAllModules) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Make sure PEHeaders works for a module loaded with LOAD_LIBRARY_AS_DATAFILE
+// as well as a module loaded normally.
+bool TestModuleLoadedAsData() {
+ const wchar_t kNewLeafName[] = L"\u03BC\u0061\u9EBA.txt";
+
+ LongNameModule longNameModule(kNewLeafName);
+ if (!longNameModule) {
+ printf(
+ "TEST-FAILED | NativeNt | "
+ "Failed to copy the executable to a long directory path\n");
+ return 1;
+ }
+
+ const wchar_t* kManualLoadModules[] = {
+ L"mshtml.dll",
+ L"shell32.dll",
+ longNameModule,
+ };
+
+ for (const auto moduleName : kManualLoadModules) {
+ // Must load a module as data first,
+ nsModuleHandle moduleAsData(::LoadLibraryExW(
+ moduleName, nullptr,
+ LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE));
+
+ // then load a module normally to map it on a different address.
+ nsModuleHandle module(::LoadLibraryW(moduleName));
+
+ if (!CompareModuleInfo(module.get(), moduleAsData.get())) {
+ return false;
+ }
+
+ PEHeaders peAsData(moduleAsData.get());
+ PEHeaders pe(module.get());
+ if (!peAsData || !pe) {
+ printf("TEST-FAIL | NativeNt | Failed to load the module\n");
+ return false;
+ }
+
+ if (peAsData.RVAToPtr<HMODULE>(0) == pe.RVAToPtr<HMODULE>(0)) {
+ printf(
+ "TEST-FAIL | NativeNt | "
+ "The module should have been mapped onto two different places\n");
+ return false;
+ }
+
+ const auto* pdb1 = peAsData.GetPdbInfo();
+ const auto* pdb2 = pe.GetPdbInfo();
+ if (pdb1 && pdb2) {
+ if (pdb1->pdbSignature != pdb2->pdbSignature ||
+ pdb1->pdbAge != pdb2->pdbAge ||
+ strcmp(pdb1->pdbFileName, pdb2->pdbFileName)) {
+ printf(
+ "TEST-FAIL | NativeNt | "
+ "PDB info from the same module did not match.\n");
+ return false;
+ }
+ } else if (pdb1 || pdb2) {
+ printf(
+ "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
+ return false;
+ }
+
+ uint64_t version1, version2;
+ bool result1 = peAsData.GetVersionInfo(version1);
+ bool result2 = pe.GetVersionInfo(version2);
+ if (result1 && result2) {
+ if (version1 != version2) {
+ printf("TEST-FAIL | NativeNt | Version mismatch\n");
+ return false;
+ }
+ } else if (result1 || result2) {
+ printf(
+ "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+LauncherResult<HMODULE> GetModuleHandleFromLeafName(const wchar_t* aName) {
+ UNICODE_STRING name;
+ ::RtlInitUnicodeString(&name, aName);
+ return nt::GetModuleHandleFromLeafName(name);
+}
+
+// Need a non-inline function to bypass compiler optimization that the thread
+// local storage pointer is cached in a register before accessing a thread-local
+// variable.
+MOZ_NEVER_INLINE PVOID SwapThreadLocalStoragePointer(PVOID aNewValue) {
+ auto oldValue = RtlGetThreadLocalStoragePointer();
+ RtlSetThreadLocalStoragePointerForTestingOnly(aNewValue);
+ return oldValue;
+}
+
+int wmain(int argc, wchar_t* argv[]) {
+ UNICODE_STRING normal;
+ ::RtlInitUnicodeString(&normal, kNormal);
+
+ UNICODE_STRING hex12;
+ ::RtlInitUnicodeString(&hex12, kHex12);
+
+ UNICODE_STRING hex16;
+ ::RtlInitUnicodeString(&hex16, kHex16);
+
+ UNICODE_STRING hex24;
+ ::RtlInitUnicodeString(&hex24, kHex24);
+
+ UNICODE_STRING hex8;
+ ::RtlInitUnicodeString(&hex8, kHex8);
+
+ UNICODE_STRING nonHex12;
+ ::RtlInitUnicodeString(&nonHex12, kNonHex12);
+
+ UNICODE_STRING hex13;
+ ::RtlInitUnicodeString(&hex13, kHex13);
+
+ UNICODE_STRING hex11;
+ ::RtlInitUnicodeString(&hex11, kHex11);
+
+ UNICODE_STRING hex15;
+ ::RtlInitUnicodeString(&hex15, kHex15);
+
+ UNICODE_STRING hex17;
+ ::RtlInitUnicodeString(&hex17, kHex17);
+
+ UNICODE_STRING prefixedHex16;
+ ::RtlInitUnicodeString(&prefixedHex16, kPrefixedHex16);
+
+ EXPECT_FAIL(Contains12DigitHexString, normal);
+ EXPECT_SUCCESS(Contains12DigitHexString, hex12);
+ EXPECT_FAIL(Contains12DigitHexString, hex13);
+ EXPECT_FAIL(Contains12DigitHexString, hex11);
+ EXPECT_FAIL(Contains12DigitHexString, hex16);
+ EXPECT_FAIL(Contains12DigitHexString, nonHex12);
+
+ EXPECT_FAIL(IsFileNameAtLeast16HexDigits, normal);
+ EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex12);
+ EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex24);
+ EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex16);
+ EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex17);
+ EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex8);
+ EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex15);
+ EXPECT_FAIL(IsFileNameAtLeast16HexDigits, prefixedHex16);
+
+ if (RtlGetProcessHeap() != ::GetProcessHeap()) {
+ printf("TEST-FAILED | NativeNt | RtlGetProcessHeap() is broken\n");
+ return 1;
+ }
+
+#ifdef HAVE_SEH_EXCEPTIONS
+ PVOID origTlsHead = nullptr;
+ bool isExceptionThrown = false;
+ // Touch sTlsData.get() several times to prevent the call to sTlsData.set()
+ // from being optimized out in PGO build.
+ printf("sTlsData#1 = %08x\n", getTlsData());
+ MOZ_SEH_TRY {
+ // Need to call SwapThreadLocalStoragePointer inside __try to make sure
+ // accessing sTlsData is caught by SEH. This is due to clang's design.
+ // https://bugs.llvm.org/show_bug.cgi?id=44174.
+ origTlsHead = SwapThreadLocalStoragePointer(nullptr);
+ setTlsData(~kTlsDataValue);
+ }
+ MOZ_SEH_EXCEPT(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH) {
+ isExceptionThrown = true;
+ }
+ SwapThreadLocalStoragePointer(origTlsHead);
+ printf("sTlsData#2 = %08x\n", getTlsData());
+ setTlsData(kTlsDataValue);
+ printf("sTlsData#3 = %08x\n", getTlsData());
+ if (!isExceptionThrown || getTlsData() != kTlsDataValue) {
+ printf(
+ "TEST-FAILED | NativeNt | RtlGetThreadLocalStoragePointer() is "
+ "broken\n");
+ return 1;
+ }
+#endif
+
+ if (RtlGetCurrentThreadId() != ::GetCurrentThreadId()) {
+ printf("TEST-FAILED | NativeNt | RtlGetCurrentThreadId() is broken\n");
+ return 1;
+ }
+
+ const wchar_t kKernel32[] = L"kernel32.dll";
+ DWORD verInfoSize = ::GetFileVersionInfoSizeW(kKernel32, nullptr);
+ if (!verInfoSize) {
+ printf(
+ "TEST-FAILED | NativeNt | Call to GetFileVersionInfoSizeW failed with "
+ "code %lu\n",
+ ::GetLastError());
+ return 1;
+ }
+
+ auto verInfoBuf = MakeUnique<char[]>(verInfoSize);
+
+ if (!::GetFileVersionInfoW(kKernel32, 0, verInfoSize, verInfoBuf.get())) {
+ printf(
+ "TEST-FAILED | NativeNt | Call to GetFileVersionInfoW failed with code "
+ "%lu\n",
+ ::GetLastError());
+ return 1;
+ }
+
+ UINT len;
+ VS_FIXEDFILEINFO* fixedFileInfo = nullptr;
+ if (!::VerQueryValueW(verInfoBuf.get(), L"\\", (LPVOID*)&fixedFileInfo,
+ &len)) {
+ printf(
+ "TEST-FAILED | NativeNt | Call to VerQueryValueW failed with code "
+ "%lu\n",
+ ::GetLastError());
+ return 1;
+ }
+
+ const uint64_t expectedVersion =
+ (static_cast<uint64_t>(fixedFileInfo->dwFileVersionMS) << 32) |
+ static_cast<uint64_t>(fixedFileInfo->dwFileVersionLS);
+
+ PEHeaders k32headers(::GetModuleHandleW(kKernel32));
+ if (!k32headers) {
+ printf(
+ "TEST-FAILED | NativeNt | Failed parsing kernel32.dll's PE headers\n");
+ return 1;
+ }
+
+ uint64_t version;
+ if (!k32headers.GetVersionInfo(version)) {
+ printf(
+ "TEST-FAILED | NativeNt | Unable to obtain version information from "
+ "kernel32.dll\n");
+ return 1;
+ }
+
+ if (version != expectedVersion) {
+ printf(
+ "TEST-FAILED | NativeNt | kernel32.dll's detected version "
+ "(0x%016llX) does not match expected version (0x%016llX)\n",
+ version, expectedVersion);
+ return 1;
+ }
+
+ Maybe<Span<IMAGE_THUNK_DATA>> iatThunks =
+ k32headers.GetIATThunksForModule("kernel32.dll");
+ if (iatThunks) {
+ printf(
+ "TEST-FAILED | NativeNt | Detected the IAT thunk for kernel32 "
+ "in kernel32.dll\n");
+ return 1;
+ }
+
+ const mozilla::nt::CodeViewRecord70* debugInfo = k32headers.GetPdbInfo();
+ if (!debugInfo) {
+ printf(
+ "TEST-FAILED | NativeNt | Unable to obtain debug information from "
+ "kernel32.dll\n");
+ return 1;
+ }
+
+#ifndef WIN32 // failure on windows10x32
+ if (stricmp(debugInfo->pdbFileName, "kernel32.pdb")) {
+ printf(
+ "TEST-FAILED | NativeNt | Unexpected PDB filename "
+ "in kernel32.dll: %s\n",
+ debugInfo->pdbFileName);
+ return 1;
+ }
+#endif
+
+ PEHeaders ntdllheaders(::GetModuleHandleW(L"ntdll.dll"));
+
+ auto ntdllBoundaries = ntdllheaders.GetBounds();
+ if (!ntdllBoundaries) {
+ printf(
+ "TEST-FAILED | NativeNt | "
+ "Unable to obtain the boundaries of ntdll.dll\n");
+ return 1;
+ }
+
+ iatThunks =
+ k32headers.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr());
+ if (!iatThunks) {
+ printf(
+ "TEST-FAILED | NativeNt | Unable to find the IAT thunk for "
+ "ntdll.dll in kernel32.dll\n");
+ return 1;
+ }
+
+ // To test the Ex version of API, we purposely get a real handle
+ // instead of a pseudo handle.
+ nsAutoHandle process(
+ ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()));
+ if (!process) {
+ printf("TEST-FAILED | NativeNt | OpenProcess() failed - %08lx\n",
+ ::GetLastError());
+ return 1;
+ }
+
+ // Test Null page, Heap, Mapped image, and Invalid handle
+ if (!TestVirtualQuery(process, nullptr) || !TestVirtualQuery(process, argv) ||
+ !TestVirtualQuery(process, kNormal) ||
+ !TestVirtualQuery(nullptr, kNormal)) {
+ return 1;
+ }
+
+ auto moduleResult = GetModuleHandleFromLeafName(kKernel32);
+ if (moduleResult.isErr() ||
+ moduleResult.inspect() != k32headers.template RVAToPtr<HMODULE>(0)) {
+ printf(
+ "TEST-FAILED | NativeNt | "
+ "GetModuleHandleFromLeafName returns a wrong value.\n");
+ return 1;
+ }
+
+ moduleResult = GetModuleHandleFromLeafName(L"invalid");
+ if (moduleResult.isOk()) {
+ printf(
+ "TEST-FAILED | NativeNt | "
+ "GetModuleHandleFromLeafName unexpectedly returns a value.\n");
+ return 1;
+ }
+
+ if (!TestModuleInfo()) {
+ return 1;
+ }
+
+ if (!TestModuleLoadedAsData()) {
+ return 1;
+ }
+
+ printf("TEST-PASS | NativeNt | All tests ran successfully\n");
+ return 0;
+}