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 // This test makes sure mozilla::nt::PEExportSection can parse the export
8 // section of a local process, and a remote process even though it's
9 // modified by an external code.
11 #include "mozilla/CmdLineAndEnvUtils.h"
12 #include "mozilla/NativeNt.h"
13 #include "nsWindowsDllInterceptor.h"
18 #define EXPORT_FUNCTION_EQ(name, func) \
19 (GetProcAddress(imageBase, name) == reinterpret_cast<void*>(func))
21 #define VERIFY_EXPORT_FUNCTION(tables, name, expected, errorMessage) \
23 if (tables.GetProcAddress(name) != reinterpret_cast<void*>(expected)) { \
24 printf("TEST-FAILED | TestPEExportSection | %s", errorMessage); \
29 using namespace mozilla::nt
;
30 using mozilla::interceptor::MMPolicyInProcess
;
31 using mozilla::interceptor::MMPolicyOutOfProcess
;
32 using LocalPEExportSection
= PEExportSection
<MMPolicyInProcess
>;
33 using RemotePEExportSection
= PEExportSection
<MMPolicyOutOfProcess
>;
35 constexpr DWORD kEventTimeoutinMs
= 5000;
36 const wchar_t kProcessControlEventName
[] =
37 L
"TestPEExportSection.Process.Control.Event";
39 enum TestResult
: int {
45 // These strings start with the same keyword to make sure we don't do substring
46 // match. Moreover, kSecretFunctionInvalid is purposely longer than the
47 // combination of the other two strings and located in between the other two
48 // strings to effectively test binary search.
49 const char kSecretFunction
[] = "Secret";
50 const char kSecretFunctionInvalid
[] = "Secret invalid long name";
51 const char kSecretFunctionWithSuffix
[] = "Secret2";
53 const wchar_t* kNoModification
= L
"--NoModification";
54 const wchar_t* kNoExport
= L
"--NoExport";
55 const wchar_t* kModifyTableEntry
= L
"--ModifyTableEntry";
56 const wchar_t* kModifyTable
= L
"--ModifyTable";
57 const wchar_t* kModifyDirectoryEntry
= L
"--ModifyDirectoryEntry";
58 const wchar_t* kExportByOrdinal
= L
"--ExportByOrdinal";
60 // Use the global variable to pass the child process's error status to the
61 // parent process. We don't use a process's exit code to keep the test simple.
62 int gChildProcessStatus
= 0;
64 // These functions are exported by linker or export section tampering at
65 // runtime. Each of function bodies needs to be different to avoid ICF.
66 extern "C" __declspec(dllexport
) int Export1() { return 0; }
67 extern "C" __declspec(dllexport
) int Export2() { return 1; }
68 int SecretFunction1() { return 100; }
69 int SecretFunction2() { return 101; }
71 // This class allocates a writable region downstream of the mapped image
72 // and prepares it as a valid export section.
73 class ExportDirectoryPatcher final
{
74 static constexpr int kRegionAllocationTryLimit
= 100;
75 static constexpr int kNumOfTableEntries
= 2;
76 // VirtualAlloc sometimes fails if a desired base address is too small.
77 // Define a minimum desired base to reduce the number of allocation tries.
78 static constexpr uintptr_t kMinimumAllocationPoint
= 0x8000000;
80 struct ExportDirectory
{
81 IMAGE_EXPORT_DIRECTORY mDirectoryHeader
;
82 DWORD mExportAddressTable
[kNumOfTableEntries
];
83 DWORD mExportNameTable
[kNumOfTableEntries
];
84 WORD mExportOrdinalTable
[kNumOfTableEntries
];
85 char mNameBuffer1
[sizeof(kSecretFunction
)];
86 char mNameBuffer2
[sizeof(kSecretFunctionWithSuffix
)];
89 static DWORD
PtrToRVA(T aPtr
, uintptr_t aBase
) {
90 return reinterpret_cast<uintptr_t>(aPtr
) - aBase
;
93 explicit ExportDirectory(uintptr_t aImageBase
) : mDirectoryHeader
{} {
94 mDirectoryHeader
.Base
= 1;
95 mExportAddressTable
[0] = PtrToRVA(SecretFunction1
, aImageBase
);
96 mExportAddressTable
[1] = PtrToRVA(SecretFunction2
, aImageBase
);
97 mExportNameTable
[0] = PtrToRVA(mNameBuffer1
, aImageBase
);
98 mExportNameTable
[1] = PtrToRVA(mNameBuffer2
, aImageBase
);
99 mExportOrdinalTable
[0] = 0;
100 mExportOrdinalTable
[1] = 1;
101 strcpy(mNameBuffer1
, kSecretFunction
);
102 strcpy(mNameBuffer2
, kSecretFunctionWithSuffix
);
106 uintptr_t mImageBase
;
107 ExportDirectory
* mNewExportDirectory
;
109 DWORD
PtrToRVA(const void* aPtr
) const {
110 return reinterpret_cast<uintptr_t>(aPtr
) - mImageBase
;
114 explicit ExportDirectoryPatcher(HMODULE aModule
)
115 : mImageBase(PEHeaders::HModuleToBaseAddr
<uintptr_t>(aModule
)),
116 mNewExportDirectory(nullptr) {
118 ::GetSystemInfo(&si
);
120 int numPagesRequired
= ((sizeof(ExportDirectory
) - 1) / si
.dwPageSize
) + 1;
122 uintptr_t desiredBase
= mImageBase
+ si
.dwAllocationGranularity
;
123 desiredBase
= std::max(desiredBase
, kMinimumAllocationPoint
);
125 for (int i
= 0; i
< kRegionAllocationTryLimit
; ++i
) {
127 ::VirtualAlloc(reinterpret_cast<void*>(desiredBase
),
128 numPagesRequired
* si
.dwPageSize
,
129 MEM_COMMIT
| MEM_RESERVE
, PAGE_READWRITE
);
131 // Use the end of a allocated page as ExportDirectory in order to test
132 // the boundary between a commit page and a non-commited page.
133 allocated
= reinterpret_cast<uint8_t*>(allocated
) +
134 (numPagesRequired
* si
.dwPageSize
) -
135 sizeof(ExportDirectory
);
136 mNewExportDirectory
= new (allocated
) ExportDirectory(mImageBase
);
140 desiredBase
+= si
.dwAllocationGranularity
;
143 gChildProcessStatus
= kTestSkip
;
145 "TEST-SKIP | TestPEExportSection | "
146 "Giving up finding an allocatable space following the mapped image.\n");
149 ~ExportDirectoryPatcher() {
150 // Intentionally leave mNewExportDirectory leaked to keep a patched data
151 // available until the process is terminated.
154 explicit operator bool() const { return !!mNewExportDirectory
; }
156 void PopulateDirectory(IMAGE_EXPORT_DIRECTORY
& aOutput
) const {
157 aOutput
.NumberOfFunctions
= aOutput
.NumberOfNames
= kNumOfTableEntries
;
158 aOutput
.AddressOfFunctions
=
159 PtrToRVA(mNewExportDirectory
->mExportAddressTable
);
160 aOutput
.AddressOfNames
= PtrToRVA(mNewExportDirectory
->mExportNameTable
);
161 aOutput
.AddressOfNameOrdinals
=
162 PtrToRVA(mNewExportDirectory
->mExportOrdinalTable
);
165 void PopulateDirectoryEntry(IMAGE_DATA_DIRECTORY
& aOutput
) const {
166 PopulateDirectory(mNewExportDirectory
->mDirectoryHeader
);
167 aOutput
.VirtualAddress
= PtrToRVA(&mNewExportDirectory
->mDirectoryHeader
);
168 aOutput
.Size
= sizeof(ExportDirectory
);
172 // This exports SecretFunction1 as "Export1" by replacing an entry of the
173 // export address table.
174 void ModifyExportAddressTableEntry() {
175 MMPolicyInProcess policy
;
176 HMODULE imageBase
= ::GetModuleHandleW(nullptr);
177 auto ourExe
= LocalPEExportSection::Get(imageBase
, policy
);
179 auto addressTableEntry
=
180 const_cast<DWORD
*>(ourExe
.FindExportAddressTableEntry("Export1"));
181 if (!addressTableEntry
) {
182 gChildProcessStatus
= kTestFail
;
186 mozilla::AutoVirtualProtect
protection(
187 addressTableEntry
, sizeof(*addressTableEntry
), PAGE_READWRITE
);
189 gChildProcessStatus
= kTestFail
;
193 *addressTableEntry
= reinterpret_cast<uintptr_t>(SecretFunction1
) -
194 PEHeaders::HModuleToBaseAddr
<uintptr_t>(imageBase
);
196 if (!EXPORT_FUNCTION_EQ("Export1", SecretFunction1
) ||
197 !EXPORT_FUNCTION_EQ("Export2", Export2
)) {
198 gChildProcessStatus
= kTestFail
;
202 // This switches the entire address table into one exporting SecretFunction1
203 // and SecretFunction2.
204 void ModifyExportAddressTable() {
205 MMPolicyInProcess policy
;
206 HMODULE imageBase
= ::GetModuleHandleW(nullptr);
207 auto ourExe
= LocalPEExportSection::Get(imageBase
, policy
);
209 auto exportDirectory
= ourExe
.GetExportDirectory();
210 if (!exportDirectory
) {
211 gChildProcessStatus
= kTestFail
;
215 mozilla::AutoVirtualProtect
protection(
216 exportDirectory
, sizeof(*exportDirectory
), PAGE_READWRITE
);
218 gChildProcessStatus
= kTestFail
;
222 ExportDirectoryPatcher
patcher(imageBase
);
227 patcher
.PopulateDirectory(*exportDirectory
);
229 if (GetProcAddress(imageBase
, "Export1") ||
230 GetProcAddress(imageBase
, "Export2") ||
231 !EXPORT_FUNCTION_EQ(kSecretFunction
, SecretFunction1
) ||
232 !EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix
, SecretFunction2
)) {
233 gChildProcessStatus
= kTestFail
;
237 // This hides all export functions by setting the table size to 0.
238 void HideExportSection() {
239 HMODULE imageBase
= ::GetModuleHandleW(nullptr);
240 PEHeaders
ourExe(imageBase
);
243 ourExe
.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT
);
245 mozilla::AutoVirtualProtect
protection(sectionTable
, sizeof(*sectionTable
),
248 gChildProcessStatus
= kTestFail
;
252 sectionTable
->VirtualAddress
= sectionTable
->Size
= 0;
254 if (GetProcAddress(imageBase
, "Export1") ||
255 GetProcAddress(imageBase
, "Export2")) {
256 gChildProcessStatus
= kTestFail
;
260 // This makes the export directory entry point to a new export section
261 // which exports SecretFunction1 and SecretFunction2.
262 void ModifyExportDirectoryEntry() {
263 HMODULE imageBase
= ::GetModuleHandleW(nullptr);
264 PEHeaders
ourExe(imageBase
);
267 ourExe
.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT
);
269 mozilla::AutoVirtualProtect
protection(sectionTable
, sizeof(*sectionTable
),
272 gChildProcessStatus
= kTestFail
;
276 ExportDirectoryPatcher
patcher(imageBase
);
281 patcher
.PopulateDirectoryEntry(*sectionTable
);
283 if (GetProcAddress(imageBase
, "Export1") ||
284 GetProcAddress(imageBase
, "Export2") ||
285 !EXPORT_FUNCTION_EQ(kSecretFunction
, SecretFunction1
) ||
286 !EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix
, SecretFunction2
)) {
287 gChildProcessStatus
= kTestFail
;
291 // This exports functions only by Ordinal by hiding the export name table.
292 void ExportByOrdinal() {
293 ModifyExportDirectoryEntry();
294 if (gChildProcessStatus
!= kTestSuccess
) {
298 MMPolicyInProcess policy
;
299 HMODULE imageBase
= ::GetModuleHandleW(nullptr);
300 auto ourExe
= LocalPEExportSection::Get(imageBase
, policy
);
302 auto exportDirectory
= ourExe
.GetExportDirectory();
303 if (!exportDirectory
) {
304 gChildProcessStatus
= kTestFail
;
308 exportDirectory
->NumberOfNames
= 0;
310 if (GetProcAddress(imageBase
, "Export1") ||
311 GetProcAddress(imageBase
, "Export2") ||
312 GetProcAddress(imageBase
, kSecretFunction
) ||
313 GetProcAddress(imageBase
, kSecretFunctionWithSuffix
) ||
314 !EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(1), SecretFunction1
) ||
315 !EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(2), SecretFunction2
)) {
316 gChildProcessStatus
= kTestFail
;
320 class ChildProcess final
{
321 nsAutoHandle mChildProcess
;
322 nsAutoHandle mChildMainThread
;
325 static int Main(const nsAutoHandle
& aEvent
, const wchar_t* aOption
) {
326 if (wcscmp(aOption
, kNoModification
) == 0) {
328 } else if (wcscmp(aOption
, kNoExport
) == 0) {
330 } else if (wcscmp(aOption
, kModifyTableEntry
) == 0) {
331 ModifyExportAddressTableEntry();
332 } else if (wcscmp(aOption
, kModifyTable
) == 0) {
333 ModifyExportAddressTable();
334 } else if (wcscmp(aOption
, kModifyDirectoryEntry
) == 0) {
335 ModifyExportDirectoryEntry();
336 } else if (wcscmp(aOption
, kExportByOrdinal
) == 0) {
340 // Letting the parent process know the child process is ready.
343 // The child process does not exit itself. It's force terminated by
344 // the parent process when all tests are done.
350 ChildProcess(const wchar_t* aExecutable
, const wchar_t* aOption
,
351 const nsAutoHandle
& aEvent
, const nsAutoHandle
& aJob
) {
352 const wchar_t* childArgv
[] = {aExecutable
, aOption
};
354 mozilla::MakeCommandLine(mozilla::ArrayLength(childArgv
), childArgv
));
356 STARTUPINFOW si
= {sizeof(si
)};
357 PROCESS_INFORMATION pi
;
358 BOOL ok
= ::CreateProcessW(aExecutable
, cmdLine
.get(), nullptr, nullptr,
359 FALSE
, 0, nullptr, nullptr, &si
, &pi
);
362 "TEST-FAILED | TestPEExportSection | "
363 "CreateProcessW falied - %08lx.\n",
368 if (aJob
&& !::AssignProcessToJobObject(aJob
, pi
.hProcess
)) {
370 "TEST-FAILED | TestPEExportSection | "
371 "AssignProcessToJobObject falied - %08lx.\n",
373 ::TerminateProcess(pi
.hProcess
, 1);
377 // Wait until requested modification is done in the child process.
378 if (::WaitForSingleObject(aEvent
, kEventTimeoutinMs
) != WAIT_OBJECT_0
) {
380 "TEST-FAILED | TestPEExportSection | "
381 "Child process was not ready in time.\n");
385 mChildProcess
.own(pi
.hProcess
);
386 mChildMainThread
.own(pi
.hThread
);
389 ~ChildProcess() { ::TerminateProcess(mChildProcess
, 0); }
391 operator HANDLE() const { return mChildProcess
; }
393 TestResult
GetStatus() const {
394 TestResult status
= kTestSuccess
;
395 if (!::ReadProcessMemory(mChildProcess
, &gChildProcessStatus
, &status
,
396 sizeof(status
), nullptr)) {
399 "TEST-FAILED | TestPEExportSection | "
400 "ReadProcessMemory failed - %08lx\n",
407 template <typename MMPolicy
>
408 TestResult
BasicTest(const MMPolicy
& aMMPolicy
) {
409 const bool isAppHelpLoaded
= ::GetModuleHandleW(L
"apphelp.dll");
411 // Use ntdll.dll because it does not have any forwarder RVA.
412 HMODULE ntdllImageBase
= ::GetModuleHandleW(L
"ntdll.dll");
413 auto ntdllExports
= PEExportSection
<MMPolicy
>::Get(ntdllImageBase
, aMMPolicy
);
415 auto exportDir
= ntdllExports
.GetExportDirectory();
417 ntdllExports
.template RVAToPtr
<const PDWORD
>(exportDir
->AddressOfNames
);
418 for (DWORD i
= 0; i
< exportDir
->NumberOfNames
; ++i
) {
420 ntdllExports
.template RVAToPtr
<const char*>(tableOfNames
[i
]);
422 if (isAppHelpLoaded
&& strcmp(name
, "NtdllDefWindowProc_W") == 0) {
423 // In this case, GetProcAddress will return
424 // apphelp!DWM8AND16BitHook_DefWindowProcW.
428 auto funcEntry
= ntdllExports
.FindExportAddressTableEntry(name
);
429 if (ntdllExports
.template RVAToPtr
<const void*>(*funcEntry
) !=
430 ::GetProcAddress(ntdllImageBase
, name
)) {
432 "TEST-FAILED | TestPEExportSection | "
433 "FindExportAddressTableEntry did not resolve ntdll!%s.\n",
439 for (DWORD i
= 0; i
< 0x10000; i
+= 0x10) {
440 if (ntdllExports
.GetProcAddress(MAKEINTRESOURCE(i
)) !=
441 ::GetProcAddress(ntdllImageBase
, MAKEINTRESOURCE(i
))) {
443 "TEST-FAILED | TestPEExportSection | "
444 "GetProcAddress did not resolve ntdll!Ordinal#%lu.\n",
450 // Test a known forwarder RVA.
451 auto k32Exports
= PEExportSection
<MMPolicy
>::Get(
452 ::GetModuleHandleW(L
"kernel32.dll"), aMMPolicy
);
453 if (k32Exports
.FindExportAddressTableEntry("HeapAlloc")) {
455 "TEST-FAILED | TestPEExportSection | "
456 "kernel32!HeapAlloc should be forwarded to ntdll!RtlAllocateHeap.\n");
460 // Test invalid names.
461 if (k32Exports
.FindExportAddressTableEntry("Invalid name") ||
462 k32Exports
.FindExportAddressTableEntry("")) {
464 "TEST-FAILED | TestPEExportSection | "
465 "FindExportAddressTableEntry should return "
466 "nullptr for a non-existent name.\n");
473 TestResult
RunChildProcessTest(
474 const wchar_t* aExecutable
, const wchar_t* aOption
,
475 const nsAutoHandle
& aEvent
, const nsAutoHandle
& aJob
,
476 TestResult (*aTestCallback
)(const RemotePEExportSection
&)) {
477 ChildProcess
childProcess(aExecutable
, aOption
, aEvent
, aJob
);
482 auto result
= childProcess
.GetStatus();
483 if (result
!= kTestSuccess
) {
487 MMPolicyOutOfProcess
policy(childProcess
);
489 // One time is enough to run BasicTest in the child process.
490 static TestResult oneTimeResult
= BasicTest
<MMPolicyOutOfProcess
>(policy
);
491 if (oneTimeResult
!= kTestSuccess
) {
492 return oneTimeResult
;
495 auto exportTableChild
=
496 RemotePEExportSection::Get(::GetModuleHandleW(nullptr), policy
);
497 return aTestCallback(exportTableChild
);
500 mozilla::LauncherResult
<nsReturnRef
<HANDLE
>> CreateJobToLimitProcessLifetime() {
502 PEHeaders
ntdllHeaders(::GetModuleHandleW(L
"ntdll.dll"));
503 if (!ntdllHeaders
.GetVersionInfo(version
)) {
505 "TEST-FAILED | TestPEExportSection | "
506 "Unable to obtain version information from ntdll.dll\n");
507 return LAUNCHER_ERROR_FROM_LAST();
510 constexpr uint64_t kWin8
= 0x60002ull
<< 32;
513 if (version
< kWin8
) {
514 // Since a process can be associated only with a single job in Win7 or
515 // older and this test program is already assigned with a job by
516 // infrastructure, we cannot use a job.
520 job
.own(::CreateJobObject(nullptr, nullptr));
523 "TEST-FAILED | TestPEExportSection | "
524 "CreateJobObject falied - %08lx.\n",
526 return LAUNCHER_ERROR_FROM_LAST();
529 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo
= {};
530 jobInfo
.BasicLimitInformation
.LimitFlags
= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
;
532 if (!::SetInformationJobObject(job
, JobObjectExtendedLimitInformation
,
533 &jobInfo
, sizeof(jobInfo
))) {
535 "TEST-FAILED | TestPEExportSection | "
536 "SetInformationJobObject falied - %08lx.\n",
538 return LAUNCHER_ERROR_FROM_LAST();
544 extern "C" int wmain(int argc
, wchar_t* argv
[]) {
545 nsAutoHandle
controlEvent(
546 ::CreateEventW(nullptr, FALSE
, FALSE
, kProcessControlEventName
));
549 return ChildProcess::Main(controlEvent
, argv
[1]);
554 "TEST-FAILED | TestPEExportSection | "
555 "Invalid arguments.\n");
559 MMPolicyInProcess policy
;
560 if (BasicTest
<MMPolicyInProcess
>(policy
)) {
564 auto exportTableSelf
=
565 LocalPEExportSection::Get(::GetModuleHandleW(nullptr), policy
);
566 if (!exportTableSelf
) {
568 "TEST-FAILED | TestPEExportSection | "
569 "LocalPEExportSection::Get failed.\n");
573 VERIFY_EXPORT_FUNCTION(exportTableSelf
, "Export1", Export1
,
574 "Local | Export1 was not exported.\n");
575 VERIFY_EXPORT_FUNCTION(exportTableSelf
, "Export2", Export2
,
576 "Local | Export2 was not exported.\n");
577 VERIFY_EXPORT_FUNCTION(
578 exportTableSelf
, "Invalid name", 0,
579 "Local | GetProcAddress should return nullptr for an invalid name.\n");
581 // We'll add the child process to a job so that, in the event of a failure in
582 // this parent process, the child process will be automatically terminated.
583 auto probablyJob
= CreateJobToLimitProcessLifetime();
584 if (probablyJob
.isErr()) {
588 nsAutoHandle
job(probablyJob
.unwrap());
590 auto result
= RunChildProcessTest(
591 argv
[0], kNoModification
, controlEvent
, job
,
592 [](const RemotePEExportSection
& aTables
) {
593 VERIFY_EXPORT_FUNCTION(aTables
, "Export1", Export1
,
594 "NoModification | Export1 was not exported.\n");
595 VERIFY_EXPORT_FUNCTION(aTables
, "Export2", Export2
,
596 "NoModification | Export2 was not exported.\n");
599 if (result
== kTestFail
) {
603 result
= RunChildProcessTest(
604 argv
[0], kNoExport
, controlEvent
, job
,
605 [](const RemotePEExportSection
& aTables
) {
606 VERIFY_EXPORT_FUNCTION(aTables
, "Export1", 0,
607 "NoExport | Export1 was exported.\n");
608 VERIFY_EXPORT_FUNCTION(aTables
, "Export2", 0,
609 "NoExport | Export2 was exported.\n");
612 if (result
== kTestFail
) {
616 result
= RunChildProcessTest(
617 argv
[0], kModifyTableEntry
, controlEvent
, job
,
618 [](const RemotePEExportSection
& aTables
) {
619 VERIFY_EXPORT_FUNCTION(
620 aTables
, "Export1", SecretFunction1
,
621 "ModifyTableEntry | SecretFunction1 was not exported.\n");
622 VERIFY_EXPORT_FUNCTION(
623 aTables
, "Export2", Export2
,
624 "ModifyTableEntry | Export2 was not exported.\n");
627 if (result
== kTestFail
) {
631 result
= RunChildProcessTest(
632 argv
[0], kModifyTable
, controlEvent
, job
,
633 [](const RemotePEExportSection
& aTables
) {
634 VERIFY_EXPORT_FUNCTION(aTables
, "Export1", 0,
635 "ModifyTable | Export1 was exported.\n");
636 VERIFY_EXPORT_FUNCTION(aTables
, "Export2", 0,
637 "ModifyTable | Export2 was exported.\n");
638 VERIFY_EXPORT_FUNCTION(
639 aTables
, kSecretFunction
, SecretFunction1
,
640 "ModifyTable | SecretFunction1 was not exported.\n");
641 VERIFY_EXPORT_FUNCTION(
642 aTables
, kSecretFunctionWithSuffix
, SecretFunction2
,
643 "ModifyTable | SecretFunction2 was not exported.\n");
644 VERIFY_EXPORT_FUNCTION(
645 aTables
, kSecretFunctionInvalid
, 0,
646 "ModifyTable | kSecretFunctionInvalid was exported.\n");
649 if (result
== kTestFail
) {
653 result
= RunChildProcessTest(
654 argv
[0], kModifyDirectoryEntry
, controlEvent
, job
,
655 [](const RemotePEExportSection
& aTables
) {
656 VERIFY_EXPORT_FUNCTION(
657 aTables
, "Export1", 0,
658 "ModifyDirectoryEntry | Export1 was exported.\n");
659 VERIFY_EXPORT_FUNCTION(
660 aTables
, "Export2", 0,
661 "ModifyDirectoryEntry | Export2 was exported.\n");
662 VERIFY_EXPORT_FUNCTION(
663 aTables
, kSecretFunction
, SecretFunction1
,
664 "ModifyDirectoryEntry | SecretFunction1 was not exported.\n");
665 VERIFY_EXPORT_FUNCTION(
666 aTables
, kSecretFunctionWithSuffix
, SecretFunction2
,
667 "ModifyDirectoryEntry | SecretFunction2 was not exported.\n");
668 VERIFY_EXPORT_FUNCTION(
669 aTables
, kSecretFunctionInvalid
, 0,
670 "ModifyDirectoryEntry | kSecretFunctionInvalid was exported.\n");
673 if (result
== kTestFail
) {
677 result
= RunChildProcessTest(
678 argv
[0], kExportByOrdinal
, controlEvent
, job
,
679 [](const RemotePEExportSection
& aTables
) {
680 VERIFY_EXPORT_FUNCTION(aTables
, "Export1", 0,
681 "ExportByOrdinal | Export1 was exported.\n");
682 VERIFY_EXPORT_FUNCTION(aTables
, "Export2", 0,
683 "ExportByOrdinal | Export2 was exported.\n");
684 VERIFY_EXPORT_FUNCTION(
685 aTables
, kSecretFunction
, 0,
686 "ModifyDirectoryEntry | kSecretFunction was exported by name.\n");
687 VERIFY_EXPORT_FUNCTION(
688 aTables
, kSecretFunctionWithSuffix
, 0,
689 "ModifyDirectoryEntry | "
690 "kSecretFunctionWithSuffix was exported by name.\n");
691 VERIFY_EXPORT_FUNCTION(
692 aTables
, MAKEINTRESOURCE(1), SecretFunction1
,
693 "ModifyDirectoryEntry | "
694 "kSecretFunction was not exported by ordinal.\n");
695 VERIFY_EXPORT_FUNCTION(
696 aTables
, MAKEINTRESOURCE(2), SecretFunction2
,
697 "ModifyDirectoryEntry | "
698 "kSecretFunctionWithSuffix was not exported by ordinal.\n");
701 if (result
== kTestFail
) {