5 #include <mach-o/loader.h>
13 #include <TargetConditionals.h>
15 #include "ClosureFileSystemPhysical.h"
16 #include "MachOAnalyzer.h"
17 #include "MachOFile.h"
19 #include "../testing/test-cases/kernel-test-runner.h"
21 const bool isLoggingEnabled
= false;
23 int entryFunc(const TestRunnerFunctions
* funcs
);
24 typedef __typeof(&entryFunc
) EntryFuncTy
;
26 TestRunnerFunctions testFuncs
= {
28 .mhs
= { nullptr, nullptr, nullptr, nullptr },
29 .basePointers
= { nullptr, nullptr, nullptr, nullptr },
35 .testTimeout
= &_TIMEOUT
,
39 const dyld3::MachOAnalyzer
* ma
= nullptr;
40 // base pointer is the same as 'ma' when the binary has __TEXT first,
41 // but will point at where we mapped __DATA if building a reverse auxKC.
42 const void* basePointer
= nullptr;
45 LoadedMachO
loadPath(const char* binaryPath
) {
46 __block Diagnostics diag
;
47 dyld3::closure::FileSystemPhysical fileSystem
;
48 dyld3::closure::LoadedFileInfo info
;
49 char realerPath
[MAXPATHLEN
];
50 __block
bool printedError
= false;
51 if (!fileSystem
.loadFile(binaryPath
, info
, realerPath
, ^(const char* format
, ...) {
52 fprintf(stderr
, "run-static: ");
54 va_start(list
, format
);
55 vfprintf(stderr
, format
, list
);
60 fprintf(stderr
, "run-static: %s: file not found\n", binaryPath
);
64 const char* currentArchName
= dyld3::MachOFile::currentArchName();
65 const dyld3::GradedArchs
& currentArchs
= dyld3::GradedArchs::forName(currentArchName
);
66 __block
const dyld3::MachOFile
* mf
= nullptr;
67 __block
uint64_t sliceOffset
= 0;
68 if ( dyld3::FatFile::isFatFile(info
.fileContent
) ) {
69 const dyld3::FatFile
* ff
= (dyld3::FatFile
*)info
.fileContent
;
70 ff
->forEachSlice(diag
, info
.fileContentLen
, ^(uint32_t sliceCpuType
, uint32_t sliceCpuSubType
,
71 const void* sliceStart
, uint64_t sliceSize
, bool& stop
) {
72 const dyld3::MachOFile
* sliceMF
= (dyld3::MachOFile
*)sliceStart
;
73 if ( currentArchs
.grade(sliceMF
->cputype
, sliceMF
->cpusubtype
, false) != 0 ) {
75 sliceOffset
= (uint64_t)mf
- (uint64_t)ff
;
81 if ( diag
.hasError() ) {
82 fprintf(stderr
, "Error: %s\n", diag
.errorMessage());
83 return { nullptr, nullptr };
86 if ( mf
== nullptr ) {
87 fprintf(stderr
, "Could not use binary '%s' because it does not contain a slice compatible with host '%s'\n",
88 binaryPath
, currentArchName
);
89 return { nullptr, nullptr };
92 mf
= (dyld3::MachOFile
*)info
.fileContent
;
93 if ( !mf
->isMachO(diag
, info
.sliceLen
) ) {
94 fprintf(stderr
, "Could not use binary '%s' because '%s'\n", binaryPath
, diag
.errorMessage());
95 return { nullptr, nullptr };
98 if ( currentArchs
.grade(mf
->cputype
, mf
->cpusubtype
, false) == 0 ) {
99 fprintf(stderr
, "Could not use binary '%s' because 'incompatible arch'\n", binaryPath
);
100 return { nullptr, nullptr };
104 if ( !mf
->isFileSet() ) {
105 fprintf(stderr
, "Could not use binary '%s' because 'it is not a static executable'\n", binaryPath
);
106 return { nullptr, nullptr };
109 uint64_t mappedSize
= ((dyld3::MachOAnalyzer
*)mf
)->mappedSize();
110 vm_address_t mappedAddr
;
111 if ( ::vm_allocate(mach_task_self(), &mappedAddr
, (size_t)mappedSize
, VM_FLAGS_ANYWHERE
) != 0 ) {
112 fprintf(stderr
, "Could not use binary '%s' because 'vm allocation failure'\n", binaryPath
);
113 return { nullptr, nullptr };
116 int fd
= open(binaryPath
, O_RDONLY
);
118 fprintf(stderr
, "Could not open binary '%s' because '%s'\n", binaryPath
, strerror(errno
));
119 return { nullptr, nullptr };
122 __block
uint64_t baseAddress
= ~0ULL;
123 __block
uint64_t textSegVMAddr
= ~0ULL;
124 mf
->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo
& info
, bool& stop
) {
125 baseAddress
= std::min(baseAddress
, info
.vmAddr
);
126 if ( strcmp(info
.segName
, "__TEXT") == 0 ) {
127 textSegVMAddr
= info
.vmAddr
;
131 uint64_t loadAddress
= (uint64_t)mappedAddr
;
132 if ( isLoggingEnabled
) {
133 fprintf(stderr
, "Mapping binary built at 0x%llx to 0x%llx\n", baseAddress
, loadAddress
);
135 mf
->forEachSegment(^(const dyld3::MachOFile::SegmentInfo
&info
, bool &stop
) {
136 uint64_t requestedLoadAddress
= info
.vmAddr
- baseAddress
+ loadAddress
;
137 if ( isLoggingEnabled
)
138 fprintf(stderr
, "Mapping %p: %s with perms %d\n", (void*)requestedLoadAddress
, info
.segName
, info
.protections
);
139 if ( info
.vmSize
== 0 )
141 size_t readBytes
= pread(fd
, (void*)requestedLoadAddress
, (uintptr_t)info
.fileSize
, sliceOffset
+ info
.fileOffset
);
142 if ( readBytes
!= info
.fileSize
) {
143 fprintf(stderr
, "Didn't read enough bytes\n");
146 // __DATA_CONST is read-only when we actually run live, but this test runner fixes up __DATA_CONST after this vm_protect
147 // For now just don't make __DATA_CONST read only
148 uint32_t protections
= info
.protections
;
149 if ( !strcmp(info
.segName
, "__DATA_CONST") )
150 protections
= VM_PROT_READ
| VM_PROT_WRITE
;
151 const bool setCurrentPermissions
= false;
152 kern_return_t r
= vm_protect(mach_task_self(), (vm_address_t
)requestedLoadAddress
, (uintptr_t)info
.vmSize
, setCurrentPermissions
, protections
);
153 if ( r
!= KERN_SUCCESS
) {
154 diag
.error("vm_protect didn't work because %d", r
);
160 if ( diag
.hasError() ) {
161 fprintf(stderr
, "Error: %s\n", diag
.errorMessage());
162 return { nullptr, nullptr };
165 if ( textSegVMAddr
!= baseAddress
) {
166 // __DATA is first. ma should still point to __TEXT
167 const dyld3::MachOAnalyzer
* ma
= (const dyld3::MachOAnalyzer
*)(mappedAddr
+ textSegVMAddr
- baseAddress
);
168 if ( !ma
->validMachOForArchAndPlatform(diag
, (size_t)mappedSize
, binaryPath
, currentArchs
, dyld3::Platform::unknown
, false) ) {
169 fprintf(stderr
, "Error: %s\n", diag
.errorMessage());
172 return { ma
, (const void*)mappedAddr
};
175 // __TEXT is first, so ma and base address are the same
176 const dyld3::MachOAnalyzer
* ma
= (const dyld3::MachOAnalyzer
*)mappedAddr
;
177 if ( !ma
->validMachOForArchAndPlatform(diag
, (size_t)mappedSize
, binaryPath
, currentArchs
, dyld3::Platform::unknown
, false) ) {
178 fprintf(stderr
, "Error: %s\n", diag
.errorMessage());
181 return { ma
, (const void*)mappedAddr
};
184 int main(int argc
, const char * argv
[]) {
185 bool unsupported
= false;
187 // HACK: Watch archs are not supported right now, so just return
195 if ( (argc
< 2) || (argc
> 5) ) {
196 fprintf(stderr
, "Usage: run-static *path to static binary* [- - *path to auc kc*]\n");
200 for (unsigned i
= 1; i
!= argc
; ++i
) {
201 if ( !strcmp(argv
[i
], "-") )
203 LoadedMachO macho
= loadPath(argv
[i
]);
204 if ( macho
.ma
== nullptr )
206 testFuncs
.mhs
[i
- 1] = macho
.ma
;
207 testFuncs
.basePointers
[i
- 1] = macho
.basePointer
;
210 uint64_t entryOffset
= 0;
211 bool usesCRT
= false;
212 const dyld3::MachOAnalyzer
* ma
= (const dyld3::MachOAnalyzer
*)testFuncs
.mhs
[0];
213 if ( !ma
->getEntry(entryOffset
, usesCRT
) ) {
214 fprintf(stderr
, "Could not use binary '%s' because 'no entry defined'\n", argv
[1]);
218 EntryFuncTy entryFunc
= (EntryFuncTy
)((uint8_t*)testFuncs
.mhs
[0] + entryOffset
);
219 #if __has_feature(ptrauth_calls)
220 entryFunc
= (EntryFuncTy
)__builtin_ptrauth_sign_unauthenticated((void*)entryFunc
, 0, 0);
222 fprintf(stderr
, "Entering static binary at %p\n", entryFunc
);
223 //kill(getpid(), SIGSTOP);
224 int returnCode
= entryFunc(&testFuncs
);
225 if ( returnCode
!= 0 ) {
226 fprintf(stderr
, "Binary '%s' returned non-zero value %d\n", argv
[1], returnCode
);