Application entry point

What is an entry point? According to the wikipedia an entry point is where the first instructions of a program are executed, and where the program has access to command-line arguments. In many languages an entry point is a function named main. For instance, in C/C++ the entry point is a function main, in Java it is defined as a static function main, in C# it is a static function Main.
In iOS & macOS applications the entry point is also defined as a main function and we will try to prove that.

Entrypoint in ObjC application

When dealing with Objective-C projects, the case is rather simple. Xcode automatically adds main.m file when we create a new project. Inside the file we can find the main function of a program called main.

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

What a function does is that it creates a string from the AppDelegate class and passes it to the UIApplicationMain. We will take a look at UIApplicationMain in a minute, but first let’s see if a main is an entry point.

To do this, we can pause the application execution to enter the LLDB console. After that we can use bt command, which will print the current backtrace.

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00007fff51af0dfa libsystem_kernel.dylib`mach_msg_trap + 10
    frame #1: 0x00007fff51af1170 libsystem_kernel.dylib`mach_msg + 60
    frame #2: 0x00007fff23da14d5 CoreFoundation`__CFRunLoopServiceMachPort + 165
    frame #3: 0x00007fff23d9c107 CoreFoundation`__CFRunLoopRun + 1383
    frame #4: 0x00007fff23d9b884 CoreFoundation`CFRunLoopRunSpecific + 404
    frame #5: 0x00007fff38b5ac1a GraphicsServices`GSEventRunModal + 139
    frame #6: 0x00007fff48c19220 UIKitCore`UIApplicationMain + 1605
    frame #7: 0x0000000109dcb1d2 ApplicationMainObjC`main(argc=1, argv=0x00007ffee5e33b98) at main.m:18:12
    frame #8: 0x00007fff519b910d libdyld.dylib`start + 1
    frame #9: 0x00007fff519b910d libdyld.dylib`start + 1

We can see what methods are pushed currently onto the stack. We may also notice that the very first method which is called (except for dynamic linker which is used for loading a binary) is our main method.

How OS knows where is the entry point?

We already know that the first method that’s been called when the application process starts is the main method, but are we 100% sure that’s true? How does the operating system know which method to call and where to find it? To understand this we should refer to the Mach-O file format and how it is structured. I strongly recommend you to read the article about parsing Mach-O files if you are not familiar with Mach-O file format.
In short, every iOS application’s binary is a Mach-O file, which consists of various regions (header, load commands, data). The part of a Mach-O file, which is responsible for telling the operating system where is the first method is a load command called LC_MAIN. We can inspect the application’s binary using otool to see the load commands.

$ otool -l ApplicationMainObjC | grep LC_MAIN -A3 -B1
Load command 12
       cmd LC_MAIN
   cmdsize 24
  entryoff 4448
 stacksize 0

The command simply prints all the load commands of a binary and filters the results using grep. What’s the most important here is the entry offset which tells where the entry point is located in the memory. The offset is related to the TEXT segment of the Mach-O file where the assembly code is located.
To verify the entry point address, we can use LLDB. First let’s print all the segments.

(lldb) image dump sections ApplicationMainObjC
SectID     Type             Load Address                             Perm File Off.  File Size  Flags      Section Name
  ---------- ---------------- ---------------------------------------  ---- ---------- ---------- ---------- ----------------------------
  0x00000100 container        [0x0000000000000000-0x0000000100000000)* ---  0x00000000 0x00000000 0x00000000 ApplicationMainObjC.__PAGEZERO
  0x00000200 container        [0x000000010cf60000-0x000000010cf63000)  r-x  0x00000000 0x00003000 0x00000000 ApplicationMainObjC.__TEXT
  0x00000001 code             [0x000000010cf60f10-0x000000010cf61473)  r-x  0x00000f10 0x00000563 0x80000400 ApplicationMainObjC.__TEXT.__text
  0x00000002 code             [0x000000010cf61474-0x000000010cf614b0)  r-x  0x00001474 0x0000003c 0x80000408 ApplicationMainObjC.__TEXT.__stubs
  0x00000003 code             [0x000000010cf614b0-0x000000010cf61524)  r-x  0x000014b0 0x00000074 0x80000400 ApplicationMainObjC.__TEXT.__stub_helper
  0x00000004 data-cstr        [0x000000010cf61524-0x000000010cf6224a)  r-x  0x00001524 0x00000d26 0x00000002 ApplicationMainObjC.__TEXT.__objc_methname
  0x00000005 data-cstr        [0x000000010cf6224a-0x000000010cf622ba)  r-x  0x0000224a 0x00000070 0x00000002 ApplicationMainObjC.__TEXT.__objc_classname
  0x00000006 data-cstr        [0x000000010cf622ba-0x000000010cf62d94)  r-x  0x000022ba 0x00000ada 0x00000002 ApplicationMainObjC.__TEXT.__objc_methtype
  0x00000007 data-cstr        [0x000000010cf62d94-0x000000010cf62e24)  r-x  0x00002d94 0x00000090 0x00000002 ApplicationMainObjC.__TEXT.__cstring
  0x00000008 regular          [0x000000010cf62e24-0x000000010cf62fb2)  r-x  0x00002e24 0x0000018e 0x00000000 ApplicationMainObjC.__TEXT.__entitlements
  0x00000009 compact-unwind   [0x000000010cf62fb4-0x000000010cf62ffc)  r-x  0x00002fb4 0x00000048 0x00000000 ApplicationMainObjC.__TEXT.__unwind_info
  0x00000300 container        [0x000000010cf63000-0x000000010cf64000)  rw-  0x00003000 0x00001000 0x00000010 ApplicationMainObjC.__DATA_CONST
  0x0000000a data-ptrs        [0x000000010cf63000-0x000000010cf63018)  rw-  0x00003000 0x00000018 0x00000006 ApplicationMainObjC.__DATA_CONST.__got
  0x0000000b objc-cfstrings   [0x000000010cf63018-0x000000010cf63038)  rw-  0x00003018 0x00000020 0x00000000 ApplicationMainObjC.__DATA_CONST.__cfstring
  0x0000000c data-ptrs        [0x000000010cf63038-0x000000010cf63050)  rw-  0x00003038 0x00000018 0x00000000 ApplicationMainObjC.__DATA_CONST.__objc_classlist
  0x0000000d regular          [0x000000010cf63050-0x000000010cf63070)  rw-  0x00003050 0x00000020 0x00000000 ApplicationMainObjC.__DATA_CONST.__objc_protolist
  0x0000000e regular          [0x000000010cf63070-0x000000010cf63078)  rw-  0x00003070 0x00000008 0x00000000 ApplicationMainObjC.__DATA_CONST.__objc_imageinfo
  0x00000400 container        [0x000000010cf64000-0x000000010cf66000)  rw-  0x00004000 0x00002000 0x00000000 ApplicationMainObjC.__DATA
  0x0000000f data-ptrs        [0x000000010cf64000-0x000000010cf64050)  rw-  0x00004000 0x00000050 0x00000007 ApplicationMainObjC.__DATA.__la_symbol_ptr
  0x00000010 data-ptrs        [0x000000010cf64050-0x000000010cf65358)  rw-  0x00004050 0x00001308 0x00000000 ApplicationMainObjC.__DATA.__objc_const
  0x00000011 data-cstr-ptr    [0x000000010cf65358-0x000000010cf65370)  rw-  0x00005358 0x00000018 0x10000005 ApplicationMainObjC.__DATA.__objc_selrefs
  0x00000012 data-ptrs        [0x000000010cf65370-0x000000010cf65380)  rw-  0x00005370 0x00000010 0x10000000 ApplicationMainObjC.__DATA.__objc_classrefs
  0x00000013 data-ptrs        [0x000000010cf65380-0x000000010cf65388)  rw-  0x00005380 0x00000008 0x10000000 ApplicationMainObjC.__DATA.__objc_superrefs
  0x00000014 regular          [0x000000010cf65388-0x000000010cf65390)  rw-  0x00005388 0x00000008 0x00000000 ApplicationMainObjC.__DATA.__objc_ivar
  0x00000015 data-ptrs        [0x000000010cf65390-0x000000010cf65480)  rw-  0x00005390 0x000000f0 0x00000000 ApplicationMainObjC.__DATA.__objc_data
  0x00000016 data             [0x000000010cf65480-0x000000010cf65608)  rw-  0x00005480 0x00000188 0x00000000 ApplicationMainObjC.__DATA.__data
  0x00000500 container        [0x000000010cf66000-0x000000010cf6e000)  r--  0x00006000 0x000075e0 0x00000000 ApplicationMainObjC.__LINKEDIT

What’s important here is that we need to take a load address of the TEXT segment, because we want to find under which address the entry point is loaded into memory. We can use lldb to print the load address in hexadecimal. The load address is simply the load address of the TEXT section shifted by the offset. We can use lldb’s p/x command, which will print the expression in hexadecimal format.

(lldb) p/x 0x000000010cf60000 + 4448
(long) $0 = 0x000000010cf61160

We can verify what symbol is under the address using image lookup.

image lookup -a 0x000000010cf61160
      Address: ApplicationMainObjC[0x0000000100001160] (ApplicationMainObjC.__TEXT.__text + 592)
      Summary: ApplicationMainObjC\`main at main.m:12

Nice! We proved that the main method is the application’s entry point. Please keep in mind that we were operating on load addresses. The same will work when we want to check if the main method is under the correct address in the Mach-O file itself. We can use Hopper to inspect that - we just need to find the start address of the TEXT section, then add the offset to this address and verify if the main method’s address is the same. I’ll leave it as an exercise.

Entry point in Swift application

In Swift, there is no file with the main function, but if we do the same steps that we’ve done in the ObjC project and print backtrace, we’ll see that the first method called is a main method as well. So how is that solved in Swift?

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        return true
    }
}

Yes, you are right, this is all because @UIApplicatinMain attribute which tells the compiler to generate the main function under the hood. Let’s see how is that implemented by a swift compiler.
The swift compiler is written mostly in C++ and it’s open source. You can take a look at the source code on Apple’s Github page. After some investigation, I’ve managed to understand a logic responsible for generating an entry point function. Here is what I’ve found. The module that creates an entry point is SILGen, which is responsible for generating swift intermediate language.
In SourceFileScope’s constructor (inside SILGen.cpp file) there is a logic that checks if the source file is in a script mode - this method returns true if a swift file contains @UIApplicationMain attribute.

class SourceFileScope {
  SILGenModule &sgm;
  SourceFile *sf;
  Optional<Scope> scope;
public:
  SourceFileScope(SILGenModule &sgm, SourceFile *sf) : sgm(sgm), sf(sf) {
    // If this is the script-mode file for the module, create a toplevel.
    if (sf->isScriptMode()) {
      ...
      SILFunction *toplevel = sgm.emitTopLevelFunction(TopLevelLoc);
      ...

    }
  }
}

Inside the constructor, the emitTopLevelFunction function is invoked which generates the main method. We can verify that by looking inside the method.

SILFunction *SILGenModule::emitTopLevelFunction(SILLocation Loc) {

  ...

  SILGenFunctionBuilder builder(*this);
  return builder.createFunction(
      SILLinkage::Public, SWIFT_ENTRY_POINT_FUNCTION, topLevelType, nullptr,
      Loc, IsBare, IsNotTransparent, IsNotSerialized, IsNotDynamic,
      ProfileCounter(), IsNotThunk, SubclassScope::NotApplicable);
}

I did not paste the whole method’s body since it’s not important right now. What’s worth mentioning here is that the method uses a builder to create a function with SWIFT_ENTRY_POINT_FUNCTION name. Let’s try to find what’s hidden behind this constant. To do this quickly we can use grep to perform recursive case insensitive search inside swift files. You can download swift compiler files from Apple’s Github as I mentioned earlier.

$ cd <swift_directory>
$ grep -Ri "SWIFT_ENTRY_POINT_FUNCTION" .
./include/swift/SIL/SILFunction.h:#define SWIFT_ENTRY_POINT_FUNCTION "main"

Yay! We’ve found the method name and it’s also called main. We now have proof that the swift compiler generates the main method based on the @UIApplicationMain attribute.

If you want to dive deeper you can also find a method that is responsible for generating the UIApplicationMain method. I can tell you that the logic is hidden inside emitArtificialTopLevel, which can be found in SILGenFunction.cpp file. Have fun!

Looking inside UIApplicationMain method

OK, right now we know that both ObjC & Swift application have the same entry point, which is the main function. The function’s implementation is the same - there’s a UIApplicationMain function invoked inside. Have you ever wondered what’s actually inside this method? Let’s try to find out!
To do this we have to find a binary where the UIApplicationMain function’s implementation is. We’ll use lldb again to search for a symbol. Run any iOS application and pause it to enter the lldb console.

(lldb) image lookup -n "UIApplicationMain"
1 match found in /Applications/Xcode-11.4.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore:
        Address: UIKitCore[0x0000000000ae4bdb] (UIKitCore.__TEXT.__text + 11412971)
        Summary: UIKitCore`UIApplicationMain

We’ve found a binary. Turns out UIApplicationMain is declared in UIKitCore, which is a private Apple framework. As we now have the framework’s directory we can use Hopper to inspect the binary. Since the method has a quite complex logic, I’ll only paste a few lines of pseudocode to give you a sneak peek of what’s inside.

int _UIApplicationMain(int arg0, int arg1, int arg2, int arg3) {
    {...}

    stack[-112] = objc_autoreleasePoolPush();

    {...}

    BKSDisplayServicesStart();
    rax = [_UIApplicationConfigurationLoader sharedLoader];
    rax = [rax retain];
    [rax startPreloadInitializationContext];
    [rax release];
    rax = [_UIScreenInitialDisplayConfigurationLoader sharedLoader];
    rax = [rax retain];
    [rax _startPreloadInitialDisplayContext];
    [rax release];
    rax = [_UIDeviceInitialDeviceConfigurationLoader sharedLoader];
    rax = [rax retain];
    [rax _startPreloadInitialDeviceContext];
    [rax release];
    _UIApplicationInitialize();
    rax = [stack[-56] retain];
    r14 = rax;
    stack[-56] = rax;

    {...}

    [rbx _startWindowServerIfNecessary];
    [rbx _startStatusBarServerIfNecessary];
    _UIApplicationInstantiateSingleton(rbx);

    {...}

    objc_autoreleasePoolPop(stack[-112]);
    [*_UIApp _run];
    [stack[-80] release];
    [stack[-56] release];
    return 0x0;
}
/* @class UIApplication */
-(void)_run {
    {...}

    [rbx _registerForUserDefaultsChanges];
    [rbx _registerForSignificantTimeChangeNotification];
    [rbx _registerForLanguageChangedNotification];
    [rbx _registerForLocaleWillChangeNotification];
    [rbx _registerForLocaleChangedNotification];
    [rbx _registerForAlertItemStateChangeNotification];
    [rbx _registerForKeyBagLockStatusNotification];
    [rbx _registerForNameLayerTreeNotification];
    [rbx _registerForBackgroundRefreshStatusChangedNotification];
    [rbx _registerForHangTracerEnabledStateChangedNotification];
    __installAfterCACommitHandler(rbx);
    [rbx _installAutoreleasePoolsIfNecessaryForMode:**_kCFRunLoopDefaultMode];
    r12 = *ivar_offset(_eventDispatcher);
    [*(rbx + r12) _installEventRunLoopSources:CFRunLoopGetMain()];

    {...}
}

UIApplicationMain function has all the logic that is needed to launch the application. One of the responsibilities of the method is to initialize application along with its delegate, configure the display & status bar, register for various notifications (such as time, language, locale change), configure the main run loop for handling events and finally call various app delegate’s methods. If you want to find out more, you can always play with Hopper to better understand the logic.

Summary

Wow, we’ve covered a lot in the article! I hope you now have a better understanding of what’s the application entry point and how the operating system handles the start of the application.