美文网首页编译
浅谈编译过程

浅谈编译过程

作者: Lucky_Man | 来源:发表于2020-04-11 23:07 被阅读0次

    前言

    笔者前端时间在运行一个组内 Swift 项目的时候,发现编译时间比较长。所以查了部分优化项目编译时间的资料(当然还有部分原因是自己的电脑配置比较低)。并打算记录2篇文章。第一篇文章主要记录关于编译过程的内容,第二篇文章记录笔者在优化 Swift 项目编译时间的一点尝试。

    本文是第一篇关于编译过程的文章。笔者将本文中介绍编译过程相关的内容,本文会按如下几个部分展开。

    1. 编译相关名词解释;
      1.1 编译器
      1.2 编译器架构
      1.3 GCC
      1.4 Clang
      1.5 LLVM

    2. 编译过程简单了解;
      2.1 词法分析;
      2.2 语法分析;
      2.3 语义分析;
      2.4 生成中间代码;
      2.5 优化中间代码;
      2.6 生成目标代码;

    3. 用具体命令代码简单分析编译过程;
      首先名词解释部分,笔者会介绍编译器、GCC、LLVM相关内容。

    一、 名词解释

    1. 编译器

    编译器不是硬件,是可以把源程序编译为目标程序的计算机程序。

    编译器(compiler)是一种计算机程序,它会将某种编程语言写成的源代码(原始语言)转换成另一种编程语言(目标语言)。 引自维基百科编译器

    编译器功能示意图

    2. 编译器架构

    编译器架构:
    2.1 Frontend:前端
    词法分析、语法分析、语义分析、生成中间代码(汇编指令)

    2.2 Optimizer:优化器
    中间代码优化(汇编器进一步优化代码生成目标文件)

    2.3 Backend:后端
    生成机器码(链接器把目标文件链接起来,生成可执行文件)

    3. GCC

    GCC 即 GNU 编译器套件(GNU Compiler Collection)是可以编译 C、Objective-C、C++等源程序的编译器。

    GNU编译器套件(GNU Compiler Collection)包括C、C++Objective-CFortranJavaAdaGo语言的前端,也包括了这些语言的库(如libstdc++、libgcj等等)。GCC的初衷是为GNU操作系统专门编写的一款编译器。GNU系统是彻底的自由软件。此处,"自由"的含义是它尊重用户的自由。 引自360百科 GCC

    4. Clang

    Clang 是C、Objective-C、C++ 等语言的编译器前端。

    Clang(发音为/ˈklæŋ/类似英文单字clang[1]) 是一个CC++Objective-CObjective-C++编程语言的编译器前端。它采用了LLVM作为其后端,而且由LLVM2.6开始,一起发布新版本。它的目标是提供一个GNU编译器套装(GCC)的替代品,支持了GNU编译器大多数的编译设置以及非官方语言的扩展。引自维基百科Clang

    5. LLVM

    LLVM(Low Level Virtual Machine 底层虚拟机),狭义的来说,LLVM 是 Clang 编译器的后端部分,用于把编译代码转化为目标文件。广义的来说 LLVM 是用来开发编译器前端和后端的组件和工具链。

    LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。

    Low Level Virtual Machine 底层虚拟机。 引自360百科LLVM

    6. 项目的整个预编译、编译、链接过程

    下图表明一个项目从源代码到可执行文件,会经历预处理、编译、装载、链接的过程。

    预编译、编译、链接过程.png

    下边笔者初步分享下 GCC 的编译过程。

    二、GCC 编译过程

    这部分,笔者分2部分初步分享查看 GCC 编译源代码到可执行文件的过程,及 GCC 编译源代码到可执行文件的命令。

    1. 查看 GCC 编译过程

    WYW:GCC wangyongwangyongwang$ GCC -ccc-print-phases main.c
    0: input, "main.c", c
    1: preprocessor, {0}, cpp-output
    2: compiler, {1}, ir
    3: backend, {2}, assembler
    4: assembler, {3}, object
    5: linker, {4}, image
    6: bind-arch, "x86_64", {5}, image
    

    2. 编译过程可能用到的命令

    // 1: preprocessor 预编译
    WYW:GCCProcess wangyongwangyongwang$ gcc -E main.c -o main.i
    // 2: compiler生成汇编文件
    WYW:GCCProcess wangyongwangyongwang$ gcc -S main.i -o main.s
    // 3: backend、4: assembler生成目标文件
    WYW:GCCProcess wangyongwangyongwang$ gcc -c main.s -o main.o
    // 5: linker、6: bind-arch生成可执行文件
    WYW:GCCProcess wangyongwangyongwang$ gcc main.o -o main
    // 执行可执行文件
    WYW:GCCProcess wangyongwangyongwang$ sudo ./main
    Hello, World!
    
    
    2.1. 前端过程
    2.1.1 词法分析

    计算机科学中将字符序列转换为标记(token)序列的过程。

    2.1.2 语法分析

    根据给定的形式文法对由单词序列构成的输入文本进行分析并确定其语法结构。

    2.1.3 语义分析

    检测程序的语义错误(如检查所需传入的参数类型)。

    2.1.4 生成中间代码

    得到易于产生并易于翻译成目标程序的抽象机程序。

    2.2 中间过程

    2.2.1 优化

    2.3 后端过程

    2.3.1 生成目标代码

    3. 链接目标文件为可执行文件

    ./可执行文件名字:执行当前路径的可执行文件名字

    下边笔者将分享下 Clang 编译过程的内容,笔者目前的理解是 Clang 可以看做是 LLVM 的前端。LLVM 广义上来说是包含 Clang 的。

    三、Clang 编译过程

    这部分,笔者分2部分初步分享查看 Clang 编译源代码到可以可执行文件的过程,及浅析 Clang 编译源代码到可执行文件的内容。

    1. Clang 编译过程

    笔者先查看了自己电脑的 Clang 版本。

    Apple LLVM version 11.0.0 (clang-1100.0.33.17)

    WYW:~ wangyongwangyongwang$ objdump --version
    Apple LLVM version 11.0.0 (clang-1100.0.33.17)
      Optimized build.
      Default target: x86_64-apple-darwin19.2.0
      Host CPU: broadwell
    
      Registered Targets:
        aarch64    - AArch64 (little endian)
        aarch64_be - AArch64 (big endian)
        arm        - ARM
        arm64      - ARM64 (little endian)
        armeb      - ARM (big endian)
        thumb      - Thumb
        thumbeb    - Thumb (big endian)
        x86        - 32-bit X86: Pentium-Pro and above
        x86-64     - 64-bit X86: EM64T and AMD64
    
    查看 Clang 编译源代码到可执行文件的过程
    WYW:LLVM wangyongwangyongwang$ clang -ccc-print-phases clang_main.c
    0: input, "clang_main.c", c
    1: preprocessor, {0}, cpp-output
    2: compiler, {1}, ir
    3: backend, {2}, assembler
    4: assembler, {3}, object
    5: linker, {4}, image
    6: bind-arch, "x86_64", {5}, image
    

    2. 浅析 Clang 编译 main.m 到可执行文件的过程

    笔者这里用的main.m 的文件内容如下:

    // 一、预编译
    // 引入头文件预编译  注释的内容不会显示在预编译后的文件中
    #import <Foundation/Foundation.h>
    #import <Foundation/NSObjCRuntime.h>
    
    // 条件编译
    #if __has_include(<UIKit/UIKit.h>)
            #import <UIKit/UIKit.h>
        #ifndef Qi_HAS_IMPORT_UIKit
            #define QUC_HAS_IMPORT_UIKit 1
        #endif
    #else
        #ifndef Qi_HAS_IMPORT_UIKit
            #define Qi_HAS_IMPORT_UIKit 0
        #endif
    #endif
    
    // 宏预编译
    #define QiShareAge 2
    // 带参数的宏预编译
    #define QiShareMember(Member, Name) Member#Name
    
    int main(int argc, char * argv[]) {
        
        // NSString * appDelegateClassName;
         @autoreleasepool {
             
             NSLog(@"Hello Clang Compile");
             
             const char *me = QiShareMember("QiShare","WYW");
             printf("member:%s \n", me);
             NSString *member = [NSString stringWithCString:me encoding:NSUTF8StringEncoding];
             NSLog(@"member:%@", member);
             // Setup code that might create autoreleased objects goes here.
             // appDelegateClassName = NSStringFromClass([AppDelegate class]);
             /** 输出内容
              * Hello Clang Compile
              * member:QiShare"WYW"
              * member:QiShare"WYW"
              */
         }
        // return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    
    2.1 预编译
    // 预编译命令
    clang -E main.m -o main_precompile.i
    
    2.1.1 预编译过程做的事情
    1. #include #import 预编译指令,将被包含的文件插入到该预编译指令的位置
    2. 删除所有的注释// 及 块注释 但是会保留注释所在行为空白行
    3. 添加行号和文件标识
    4. 因为编译器有需要,会保留#pragma 编译指令
    
    2.1.2 预编译后生成的代码
    # 1 "main.m"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 374 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "main.m" 2
    # 11 "main.m"
    # 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 1 3
    
    typedef enum __attribute__((enum_extensibility(closed))) NSComparisonResult : NSInteger NSComparisonResult; enum NSComparisonResult : NSInteger {
        NSOrderedAscending = -1L,
        NSOrderedSame,
        NSOrderedDescending
    };
    
    
    typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
    
    
    typedef enum __attribute__((flag_enum,enum_extensibility(open))) NSEnumerationOptions : NSUInteger NSEnumerationOptions; enum NSEnumerationOptions : NSUInteger {
        NSEnumerationConcurrent = (1UL << 0),
        NSEnumerationReverse = (1UL << 1),
    };
    
    # 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
    # 193 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
    # 12 "main.m" 2
    # 31 "main.m"
    int main(int argc, char * argv[]) {
    
    
         @autoreleasepool {
    
             NSLog(@"Hello Clang Compile");
    
             const char *me = "QiShare""\"WYW\"";
             printf("member:%s \n", me);
             NSString *member = [NSString stringWithCString:me encoding:NSUTF8StringEncoding];
             NSLog(@"member:%@", member);
    
    
    
    
    
    
    
         }
    
    }
    
    

    下边2张图左边是生成的预编译文件,右侧是 <Foundation/NSObjCRuntime.h> 中的内容。可以发现有一一对应关系。

    precompile1 precompile2
    2.1.3 预编译小结

    可以发现预编译 .m 文件生成的 .i 文件会把头文件导入,导入方式会具体的指明头文件在电脑上的具体问题;
    会去除注释,相应地会把注释换成空白行;
    添加行号。如main.m 的int main函数的在main.m 文件中位置是第31行。

    2.2 词法分析

    clang -fmodules -E -Xclang -dump-tokens main.m

    clang -fmodules -E -Xclang -dump-tokens main.m > main_precompile.i

    // 词法分析结果
    annot_module_include '#import <Foundation/Foundation.h>
    #import <Foundation/NSObjCRuntime.h>
    
    // 条件编译
    #if __has_include(<UIKit/UIKit.h>)
            #import <UIKit/UIKit.h>
        #ifndef Qi_HAS_IMPORT_UIKit
            #define QUC_HAS_IMPORT_UIKit 1
        #endif
    #else
      '        Loc=<main.m:11:1>
    annot_module_include '#import <Foundation/NSObjCRuntime.h>
    
    // 条件编译
    #if __has_include(<UIKit/UIKit.h>)
            #import <UIKit/UIKit.h>
        #ifndef Qi_HAS_IMPORT_UIKit
            #define QUC_HAS_IMPORT_UIKit 1
        #endif
    #else
        #ifndef Qi_HAS_IMPORT_UIKit
            #define Qi_HAS_IMPORT_UIKit 0
    '        Loc=<main.m:12:1>
    int 'int'     [StartOfLine]    Loc=<main.m:31:1>
    identifier 'main'     [LeadingSpace]    Loc=<main.m:31:5>
    l_paren '('        Loc=<main.m:31:9>
    int 'int'        Loc=<main.m:31:10>
    identifier 'argc'     [LeadingSpace]    Loc=<main.m:31:14>
    comma ','        Loc=<main.m:31:18>
    char 'char'     [LeadingSpace]    Loc=<main.m:31:20>
    star '*'     [LeadingSpace]    Loc=<main.m:31:25>
    identifier 'argv'     [LeadingSpace]    Loc=<main.m:31:27>
    l_square '['        Loc=<main.m:31:31>
    r_square ']'        Loc=<main.m:31:32>
    r_paren ')'        Loc=<main.m:31:33>
    l_brace '{'     [LeadingSpace]    Loc=<main.m:31:35>
    at '@'     [StartOfLine] [LeadingSpace]    Loc=<main.m:34:6>
    identifier 'autoreleasepool'        Loc=<main.m:34:7>
    l_brace '{'     [LeadingSpace]    Loc=<main.m:34:23>
    identifier 'NSLog'     [StartOfLine] [LeadingSpace]    Loc=<main.m:36:10>
    l_paren '('        Loc=<main.m:36:15>
    at '@'        Loc=<main.m:36:16>
    string_literal '"Hello Clang Compile"'        Loc=<main.m:36:17>
    r_paren ')'        Loc=<main.m:36:38>
    semi ';'        Loc=<main.m:36:39>
    const 'const'     [StartOfLine] [LeadingSpace]    Loc=<main.m:38:10>
    char 'char'     [LeadingSpace]    Loc=<main.m:38:16>
    star '*'     [LeadingSpace]    Loc=<main.m:38:21>
    identifier 'me'        Loc=<main.m:38:22>
    equal '='     [LeadingSpace]    Loc=<main.m:38:25>
    string_literal '"QiShare"'     [LeadingSpace]    Loc=<main.m:38:27 <Spelling=main.m:38:41>>
    string_literal '"\"WYW\""'        Loc=<main.m:38:27 <Spelling=<scratch space>:3:1>>
    semi ';'        Loc=<main.m:38:57>
    identifier 'printf'     [StartOfLine] [LeadingSpace]    Loc=<main.m:39:10>
    l_paren '('        Loc=<main.m:39:16>
    string_literal '"member:%s \n"'        Loc=<main.m:39:17>
    comma ','        Loc=<main.m:39:33>
    identifier 'me'     [LeadingSpace]    Loc=<main.m:39:35>
    r_paren ')'        Loc=<main.m:39:37>
    semi ';'        Loc=<main.m:39:38>
    identifier 'NSString'     [StartOfLine] [LeadingSpace]    Loc=<main.m:40:10>
    star '*'     [LeadingSpace]    Loc=<main.m:40:19>
    identifier 'member'        Loc=<main.m:40:20>
    equal '='     [LeadingSpace]    Loc=<main.m:40:27>
    l_square '['     [LeadingSpace]    Loc=<main.m:40:29>
    identifier 'NSString'        Loc=<main.m:40:30>
    identifier 'stringWithCString'     [LeadingSpace]    Loc=<main.m:40:39>
    colon ':'        Loc=<main.m:40:56>
    identifier 'me'        Loc=<main.m:40:57>
    identifier 'encoding'     [LeadingSpace]    Loc=<main.m:40:60>
    colon ':'        Loc=<main.m:40:68>
    identifier 'NSUTF8StringEncoding'        Loc=<main.m:40:69>
    r_square ']'        Loc=<main.m:40:89>
    semi ';'        Loc=<main.m:40:90>
    identifier 'NSLog'     [StartOfLine] [LeadingSpace]    Loc=<main.m:41:10>
    l_paren '('        Loc=<main.m:41:15>
    at '@'        Loc=<main.m:41:16>
    string_literal '"member:%@"'        Loc=<main.m:41:17>
    comma ','        Loc=<main.m:41:30>
    identifier 'member'     [LeadingSpace]    Loc=<main.m:41:32>
    r_paren ')'        Loc=<main.m:41:38>
    semi ';'        Loc=<main.m:41:39>
    r_brace '}'     [StartOfLine] [LeadingSpace]    Loc=<main.m:49:6>
    r_brace '}'     [StartOfLine]    Loc=<main.m:51:1>
    eof ''        Loc=<main.m:51:2>
    
    词法分析
    2.2.1 词法分析小结

    根据上述截图,可以发现词法分析的过程中,会把关键字和方法名描述符,逗号,分号、括号的行号和列号都会记录下来。

    2.3 语法分析

    语法分析命令:clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

    // WYW:clang wangyongwangyongwang$ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
    // 语法分析输出内容
    TranslationUnitDecl 0x7fbeb702da08 <<invalid sloc>> <invalid sloc>
    |-TypedefDecl 0x7fbeb702e2a0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
    | `-BuiltinType 0x7fbeb702dfa0 '__int128'
    |-TypedefDecl 0x7fbeb702e308 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
    | `-BuiltinType 0x7fbeb702dfc0 'unsigned __int128'
    |-TypedefDecl 0x7fbeb702e3a0 <<invalid sloc>> <invalid sloc> implicit SEL 'SEL *'
    | `-PointerType 0x7fbeb702e360 'SEL *' imported
    |   `-BuiltinType 0x7fbeb702e200 'SEL'
    |-TypedefDecl 0x7fbeb702e478 <<invalid sloc>> <invalid sloc> implicit id 'id'
    | `-ObjCObjectPointerType 0x7fbeb702e420 'id' imported
    |   `-ObjCObjectType 0x7fbeb702e3f0 'id' imported
    |-TypedefDecl 0x7fbeb702e558 <<invalid sloc>> <invalid sloc> implicit Class 'Class'
    | `-ObjCObjectPointerType 0x7fbeb702e500 'Class' imported
    |   `-ObjCObjectType 0x7fbeb702e4d0 'Class' imported
    |-ObjCInterfaceDecl 0x7fbeb702e5a8 <<invalid sloc>> <invalid sloc> implicit Protocol
    |-TypedefDecl 0x7fbeb702e8e8 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
    | `-RecordType 0x7fbeb702e700 'struct __NSConstantString_tag'
    |   `-Record 0x7fbeb702e670 '__NSConstantString_tag'
    |-TypedefDecl 0x7fbeb702e980 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
    | `-PointerType 0x7fbeb702e940 'char *'
    |   `-BuiltinType 0x7fbeb702daa0 'char'
    |-TypedefDecl 0x7fbeb7834868 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
    | `-ConstantArrayType 0x7fbeb7834810 'struct __va_list_tag [1]' 1
    |   `-RecordType 0x7fbeb7834690 'struct __va_list_tag'
    |     `-Record 0x7fbeb7834600 '__va_list_tag'
    |-ImportDecl 0x7fbeb79ef2a8 <main.m:11:1> col:1 implicit Foundation
    |-ImportDecl 0x7fbeb79ef2e0 <line:12:1> col:1 implicit Foundation.NSObjCRuntime
    |-FunctionDecl 0x7fbeb79ef580 <line:31:1, line:51:1> line:31:5 main 'int (int, char **)'
    | |-ParmVarDecl 0x7fbeb79ef330 <col:10, col:14> col:14 argc 'int'
    | |-ParmVarDecl 0x7fbeb79ef440 <col:20, col:32> col:27 argv 'char **':'char **'
    | `-CompoundStmt 0x7fbeb88158a8 <col:35, line:51:1>
    |   `-ObjCAutoreleasePoolStmt 0x7fbeb8815890 <line:34:6, line:49:6>
    |     `-CompoundStmt 0x7fbeb8815858 <line:34:23, line:49:6>
    |       |-CallExpr 0x7fbeb7a000d0 <line:36:10, col:38> 'void'
    |       | |-ImplicitCastExpr 0x7fbeb7a000b8 <col:10> 'void (*)(id, ...)' <FunctionToPointerDecay>
    |       | | `-DeclRefExpr 0x7fbeb79fffa8 <col:10> 'void (id, ...)' Function 0x7fbeb79ef688 'NSLog' 'void (id, ...)'
    |       | `-ImplicitCastExpr 0x7fbeb7a000f8 <col:16, col:17> 'id':'id' <BitCast>
    |       |   `-ObjCStringLiteral 0x7fbeb7a00038 <col:16, col:17> 'NSString *'
    |       |     `-StringLiteral 0x7fbeb7a00008 <col:17> 'char [20]' lvalue "Hello Clang Compile"
    |       |-DeclStmt 0x7fbeb7a005d0 <line:38:10, col:57>
    |       | `-VarDecl 0x7fbeb7a00128 <col:10, <scratch space>:3:1> main.m:38:22 used me 'const char *' cinit
    |       |   `-ImplicitCastExpr 0x7fbeb7a00208 <col:41, <scratch space>:3:1> 'const char *' <NoOp>
    |       |     `-ImplicitCastExpr 0x7fbeb7a001f0 <main.m:38:41, <scratch space>:3:1> 'char *' <ArrayToPointerDecay>
    |       |       `-StringLiteral 0x7fbeb7a001c8 <main.m:38:41, <scratch space>:3:1> 'char [13]' lvalue "QiShare\"WYW\""
    |       |-CallExpr 0x7fbeb7a006f0 <main.m:39:10, col:37> 'int'
    |       | |-ImplicitCastExpr 0x7fbeb7a006d8 <col:10> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
    |       | | `-DeclRefExpr 0x7fbeb7a005e8 <col:10> 'int (const char *, ...)' Function 0x7fbeb7a00228 'printf' 'int (const char *, ...)'
    |       | |-ImplicitCastExpr 0x7fbeb7a00738 <col:17> 'const char *' <NoOp>
    |       | | `-ImplicitCastExpr 0x7fbeb7a00720 <col:17> 'char *' <ArrayToPointerDecay>
    |       | |   `-StringLiteral 0x7fbeb7a00648 <col:17> 'char [14]' lvalue "member\357\274\232%s \n"
    |       | `-ImplicitCastExpr 0x7fbeb7a00750 <col:35> 'const char *' <LValueToRValue>
    |       |   `-DeclRefExpr 0x7fbeb7a00670 <col:35> 'const char *' lvalue Var 0x7fbeb7a00128 'me' 'const char *'
    |       |-DeclStmt 0x7fbeb88156c8 <line:40:10, col:90>
    |       | `-VarDecl 0x7fbeb7a00780 <col:10, col:89> col:20 used member 'NSString *' cinit
    |       |   `-ObjCMessageExpr 0x7fbeb8815688 <col:29, col:89> 'NSString * _Nullable':'NSString *' selector=stringWithCString:encoding: class='NSString'
    |       |     |-ImplicitCastExpr 0x7fbeb8815670 <col:57> 'const char *' <LValueToRValue>
    |       |     | `-DeclRefExpr 0x7fbeb8815000 <col:57> 'const char *' lvalue Var 0x7fbeb7a00128 'me' 'const char *'
    |       |     `-DeclRefExpr 0x7fbeb8815330 <col:69> 'NSStringEncoding':'unsigned long' EnumConstant 0x7fbeb8815028 'NSUTF8StringEncoding' 'NSStringEncoding':'unsigned long'
    |       `-CallExpr 0x7fbeb88157d0 <line:41:10, col:38> 'void'
    |         |-ImplicitCastExpr 0x7fbeb88157b8 <col:10> 'void (*)(id, ...)' <FunctionToPointerDecay>
    |         | `-DeclRefExpr 0x7fbeb88156e0 <col:10> 'void (id, ...)' Function 0x7fbeb79ef688 'NSLog' 'void (id, ...)'
    |         |-ImplicitCastExpr 0x7fbeb8815800 <col:16, col:17> 'id':'id' <BitCast>
    |         | `-ObjCStringLiteral 0x7fbeb8815760 <col:16, col:17> 'NSString *'
    |         |   `-StringLiteral 0x7fbeb8815738 <col:17> 'char [12]' lvalue "member\357\274\232%@"
    |         `-ImplicitCastExpr 0x7fbeb8815818 <col:32> 'NSString *' <LValueToRValue>
    |           `-DeclRefExpr 0x7fbeb8815780 <col:32> 'NSString *' lvalue Var 0x7fbeb7a00780 'member' 'NSString *'
    `-<undeserialized declarations>
    
    语法分析

    可以发现语法分析过程中会以语法树的结构说明待编译文件的目录,比如会说明main函数的开始结束行,会说明自动释放池参数、字符串变量、调用函数的行号等信息。

    笔者查到语法分析器的用途有:

    • 反编译语法分析
    • 代码高亮
    • 关键字匹配
    • 作用域判断
    • 代码压缩

    其实我们可以尝试制造语法错误。更具体地查看词法分析、语法分析部分的用处。

    2.3.1 制造语法错误 1

    如果是出现如下错误,词法分析的时候并不会有问题,语法分析的时候会报错。

    int int main(int argc, char * argv[]) {
        
    }
    

    词法分析结果

    '       Loc=<main.m:12:1>
    int 'int'    [StartOfLine]  Loc=<main.m:31:1>
    int 'int'    [LeadingSpace] Loc=<main.m:31:5>
    identifier 'main'    [LeadingSpace] Loc=<main.m:31:9>
    l_paren '('     Loc=<main.m:31:13>
    

    语法分析结果

    main.m:31:5: error: cannot combine with previous 'int' declaration specifier
    int int main(int argc, char * argv[]) {
        ^
    TranslationUnitDecl 0x7ff14302c808 <<invalid sloc>> <invalid sloc>
    
    ...
    
    |           `-DeclRefExpr 0x7ff143922b80 <col:32> 'NSString *' lvalue Var 0x7ff14391f780 'member' 'NSString *'
    `-<undeserialized declarations>
    1 error generated.
    
    2.3.2 制造语法错误 2
    void logSomething(NSString *str1, NSString *str2) {
        
        NSLog(@"str1:%@--str2:%@", str1, str2);
    }
    
    int main(int argc, char * argv[]) {
        
        // NSString * appDelegateClassName;
         @autoreleasepool {
            logSomething(1, 2);
         }
    }
    
    main.m:41:23: warning: incompatible integer to pointer conversion passing 'int'
          to parameter of type 'NSString *' [-Wint-conversion]
             logSomething(1, 2);
                          ^
    main.m:31:29: note: passing argument to parameter 'str1' here
    void logSomething(NSString *str1, NSString *str2) {
                                ^
    main.m:41:26: warning: incompatible integer to pointer conversion passing 'int'
          to parameter of type 'NSString *' [-Wint-conversion]
             logSomething(1, 2);
                             ^
    main.m:31:45: note: passing argument to parameter 'str2' here
    void logSomething(NSString *str1, NSString *str2) {
                                                ^
    
    

    可以发现像两个 int 关键字连续出现的语法错误,在词法分析的过程中并不会报错,在语法分析的时候,会有相应报错。

    2.4 生成中间代码

    生成中间代码可用命令:clang -S -fobjc-arc -emit-llvm main.m -o main.ll
    可以用 cat 命令查看中间代码内容。

    2.4.1 中间代码缩减

    下列代码是中间代码,中间代码中会有文件名和系统名的标识。
    中间代码中会有常量字符串,缓存、实例变量、消息列表、属性列表等信息说明。
    笔者不怎么了解中间代码,不做分析。

    WYW:clang wangyongwangyongwang$ clang -S -fobjc-arc -emit-llvm main.m -o main.ll
    WYW:clang wangyongwangyongwang$ cat main.ll
    ; ModuleID = 'main.m'
    source_filename = "main.m"
    target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx10.15.0"
        
    // 常量字符串,缓存、实例变量、消息列表、属性列表等信息
    %0 = type opaque
    %struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }
    %struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
    %struct._objc_cache = type opaque
    %struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }
    %struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] }
    %struct._objc_method = type { i8*, i8*, i8* }
    %struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
    %struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }
    %struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] }
    %struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
    %struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
    %struct._prop_t = type { i8*, i8* }
    
    
    @__CFConstantStringClassReference = external global [0 x i32]
    @.str = private unnamed_addr constant [20 x i8] c"Hello Clang Compile\00", section "__TEXT,__cstring,cstring_literals", align 1
    @_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str, i32 0, i32 0), i64 19 }, section "__DATA,__cfstring", align 8
    @.str.1 = private unnamed_addr constant [13 x i8] c"QiShare\22WYW\22\00", align 1
    @.str.2 = private unnamed_addr constant [14 x i8] c"member\EF\BC\9A%s \0A\00", align 1
    
    ; Function Attrs: nounwind
    declare i8* @llvm.objc.autoreleasePoolPush() #1
    
    declare void @NSLog(i8*, ...) #2
    
    declare i32 @printf(i8*, ...) #2
    
    ; Function Attrs: nonlazybind
    declare i8* @objc_msgSend(i8*, i8*, ...) #3
    
    ; Function Attrs: nounwind
    declare i8* @llvm.objc.retainAutoreleasedReturnValue(i8*) #1
    
    ; Function Attrs: nounwind
    declare void @llvm.objc.storeStrong(i8**, i8*) #1
    
    ; Function Attrs: nounwind
    declare void @llvm.objc.autoreleasePoolPop(i8*) #1
    
    !0 = !{i32 2, !"SDK Version", [2 x i32] [i32 10, i32 15]}
    !1 = !{i32 1, !"Objective-C Version", i32 2}
    !2 = !{i32 1, !"Objective-C Image Info Version", i32 0}
    !3 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
    !4 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
    !5 = !{i32 1, !"Objective-C Class Properties", i32 64}
    !6 = !{i32 1, !"wchar_size", i32 4}
    !7 = !{i32 7, !"PIC Level", i32 2}
    !8 = !{!"Apple clang version 11.0.0 (clang-1100.0.33.17)"}
    !9 = !{}
    
    生成汇编

    生成汇编文件:clang -S main.m -o main_compile.s
    并通过cat main_compile.s 查看文件内容。

    删减版汇编文件

    下述汇编文件是笔者参照网上资料做了部分注释后的内容。

    // WYW:clang wangyongwangyongwang$ clang -S main.m -o main_compile.s
    // WYW:clang wangyongwangyongwang$ cat main_compile.s
        
        
    // 汇编指令
    // .section 指令指定接下来会执行哪一个段。
        .section    __TEXT,__text,regular,pure_instructions
        .build_version macos, 10, 15    sdk_version 10, 15
    // .globl 指令说明 _main 是一个外部符号。这就是我们的 main() 函数。这个函数对于二进制文件外部来说是可见的,因为系统要调用它来运行可执行文件。
        .globl  _main                   ## -- Begin function main
    // .align 指令指出了后面代码的对齐方式。在我们的代码中,后面的代码会按照 16(2^4) 字节对齐,如果需要的话,用 0x90 补齐。
        .p2align    4, 0x90
    
    
    // main 函数的头部:
    _main:                                  ## @main
        
    // .cfi_startproc 指令通常用于函数的开始处。CFI 是调用帧信息 (Call Frame Information) 的缩写。这个调用 帧 以松散的方式对应着一个函数。它与后面的 .cfi_endproc 相匹配,以此标记出 main() 函数结束的地方。
        .cfi_startproc
    
    ## %bb.0:
    // 在 OS X上,我们会有 X86_64 的代码,对于这种架构,有一个东西叫做 ABI ( 应用二进制接口 application binary interface),ABI 指定了函数调用是如何在汇编代码层面上工作的。在函数调用期间,ABI 会让 rbp 寄存器 (基础指针寄存器 base pointer register) 被保护起来。当函数调用返回时,确保 rbp 寄存器的值跟之前一样,这是属于 main 函数的职责。pushq %rbp 将 rbp 的值 push 到栈中,以便我们以后将其 pop 出来。
        pushq   %rbp
    
    // 接下来是两个 CFI 指令:这将会输出一些关于生成调用堆栈展开和调试的信息。我们改变了堆栈和基础指针,而这两个指令可以告诉编译器它们都在哪儿,或者更确切的,它们可以确保之后调试器要使用这些信息时,能找到对应的东西。
        .cfi_def_cfa_offset 16
        .cfi_offset %rbp, -16
    // 接下来,movq %rsp, %rbp 将把局部变量放置到栈上。subq $32, %rsp 将栈指针移动 32 个字节,也就是函数会调用的位置。我们先将老的栈指针存储到 rbp 中,然后将此作为我们局部变量的基址,接着我们更新堆栈指针到我们将会使用的位置。
        movq    %rsp, %rbp
        .cfi_def_cfa_register %rbp
        subq    $64, %rsp
        movl    %edi, -4(%rbp)
        movq    %rsi, -16(%rbp)
    // 调用 autoreleasePoolPush
        callq   _objc_autoreleasePoolPush
        leaq    L__unnamed_cfstring_(%rip), %rsi
        movq    %rsi, %rdi
        movq    %rax, -40(%rbp)         ## 8-byte Spill
        movb    $0, %al
    // 调用 NSLog
        callq   _NSLog
        leaq    L_.str.1(%rip), %rsi
        movq    %rsi, -24(%rbp)
        movq    -24(%rbp), %rsi
        leaq    L_.str.2(%rip), %rdi
        movb    $0, %al
    // 调用 printf
        callq   _printf
        movq    L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rsi
        movq    -24(%rbp), %rdx
        movq    L_OBJC_SELECTOR_REFERENCES_(%rip), %rdi
        movq    %rdi, -48(%rbp)         ## 8-byte Spill
        movq    %rsi, %rdi
        movq    -48(%rbp), %rsi         ## 8-byte Reload
        movl    $4, %ecx
        movl    %eax, -52(%rbp)         ## 4-byte Spill
        
        callq   *_objc_msgSend@GOTPCREL(%rip)
        leaq    L__unnamed_cfstring_.4(%rip), %rcx
        movq    %rax, -32(%rbp)
        movq    -32(%rbp), %rsi
        movq    %rcx, %rdi
        movb    $0, %al
    
        callq   _NSLog
        movq    -40(%rbp), %rdi         ## 8-byte Reload
        callq   _objc_autoreleasePoolPop
        xorl    %eax, %eax
        addq    $64, %rsp
        popq    %rbp
        retq
    // main() 函数结束的地方
        .cfi_endproc
                                            ## -- End function
        .section    __TEXT,__cstring,cstring_literals
    L_.str:                                 ## @.str
        .asciz  "Hello Clang Compile"
    
    2.5 生成目标文件

    生成目标文件的命令为 clang -fmodules -c main.m -o main_obj.o
    可以通过 objdump 查看目标文件中的内容。
    objdump -t main_obj.oobjdump -syms main_obj.o 用于查看目标文件符号表入口。
    更多 objdump 命令可查看 14. objdump 二进制文件分析

    // clang -fmodules -c main.m -o main_obj.o
    // WYW:clang wangyongwangyongwang$ objdump -t main_obj.o
    
    main_obj.o: file format Mach-O 64-bit x86-64
    
    SYMBOL TABLE:
    0000000000000140 l     O __TEXT,__ustring   l_.str.3
    0000000000000000 g     F __TEXT,__text  _main
    0000000000000000         *UND*  _NSLog
    0000000000000000         *UND*  _OBJC_CLASS_$_NSString
    0000000000000000         *UND*  ___CFConstantStringClassReference
    0000000000000000         *UND*  _objc_autoreleasePoolPop
    0000000000000000         *UND*  _objc_autoreleasePoolPush
    0000000000000000         *UND*  _objc_msgSend
    0000000000000000         *UND*  _printf
    

    下列大部分内容引自:Mach-O 可执行文件

    2.5.1 查看目标文件内容 section

    一个可执行文件包含多个段(section)。可执行文件不同的部分将加载进不同的 section,并且每个 section 会转换进某个 segment 里。这个概念对于所有的可执行文件都是成立的。

    我们来看看 a.out 二进制中的 section。我们可以使用 size 工具来观察

    xcrun size -x -l -m main_obj.o

    WYW:clang wangyongwangyongwang$ xcrun size -x -l -m main_obj.o
    Segment : 0x1c0 (vmaddr 0x0 fileoff 1488)
        Section (__TEXT, __text): 0x9b (addr 0x0 offset 1488)
        Section (__TEXT, __cstring): 0x2f (addr 0x9b offset 1643)
        Section (__DATA, __cfstring): 0x40 (addr 0xd0 offset 1696)
        Section (__DATA, __objc_classrefs): 0x8 (addr 0x110 offset 1760)
        Section (__TEXT, __objc_methname): 0x1c (addr 0x118 offset 1768)
        Section (__DATA, __objc_selrefs): 0x8 (addr 0x138 offset 1800)
        Section (__TEXT, __ustring): 0x14 (addr 0x140 offset 1808)
        Section (__DATA, __objc_imageinfo): 0x8 (addr 0x154 offset 1828)
        Section (__LD, __compact_unwind): 0x20 (addr 0x160 offset 1840)
        Section (__TEXT, __eh_frame): 0x40 (addr 0x180 offset 1872)
        total 0x1b2
    total 0x1c0
    

    __TEXT segment 包含了被执行的代码。它被以只读和可执行的方式映射。进程被允许执行这些代码,但是不能修改。这些代码也不能对自己做出修改,因此这些被映射的页从来不会被改变。

    __DATA segment 以可读写和不可执行的方式映射。它包含了将会被更改的数据。

    otool(1) 来观察一个 section 中的内容:

    xcrun otool -s __TEXT __text main_obj.o

    WYW:clang wangyongwangyongwang$ xcrun otool -s __TEXT __text main_obj.o
    main_obj.o:
    Contents of (__TEXT,__text) section
    0000000000000000    55 48 89 e5 48 83 ec 40 89 7d fc 48 89 75 f0 e8 
    0000000000000010    00 00 00 00 48 8d 35 b5 00 00 00 48 89 f7 48 89 
    0000000000000020    45 d8 b0 00 e8 00 00 00 00 48 8d 35 7f 00 00 00 
    0000000000000030    48 89 75 e8 48 8b 75 e8 48 8d 3d 7d 00 00 00 b0 
    0000000000000040    00 e8 00 00 00 00 48 8b 35 c3 00 00 00 48 8b 55 
    0000000000000050    e8 48 8b 3d e0 00 00 00 48 89 7d d0 48 89 f7 48 
    0000000000000060    8b 75 d0 b9 04 00 00 00 89 45 cc ff 15 00 00 00 
    0000000000000070    00 48 8d 0d 78 00 00 00 48 89 45 e0 48 8b 75 e0 
    0000000000000080    48 89 cf b0 00 e8 00 00 00 00 48 8b 7d d8 e8 00 
    0000000000000090    00 00 00 31 c0 48 83 c4 40 5d c3 
    
    2.5.2 反汇编

    xcrun otool -v -t main_obj.o,通过添加 -v 来查看反汇编代码:

    WYW:clang wangyongwangyongwang$ xcrun otool -v -t main_obj.o
    main_obj.o:
    (__TEXT,__text) section
    _main:
    0000000000000000    pushq   %rbp
    0000000000000001    movq    %rsp, %rbp
    0000000000000004    subq    $0x40, %rsp
    0000000000000008    movl    %edi, -0x4(%rbp)
    000000000000000b    movq    %rsi, -0x10(%rbp)
    000000000000000f    callq   0x14
    0000000000000014    leaq    0xb5(%rip), %rsi
    000000000000001b    movq    %rsi, %rdi
    000000000000001e    movq    %rax, -0x28(%rbp)
    0000000000000022    movb    $0x0, %al
    0000000000000024    callq   0x29
    0000000000000029    leaq    0x7f(%rip), %rsi
    0000000000000030    movq    %rsi, -0x18(%rbp)
    0000000000000034    movq    -0x18(%rbp), %rsi
    0000000000000038    leaq    0x7d(%rip), %rdi
    000000000000003f    movb    $0x0, %al
    0000000000000041    callq   0x46
    0000000000000046    movq    0xc3(%rip), %rsi
    000000000000004d    movq    -0x18(%rbp), %rdx
    0000000000000051    movq    0xe0(%rip), %rdi
    0000000000000058    movq    %rdi, -0x30(%rbp)
    000000000000005c    movq    %rsi, %rdi
    000000000000005f    movq    -0x30(%rbp), %rsi
    0000000000000063    movl    $0x4, %ecx
    0000000000000068    movl    %eax, -0x34(%rbp)
    000000000000006b    callq   *(%rip)
    0000000000000071    leaq    0x78(%rip), %rcx
    0000000000000078    movq    %rax, -0x20(%rbp)
    000000000000007c    movq    -0x20(%rbp), %rsi
    0000000000000080    movq    %rcx, %rdi
    0000000000000083    movb    $0x0, %al
    0000000000000085    callq   0x8a
    000000000000008a    movq    -0x28(%rbp), %rdi
    000000000000008e    callq   0x93
    0000000000000093    xorl    %eax, %eax
    0000000000000095    addq    $0x40, %rsp
    0000000000000099    popq    %rbp
    000000000000009a    retq
    
    

    otool(1) 可用于查看一个 section 中的内容:

    WYW:clang wangyongwangyongwang$ xcrun otool -s __TEXT __cstring main_obj.o
    main_obj.o:
    Contents of (__TEXT,__cstring) section
    000000000000009b    48 65 6c 6c 6f 20 43 6c 61 6e 67 20 43 6f 6d 70 
    00000000000000ab    69 6c 65 00 51 69 53 68 61 72 65 22 57 59 57 22 
    00000000000000bb    00 6d 65 6d 62 65 72 ef bc 9a 25 73 20 0a 00 
    
    2.5.3 反汇编目标文件

    下边我们可以反汇编目标文件,来查看逆向反汇编后得到的汇编文件,和之前正向用于生成目标文件的汇编文件是否一致。
    下图表明,反汇编的汇编文件,和之前正向生成目标文件的汇编文件的内容基本一样。
    只是反汇编文件中去除了部分命令。

    目标文件反汇编
    2.6 生成可执行文件及执行文件
    // 生成执行文件
    WYW:clang wangyongwangyongwang$ clang main_obj.o -o mainExec
    
    // 执行可执行文件
    WYW:clang wangyongwangyongwang$ ./mainExec
    2020-04-01 22:12:15.766 mainExec[84398:2934623] Hello Clang Compile
    member:QiShare"WYW" 
    2020-04-01 22:12:15.767 mainExec[84398:2934623] member:QiShare"WYW"
    

    四、参考学习网址

    相关文章

      网友评论

        本文标题:浅谈编译过程

        本文链接:https://www.haomeiwen.com/subject/hwqmmhtx.html