Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtentrypoint_ios.mm
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <QtCore/qprocessordetection.h>
6#include <QtCore/qminmax.h>
7#include <QtCore/qdebug.h>
8#include <QtCore/qfile.h>
9#include <QtCore/qdir.h>
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/private/qcoreapplication_p.h>
12#include <QtCore/private/qsystemerror_p.h>
13#include <QtCore/private/qthread_p.h>
14#include <QtCore/private/qcore_mac_p.h>
15#include <QtCore/private/qiosrunloopintegration_p.h>
16#include <QtCore/private/qeventdispatcher_cf_p.h>
17
18#import <Foundation/NSArray.h>
19#import <Foundation/NSString.h>
20#import <Foundation/NSProcessInfo.h>
21#import <Foundation/NSThread.h>
22#import <Foundation/NSNotification.h>
23
24#import <UIKit/UIApplication.h>
25#import <UIKit/UISceneSession.h>
26
27#if defined(Q_OS_VISIONOS)
28extern "C" void runSwiftAppMain();
29#endif
30
31#include <setjmp.h> // Here be dragons
32
33#include <sys/mman.h>
34
35#define qAlignDown(val, align) val & ~(align - 1)
36#define qAlignUp(val, align) qAlignDown(val + (align - 1), align)
37
38static const size_t kBytesPerKiloByte = 1024;
39static const long kPageSize = sysconf(_SC_PAGESIZE);
40
41using namespace QT_PREPEND_NAMESPACE(QtPrivate);
42
43/*
44 The following diagram shows the layout of the reserved
45 stack in relation to the regular stack, and the basic
46 flow of the initial startup sequence. Note how we end
47 up back in applicationDidLaunch after the user's main
48 recurses into qApp-exec(), which allows us to return
49 from applicationDidLaunch and spin the run-loop at the
50 same level (UIApplicationMain) as iOS nativly does.
51
52 +-----------------------------+
53 | qtmn() |
54 | +--------------------+ <-- base
55 | +----> main() | |
56 | | +--------------------+ |
57 | | | ... | |
58 | | +--------------------+ |
59 | | | qApp->exec() | |
60 | | +--------------------+ |
61 | | | processEvents() | |
62 | | | | |
63 | | +--+ longjmp(a) | |
64 | | | | | |
65 | | | +--------------------+ |
66 | | | | | |
67 | | | | | |
68 | | | | unused | |
69 | | | | | |
70 | | | | | |
71 | | | +--------------------+ <-- limit
72 | | | | memory guard | |
73 | | | +--------------------+ <-- reservedStack
74 +-|-|-------------------------+
75 | | | UIApplicationMain() |
76 +-|-|-------------------------+
77 | | | applicationDidLaunch() |
78 | | | |
79 | | +--> setjmp(a) |
80 | +----+ trampoline() |
81 | |
82 +-----------------------------+
83
84 Note: the diagram does not reflect alignment issues.
85*/
86
87namespace
88{
89 rlimit stackLimit = {0, 0};
90 rlim_t originalStackSize = 0;
91
92 struct Stack
93 {
94 uintptr_t base;
95 uintptr_t limit;
96
97 static size_t computeSize(size_t requestedSize)
98 {
99 if (!requestedSize)
100 return 0;
101
102 // The stack size must be a multiple of 4 KB
103 size_t stackSize = qAlignUp(requestedSize, 4 * kBytesPerKiloByte);
104
105 // Be at least 16 KB
106 stackSize = qMax(16 * kBytesPerKiloByte, stackSize);
107
108 // Have enough extra space for our (aligned) memory guard
109 stackSize += (2 * kPageSize);
110
111 // But not exceed the 1MB maximum (adjusted to account for current stack usage)
112 stackSize = qMin(stackSize, ((1024 - 64) * kBytesPerKiloByte));
113
114 // Which we verify, just in case
115 if (Q_UNLIKELY(stackSize > originalStackSize))
116 qFatal("Unexpectedly exceeded stack limit");
117
118 return stackSize;
119 }
120
121 void adopt(void* memory, size_t size)
122 {
123 uintptr_t memoryStart = uintptr_t(memory);
124
125 // Add memory guard at the end of the reserved stack, so that any stack
126 // overflow during the user's main will trigger an exception at that point,
127 // and not when we return and find that the current stack has been smashed.
128 // We allow read though, so that garbage-collection can pass through our
129 // stack in its mark phase without triggering access violations.
130 uintptr_t memoryGuardStart = qAlignUp(memoryStart, kPageSize);
131 if (mprotect((void*)memoryGuardStart, kPageSize, PROT_READ))
132 qWarning() << "Failed to add memory guard:" << strerror(errno);
133
134 // We don't consider the memory guard part of the usable stack space
135 limit = memoryGuardStart + kPageSize;
136
137 // The stack grows downwards in memory, so the stack base is actually
138 // at the end of the reserved stack space. And, as the memory guard
139 // was page aligned, we need to align down the base as well, to
140 // keep the requirement that the stack size is a multiple of 4K.
141 base = qAlignDown(memoryStart + size, kPageSize);
142 }
143
144 bool isValid()
145 {
146 return base && limit;
147 }
148
149 size_t size()
150 {
151 return base - limit;
152 }
153
154 static const int kScribblePattern;
155
156 void scribble()
157 {
158 memset_pattern4((void*)limit, &kScribblePattern, size());
159 }
160
161 void printUsage()
162 {
163 uintptr_t highWaterMark = limit;
164 for (; highWaterMark < base; highWaterMark += 4) {
165 if (memcmp((void*)highWaterMark, &kScribblePattern, 4))
166 break;
167 }
168
169 qDebug("main() used roughly %lu bytes of stack space", (base - highWaterMark));
170 }
171 };
172
173 const int Stack::kScribblePattern = 0xfafafafa;
174
175 Stack userMainStack;
176
177 jmp_buf processEventEnterJumpPoint;
178 jmp_buf processEventExitJumpPoint;
179
180 bool applicationAboutToTerminate = false;
181 jmp_buf applicationWillTerminateJumpPoint;
182
183 bool debugStackUsage = false;
184
185 struct {
186 QAppleLogActivity UIApplicationMain;
187 QAppleLogActivity applicationDidFinishLaunching;
188 } logActivity;
189
190 void updateStackLimit()
191 {
192 qCDebug(lcEventDispatcher) << "Updating RLIMIT_STACK soft limit from"
193 << originalStackSize << "to" << userMainStack.size();
194
195 stackLimit.rlim_cur = userMainStack.size();
196 if (setrlimit(RLIMIT_STACK, &stackLimit) != 0) {
197 qCWarning(lcEventDispatcher) << "Failed to update RLIMIT_STACK soft limit"
198 << QSystemError::stdString();
199 }
200 }
201
202 void restoreStackLimit()
203 {
204 qCDebug(lcEventDispatcher) << "Restoring RLIMIT_STACK soft limit from"
205 << stackLimit.rlim_cur << "back to" << originalStackSize;
206
207 stackLimit.rlim_cur = originalStackSize;
208 if (setrlimit(RLIMIT_STACK, &stackLimit) != 0) {
209 qCWarning(lcEventDispatcher) << "Failed to update RLIMIT_STACK soft limit"
210 << QSystemError::stdString();
211 }
212 }
213}
214
215static int infoPlistValue(NSString* key, int defaultValue)
216{
217 static NSBundle *bundle = [NSBundle mainBundle];
218 NSNumber* value = [bundle objectForInfoDictionaryKey:key];
219 return value ? [value intValue] : defaultValue;
220}
221
222/*
223 We replace the application delegate with our QIOSApplicationDelegate
224 later on, but we need a placeholder delegate that implements dynamic
225 scene management, otherwise iOS will mistakenly complain that we
226 haven't adopted the UIScene lifecycle yet.
227*/
228@interface QIOSDynamicSceneDelegate : UIResponder <UIApplicationDelegate>
229@end
230
231@implementation QIOSDynamicSceneDelegate
232- (UISceneConfiguration *)application:(UIApplication *)application
233 configurationForConnectingSceneSession:(UISceneSession *)session
234 options:(UISceneConnectionOptions *)options
235{
236 // If we're connecting a session that has its configuration in
237 // the Info.plist we just want to pass on the static config.
238 return session.configuration;
239}
240@end
241
242extern "C" int qt_main_wrapper(int argc, char *argv[])
243{
244 @autoreleasepool {
245 if (Q_UNLIKELY(getrlimit(RLIMIT_STACK, &stackLimit) != 0))
246 qFatal("Failed to get stack limits");
247
248 originalStackSize = stackLimit.rlim_cur;
249
250 size_t defaultStackSize = 512 * kBytesPerKiloByte; // Same as secondary threads
251
252 uint requestedStackSize = qMax(0, infoPlistValue(@"QtRunLoopIntegrationStackSize", defaultStackSize));
253
254 if (infoPlistValue(@"QtRunLoopIntegrationDisableSeparateStack", false))
255 requestedStackSize = 0;
256
257 QT_WARNING_PUSH
258#if Q_CC_CLANG >= 1800
259 QT_WARNING_DISABLE_CLANG("-Wvla-cxx-extension")
260#endif
261 // The user-main stack _must_ live on the stack, so that the stack pointer
262 // during user-main is within pthread_get_stackaddr_np/pthread_get_stacksize_np.
263 char reservedStack[Stack::computeSize(requestedStackSize)];
264 QT_WARNING_POP
265
266 if (sizeof(reservedStack) > 0) {
267 userMainStack.adopt(reservedStack, sizeof(reservedStack));
268
269 if (infoPlistValue(@"QtRunLoopIntegrationDebugStackUsage", false)) {
270 debugStackUsage = true;
271 userMainStack.scribble();
272 qDebug("Effective stack size is %lu bytes", userMainStack.size());
273 }
274 }
275
276 logActivity.UIApplicationMain = QT_APPLE_LOG_ACTIVITY(
277 lcEventDispatcher().isDebugEnabled(), "UIApplicationMain").enter();
278
279#if defined(Q_OS_VISIONOS)
280 Q_UNUSED(argc);
281 Q_UNUSED(argv);
282 qCDebug(lcEventDispatcher) << "Starting Swift app";
283 runSwiftAppMain();
284 Q_UNREACHABLE();
285#else
286 qCDebug(lcEventDispatcher) << "Running UIApplicationMain";
287 return UIApplicationMain(argc, argv, nil,
288 NSStringFromClass(QIOSDynamicSceneDelegate.class));
289#endif
290 }
291}
292
300
301static void __attribute__((noinline, noreturn)) user_main_trampoline()
302{
303 NSArray<NSString *> *arguments = [[NSProcessInfo processInfo] arguments];
304 int argc = arguments.count;
305 char **argv = new char*[argc];
306
307 for (int i = 0; i < argc; ++i) {
308 NSString *arg = [arguments objectAtIndex:i];
309
310 NSStringEncoding cStringEncoding = [NSString defaultCStringEncoding];
311 unsigned int bufferSize = [arg lengthOfBytesUsingEncoding:cStringEncoding] + 1;
312 argv[i] = reinterpret_cast<char *>(malloc(bufferSize));
313
314 if (Q_UNLIKELY(![arg getCString:argv[i] maxLength:bufferSize encoding:cStringEncoding]))
315 qFatal("Could not convert argv[%d] to C string", i);
316 }
317
318 updateStackLimit();
319
320 static const bool isRunningViaTestRunner =
321 qEnvironmentVariableIsSet("QT_RUNNING_VIA_TEST_RUNNER");
322
323 if (isRunningViaTestRunner) {
324 qCDebug(lcEventDispatcher) << "Overriding working directory for test runner";
325 QDir::temp().mkdir("testrunner");
326 QDir::setCurrent(QDir::temp().filePath("testrunner"));
327 } else {
328 // Set current directory to app bundle folder for the convenience of
329 // examples that load resources relative to the deployment path.
330 // FIXME: This is akward, we should use the home dir or container
331 QDir::setCurrent(QString::fromNSString(NSBundle.mainBundle.bundlePath));
332 }
333
334 int main(int argc, char *argv[]);
335
336 int exitCode = main(argc, argv);
337 delete[] argv;
338
339 logActivity.applicationDidFinishLaunching.enter();
340 qCDebug(lcEventDispatcher) << "Returned from main with exit code " << exitCode;
341
342 if (isRunningViaTestRunner) {
343 // When running via `simctl launch` or `devicectl device process launch`
344 // the exit code is not always propagated, so we manually save it to disk.
345 QFile exitCodeFile(QDir::tempPath() + "/qt_exit_code.txt");
346 if (exitCodeFile.open(QIODevice::WriteOnly | QIODevice::Text))
347 exitCodeFile.write(QByteArray::number(exitCode));
348 else
349 qCWarning(lcEventDispatcher) << "Failed to write exit code to" << exitCodeFile.fileName();
350 }
351
352 if (Q_UNLIKELY(debugStackUsage))
353 userMainStack.printUsage();
354
355 restoreStackLimit();
356
357 logActivity.applicationDidFinishLaunching.leave();
358
359 if (applicationAboutToTerminate)
360 longjmp(applicationWillTerminateJumpPoint, kJumpedFromUserMainTrampoline);
361
362 // We end up here if the user's main() never calls QApplication::exec(),
363 // or if we return from exec() after quitting the application. If so we
364 // follow the expected behavior from the point of the user's main(), which
365 // is to exit with the given exit code.
366 exit(exitCode);
367}
368
369void registerApplicationLifecycleCallbacks()
370{
371 [[NSNotificationCenter defaultCenter]
372 addObserver:UIApplication.class
373 selector:@selector(qt_applicationDidFinishLaunching:)
374 name:UIApplicationDidFinishLaunchingNotification
375 object:nil];
376
377 [[NSNotificationCenter defaultCenter]
378 addObserver:UIApplication.class
379 selector:@selector(qt_applicationWillTerminate)
380 name:UIApplicationWillTerminateNotification
381 object:nil];
382}
383Q_CONSTRUCTOR_FUNCTION(registerApplicationLifecycleCallbacks)
384
385@implementation UIApplication (QtRunLoopIntegration)
386
387+ (bool)qt_rootLevelRunLoopIntegration
388{
389 // If we don't have a stack set up, we're not running inside
390 // iOS' native/root level run-loop in UIApplicationMain.
391 return userMainStack.isValid();
392}
393
394+ (void)qt_applicationDidFinishLaunching:(NSNotification *)notification
395{
396 logActivity.applicationDidFinishLaunching = QT_APPLE_LOG_ACTIVITY_WITH_PARENT(
397 lcEventDispatcher().isDebugEnabled(), "applicationDidFinishLaunching", logActivity.UIApplicationMain).enter();
398
399 qCDebug(lcEventDispatcher) << "Application launched with options" << notification.userInfo;
400
401 if (![self qt_rootLevelRunLoopIntegration]) {
402 // We schedule the main-redirection for the next run-loop pass, so that we
403 // can return from this function and let UIApplicationMain finish its job.
404 // This results in running Qt's application eventloop as a nested runloop.
405 qCDebug(lcEventDispatcher) << "Scheduling main() on next run-loop pass";
406 CFRunLoopTimerRef userMainTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
407 CFAbsoluteTimeGetCurrent(), 0, 0, 0, ^(CFRunLoopTimerRef) { user_main_trampoline(); });
408 CFRunLoopAddTimer(CFRunLoopGetMain(), userMainTimer, kCFRunLoopCommonModes);
409 CFRelease(userMainTimer);
410 return;
411 }
412
413 switch (setjmp(processEventEnterJumpPoint)) {
414 case kJumpPointSetSuccessfully: {
415 qCDebug(lcEventDispatcher) << "Running main() on separate stack";
416 QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "main()");
417
418#if defined(Q_PROCESSOR_X86)
419# define FUNCTION_CALL_ALIGNMENT 16
420# if defined(Q_PROCESSOR_X86_32)
421# define SET_STACK_POINTER "mov %0, %%esp"
422# elif defined(Q_PROCESSOR_X86_64)
423# define SET_STACK_POINTER "movq %0, %%rsp"
424# endif
425#elif defined(Q_PROCESSOR_ARM)
426# // Valid for both 32 and 64-bit ARM
427# define FUNCTION_CALL_ALIGNMENT 4
428# define SET_STACK_POINTER "mov sp, %0"
429#else
430# error "Unknown processor family"
431#endif
432
433 // Redirect the stack pointer to the start of the reserved stack. This ensures
434 // that when we longjmp out of the event dispatcher and continue execution, the
435 // 'Qt main' call-stack will not be smashed, as it lives in a part of the stack
436 // that was allocated back in main().
437 __asm__ __volatile__(
438 SET_STACK_POINTER
439 : /* no outputs */
440 : "r" (qAlignDown(userMainStack.base, FUNCTION_CALL_ALIGNMENT))
441 );
442
443 user_main_trampoline();
444
445 Q_UNREACHABLE();
446 break;
447 }
448 case kJumpedFromEventDispatcherProcessEvents:
449 // We've returned from the longjmp in the event dispatcher,
450 // and the stack has been restored to its old self.
451 logActivity.UIApplicationMain.enter();
452 qCDebug(lcEventDispatcher) << "↳ Jumped from processEvents due to exec";
453
454 if (Q_UNLIKELY(debugStackUsage))
455 userMainStack.printUsage();
456
457 restoreStackLimit();
458
459 break;
460 default:
461 qFatal("Unexpected jump result in event loop integration");
462 }
463}
464
465+ (bool)qt_eventDispatcherEnteredProcessEvents
466{
467 // We set a new jump point here that we can return to when the event loop
468 // is asked to exit, so that we can return from QEventLoop::exec().
469 switch (setjmp(processEventExitJumpPoint)) {
470 case kJumpPointSetSuccessfully:
471 qCDebug(lcEventDispatcher) << "QEventLoop exec detected, jumping back to system runloop ↵";
472 longjmp(processEventEnterJumpPoint, kJumpedFromEventDispatcherProcessEvents);
473 break;
474 case kJumpedFromEventLoopExecInterrupt:
475 // The event loop has exited (either by the hand of the user, or the iOS termination
476 // signal), and we jumped back though processEventExitJumpPoint. We return from processEvents,
477 // which will emit aboutToQuit if it's QApplication's event loop, and then return to the user's
478 // main, which can do whatever it wants, including calling exec() on the application again.
479 qCDebug(lcEventDispatcher) << "⇢ System runloop exited, returning with eventsProcessed = true";
480 updateStackLimit();
481 return true;
482 default:
483 qFatal("Unexpected jump result in event loop integration");
484 }
485
486 Q_UNREACHABLE();
487}
488
489+ (void)qt_eventDispatcherInterruptEventLoopExec
490{
491 // We re-set applicationProcessEventsReturnPoint here so that future
492 // calls to QEventLoop::exec() will end up back here after entering
493 // processEvents, instead of back in didFinishLaunchingWithOptions.
494 switch (setjmp(processEventEnterJumpPoint)) {
495 case kJumpPointSetSuccessfully:
496 qCDebug(lcEventDispatcher) << "Jumping into processEvents due to system runloop exit ⇢";
497 logActivity.UIApplicationMain.leave();
498 longjmp(processEventExitJumpPoint, kJumpedFromEventLoopExecInterrupt);
499 break;
500 case kJumpedFromEventDispatcherProcessEvents:
501 // QEventLoop was re-executed
502 logActivity.UIApplicationMain.enter();
503 qCDebug(lcEventDispatcher) << "↳ Jumped from processEvents due to re-exec";
504 restoreStackLimit();
505 break;
506 default:
507 qFatal("Unexpected jump result in event loop integration");
508 }
509}
510
511+ (void)qt_applicationWillTerminate
512{
513 QAppleLogActivity applicationWillTerminateActivity = QT_APPLE_LOG_ACTIVITY_WITH_PARENT(
514 lcEventDispatcher().isDebugEnabled(), "applicationWillTerminate", logActivity.UIApplicationMain).enter();
515 qCDebug(lcEventDispatcher) << "Application about to be terminated by iOS";
516
517 if (![self qt_rootLevelRunLoopIntegration])
518 return;
519
520 // Normally iOS just sends SIGKILL to quit applications, in which case there's
521 // no chance for us to clean up anything, but in some rare cases iOS will tell
522 // us that the application is about to be terminated.
523
524 // We try to play nice with Qt by ending the main event loop, which will result
525 // in QCoreApplication::aboutToQuit() being emitted, and main() returning to the
526 // trampoline. The trampoline then redirects us back here, so that we can return
527 // to UIApplicationMain instead of calling exit().
528
529 applicationAboutToTerminate = true;
530 switch (setjmp(applicationWillTerminateJumpPoint)) {
531 case kJumpPointSetSuccessfully:
532 qCDebug(lcEventDispatcher) << "Exiting qApp with SIGTERM exit code";
533 qApp->exit([self qt_applicationWillTerminateExitCode]);
534
535 // The runloop will not exit when the application is about to terminate,
536 // so we'll never see the exit activity and have a chance to return from
537 // QEventLoop::exec(). We initiate the return manually as a workaround.
538 qCDebug(lcEventDispatcher) << "Manually triggering return from event loop exec";
539 applicationWillTerminateActivity.leave();
540 [self qt_eventDispatcherInterruptEventLoopExec];
541 break;
542 case kJumpedFromUserMainTrampoline:
543 applicationWillTerminateActivity.enter();
544 // The user's main has returned, so we're ready to let iOS terminate the application
545 qCDebug(lcEventDispatcher) << "kJumpedFromUserMainTrampoline, allowing iOS to terminate";
546 applicationWillTerminateActivity.leave();
547 break;
548 default:
549 qFatal("Unexpected jump result in event loop integration");
550 }
551}
552
553+ (bool)qt_applicationAboutToTerminate
554{
555 return applicationAboutToTerminate;
556}
557
558+ (char)qt_applicationWillTerminateExitCode
559{
560 // We treat applicationWillTerminate as SIGTERM, even if it can't be ignored,
561 // and follow the bash convention of encoding the signal number in the upper
562 // four bits of the exit code (exit(3) will only pass on the lower 8 bits).
563 return char(SIGTERM | 0x80);
564}
565
566@end
static const size_t kBytesPerKiloByte
#define qAlignUp(val, align)
#define qAlignDown(val, align)
int qt_main_wrapper(int argc, char *argv[])
static const long kPageSize
static int infoPlistValue(NSString *key, int defaultValue)
static void __attribute__((noinline, noreturn)) user_main_trampoline()
SetJumpResult
@ kJumpedFromUserMainTrampoline
@ kJumpedFromEventLoopExecInterrupt
@ kJumpedFromEventDispatcherProcessEvents
@ kJumpPointSetSuccessfully
int main(int argc, char *argv[])
[ctor_close]