Introduction to the

Much longer, today’s hydrological article. A brief introduction to the underlying knowledge of iOS compilation helps us fully understand the process of iOS compilation, and I believe it will be of some help to our subsequent development.

Source code to executable process

It helps to see how iOS code goes from source to executable, from compilation to execution

  1. The compiler Clang compiles the source xxx.m into the object file xxx.o
  2. The linker packages the object file link into the final executable file, Mach-o
  3. When the App ICON is clicked, the dynamic linker DYLD will load the executable file and the dependent dynamic library, and finally execute it in main.m. At this point, the App is started

The compiler

A compiler is a program that converts a programming language into a target language. Most compilers consist of two parts: a front end and a back end.

  • The front end is responsible for lexical analysis, grammar analysis, generation of intermediate code;
  • The back end takes intermediate code as input, does line architecture-independent code optimization, and then generates different machine code for different architectures.

The front and back ends rely on a uniform format of intermediate code (IR) so that the front and back ends can change independently. Adding a language requires only a change in the front end, while adding a CPU architecture requires only a change in the back end.

Objective C/C/C++ uses a compiler with clang front end, Swift swift and LLVM back end.

LLVM is a collection of modular and reusable compiler and toolchain technologies. Clang, a subproject of LLVM, is a C, C++, and Objective-C compiler designed to provide surprisingly fast compilation, up to 3 times faster than GCC,

LLVM can also provide a well-written intermediate representation IR, which means it can be used as a back end for multiple languages, providing language-independent optimizations while facilitating code generation for multiple cpus.

The compilation process

Objective-c’s compiler front end is Clang, which was created as an alternative to GCC for faster compilation. To get an idea of how Clang compilation works, look at the following diagram:

Here we use clang command to analyze the source code compilation process in detail:

Start by typing on the command line

clang -ccc-print-phases main.m
Copy the code

You can see several different stages required for source file compilation

➜ clang-ccC-print-Phases main.m 0: input,"main.m", objective-C 1: preprocessor, {0}, objective-c-pcp-output // Backend, {2}, assembler // Generates assembler 4: Assembler, {3}, object // Generates target files. O 5: linker, {4}, image // link to executable file 6:bind-arch, "x86_64", {5}, image
Copy the code

Let’s create a new main.m and look at what each step does in detail

main.m
#include <stdio.h>

int main() {
  printf("hello world\n");
  return 0;
}
Copy the code

Pretreatment (preprocessor)

We use the following command to see the results of clang preprocessing:

clang -E main.m
Copy the code

Note: if main.m uses UIKit or other classes, you can add the -sysroot parameter after the command. Remember to change the SDK to your own version. As follows:

clang -E main.m -isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.0. The SDKCopy the code

As you can see, the preprocessed file has a lot of lines, and you can find the main function at the end

# 13 "/ Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.0 SDK/Sy stem/Library/Frameworks/UIKit.framework/Headers/ShareSheet.h" 2 3
# 17 "/ Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.0 SDK/Sy stem/Library/Frameworks/UIKit.framework/Headers/UIKit.h" 2 3
# 10 "main.m" 2
# 1 "./AppDelegate.h" 1
# 11 "./AppDelegate.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
@property (strong, nonatomic) UIWindow *window;
 
@end
# 11 "main.m" 2
 
int main(int argc, char * argv[]) {
    @autoreleasepool {
        returnUIApplicationMain(argc, argv, ((void *)0), NSStringFromClass([AppDelegate class])); }}Copy the code

Preprocessing takes the place of header import (recursion), macro substitution #define, comment processing, conditional compilation (#ifdef), #pargma processing, etc. For example, #include “stdio.h” tells the preprocessor to replace this line with the contents of the header file stdio.h. The process is recursive: stdio.h may also contain its header file.

Lexical analysis (Lexical Anaysis)

After the preprocessing is complete, a lexical analysis is performed, where the code is sliced into tokens such as brackets, equals signs, and strings.

clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
Copy the code

Semantic analysis

The syntax analysis verifies the correctness of the syntax, and then forms an abstract syntax tree AST from all nodes. With an abstract syntax tree, Clang can analyze the tree to find errors in code. Such as a type mismatch, or an unimplemented message sent to target in Objective C.

In the industry, Clang custom plug-ins or static detection plug-ins are analyzed based on AST syntax tree. We’ll learn more about that later. The AST is the main data structure that developers interact with when writing Clang plug-ins, and Clang also provides a number of apis to read the AST. More details: Introduction to the Clang AST.

clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
Copy the code

You can see the related AST results in the output, as shown below:

CodeGen

CodeGen is responsible for translating the syntax tree from the top down step by step into LLVM IR, which is the output at the front end of the compilation process and the input at the back end. Objective C code also Bridges runtime in this step: property synthesis, ARC handling, etc.

clang -S -fobjc-arc -emit-llvm main.m -o main.ll
Copy the code

View main.ll as follows:

. ; Function Attrs: noinline optnone ssp uwtable define i32 @main(i32, i8**)# 0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %6 = call i8* @llvm.objc.autoreleasePoolPush() # 1
  %7 = load i32, i32* %4, align 4
  %8 = load i8**, i8*** %5, align 8
  %9 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_The $_". align 8 %10 = bitcast %struct._class_t* %9 to i8* %11 = call i8* @objc_opt_class(i8* %10) %12 = call %0* @NSStringFromClass(i8* %11) %13 = bitcast %0* %12 to i8* %14 = notail call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %13)# 1
  %15 = bitcast i8* %14 to %0*
  %16 = call i32 @UIApplicationMain(i32 %7, i8** %8, %0* null, %0* %15)
  store i32 %16, i32* %3, align 4
  %17 = bitcast %0* %15 to i8*
  call void @llvm.objc.release(i8* %17) # 1,! clang.imprecise_release ! 10
  call void @llvm.objc.autoreleasePoolPop(i8* %6)
  %18 = load i32, i32* %3, align 4
  ret i32 %18
}
 
; Function Attrs: nounwind
...
Copy the code

If bitcode is enabled in the project configuration, Apple will make further optimization, and a new backend architecture can still be generated using this optimized bitcode.

clang -emit-llvm -c main.m -o main.bc
Copy the code

Generating assembly code

clang -S -fobjc-arc main.m -o main.s
Copy the code

Generate object file

The assembler takes assembly code as input, converts assembly code into machine code, and finally outputs object files.

clang -fmodules -c main.m -o main.o
Copy the code

Next we use the nm command to look at the symbols in main.o

➜  BuildTest nm -nm main.o
                 (undefined) external _printf
0000000000000000 (__TEXT,__text) external _main
Copy the code

You can see that _printf is a undefined external. Undefined means the _printf symbol is temporarily not found in the current file, while external means the symbol is externally accessible, corresponding to the file private symbol is non-external.

Generate an executable file

The linker can combine the compiled. O files with the (dylib,a, TBD) files to create a Mach-o file

clang main.o -o main
Copy the code

Then on the command line, execute./main, and you see the output: Hello world. Finally, we use the nm command to analyze the symbol table of executable files:

➜ BuildTest nm-nm main (undefined) external _printf (from libSystem) (undefined) External dyLD_stub_binder (from libSystem) 0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header 0000000100000f60 (__TEXT,__text) external _main 0000000100002008 (__DATA,__data) non-external __dyld_privateCopy the code

You can see that _printf is still undefined, but with the following information: from libSystem, which means that this symbol comes from libSystem and is bound dynamically at runtime.

This is the complete process for compiling source files in Clang.

Xcode to view Clang compiled. M file information

If you want to see it in Xcode, you can view the clang compilation information for each.m file in the build corresponding to the target in the Show The Report Navigator, as shown below:

Pick up any.m file to compile the information, and you’ll see that Xcode describes the task first:

CompileC /Users/chenaibin/Library/Developer/Xcode/DerivedData/PodIntegrationDemo-achbuytjuwbatqbzvlwflifarxwa/Build/Intermediates .noindex/Pods.build/Debug-iphonesimulator/podLibB.build/Objects-normal/x86_64/podClsB.o /Users/chenaibin/Work/DiDi/iOSDemo/BuildErrorDemo/podLibB/Classes/podClsB.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler (in target 'podLibB' from project 'Pods')
Copy the code

Next, the pair updates the working PATH and sets the PATH

cd /Users/chenaibin/Work/DiDi/iOSDemo/BuildErrorDemo/PodIntegrationDemo/Pods
export LANG=en_US.US-ASCII
export PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app /Contents/Developer/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
Copy the code

Next comes the actual compile command

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -target X86_64 - apple - ios9.0 - simulator - fmessage - length = 0 - fobjc - arc... -Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.0. The SDK -iquote ... -I... -F... -c /... /podClsB.m -o /... /podClsB.oCopy the code

Clang uses the following command parameters:

-x compiled languages such as Objective-C-ARCH compiled architectures such as ARM64-f-fAt the beginning of. -w starts with -w, you can use these custom build warnings. -D starts with -d, which means precompiled macros, -iphonesimulator13.0.sdk Version of iOS SDK used for compilation -i write compilation information to the specified auxiliary file -f Required Framework -C identifier that indicates the need to run a preprocessor, parse, type check, LLVM generation optimization and assembly code generation. O file -O compilation resultsCopy the code

Xcode common compiler error analysis

1. Duplicate Symbols fails

The first common compilation error is caused by duplicate symbols. In the following figure, duplicate classes exist in the linked executable file.

Note: Since our project is built by CocoaPods, in xcConfig the OTHER_LINK_FLAG is always set to $(inherited) -objc…… by default If -objc is manually removed, there will be no duplicate symbols error during compilation. If -objc is manually removed, there will be no duplicate symbols error. However, it is possible to run an unrecognized selector sent to class XXX because the static library categories are not linked into the executable by the linker.

-ObjC will link all classes and categories in the static library into the executable, so there will be an error of duplicate Symbols. Here’s the official description:

This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.

2. symbol(s) not found for architecture x86_64/arm64

The second common error is that the symbol is not found in a schema. This is because a referenced static library does not contain the schema type in the current project schema. The solution is to merge the static library.

The reasons for the error are as follows:

Tip: In this case, sometimes multiple POD updates will not resolve the cause of the error. This is because you have cached the problem static library file locally. You can find the relevant class library in the following directory and delete it. Then run pod Install to download the static library file after fix.

Official CocoaPods cache directory: ~ / Library/Caches/CocoaPods/Pods

There is another case for this error, which can be encountered when the same POD is integrated on multiple different sides. The error message is as follows:

PodA integrates podA and podB in ProjectA. PodA integrates classes in podB using #if __has_include(” cls.h in podB “). When switching to ProjectB, there is only one library that depends on podA, which will cause the error in the figure to be compiled.

Solution: just recompile podA in source code in ProjectB.

Application scenarios

Clang Attributes

In normal development, we often encounter the use of __attribute__ in the header file, which is a high-level compiler directive that allows the developer to specify more compile checks and some advanced compile-time optimizations.

The syntax for __attribute__ is: attribute ((attribute-list)) placed in the declaration semicolon “; “. The front.

For example, declaring a property or method is deprecated in the current version, which is most common in tripartite libraries

@property (strong,nonatomic)CLASSNAME * property __deprecated;

Here are a few common uses of __attribute__ in iOS development:

// Deprecate API for API updates#define __deprecated __attribute__((deprecated))// Deprecate with description#define __deprecated_msg(_msg) __attribute__((deprecated(_msg)))// When an __unavailable variable/method is encountered, the compiler simply throws an Error#define __unavailable __attribute__((unavailable))// Tell the compiler not to throw a warning even if the variable/method is not being used#define __unused __attribute__((unused))// The opposite of __unused#define __used __attribute__((used))// Warn if the method return value is not used#define __result_use_check __attribute__((__warn_unused_result__))The OC method is not available in Swift#define __swift_unavailable(_msg) __attribute__((__availability__(swift, unavailable, message=_msg)))
Copy the code

Clang warning processing

When masking some of the Warning information in XCode, we can use the following. Control compilation options for code blocks through Clang Diagnostic Push/POP.

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"/ / / code#pragma clang diagnostic pop
Copy the code

pretreatment

Preprocessing allows us to implement conditional compilation by allowing us to customize compiler variables. For example, we often use the DEBUG macro:

#ifdef DEBUG/ /...#else/ /...#endif
Copy the code

We can select the Build Setting option in XCode’s Target and search for Proprecess to see the defined preprocessor macro.

IOS currently uses CocoaPods to manage projects. You can also configure precompiled macros in the PodSpec file of each Pod, and CocoaPods will write this information to the Pod’s XCconfig file when building projects.

# Pod. Podspec example
s.subspec 'YourSubSpec' do | ss |
  ss.source_files = 'Pod/Classes/**/*'
  ss.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS'= >'$(inherited) YOUR_CUSTOM_DEFINE=1' }
end
Copy the code

Note: GCC_PREPROCESSOR_DEFINITIONS defined by podA do not apply to podB. If you want to solve this problem, it is recommended to define a separate subspec in the podB to configure the value of the precompiled macro, and to control the value of the precompiled macro in the outer project by differentiating whether the subspec of the podB is introduced or not.

Clang plug-in development

So once we get to the syntax analysis, we can take the abstract syntax tree AST, and then we can analyze that tree, either static code analysis or garbage code analysis, and there’s a lot of information on the web about that. If you are interested, you can search for Introduction to the Clang AST

conclusion

The above content mainly introduces the knowledge related to iOS compilation, if there is a content error, welcome to correct.