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/. */
8 #include "mozilla/NativeNt.h"
9 #include "mozilla/ThreadLocal.h"
10 #include "mozilla/UniquePtr.h"
11 #include "mozilla/WindowsEnumProcessModules.h"
18 const wchar_t kNormal
[] = L
"Foo.dll";
19 const wchar_t kHex12
[] = L
"Foo.ABCDEF012345.dll";
20 const wchar_t kHex15
[] = L
"ABCDEF012345678.dll";
21 const wchar_t kHex16
[] = L
"ABCDEF0123456789.dll";
22 const wchar_t kHex17
[] = L
"ABCDEF0123456789a.dll";
23 const wchar_t kHex24
[] = L
"ABCDEF0123456789cdabef98.dll";
24 const wchar_t kHex8
[] = L
"01234567.dll";
25 const wchar_t kNonHex12
[] = L
"Foo.ABCDEFG12345.dll";
26 const wchar_t kHex13
[] = L
"Foo.ABCDEF0123456.dll";
27 const wchar_t kHex11
[] = L
"Foo.ABCDEF01234.dll";
28 const wchar_t kPrefixedHex16
[] = L
"Pabcdef0123456789.dll";
29 const uint32_t kTlsDataValue
= 1234;
30 static MOZ_THREAD_LOCAL(uint32_t) sTlsData
;
32 // Need non-inline functions to bypass compiler optimization that the thread
33 // local storage pointer is cached in a register before accessing a thread-local
34 // variable. See bug 1803322 for a motivating example.
35 MOZ_NEVER_INLINE
uint32_t getTlsData() { return sTlsData
.get(); }
36 MOZ_NEVER_INLINE
void setTlsData(uint32_t x
) { sTlsData
.set(x
); }
38 const char kFailFmt
[] =
39 "TEST-FAILED | NativeNt | %s(%s) should have returned %s but did not\n";
41 #define RUN_TEST(fn, varName, expected) \
42 if (fn(varName) == !expected) { \
43 printf(kFailFmt, #fn, #varName, #expected); \
47 #define EXPECT_FAIL(fn, varName) RUN_TEST(fn, varName, false)
49 #define EXPECT_SUCCESS(fn, varName) RUN_TEST(fn, varName, true)
51 using namespace mozilla
;
52 using namespace mozilla::nt
;
54 bool TestVirtualQuery(HANDLE aProcess
, LPCVOID aAddress
) {
55 MEMORY_BASIC_INFORMATION info1
= {}, info2
= {};
56 SIZE_T result1
= ::VirtualQueryEx(aProcess
, aAddress
, &info1
, sizeof(info1
)),
57 result2
= mozilla::nt::VirtualQueryEx(aProcess
, aAddress
, &info2
,
59 if (result1
!= result2
) {
60 printf("TEST-FAILED | NativeNt | The returned values mismatch\n");
69 if (memcmp(&info1
, &info2
, result1
) != 0) {
70 printf("TEST-FAILED | NativeNt | The returned structures mismatch\n");
77 // This class copies the self executable file to the %temp%\<outer>\<inner>
78 // folder. The length of its path is longer than MAX_PATH.
79 class LongNameModule
{
80 wchar_t mOuterDirBuffer
[MAX_PATH
];
81 wchar_t mInnerDirBuffer
[MAX_PATH
* 2];
82 wchar_t mTargetFileBuffer
[MAX_PATH
* 2];
84 const wchar_t* mOuterDir
;
85 const wchar_t* mInnerDir
;
86 const wchar_t* mTargetFile
;
89 explicit LongNameModule(const wchar_t* aNewLeafNameAfterCopy
)
90 : mOuterDir(nullptr), mInnerDir(nullptr), mTargetFile(nullptr) {
91 const wchar_t kFolderName160Chars
[] =
92 L
"0123456789ABCDEF0123456789ABCDEF"
93 L
"0123456789ABCDEF0123456789ABCDEF"
94 L
"0123456789ABCDEF0123456789ABCDEF"
95 L
"0123456789ABCDEF0123456789ABCDEF"
96 L
"0123456789ABCDEF0123456789ABCDEF";
97 UniquePtr
<wchar_t[]> thisExe
= GetFullBinaryPath();
102 // If the buffer is too small, GetTempPathW returns the required
103 // length including a null character, while on a successful case
104 // it returns the number of copied characters which does not include
105 // a null character. This means len == MAX_PATH should never happen
106 // and len > MAX_PATH means GetTempPathW failed.
107 wchar_t tempDir
[MAX_PATH
];
108 DWORD len
= ::GetTempPathW(MAX_PATH
, tempDir
);
109 if (!len
|| len
>= MAX_PATH
) {
113 if (FAILED(::StringCbPrintfW(mOuterDirBuffer
, sizeof(mOuterDirBuffer
),
114 L
"\\\\?\\%s%s", tempDir
,
115 kFolderName160Chars
)) ||
116 !::CreateDirectoryW(mOuterDirBuffer
, nullptr)) {
119 mOuterDir
= mOuterDirBuffer
;
121 if (FAILED(::StringCbPrintfW(mInnerDirBuffer
, sizeof(mInnerDirBuffer
),
122 L
"\\\\?\\%s%s\\%s", tempDir
,
123 kFolderName160Chars
, kFolderName160Chars
)) ||
124 !::CreateDirectoryW(mInnerDirBuffer
, nullptr)) {
127 mInnerDir
= mInnerDirBuffer
;
129 if (FAILED(::StringCbPrintfW(mTargetFileBuffer
, sizeof(mTargetFileBuffer
),
130 L
"\\\\?\\%s%s\\%s\\%s", tempDir
,
131 kFolderName160Chars
, kFolderName160Chars
,
132 aNewLeafNameAfterCopy
)) ||
133 !::CopyFileW(thisExe
.get(), mTargetFileBuffer
,
134 /*bFailIfExists*/ TRUE
)) {
137 mTargetFile
= mTargetFileBuffer
;
142 ::DeleteFileW(mTargetFile
);
145 ::RemoveDirectoryW(mInnerDir
);
148 ::RemoveDirectoryW(mOuterDir
);
152 operator const wchar_t*() const { return mTargetFile
; }
155 // Make sure module info retrieved from nt::PEHeaders is the same as one
156 // retrieved from GetModuleInformation API.
157 bool CompareModuleInfo(HMODULE aModuleForApi
, HMODULE aModuleForPEHeader
) {
158 MODULEINFO moduleInfo
;
159 if (!::GetModuleInformation(::GetCurrentProcess(), aModuleForApi
, &moduleInfo
,
160 sizeof(moduleInfo
))) {
161 printf("TEST-FAILED | NativeNt | GetModuleInformation failed - %08lx\n",
166 PEHeaders
headers(aModuleForPEHeader
);
168 printf("TEST-FAILED | NativeNt | Failed to instantiate PEHeaders\n");
172 Maybe
<Range
<const uint8_t>> bounds
= headers
.GetBounds();
174 printf("TEST-FAILED | NativeNt | PEHeaders::GetBounds failed\n");
178 if (bounds
->length() != moduleInfo
.SizeOfImage
) {
179 printf("TEST-FAILED | NativeNt | SizeOfImage does not match\n");
183 // GetModuleInformation sets EntryPoint to 0 for executables
184 // except the running self.
185 static const HMODULE sSelf
= ::GetModuleHandleW(nullptr);
186 if (aModuleForApi
!= sSelf
&&
187 !(headers
.GetFileCharacteristics() & IMAGE_FILE_DLL
)) {
188 if (moduleInfo
.EntryPoint
) {
190 "TEST-FAIL | NativeNt | "
191 "GetModuleInformation returned a non-zero entrypoint "
192 "for an executable\n");
196 // Cannot verify PEHeaders::GetEntryPoint.
200 // For a module whose entrypoint is 0 (e.g. ntdll.dll or win32u.dll),
201 // MODULEINFO::EntryPoint is set to 0, while PEHeaders::GetEntryPoint
202 // returns the imagebase (RVA=0).
203 intptr_t rvaEntryPoint
=
204 moduleInfo
.EntryPoint
205 ? reinterpret_cast<uintptr_t>(moduleInfo
.EntryPoint
) -
206 reinterpret_cast<uintptr_t>(moduleInfo
.lpBaseOfDll
)
208 if (rvaEntryPoint
< 0) {
209 printf("TEST-FAILED | NativeNt | MODULEINFO is invalid\n");
213 if (headers
.RVAToPtr
<FARPROC
>(rvaEntryPoint
) != headers
.GetEntryPoint()) {
214 printf("TEST-FAILED | NativeNt | Entrypoint does not match\n");
221 bool TestModuleInfo() {
222 UNICODE_STRING newLeafName
;
223 ::RtlInitUnicodeString(&newLeafName
,
224 L
"\u672D\u5E4C\u5473\u564C.\u30E9\u30FC\u30E1\u30F3");
226 LongNameModule
longNameModule(newLeafName
.Buffer
);
227 if (!longNameModule
) {
229 "TEST-FAILED | NativeNt | "
230 "Failed to copy the executable to a long directory path\n");
235 nsModuleHandle
module(::LoadLibraryW(longNameModule
));
237 bool detectedTarget
= false;
238 bool passedAllModules
= true;
239 auto moduleCallback
= [&](const wchar_t* aModulePath
, HMODULE aModule
) {
240 UNICODE_STRING modulePath
, moduleName
;
241 ::RtlInitUnicodeString(&modulePath
, aModulePath
);
242 GetLeafName(&moduleName
, &modulePath
);
243 if (::RtlEqualUnicodeString(&moduleName
, &newLeafName
,
244 /*aCaseInsensitive*/ TRUE
)) {
245 detectedTarget
= true;
248 if (!CompareModuleInfo(aModule
, aModule
)) {
249 passedAllModules
= false;
253 if (!mozilla::EnumerateProcessModules(moduleCallback
)) {
254 printf("TEST-FAILED | NativeNt | EnumerateProcessModules failed\n");
258 if (!detectedTarget
) {
260 "TEST-FAILED | NativeNt | "
261 "EnumerateProcessModules missed the target file\n");
265 if (!passedAllModules
) {
273 // Make sure PEHeaders works for a module loaded with LOAD_LIBRARY_AS_DATAFILE
274 // as well as a module loaded normally.
275 bool TestModuleLoadedAsData() {
276 const wchar_t kNewLeafName
[] = L
"\u03BC\u0061\u9EBA.txt";
278 LongNameModule
longNameModule(kNewLeafName
);
279 if (!longNameModule
) {
281 "TEST-FAILED | NativeNt | "
282 "Failed to copy the executable to a long directory path\n");
286 const wchar_t* kManualLoadModules
[] = {
292 for (const auto moduleName
: kManualLoadModules
) {
293 // Must load a module as data first,
294 nsModuleHandle
moduleAsData(::LoadLibraryExW(
296 LOAD_LIBRARY_AS_DATAFILE
| LOAD_LIBRARY_AS_IMAGE_RESOURCE
));
298 // then load a module normally to map it on a different address.
299 nsModuleHandle
module(::LoadLibraryW(moduleName
));
301 if (!CompareModuleInfo(module
.get(), moduleAsData
.get())) {
305 PEHeaders
peAsData(moduleAsData
.get());
306 PEHeaders
pe(module
.get());
307 if (!peAsData
|| !pe
) {
308 printf("TEST-FAIL | NativeNt | Failed to load the module\n");
312 if (peAsData
.RVAToPtr
<HMODULE
>(0) == pe
.RVAToPtr
<HMODULE
>(0)) {
314 "TEST-FAIL | NativeNt | "
315 "The module should have been mapped onto two different places\n");
319 const auto* pdb1
= peAsData
.GetPdbInfo();
320 const auto* pdb2
= pe
.GetPdbInfo();
322 if (pdb1
->pdbSignature
!= pdb2
->pdbSignature
||
323 pdb1
->pdbAge
!= pdb2
->pdbAge
||
324 strcmp(pdb1
->pdbFileName
, pdb2
->pdbFileName
)) {
326 "TEST-FAIL | NativeNt | "
327 "PDB info from the same module did not match.\n");
330 } else if (pdb1
|| pdb2
) {
332 "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
336 uint64_t version1
, version2
;
337 bool result1
= peAsData
.GetVersionInfo(version1
);
338 bool result2
= pe
.GetVersionInfo(version2
);
339 if (result1
&& result2
) {
340 if (version1
!= version2
) {
341 printf("TEST-FAIL | NativeNt | Version mismatch\n");
344 } else if (result1
|| result2
) {
346 "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
354 LauncherResult
<HMODULE
> GetModuleHandleFromLeafName(const wchar_t* aName
) {
356 ::RtlInitUnicodeString(&name
, aName
);
357 return nt::GetModuleHandleFromLeafName(name
);
360 // Need a non-inline function to bypass compiler optimization that the thread
361 // local storage pointer is cached in a register before accessing a thread-local
363 MOZ_NEVER_INLINE PVOID
SwapThreadLocalStoragePointer(PVOID aNewValue
) {
364 auto oldValue
= RtlGetThreadLocalStoragePointer();
365 RtlSetThreadLocalStoragePointerForTestingOnly(aNewValue
);
370 bool TestCheckStack() {
371 auto stackBase
= reinterpret_cast<uint8_t*>(RtlGetThreadStackBase());
372 auto stackLimit
= reinterpret_cast<uint8_t*>(RtlGetThreadStackLimit());
373 uint8_t* stackPointer
= nullptr;
374 asm volatile("mov %%rsp, %0;" : "=r"(stackPointer
));
375 if (!(stackLimit
< stackBase
&& stackLimit
<= stackPointer
&&
376 stackPointer
< stackBase
)) {
377 printf("TEST-FAIL | NativeNt | Stack addresses are not coherent.\n");
380 uintptr_t committedBytes
= stackPointer
- stackLimit
;
381 const uint32_t maxExtraCommittedBytes
= 0x10000;
382 if ((committedBytes
+ maxExtraCommittedBytes
) >
383 std::numeric_limits
<uint32_t>::max()) {
385 "TEST-FAIL | NativeNt | The stack limit is too high to perform the "
389 for (uint32_t extraSize
= 0; extraSize
< maxExtraCommittedBytes
;
391 CheckStack(static_cast<uint32_t>(committedBytes
) + extraSize
);
392 auto expectedNewLimit
= stackLimit
- ((extraSize
+ 0xFFF) & ~0xFFF);
393 if (expectedNewLimit
!= RtlGetThreadStackLimit()) {
395 "TEST-FAIL | NativeNt | CheckStack did not grow the stack "
396 "correctly (expected: %p, got: %p).\n",
397 expectedNewLimit
, RtlGetThreadStackLimit());
405 int wmain(int argc
, wchar_t* argv
[]) {
406 UNICODE_STRING normal
;
407 ::RtlInitUnicodeString(&normal
, kNormal
);
409 UNICODE_STRING hex12
;
410 ::RtlInitUnicodeString(&hex12
, kHex12
);
412 UNICODE_STRING hex16
;
413 ::RtlInitUnicodeString(&hex16
, kHex16
);
415 UNICODE_STRING hex24
;
416 ::RtlInitUnicodeString(&hex24
, kHex24
);
419 ::RtlInitUnicodeString(&hex8
, kHex8
);
421 UNICODE_STRING nonHex12
;
422 ::RtlInitUnicodeString(&nonHex12
, kNonHex12
);
424 UNICODE_STRING hex13
;
425 ::RtlInitUnicodeString(&hex13
, kHex13
);
427 UNICODE_STRING hex11
;
428 ::RtlInitUnicodeString(&hex11
, kHex11
);
430 UNICODE_STRING hex15
;
431 ::RtlInitUnicodeString(&hex15
, kHex15
);
433 UNICODE_STRING hex17
;
434 ::RtlInitUnicodeString(&hex17
, kHex17
);
436 UNICODE_STRING prefixedHex16
;
437 ::RtlInitUnicodeString(&prefixedHex16
, kPrefixedHex16
);
439 EXPECT_FAIL(Contains12DigitHexString
, normal
);
440 EXPECT_SUCCESS(Contains12DigitHexString
, hex12
);
441 EXPECT_FAIL(Contains12DigitHexString
, hex13
);
442 EXPECT_FAIL(Contains12DigitHexString
, hex11
);
443 EXPECT_FAIL(Contains12DigitHexString
, hex16
);
444 EXPECT_FAIL(Contains12DigitHexString
, nonHex12
);
446 EXPECT_FAIL(IsFileNameAtLeast16HexDigits
, normal
);
447 EXPECT_FAIL(IsFileNameAtLeast16HexDigits
, hex12
);
448 EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits
, hex24
);
449 EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits
, hex16
);
450 EXPECT_SUCCESS(IsFileNameAtLeast16HexDigits
, hex17
);
451 EXPECT_FAIL(IsFileNameAtLeast16HexDigits
, hex8
);
452 EXPECT_FAIL(IsFileNameAtLeast16HexDigits
, hex15
);
453 EXPECT_FAIL(IsFileNameAtLeast16HexDigits
, prefixedHex16
);
455 if (RtlGetProcessHeap() != ::GetProcessHeap()) {
456 printf("TEST-FAILED | NativeNt | RtlGetProcessHeap() is broken\n");
460 #ifdef HAVE_SEH_EXCEPTIONS
461 PVOID origTlsHead
= nullptr;
462 bool isExceptionThrown
= false;
463 // Touch sTlsData.get() several times to prevent the call to sTlsData.set()
464 // from being optimized out in PGO build.
465 printf("sTlsData#1 = %08x\n", getTlsData());
467 // Need to call SwapThreadLocalStoragePointer inside __try to make sure
468 // accessing sTlsData is caught by SEH. This is due to clang's design.
469 // https://bugs.llvm.org/show_bug.cgi?id=44174.
470 origTlsHead
= SwapThreadLocalStoragePointer(nullptr);
471 setTlsData(~kTlsDataValue
);
473 MOZ_SEH_EXCEPT(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
474 ? EXCEPTION_EXECUTE_HANDLER
475 : EXCEPTION_CONTINUE_SEARCH
) {
476 isExceptionThrown
= true;
478 SwapThreadLocalStoragePointer(origTlsHead
);
479 printf("sTlsData#2 = %08x\n", getTlsData());
480 setTlsData(kTlsDataValue
);
481 printf("sTlsData#3 = %08x\n", getTlsData());
482 if (!isExceptionThrown
|| getTlsData() != kTlsDataValue
) {
484 "TEST-FAILED | NativeNt | RtlGetThreadLocalStoragePointer() is "
490 if (RtlGetCurrentThreadId() != ::GetCurrentThreadId()) {
491 printf("TEST-FAILED | NativeNt | RtlGetCurrentThreadId() is broken\n");
495 const wchar_t kKernel32
[] = L
"kernel32.dll";
496 DWORD verInfoSize
= ::GetFileVersionInfoSizeW(kKernel32
, nullptr);
499 "TEST-FAILED | NativeNt | Call to GetFileVersionInfoSizeW failed with "
505 auto verInfoBuf
= MakeUnique
<char[]>(verInfoSize
);
507 if (!::GetFileVersionInfoW(kKernel32
, 0, verInfoSize
, verInfoBuf
.get())) {
509 "TEST-FAILED | NativeNt | Call to GetFileVersionInfoW failed with code "
516 VS_FIXEDFILEINFO
* fixedFileInfo
= nullptr;
517 if (!::VerQueryValueW(verInfoBuf
.get(), L
"\\", (LPVOID
*)&fixedFileInfo
,
520 "TEST-FAILED | NativeNt | Call to VerQueryValueW failed with code "
526 const uint64_t expectedVersion
=
527 (static_cast<uint64_t>(fixedFileInfo
->dwFileVersionMS
) << 32) |
528 static_cast<uint64_t>(fixedFileInfo
->dwFileVersionLS
);
530 PEHeaders
k32headers(::GetModuleHandleW(kKernel32
));
533 "TEST-FAILED | NativeNt | Failed parsing kernel32.dll's PE headers\n");
538 if (!k32headers
.GetVersionInfo(version
)) {
540 "TEST-FAILED | NativeNt | Unable to obtain version information from "
545 if (version
!= expectedVersion
) {
547 "TEST-FAILED | NativeNt | kernel32.dll's detected version "
548 "(0x%016llX) does not match expected version (0x%016llX)\n",
549 version
, expectedVersion
);
553 Maybe
<Span
<IMAGE_THUNK_DATA
>> iatThunks
=
554 k32headers
.GetIATThunksForModule("kernel32.dll");
557 "TEST-FAILED | NativeNt | Detected the IAT thunk for kernel32 "
558 "in kernel32.dll\n");
562 const mozilla::nt::CodeViewRecord70
* debugInfo
= k32headers
.GetPdbInfo();
565 "TEST-FAILED | NativeNt | Unable to obtain debug information from "
570 #ifndef WIN32 // failure on windows10x32
571 if (stricmp(debugInfo
->pdbFileName
, "kernel32.pdb")) {
573 "TEST-FAILED | NativeNt | Unexpected PDB filename "
574 "in kernel32.dll: %s\n",
575 debugInfo
->pdbFileName
);
580 PEHeaders
ntdllheaders(::GetModuleHandleW(L
"ntdll.dll"));
582 auto ntdllBoundaries
= ntdllheaders
.GetBounds();
583 if (!ntdllBoundaries
) {
585 "TEST-FAILED | NativeNt | "
586 "Unable to obtain the boundaries of ntdll.dll\n");
591 k32headers
.GetIATThunksForModule("ntdll.dll", ntdllBoundaries
.ptr());
594 "TEST-FAILED | NativeNt | Unable to find the IAT thunk for "
595 "ntdll.dll in kernel32.dll\n");
599 // To test the Ex version of API, we purposely get a real handle
600 // instead of a pseudo handle.
601 nsAutoHandle
process(
602 ::OpenProcess(PROCESS_QUERY_INFORMATION
, FALSE
, GetCurrentProcessId()));
604 printf("TEST-FAILED | NativeNt | OpenProcess() failed - %08lx\n",
609 // Test Null page, Heap, Mapped image, and Invalid handle
610 if (!TestVirtualQuery(process
, nullptr) || !TestVirtualQuery(process
, argv
) ||
611 !TestVirtualQuery(process
, kNormal
) ||
612 !TestVirtualQuery(nullptr, kNormal
)) {
616 auto moduleResult
= GetModuleHandleFromLeafName(kKernel32
);
617 if (moduleResult
.isErr() ||
618 moduleResult
.inspect() != k32headers
.template RVAToPtr
<HMODULE
>(0)) {
620 "TEST-FAILED | NativeNt | "
621 "GetModuleHandleFromLeafName returns a wrong value.\n");
625 moduleResult
= GetModuleHandleFromLeafName(L
"invalid");
626 if (moduleResult
.isOk()) {
628 "TEST-FAILED | NativeNt | "
629 "GetModuleHandleFromLeafName unexpectedly returns a value.\n");
633 if (!TestModuleInfo()) {
637 if (!TestModuleLoadedAsData()) {
642 if (!TestCheckStack()) {
647 printf("TEST-PASS | NativeNt | All tests ran successfully\n");