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"
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); \
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
,
58 if (result1
!= result2
) {
59 printf("TEST-FAILED | NativeNt | The returned values mismatch\n");
68 if (memcmp(&info1
, &info2
, result1
) != 0) {
69 printf("TEST-FAILED | NativeNt | The returned structures mismatch\n");
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
;
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();
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
) {
112 if (FAILED(::StringCbPrintfW(mOuterDirBuffer
, sizeof(mOuterDirBuffer
),
113 L
"\\\\?\\%s%s", tempDir
,
114 kFolderName160Chars
)) ||
115 !::CreateDirectoryW(mOuterDirBuffer
, nullptr)) {
118 mOuterDir
= mOuterDirBuffer
;
120 if (FAILED(::StringCbPrintfW(mInnerDirBuffer
, sizeof(mInnerDirBuffer
),
121 L
"\\\\?\\%s%s\\%s", tempDir
,
122 kFolderName160Chars
, kFolderName160Chars
)) ||
123 !::CreateDirectoryW(mInnerDirBuffer
, nullptr)) {
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
)) {
136 mTargetFile
= mTargetFileBuffer
;
141 ::DeleteFileW(mTargetFile
);
144 ::RemoveDirectoryW(mInnerDir
);
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",
165 PEHeaders
headers(aModuleForPEHeader
);
167 printf("TEST-FAILED | NativeNt | Failed to instantiate PEHeaders\n");
171 Maybe
<Range
<const uint8_t>> bounds
= headers
.GetBounds();
173 printf("TEST-FAILED | NativeNt | PEHeaders::GetBounds failed\n");
177 if (bounds
->length() != moduleInfo
.SizeOfImage
) {
178 printf("TEST-FAILED | NativeNt | SizeOfImage does not match\n");
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
) {
189 "TEST-FAIL | NativeNt | "
190 "GetModuleInformation returned a non-zero entrypoint "
191 "for an executable\n");
195 // Cannot verify PEHeaders::GetEntryPoint.
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
)
207 if (rvaEntryPoint
< 0) {
208 printf("TEST-FAILED | NativeNt | MODULEINFO is invalid\n");
212 if (headers
.RVAToPtr
<FARPROC
>(rvaEntryPoint
) != headers
.GetEntryPoint()) {
213 printf("TEST-FAILED | NativeNt | Entrypoint does not match\n");
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
) {
228 "TEST-FAILED | NativeNt | "
229 "Failed to copy the executable to a long directory path\n");
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");
257 if (!detectedTarget
) {
259 "TEST-FAILED | NativeNt | "
260 "EnumerateProcessModules missed the target file\n");
264 if (!passedAllModules
) {
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
) {
280 "TEST-FAILED | NativeNt | "
281 "Failed to copy the executable to a long directory path\n");
285 const wchar_t* kManualLoadModules
[] = {
291 for (const auto moduleName
: kManualLoadModules
) {
292 // Must load a module as data first,
293 nsModuleHandle
moduleAsData(::LoadLibraryExW(
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())) {
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");
311 if (peAsData
.RVAToPtr
<HMODULE
>(0) == pe
.RVAToPtr
<HMODULE
>(0)) {
313 "TEST-FAIL | NativeNt | "
314 "The module should have been mapped onto two different places\n");
318 const auto* pdb1
= peAsData
.GetPdbInfo();
319 const auto* pdb2
= pe
.GetPdbInfo();
321 if (pdb1
->pdbSignature
!= pdb2
->pdbSignature
||
322 pdb1
->pdbAge
!= pdb2
->pdbAge
||
323 strcmp(pdb1
->pdbFileName
, pdb2
->pdbFileName
)) {
325 "TEST-FAIL | NativeNt | "
326 "PDB info from the same module did not match.\n");
329 } else if (pdb1
|| pdb2
) {
331 "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
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");
343 } else if (result1
|| result2
) {
345 "TEST-FAIL | NativeNt | Failed to get PDB info from the module.\n");
353 LauncherResult
<HMODULE
> GetModuleHandleFromLeafName(const wchar_t* aName
) {
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
362 MOZ_NEVER_INLINE PVOID
SwapThreadLocalStoragePointer(PVOID aNewValue
) {
363 auto oldValue
= RtlGetThreadLocalStoragePointer();
364 RtlSetThreadLocalStoragePointerForTestingOnly(aNewValue
);
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
);
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");
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());
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
) {
447 "TEST-FAILED | NativeNt | RtlGetThreadLocalStoragePointer() is "
453 if (RtlGetCurrentThreadId() != ::GetCurrentThreadId()) {
454 printf("TEST-FAILED | NativeNt | RtlGetCurrentThreadId() is broken\n");
458 const wchar_t kKernel32
[] = L
"kernel32.dll";
459 DWORD verInfoSize
= ::GetFileVersionInfoSizeW(kKernel32
, nullptr);
462 "TEST-FAILED | NativeNt | Call to GetFileVersionInfoSizeW failed with "
468 auto verInfoBuf
= MakeUnique
<char[]>(verInfoSize
);
470 if (!::GetFileVersionInfoW(kKernel32
, 0, verInfoSize
, verInfoBuf
.get())) {
472 "TEST-FAILED | NativeNt | Call to GetFileVersionInfoW failed with code "
479 VS_FIXEDFILEINFO
* fixedFileInfo
= nullptr;
480 if (!::VerQueryValueW(verInfoBuf
.get(), L
"\\", (LPVOID
*)&fixedFileInfo
,
483 "TEST-FAILED | NativeNt | Call to VerQueryValueW failed with code "
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
));
496 "TEST-FAILED | NativeNt | Failed parsing kernel32.dll's PE headers\n");
501 if (!k32headers
.GetVersionInfo(version
)) {
503 "TEST-FAILED | NativeNt | Unable to obtain version information from "
508 if (version
!= expectedVersion
) {
510 "TEST-FAILED | NativeNt | kernel32.dll's detected version "
511 "(0x%016llX) does not match expected version (0x%016llX)\n",
512 version
, expectedVersion
);
516 Maybe
<Span
<IMAGE_THUNK_DATA
>> iatThunks
=
517 k32headers
.GetIATThunksForModule("kernel32.dll");
520 "TEST-FAILED | NativeNt | Detected the IAT thunk for kernel32 "
521 "in kernel32.dll\n");
525 const mozilla::nt::CodeViewRecord70
* debugInfo
= k32headers
.GetPdbInfo();
528 "TEST-FAILED | NativeNt | Unable to obtain debug information from "
533 #ifndef WIN32 // failure on windows10x32
534 if (stricmp(debugInfo
->pdbFileName
, "kernel32.pdb")) {
536 "TEST-FAILED | NativeNt | Unexpected PDB filename "
537 "in kernel32.dll: %s\n",
538 debugInfo
->pdbFileName
);
543 PEHeaders
ntdllheaders(::GetModuleHandleW(L
"ntdll.dll"));
545 auto ntdllBoundaries
= ntdllheaders
.GetBounds();
546 if (!ntdllBoundaries
) {
548 "TEST-FAILED | NativeNt | "
549 "Unable to obtain the boundaries of ntdll.dll\n");
554 k32headers
.GetIATThunksForModule("ntdll.dll", ntdllBoundaries
.ptr());
557 "TEST-FAILED | NativeNt | Unable to find the IAT thunk for "
558 "ntdll.dll in kernel32.dll\n");
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()));
567 printf("TEST-FAILED | NativeNt | OpenProcess() failed - %08lx\n",
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
)) {
579 auto moduleResult
= GetModuleHandleFromLeafName(kKernel32
);
580 if (moduleResult
.isErr() ||
581 moduleResult
.inspect() != k32headers
.template RVAToPtr
<HMODULE
>(0)) {
583 "TEST-FAILED | NativeNt | "
584 "GetModuleHandleFromLeafName returns a wrong value.\n");
588 moduleResult
= GetModuleHandleFromLeafName(L
"invalid");
589 if (moduleResult
.isOk()) {
591 "TEST-FAILED | NativeNt | "
592 "GetModuleHandleFromLeafName unexpectedly returns a value.\n");
596 if (!TestModuleInfo()) {
600 if (!TestModuleLoadedAsData()) {
604 printf("TEST-PASS | NativeNt | All tests ran successfully\n");