16 #include <mach/mach.h>
17 #include <mach-o/dyld.h>
18 #include <mach/mach_vm.h>
19 #include <sys/fsgetpath.h>
20 #include <mach-o/getsect.h>
21 #include <mach/vm_region.h>
22 #include <dispatch/private.h>
23 #include <dispatch/dispatch.h>
31 #include "execserverServer.h"
33 union catch_mach_exc_request_reply
{
34 union __RequestUnion__catch_mach_exc_subsystem request
;
35 union __ReplyUnion__catch_mach_exc_subsystem reply
;
39 #include "test_support.h"
41 extern const int NXArgc
;
42 extern const char** NXArgv
;
43 extern const char** environ
;
44 extern char* __progname
;
46 static const cpu_type_t currentArch
= CPU_TYPE_X86_64
;
48 static const cpu_type_t currentArch
= CPU_TYPE_I386
;
50 static const cpu_type_t currentArch
= CPU_TYPE_ARM64
;
52 static const cpu_type_t currentArch
= CPU_TYPE_ARM
;
57 ScopedLock() : _lock(OS_UNFAIR_LOCK_INIT
) {}
60 os_unfair_lock_lock(&_lock
);
62 os_unfair_lock_unlock(&_lock
);
68 template <typename T
, int QUANT
=4, int INIT
=1>
73 T
& operator[](size_t idx
) { assert(idx
< _usedCount
); return _elements
[idx
]; }
74 const T
& operator[](size_t idx
) const { assert(idx
< _usedCount
); return _elements
[idx
]; }
75 T
& back() { assert(_usedCount
> 0); return _elements
[_usedCount
-1]; }
76 uintptr_t count() const { return _usedCount
; }
77 uintptr_t maxCount() const { return _allocCount
; }
78 bool empty() const { return (_usedCount
== 0); }
79 uintptr_t index(const T
& element
) { return &element
- _elements
; }
80 void push_back(const T
& t
) { verifySpace(1); _elements
[_usedCount
++] = t
; }
81 void pop_back() { assert(_usedCount
> 0); _usedCount
--; }
82 T
* begin() { return &_elements
[0]; }
83 T
* end() { return &_elements
[_usedCount
]; }
84 const T
* begin() const { return &_elements
[0]; }
85 const T
* end() const { return &_elements
[_usedCount
]; }
86 bool contains(const T
& targ
) const { for (const T
& a
: *this) { if ( a
== targ
) return true; } return false; }
90 void growTo(uintptr_t n
);
91 void verifySpace(uintptr_t n
) { if (this->_usedCount
+n
> this->_allocCount
) growTo(this->_usedCount
+ n
); }
94 T
* _elements
= _initialAlloc
;
95 uintptr_t _allocCount
= INIT
;
96 uintptr_t _usedCount
= 0;
97 T _initialAlloc
[INIT
] = { };
101 template <typename T
, int QUANT
, int INIT
>
102 inline void GrowableArray
<T
,QUANT
,INIT
>::growTo(uintptr_t n
)
104 uintptr_t newCount
= (n
+ QUANT
- 1) & (-QUANT
);
105 T
* newArray
= (T
*)::malloc(sizeof(T
)*newCount
);
106 T
* oldArray
= this->_elements
;
107 if ( this->_usedCount
!= 0 )
108 ::memcpy(newArray
, oldArray
, sizeof(T
)*this->_usedCount
);
109 this->_elements
= newArray
;
110 this->_allocCount
= newCount
;
111 if ( oldArray
!= this->_initialAlloc
)
115 template <typename T
, int QUANT
, int INIT
>
116 inline void GrowableArray
<T
,QUANT
,INIT
>::erase(T
& targ
)
118 intptr_t index
= &targ
- _elements
;
120 assert(index
< (intptr_t)_usedCount
);
121 intptr_t moveCount
= _usedCount
-index
-1;
123 ::memcpy(&_elements
[index
], &_elements
[index
+1], moveCount
*sizeof(T
));
129 static TestState
* getState();
130 void _PASSV(const char* file
, unsigned line
, const char* format
, va_list args
) __attribute__ ((noreturn
));
131 void _FAILV(const char* file
, unsigned line
, const char* format
, va_list args
) __attribute__ ((noreturn
));
132 void _LOGV(const char* file
, unsigned line
, const char* format
, va_list args
);
133 GrowableArray
<std::pair
<mach_port_t
, _dyld_test_crash_handler_t
>>& getCrashHandlers();
143 static uint8_t hexCharToUInt(const char hexByte
, uint8_t* value
);
144 static uint64_t hexToUInt64(const char* startHexByte
, const char** endHexByte
);
147 GrowableArray
<const char *> logs
;
148 const char *testName
;
153 GrowableArray
<std::pair
<mach_port_t
, _dyld_test_crash_handler_t
>> crashHandlers
;
156 // Okay, this is tricky. We need something with roughly he semantics of a weak def, but without using weak defs as their presence
157 // m ay impact certain tests. Instead we do the following:
159 // 1. Embed a stuct containing a lock and a pointer to our global state object in eahc binary
160 // 2. Once per binary we walk the entire image list looking for the first entry that also has state data
161 // 3. If it has state we lock its initializaion lock, and if it is not initialized we initialize it
162 // 4. We then copy the initalized pointer into our own state, and unlock the initializer lock
164 // This should work because the image list forms a stable ordering. The one loose end is if an executable is running where logging
165 // is only used in dylibs that are all being dlopned() and dlclosed. Since many dylibs cannot be dlclosed that should be a non-issue
169 __attribute__((section("__DATA,__dyld_test")))
170 static std::atomic
<TestState
*> sState
;
173 catch_mach_exception_raise(mach_port_t exception_port
,
176 exception_type_t exception
,
177 mach_exception_data_t code
,
178 mach_msg_type_number_t codeCnt
)
180 _dyld_test_crash_handler_t crashHandler
= NULL
;
181 for (const auto& handler
: TestState::getState()->getCrashHandlers()) {
182 if (handler
.first
== exception_port
) {
183 crashHandler
= handler
.second
;
187 if (exception
== EXC_CORPSE_NOTIFY
) {
197 catch_mach_exception_raise_state(mach_port_t exception_port
,
198 exception_type_t exception
,
199 const mach_exception_data_t code
,
200 mach_msg_type_number_t codeCnt
,
202 const thread_state_t old_state
,
203 mach_msg_type_number_t old_stateCnt
,
204 thread_state_t new_state
,
205 mach_msg_type_number_t
* new_stateCnt
)
207 return KERN_NOT_SUPPORTED
;
211 catch_mach_exception_raise_state_identity(mach_port_t exception_port
,
214 exception_type_t exception
,
215 mach_exception_data_t code
,
216 mach_msg_type_number_t codeCnt
,
218 thread_state_t old_state
,
219 mach_msg_type_number_t old_stateCnt
,
220 thread_state_t new_state
,
221 mach_msg_type_number_t
* new_stateCnt
)
223 return KERN_NOT_SUPPORTED
;
226 _process::_process() : executablePath(nullptr), args(nullptr), env(nullptr), stdoutHandler(nullptr), stderrHandler(nullptr),
227 crashHandler(nullptr), exitHandler(nullptr), pid(0), arch(currentArch
), suspended(false), async(false) {}
228 _process::~_process() {
229 if (stdoutHandler
) { Block_release(stdoutHandler
);}
230 if (stderrHandler
) { Block_release(stderrHandler
);}
231 if (crashHandler
) { Block_release(crashHandler
);}
232 if (exitHandler
) { Block_release(exitHandler
);}
235 void _process::set_executable_path(const char* EP
) { executablePath
= EP
; }
236 void _process::set_args(const char** A
) { args
= A
; }
237 void _process::set_env(const char** E
) { env
= E
; }
238 void _process::set_stdout_handler(_dyld_test_reader_t SOH
) { stdoutHandler
= Block_copy(SOH
); };
239 void _process::set_stderr_handler(_dyld_test_reader_t SEH
) { stderrHandler
= Block_copy(SEH
); }
240 void _process::set_exit_handler(_dyld_test_exit_handler_t EH
) { exitHandler
= Block_copy(EH
); }
241 void _process::set_crash_handler(_dyld_test_crash_handler_t CH
) { crashHandler
= Block_copy(CH
); }
242 void _process::set_launch_suspended(bool S
) { suspended
= S
; }
243 void _process::set_launch_async(bool S
) { async
= S
; }
244 void _process::set_launch_arch(cpu_type_t A
) { arch
= A
; }
246 pid_t
_process::launch() {
247 dispatch_queue_t queue
= dispatch_queue_create("com.apple.dyld.test.launch", NULL
);
248 dispatch_block_t oneShotSemaphoreBlock
= dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS
, ^{});
249 posix_spawn_file_actions_t fileActions
= NULL
;
250 posix_spawnattr_t attr
= NULL
;
251 dispatch_source_t stdoutSource
= NULL
;
252 dispatch_source_t stderrSource
= NULL
;
256 if (posix_spawn_file_actions_init(&fileActions
) != 0) {
257 FAIL("Setting up spawn filea actions");
259 if (posix_spawnattr_init(&attr
) != 0) { FAIL("Setting up spawn attr"); }
260 if (posix_spawnattr_setflags(&attr
, POSIX_SPAWN_START_SUSPENDED
) != 0) {
261 FAIL("Setting up spawn attr: POSIX_SPAWN_START_SUSPENDED");
264 if (pipe(stdoutPipe
) != 0) { FAIL("Setting up pipe"); }
265 if (posix_spawn_file_actions_addclose(&fileActions
, stdoutPipe
[0]) != 0) { FAIL("Setting up pipe"); }
266 if (posix_spawn_file_actions_adddup2(&fileActions
, stdoutPipe
[1], STDOUT_FILENO
) != 0) { FAIL("Setting up pipe"); }
267 if (posix_spawn_file_actions_addclose(&fileActions
, stdoutPipe
[1]) != 0) { FAIL("Setting up pipe"); }
268 fcntl((int)stdoutPipe
[0], F_SETFL
, O_NONBLOCK
);
269 stdoutSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, (uintptr_t)stdoutPipe
[0], 0, queue
);
270 dispatch_source_set_event_handler(stdoutSource
, ^{
271 int fd
= (int)dispatch_source_get_handle(stdoutSource
);
278 size
= read(fd
, &buffer
[0], 16384);
282 dispatch_source_set_cancel_handler(stdoutSource
, ^{
283 dispatch_release(stdoutSource
);
285 dispatch_resume(stdoutSource
);
287 if (pipe(stderrPipe
) != 0) { FAIL("Setting up pipe"); }
288 if (posix_spawn_file_actions_addclose(&fileActions
, stderrPipe
[0]) != 0) { FAIL("Setting up pipe"); }
289 if (posix_spawn_file_actions_adddup2(&fileActions
, stderrPipe
[1], STDERR_FILENO
) != 0) { FAIL("Setting up pipe"); }
290 if (posix_spawn_file_actions_addclose(&fileActions
, stderrPipe
[1]) != 0) { FAIL("Setting up pipe"); }
291 fcntl((int)stderrPipe
[0], F_SETFL
, O_NONBLOCK
);
292 stderrSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
, (uintptr_t)stderrPipe
[0], 0, queue
);
293 dispatch_source_set_event_handler(stderrSource
, ^{
294 int fd
= (int)dispatch_source_get_handle(stderrSource
);
301 size
= read(fd
, &buffer
[0], 16384);
305 dispatch_source_set_cancel_handler(stderrSource
, ^{
306 dispatch_release(stderrSource
);
308 dispatch_resume(stderrSource
);
311 auto& crashHandlers
= TestState::getState()->getCrashHandlers();
312 mach_port_t exceptionPort
= MACH_PORT_NULL
;
313 mach_port_options_t options
= { .flags
= MPO_CONTEXT_AS_GUARD
| MPO_STRICT
| MPO_INSERT_SEND_RIGHT
, .mpl
= { 1 }};
314 if ( mach_port_construct(mach_task_self(), &options
, (mach_port_context_t
)exceptionPort
, &exceptionPort
) != KERN_SUCCESS
) {
315 FAIL("Could not construct port");
317 if (posix_spawnattr_setexceptionports_np(&attr
, EXC_MASK_CRASH
| EXC_MASK_CORPSE_NOTIFY
, exceptionPort
,
318 EXCEPTION_DEFAULT
| MACH_EXCEPTION_CODES
, 0) != 0) {
319 FAIL("posix_spawnattr_setexceptionports_np failed");
321 crashHandlers
.push_back(std::make_pair(exceptionPort
, crashHandler
));
322 dispatch_source_t crashSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, exceptionPort
, 0, queue
);
323 dispatch_source_set_event_handler(crashSource
, ^{
324 dispatch_mig_server(crashSource
, sizeof(union catch_mach_exc_request_reply
), ::mach_exc_server
);
326 dispatch_source_set_cancel_handler(crashSource
, ^{
327 mach_port_destruct(mach_task_self(), exceptionPort
, 0, (mach_port_context_t
)exceptionPort
);
329 dispatch_resume(crashSource
);
335 for (argc
= 0; args
[argc
] != NULL
; ++argc
) {}
338 const char *argv
[argc
+1];
339 argv
[0] = executablePath
;
340 for (uint32_t i
= 1; i
< argc
; ++i
) {
345 int result
= posix_spawn(&pid
, executablePath
, &fileActions
, &attr
, (char **)argv
, (char **)env
);
347 FAIL("posix_spawn(%s) failed, err=%d", executablePath
, result
);
349 dispatch_source_t exitSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (pid_t
)pid
,
350 DISPATCH_PROC_EXIT
, queue
);
351 dispatch_source_set_event_handler(exitSource
, ^{
353 exitHandler((pid_t
)dispatch_source_get_handle(exitSource
));
355 dispatch_source_cancel(exitSource
);
357 dispatch_source_cancel(stdoutSource
);
360 dispatch_source_cancel(stderrSource
);
362 oneShotSemaphoreBlock();
363 dispatch_source_cancel(exitSource
);
365 dispatch_resume(exitSource
);
368 close(stdoutPipe
[1]);
371 close(stderrPipe
[1]);
374 posix_spawn_file_actions_destroy(&fileActions
);
376 posix_spawnattr_destroy(&attr
);
381 dispatch_block_wait(oneShotSemaphoreBlock
, DISPATCH_TIME_FOREVER
);
383 Block_release(oneShotSemaphoreBlock
);
384 dispatch_release(queue
);
388 void *_process::operator new(size_t size
) {
392 void _process::operator delete(void *ptr
) {
396 // MARK: Private implementation details
400 void forEachEnvVar(const char* envp
[], F
&& f
) {
401 for (uint32_t i
= 0; envp
[i
] != nullptr; ++i
) {
402 const char* envBegin
= envp
[i
];
403 const char* envEnd
= strchr(envp
[i
], '=');
404 if (!envEnd
) { continue; }
405 size_t envSize
= (envEnd
-envBegin
)+1;
406 const char* valBegin
= envEnd
+1;
407 const char* valEnd
= strchr(envp
[i
], '\0');
408 if (!valEnd
) { continue; }
409 size_t valSize
= (valEnd
-valBegin
)+1;
412 strlcpy(&env
[0], envBegin
, envSize
);
413 strlcpy(&val
[0], valBegin
, valSize
);
418 uint8_t TestState::hexCharToUInt(const char hexByte
, uint8_t* value
) {
419 if (hexByte
>= '0' && hexByte
<= '9') {
420 *value
= hexByte
- '0';
422 } else if (hexByte
>= 'A' && hexByte
<= 'F') {
423 *value
= hexByte
- 'A' + 10;
425 } else if (hexByte
>= 'a' && hexByte
<= 'f') {
426 *value
= hexByte
- 'a' + 10;
433 uint64_t TestState::hexToUInt64(const char* startHexByte
, const char** endHexByte
) {
435 if (endHexByte
== NULL
) {
436 endHexByte
= &scratch
;
438 if (startHexByte
== NULL
)
441 if (startHexByte
[0] == '0' && startHexByte
[1] == 'x') {
444 *endHexByte
= startHexByte
+ 16;
447 for (uint32_t i
= 0; i
< 16; ++i
) {
449 if (!hexCharToUInt(startHexByte
[i
], &value
)) {
450 *endHexByte
= &startHexByte
[i
];
453 retval
= (retval
<< 4) + value
;
458 TestState::TestState() : testName(__progname
), logImmediate(false), logOnSuccess(false), checkForLeaks(false), output(Console
) {
459 forEachEnvVar(environ
, [this](const char* env
, const char* val
) {
460 if (strcmp(env
, "TEST_LOG_IMMEDIATE") == 0) {
463 if (strcmp(env
, "TEST_LOG_ON_SUCCESS") == 0) {
466 if (strcmp(env
, "MallocStackLogging") == 0) {
467 checkForLeaks
= true;
469 if (strcmp(env
, "TEST_OUTPUT") == 0) {
470 if (strcmp(val
, "BATS") == 0) {
472 } else if (strcmp(val
, "XCTest") == 0) {
477 if (output
== BATS
) {
480 printf(" MallocStackLogging=1 MallocDebugReport=none");
482 forEachEnvVar(environ
, [this](const char* env
, const char* val
) {
483 if ((strncmp(env
, "DYLD_", 5) == 0) || (strncmp(env
, "TEST_", 5) == 0)) {
484 printf(" %s=%s", env
, val
);
487 printf(" %s", testName
);
488 for (uint32_t i
= 1; i
< NXArgc
; ++i
) {
489 printf(" %s", NXArgv
[i
]);
495 static std::atomic
<TestState
*>& getExecutableImageState() {
496 uint32_t imageCnt
= _dyld_image_count();
497 for (uint32_t i
= 0; i
< imageCnt
; ++i
) {
499 const struct mach_header_64
* mh
= (const struct mach_header_64
*)_dyld_get_image_header(i
);
501 const struct mach_header
* mh
= _dyld_get_image_header(i
);
503 if (mh
->filetype
!= MH_EXECUTE
) {
507 auto state
= (std::atomic
<TestState
*>*)getsectiondata(mh
, "__DATA", "__dyld_test", &size
);
509 fprintf(stderr
, "Could not find test state in main executable TestState\n");
514 fprintf(stderr
, "Could not find test state in main executable\n");
518 GrowableArray
<std::pair
<mach_port_t
, _dyld_test_crash_handler_t
>>& TestState::getCrashHandlers() {
519 return crashHandlers
;
522 TestState
* TestState::getState() {
524 auto& state
= getExecutableImageState();
525 if (state
== nullptr) {
526 void *temp
= malloc(sizeof(TestState
));
527 auto newState
= new (temp
) TestState();
528 TestState
* expected
= nullptr;
529 if(!state
.compare_exchange_strong(expected
, newState
)) {
530 newState
->~TestState();
536 assert(sState
!= nullptr);
540 __attribute__((noreturn
))
541 void TestState::runLeaks(void) {
542 auto testState
= TestState::getState();
543 pid_t pid
= getpid();
545 sprintf(&pidString
[0], "%d", pid
);
547 printf("Insufficient priviledges, skipping Leak check: %s\n", testState
->testName
);
550 const char *args
[] = { pidString
, NULL
};
551 // We do this instead of using a dispatch_semaphore to prevent priority inversions
552 __block dispatch_data_t leaksOutput
= NULL
;
554 process
.set_executable_path("/usr/bin/leaks");
555 process
.set_args(args
);
556 process
.set_stdout_handler(^(int fd
) {
560 size
= read(fd
, &buffer
[0], 16384);
561 if (size
== -1) { break; }
562 dispatch_data_t data
= dispatch_data_create(&buffer
[0], size
, NULL
, DISPATCH_DATA_DESTRUCTOR_DEFAULT
);
566 leaksOutput
= dispatch_data_create_concat(leaksOutput
, data
);
570 process
.set_exit_handler(^(pid_t pid
) {
572 (void)waitpid(pid
, &status
, 0);
574 int exitStatus
= WEXITSTATUS(status
);
575 if (exitStatus
== 0) {
581 __unused dispatch_data_t map
= dispatch_data_create_map(leaksOutput
, &buffer
, &size
);
582 FAIL("Found Leaks:\n\n%s", buffer
);
587 testState
->checkForLeaks
= false;
588 (void)process
.launch();
592 void TestState::_PASSV(const char* file
, unsigned line
, const char* format
, va_list args
) {
593 if (output
== None
) {
599 _IOlock
.withLock([this,&format
,&args
,&file
,&line
](){
600 if (output
== Console
) {
601 printf("[\033[0;32mPASS\033[0m] %s: ", testName
);
602 vprintf(format
, args
);
604 if (logOnSuccess
&& logs
.count()) {
605 printf("[\033[0;33mLOG\033[0m]\n");
606 for (const auto& log
: logs
) {
607 printf("\t%s\n", log
);
610 } else if (output
== BATS
) {
611 printf("[PASS] %s: ", testName
);
612 vprintf(format
, args
);
614 if (logOnSuccess
&& logs
.count()) {
616 for (const auto& log
: logs
) {
617 printf("\t%s\n", log
);
620 } else if (output
== XCTest
) {
621 printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
622 printf("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
623 printf("<plist version=\"1.0\">");
625 printf("<key>PASS</key><true />");
634 void _PASS(const char* file
, unsigned line
, const char* format
, ...) {
636 va_start (args
, format
);
637 TestState::getState()->_PASSV(file
, line
, format
, args
);
641 void TestState::_FAILV(const char* file
, unsigned line
, const char* format
, va_list args
) {
642 if (output
== None
) {
645 _IOlock
.withLock([this,&format
,&args
,&file
,&line
](){
646 if (output
== Console
) {
647 printf("[\033[0;31mFAIL\033[0m] %s: ", testName
);
648 vprintf(format
, args
);
650 printf("[\033[0;33mLOG\033[0m]\n");
652 for (const auto& log
: logs
) {
653 printf("\t%s\n", log
);
656 } else if (output
== BATS
) {
657 printf("[FAIL] %s: ", testName
);
658 vprintf(format
, args
);
662 for (const auto& log
: logs
) {
663 printf("\t%s\n", log
);
666 } else if (output
== XCTest
) {
668 printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
669 printf("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
670 printf("<plist version=\"1.0\">");
672 printf("<key>PASS</key><false />");
673 printf("<key>FILE</key><string>%s</string>", file
);
674 printf("<key>LINE</key><integer>%u</integer>", line
);
675 vasprintf(&buffer
, format
, args
);
676 printf("<key>INFO</key><string>%s</string>", buffer
);
685 void _FAIL(const char* file
, unsigned line
, const char* format
, ...) {
687 va_start (args
, format
);
688 TestState::getState()->_FAILV(file
, line
, format
, args
);
692 void TestState::_LOGV(const char* file
, unsigned line
, const char* format
, va_list args
) {
693 _IOlock
.withLock([this,&format
,&args
](){
695 vprintf(format
, args
);
699 vasprintf(&str
, format
, args
);
705 void _LOG(const char* file
, unsigned line
, const char* format
, ...) {
707 va_start (args
, format
);
708 TestState::getState()->_LOGV(file
, line
, format
, args
);
712 void _TIMEOUT(const char* file
, unsigned line
, uint64_t seconds
) {
713 _LOG(file
, line
, "Registering %llu second test timeout", seconds
);
714 dispatch_source_t source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER
, 0, 0, DISPATCH_TARGET_QUEUE_DEFAULT
);
715 dispatch_time_t milestone
= dispatch_time(DISPATCH_WALLTIME_NOW
, seconds
* NSEC_PER_SEC
);
716 dispatch_source_set_timer(source
, milestone
, 0, 0);
717 dispatch_source_set_event_handler(source
, ^{
718 FAIL("Test timed out");
720 dispatch_resume(source
);