Bug 1837620 - Part 2: Remove baseline ICs that guard objects when the objects becomes...
[gecko.git] / mozglue / tests / TestNativeNt.cpp
blobd4f671e33cce5c9a31f33c7e14ff252d2a947227
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
7 #include "nscore.h"
8 #include "mozilla/NativeNt.h"
9 #include "mozilla/ThreadLocal.h"
10 #include "mozilla/UniquePtr.h"
11 #include "mozilla/WindowsEnumProcessModules.h"
13 #include <stdio.h>
14 #include <windows.h>
15 #include <strsafe.h>
17 const wchar_t kNormal[] = L"Foo.dll";
18 const wchar_t kHex12[] = L"Foo.ABCDEF012345.dll";
19 const wchar_t kHex15[] = L"ABCDEF012345678.dll";
20 const wchar_t kHex16[] = L"ABCDEF0123456789.dll";
21 const wchar_t kHex17[] = L"ABCDEF0123456789a.dll";
22 const wchar_t kHex24[] = L"ABCDEF0123456789cdabef98.dll";
23 const wchar_t kHex8[] = L"01234567.dll";
24 const wchar_t kNonHex12[] = L"Foo.ABCDEFG12345.dll";
25 const wchar_t kHex13[] = L"Foo.ABCDEF0123456.dll";
26 const wchar_t kHex11[] = L"Foo.ABCDEF01234.dll";
27 const wchar_t kPrefixedHex16[] = L"Pabcdef0123456789.dll";
28 const uint32_t kTlsDataValue = 1234;
29 static MOZ_THREAD_LOCAL(uint32_t) sTlsData;
31 // Need non-inline functions to bypass compiler optimization that the thread
32 // local storage pointer is cached in a register before accessing a thread-local
33 // variable. See bug 1803322 for a motivating example.
34 MOZ_NEVER_INLINE uint32_t getTlsData() { return sTlsData.get(); }
35 MOZ_NEVER_INLINE void setTlsData(uint32_t x) { sTlsData.set(x); }
37 const char kFailFmt[] =
38 "TEST-FAILED | NativeNt | %s(%s) should have returned %s but did not\n";
40 #define RUN_TEST(fn, varName, expected) \
41 if (fn(varName) == !expected) { \
42 printf(kFailFmt, #fn, #varName, #expected); \
43 return 1; \
46 #define EXPECT_FAIL(fn, varName) RUN_TEST(fn, varName, false)
48 #define EXPECT_SUCCESS(fn, varName) RUN_TEST(fn, varName, true)
50 using namespace mozilla;
51 using namespace mozilla::nt;
53 bool TestVirtualQuery(HANDLE aProcess, LPCVOID aAddress) {
54 MEMORY_BASIC_INFORMATION info1 = {}, info2 = {};
55 SIZE_T result1 = ::VirtualQueryEx(aProcess, aAddress, &info1, sizeof(info1)),
56 result2 = mozilla::nt::VirtualQueryEx(aProcess, aAddress, &info2,
57 sizeof(info2));
58 if (result1 != result2) {
59 printf("TEST-FAILED | NativeNt | The returned values mismatch\n");
60 return false;
63 if (!result1) {
64 // Both APIs failed.
65 return true;
68 if (memcmp(&info1, &info2, result1) != 0) {
69 printf("TEST-FAILED | NativeNt | The returned structures mismatch\n");
70 return false;
73 return true;
76 // This class copies the self executable file to the %temp%\<outer>\<inner>
77 // folder. The length of its path is longer than MAX_PATH.
78 class LongNameModule {
79 wchar_t mOuterDirBuffer[MAX_PATH];
80 wchar_t mInnerDirBuffer[MAX_PATH * 2];
81 wchar_t mTargetFileBuffer[MAX_PATH * 2];
83 const wchar_t* mOuterDir;
84 const wchar_t* mInnerDir;
85 const wchar_t* mTargetFile;
87 public:
88 explicit LongNameModule(const wchar_t* aNewLeafNameAfterCopy)
89 : mOuterDir(nullptr), mInnerDir(nullptr), mTargetFile(nullptr) {
90 const wchar_t kFolderName160Chars[] =
91 L"0123456789ABCDEF0123456789ABCDEF"
92 L"0123456789ABCDEF0123456789ABCDEF"
93 L"0123456789ABCDEF0123456789ABCDEF"
94 L"0123456789ABCDEF0123456789ABCDEF"
95 L"0123456789ABCDEF0123456789ABCDEF";
96 UniquePtr<wchar_t[]> thisExe = GetFullBinaryPath();
97 if (!thisExe) {
98 return;
101 // If the buffer is too small, GetTempPathW returns the required
102 // length including a null character, while on a successful case
103 // it returns the number of copied characters which does not include
104 // a null character. This means len == MAX_PATH should never happen
105 // and len > MAX_PATH means GetTempPathW failed.
106 wchar_t tempDir[MAX_PATH];
107 DWORD len = ::GetTempPathW(MAX_PATH, tempDir);
108 if (!len || len >= MAX_PATH) {
109 return;
112 if (FAILED(::StringCbPrintfW(mOuterDirBuffer, sizeof(mOuterDirBuffer),
113 L"\\\\?\\%s%s", tempDir,
114 kFolderName160Chars)) ||
115 !::CreateDirectoryW(mOuterDirBuffer, nullptr)) {
116 return;
118 mOuterDir = mOuterDirBuffer;
120 if (FAILED(::StringCbPrintfW(mInnerDirBuffer, sizeof(mInnerDirBuffer),
121 L"\\\\?\\%s%s\\%s", tempDir,
122 kFolderName160Chars, kFolderName160Chars)) ||
123 !::CreateDirectoryW(mInnerDirBuffer, nullptr)) {
124 return;
126 mInnerDir = mInnerDirBuffer;
128 if (FAILED(::StringCbPrintfW(mTargetFileBuffer, sizeof(mTargetFileBuffer),
129 L"\\\\?\\%s%s\\%s\\%s", tempDir,
130 kFolderName160Chars, kFolderName160Chars,
131 aNewLeafNameAfterCopy)) ||
132 !::CopyFileW(thisExe.get(), mTargetFileBuffer,
133 /*bFailIfExists*/ TRUE)) {
134 return;
136 mTargetFile = mTargetFileBuffer;
139 ~LongNameModule() {
140 if (mTargetFile) {
141 ::DeleteFileW(mTargetFile);
143 if (mInnerDir) {
144 ::RemoveDirectoryW(mInnerDir);
146 if (mOuterDir) {
147 ::RemoveDirectoryW(mOuterDir);
151 operator const wchar_t*() const { return mTargetFile; }
154 // Make sure module info retrieved from nt::PEHeaders is the same as one
155 // retrieved from GetModuleInformation API.
156 bool CompareModuleInfo(HMODULE aModuleForApi, HMODULE aModuleForPEHeader) {
157 MODULEINFO moduleInfo;
158 if (!::GetModuleInformation(::GetCurrentProcess(), aModuleForApi, &moduleInfo,
159 sizeof(moduleInfo))) {
160 printf("TEST-FAILED | NativeNt | GetModuleInformation failed - %08lx\n",
161 ::GetLastError());
162 return false;
165 PEHeaders headers(aModuleForPEHeader);
166 if (!headers) {
167 printf("TEST-FAILED | NativeNt | Failed to instantiate PEHeaders\n");
168 return false;
171 Maybe<Range<const uint8_t>> bounds = headers.GetBounds();
172 if (!bounds) {
173 printf("TEST-FAILED | NativeNt | PEHeaders::GetBounds failed\n");
174 return false;
177 if (bounds->length() != moduleInfo.SizeOfImage) {
178 printf("TEST-FAILED | NativeNt | SizeOfImage does not match\n");
179 return false;
182 // GetModuleInformation sets EntryPoint to 0 for executables
183 // except the running self.
184 static const HMODULE sSelf = ::GetModuleHandleW(nullptr);
185 if (aModuleForApi != sSelf &&
186 !(headers.GetFileCharacteristics() & IMAGE_FILE_DLL)) {
187 if (moduleInfo.EntryPoint) {
188 printf(
189 "TEST-FAIL | NativeNt | "
190 "GetModuleInformation returned a non-zero entrypoint "
191 "for an executable\n");
192 return false;
195 // Cannot verify PEHeaders::GetEntryPoint.
196 return true;
199 // For a module whose entrypoint is 0 (e.g. ntdll.dll or win32u.dll),
200 // MODULEINFO::EntryPoint is set to 0, while PEHeaders::GetEntryPoint
201 // returns the imagebase (RVA=0).
202 intptr_t rvaEntryPoint =
203 moduleInfo.EntryPoint
204 ? reinterpret_cast<uintptr_t>(moduleInfo.EntryPoint) -
205 reinterpret_cast<uintptr_t>(moduleInfo.lpBaseOfDll)
206 : 0;
207 if (rvaEntryPoint < 0) {
208 printf("TEST-FAILED | NativeNt | MODULEINFO is invalid\n");
209 return false;
212 if (headers.RVAToPtr<FARPROC>(rvaEntryPoint) != headers.GetEntryPoint()) {
213 printf("TEST-FAILED | NativeNt | Entrypoint does not match\n");
214 return false;
217 return true;
220 bool TestModuleInfo() {
221 UNICODE_STRING newLeafName;
222 ::RtlInitUnicodeString(&newLeafName,
223 L"\u672D\u5E4C\u5473\u564C.\u30E9\u30FC\u30E1\u30F3");
225 LongNameModule longNameModule(newLeafName.Buffer);
226 if (!longNameModule) {
227 printf(
228 "TEST-FAILED | NativeNt | "
229 "Failed to copy the executable to a long directory path\n");
230 return 1;
234 nsModuleHandle module(::LoadLibraryW(longNameModule));
236 bool detectedTarget = false;
237 bool passedAllModules = true;
238 auto moduleCallback = [&](const wchar_t* aModulePath, HMODULE aModule) {
239 UNICODE_STRING modulePath, moduleName;
240 ::RtlInitUnicodeString(&modulePath, aModulePath);
241 GetLeafName(&moduleName, &modulePath);
242 if (::RtlEqualUnicodeString(&moduleName, &newLeafName,
243 /*aCaseInsensitive*/ TRUE)) {
244 detectedTarget = true;
247 if (!CompareModuleInfo(aModule, aModule)) {
248 passedAllModules = false;
252 if (!mozilla::EnumerateProcessModules(moduleCallback)) {
253 printf("TEST-FAILED | NativeNt | EnumerateProcessModules failed\n");
254 return false;
257 if (!detectedTarget) {
258 printf(
259 "TEST-FAILED | NativeNt | "
260 "EnumerateProcessModules missed the target file\n");
261 return false;
264 if (!passedAllModules) {
265 return false;
269 return true;
272 // Make sure PEHeaders works for a module loaded with LOAD_LIBRARY_AS_DATAFILE
273 // as well as a module loaded normally.
274 bool TestModuleLoadedAsData() {
275 const wchar_t kNewLeafName[] = L"\u03BC\u0061\u9EBA.txt";
277 LongNameModule longNameModule(kNewLeafName);
278 if (!longNameModule) {
279 printf(
280 "TEST-FAILED | NativeNt | "
281 "Failed to copy the executable to a long directory path\n");
282 return 1;
285 const wchar_t* kManualLoadModules[] = {
286 L"mshtml.dll",
287 L"shell32.dll",
288 longNameModule,
291 for (const auto moduleName : kManualLoadModules) {
292 // Must load a module as data first,
293 nsModuleHandle moduleAsData(::LoadLibraryExW(
294 moduleName, nullptr,
295 LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE));
297 // then load a module normally to map it on a different address.
298 nsModuleHandle module(::LoadLibraryW(moduleName));
300 if (!CompareModuleInfo(module.get(), moduleAsData.get())) {
301 return false;
304 PEHeaders peAsData(moduleAsData.get());
305 PEHeaders pe(module.get());
306 if (!peAsData || !pe) {
307 printf("TEST-FAIL | NativeNt | Failed to load the module\n");
308 return false;
311 if (peAsData.RVAToPtr<HMODULE>(0) == pe.RVAToPtr<HMODULE>(0)) {
312 printf(
313 "TEST-FAIL | NativeNt | "
314 "The module should have been mapped onto two different places\n");
315 return false;
318 const auto* pdb1 = peAsData.GetPdbInfo();
319 const auto* pdb2 = pe.GetPdbInfo();
320 if (pdb1 && pdb2) {
321 if (pdb1->pdbSignature != pdb2->pdbSignature ||
322 pdb1->pdbAge != pdb2->pdbAge ||
323 strcmp(pdb1->pdbFileName, pdb2->pdbFileName)) {
324 printf(
325 "TEST-FAIL | NativeNt | "
326 "PDB info from the same module did not match.\n");
327 return false;
329 } else if (pdb1 || pdb2) {
330 printf(
331 "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
332 return false;
335 uint64_t version1, version2;
336 bool result1 = peAsData.GetVersionInfo(version1);
337 bool result2 = pe.GetVersionInfo(version2);
338 if (result1 && result2) {
339 if (version1 != version2) {
340 printf("TEST-FAIL | NativeNt | Version mismatch\n");
341 return false;
343 } else if (result1 || result2) {
344 printf(
345 "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
346 return false;
350 return true;
353 LauncherResult<HMODULE> GetModuleHandleFromLeafName(const wchar_t* aName) {
354 UNICODE_STRING name;
355 ::RtlInitUnicodeString(&name, aName);
356 return nt::GetModuleHandleFromLeafName(name);
359 // Need a non-inline function to bypass compiler optimization that the thread
360 // local storage pointer is cached in a register before accessing a thread-local
361 // variable.
362 MOZ_NEVER_INLINE PVOID SwapThreadLocalStoragePointer(PVOID aNewValue) {
363 auto oldValue = RtlGetThreadLocalStoragePointer();
364 RtlSetThreadLocalStoragePointerForTestingOnly(aNewValue);
365 return oldValue;
368 int wmain(int argc, wchar_t* argv[]) {
369 UNICODE_STRING normal;
370 ::RtlInitUnicodeString(&normal, kNormal);
372 UNICODE_STRING hex12;
373 ::RtlInitUnicodeString(&hex12, kHex12);
375 UNICODE_STRING hex16;
376 ::RtlInitUnicodeString(&hex16, kHex16);
378 UNICODE_STRING hex24;
379 ::RtlInitUnicodeString(&hex24, kHex24);
381 UNICODE_STRING hex8;
382 ::RtlInitUnicodeString(&hex8, kHex8);
384 UNICODE_STRING nonHex12;
385 ::RtlInitUnicodeString(&nonHex12, kNonHex12);
387 UNICODE_STRING hex13;
388 ::RtlInitUnicodeString(&hex13, kHex13);
390 UNICODE_STRING hex11;
391 ::RtlInitUnicodeString(&hex11, kHex11);
393 UNICODE_STRING hex15;
394 ::RtlInitUnicodeString(&hex15, kHex15);
396 UNICODE_STRING hex17;
397 ::RtlInitUnicodeString(&hex17, kHex17);
399 UNICODE_STRING prefixedHex16;
400 ::RtlInitUnicodeString(&prefixedHex16, kPrefixedHex16);
402 EXPECT_FAIL(Contains12DigitHexString, normal);
403 EXPECT_SUCCESS(Contains12DigitHexString, hex12);
404 EXPECT_FAIL(Contains12DigitHexString, hex13);
405 EXPECT_FAIL(Contains12DigitHexString, hex11);
406 EXPECT_FAIL(Contains12DigitHexString, hex16);
407 EXPECT_FAIL(Contains12DigitHexString, nonHex12);
409 EXPECT_FAIL(IsFileNameAtLeast16HexDigits, normal);
410 EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex12);
411 EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex24);
412 EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex16);
413 EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits, hex17);
414 EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex8);
415 EXPECT_FAIL(IsFileNameAtLeast16HexDigits, hex15);
416 EXPECT_FAIL(IsFileNameAtLeast16HexDigits, prefixedHex16);
418 if (RtlGetProcessHeap() != ::GetProcessHeap()) {
419 printf("TEST-FAILED | NativeNt | RtlGetProcessHeap() is broken\n");
420 return 1;
423 #ifdef HAVE_SEH_EXCEPTIONS
424 PVOID origTlsHead = nullptr;
425 bool isExceptionThrown = false;
426 // Touch sTlsData.get() several times to prevent the call to sTlsData.set()
427 // from being optimized out in PGO build.
428 printf("sTlsData#1 = %08x\n", getTlsData());
429 MOZ_SEH_TRY {
430 // Need to call SwapThreadLocalStoragePointer inside __try to make sure
431 // accessing sTlsData is caught by SEH. This is due to clang's design.
432 // https://bugs.llvm.org/show_bug.cgi?id=44174.
433 origTlsHead = SwapThreadLocalStoragePointer(nullptr);
434 setTlsData(~kTlsDataValue);
436 MOZ_SEH_EXCEPT(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
437 ? EXCEPTION_EXECUTE_HANDLER
438 : EXCEPTION_CONTINUE_SEARCH) {
439 isExceptionThrown = true;
441 SwapThreadLocalStoragePointer(origTlsHead);
442 printf("sTlsData#2 = %08x\n", getTlsData());
443 setTlsData(kTlsDataValue);
444 printf("sTlsData#3 = %08x\n", getTlsData());
445 if (!isExceptionThrown || getTlsData() != kTlsDataValue) {
446 printf(
447 "TEST-FAILED | NativeNt | RtlGetThreadLocalStoragePointer() is "
448 "broken\n");
449 return 1;
451 #endif
453 if (RtlGetCurrentThreadId() != ::GetCurrentThreadId()) {
454 printf("TEST-FAILED | NativeNt | RtlGetCurrentThreadId() is broken\n");
455 return 1;
458 const wchar_t kKernel32[] = L"kernel32.dll";
459 DWORD verInfoSize = ::GetFileVersionInfoSizeW(kKernel32, nullptr);
460 if (!verInfoSize) {
461 printf(
462 "TEST-FAILED | NativeNt | Call to GetFileVersionInfoSizeW failed with "
463 "code %lu\n",
464 ::GetLastError());
465 return 1;
468 auto verInfoBuf = MakeUnique<char[]>(verInfoSize);
470 if (!::GetFileVersionInfoW(kKernel32, 0, verInfoSize, verInfoBuf.get())) {
471 printf(
472 "TEST-FAILED | NativeNt | Call to GetFileVersionInfoW failed with code "
473 "%lu\n",
474 ::GetLastError());
475 return 1;
478 UINT len;
479 VS_FIXEDFILEINFO* fixedFileInfo = nullptr;
480 if (!::VerQueryValueW(verInfoBuf.get(), L"\\", (LPVOID*)&fixedFileInfo,
481 &len)) {
482 printf(
483 "TEST-FAILED | NativeNt | Call to VerQueryValueW failed with code "
484 "%lu\n",
485 ::GetLastError());
486 return 1;
489 const uint64_t expectedVersion =
490 (static_cast<uint64_t>(fixedFileInfo->dwFileVersionMS) << 32) |
491 static_cast<uint64_t>(fixedFileInfo->dwFileVersionLS);
493 PEHeaders k32headers(::GetModuleHandleW(kKernel32));
494 if (!k32headers) {
495 printf(
496 "TEST-FAILED | NativeNt | Failed parsing kernel32.dll's PE headers\n");
497 return 1;
500 uint64_t version;
501 if (!k32headers.GetVersionInfo(version)) {
502 printf(
503 "TEST-FAILED | NativeNt | Unable to obtain version information from "
504 "kernel32.dll\n");
505 return 1;
508 if (version != expectedVersion) {
509 printf(
510 "TEST-FAILED | NativeNt | kernel32.dll's detected version "
511 "(0x%016llX) does not match expected version (0x%016llX)\n",
512 version, expectedVersion);
513 return 1;
516 Maybe<Span<IMAGE_THUNK_DATA>> iatThunks =
517 k32headers.GetIATThunksForModule("kernel32.dll");
518 if (iatThunks) {
519 printf(
520 "TEST-FAILED | NativeNt | Detected the IAT thunk for kernel32 "
521 "in kernel32.dll\n");
522 return 1;
525 const mozilla::nt::CodeViewRecord70* debugInfo = k32headers.GetPdbInfo();
526 if (!debugInfo) {
527 printf(
528 "TEST-FAILED | NativeNt | Unable to obtain debug information from "
529 "kernel32.dll\n");
530 return 1;
533 #ifndef WIN32 // failure on windows10x32
534 if (stricmp(debugInfo->pdbFileName, "kernel32.pdb")) {
535 printf(
536 "TEST-FAILED | NativeNt | Unexpected PDB filename "
537 "in kernel32.dll: %s\n",
538 debugInfo->pdbFileName);
539 return 1;
541 #endif
543 PEHeaders ntdllheaders(::GetModuleHandleW(L"ntdll.dll"));
545 auto ntdllBoundaries = ntdllheaders.GetBounds();
546 if (!ntdllBoundaries) {
547 printf(
548 "TEST-FAILED | NativeNt | "
549 "Unable to obtain the boundaries of ntdll.dll\n");
550 return 1;
553 iatThunks =
554 k32headers.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr());
555 if (!iatThunks) {
556 printf(
557 "TEST-FAILED | NativeNt | Unable to find the IAT thunk for "
558 "ntdll.dll in kernel32.dll\n");
559 return 1;
562 // To test the Ex version of API, we purposely get a real handle
563 // instead of a pseudo handle.
564 nsAutoHandle process(
565 ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()));
566 if (!process) {
567 printf("TEST-FAILED | NativeNt | OpenProcess() failed - %08lx\n",
568 ::GetLastError());
569 return 1;
572 // Test Null page, Heap, Mapped image, and Invalid handle
573 if (!TestVirtualQuery(process, nullptr) || !TestVirtualQuery(process, argv) ||
574 !TestVirtualQuery(process, kNormal) ||
575 !TestVirtualQuery(nullptr, kNormal)) {
576 return 1;
579 auto moduleResult = GetModuleHandleFromLeafName(kKernel32);
580 if (moduleResult.isErr() ||
581 moduleResult.inspect() != k32headers.template RVAToPtr<HMODULE>(0)) {
582 printf(
583 "TEST-FAILED | NativeNt | "
584 "GetModuleHandleFromLeafName returns a wrong value.\n");
585 return 1;
588 moduleResult = GetModuleHandleFromLeafName(L"invalid");
589 if (moduleResult.isOk()) {
590 printf(
591 "TEST-FAILED | NativeNt | "
592 "GetModuleHandleFromLeafName unexpectedly returns a value.\n");
593 return 1;
596 if (!TestModuleInfo()) {
597 return 1;
600 if (!TestModuleLoadedAsData()) {
601 return 1;
604 printf("TEST-PASS | NativeNt | All tests ran successfully\n");
605 return 0;