入门一

作者: 05fad25aea68 | 来源:发表于2019-01-09 11:00 被阅读2次

    C程序设计语言的汇编解释(ARM64版) 第一章 一些实例

    不多废话直接从实例开始吧!

    1.1 开始

    讲语言的第一个例子自然是在控制台打印:

    hello, world
    

    想必大家都可以很轻易的用C写出如下代码:

    #include <stdio.h>
    
    main()
    {
        printf("hello, world\n");
    }
    

    将上述代码保存至helloworld.c, 并使用clang进行编译:

    clang helloworld.c
    

    得到a.out产物, 在命令行执行它, 将打印出如下内容:

    hello, world
    

    咳咳, 这并不是想要的结果, 还是祭出clang汇编吧:

    // 注意,以下代码将默认生成pc版的汇编指令
    clang -S helloworld.c
    // ARM64汇编需要如下命令,指定架构和系统头文件所在的目录,请务必将isysroot的sdk版本修改为自己xcode中存在的版本!
    clang -S -arch arm64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.1.sdk helloworld.c
    

    该指令会生成同名文件helloworld.s, 其内容如下:

            .section        __TEXT,__text,regular,pure_instructions
            .ios_version_min 11, 1
            .globl  _main
            .p2align        2
    _main:                                  ; @main
    ; BB#0:
            sub     sp, sp, #32             ; =32
            stp     x29, x30, [sp, #16]     ; 8-byte Folded Spill
            add     x29, sp, #16            ; =16
            adrp    x0, l_.str@PAGE
            add     x0, x0, l_.str@PAGEOFF
            bl      _printf
            mov     w8, #0
            stur    w0, [x29, #-4]          ; 4-byte Folded Spill
            mov      x0, x8
            ldp     x29, x30, [sp, #16]     ; 8-byte Folded Reload
            add     sp, sp, #32             ; =32
            ret
    
            .section        __TEXT,__cstring,cstring_literals
    l_.str:                                 ; @.str
            .asciz  "hello, world\n"
    
    .subsections_via_symbols
    

    代码中类似.section或.globl等以'.'开头的, 被称之为汇编器指令, 用于告知汇编器相关的信息(如'.section'后面的节的名称和属性)或者进行特定操作。

    类似main:或l.str:的被称之为标签(label), 用于辅助定位代码或者资源地址, 也便于开发者理解和记忆。

    类似pushq或movq的, 被称之为汇编指令, 它们会被汇编器编译为机器代码, 最终被cpu所执行。

    上述代码中, 用.section指令生成了两个节:

    • __TEXT**,**__text用来存放代码指令, 代码一般都放在这一节。
    • __TEXT**,**__cstring用来存放c string也就是C语言字符串, 可以看到'hello, worldn'字符串就存放在这里。'.asciz'表示零结尾的字符串。
      'hello, worldn'字符串前一行是一个标签(label)'l_.str'在之前的代码中'adrp x0,l_.str@PAGE'指令引用了'L_.str'这个标签标签之后的'@PAGE'表示获取标签所在的页的地址(一个按0x1000或0x4000对齐的地址)下一条指令'add x0, x0,l_.str@PAGEOFF'中的'@PAGEOFF'表示获取标签地址对应页地址的偏移在经过汇编器汇编后会将标汇编为字符串所存放的地址让程序可以定位到字符串。

    指令'bl _printf'调用_printf方法它会将'x0'寄存器里的内容作为第一个参数(里面存放的是"hello, worldn"字符串的地址)。按照ARM64的Calling Convention整形的参数前8个会按顺序放到'x0-x7'寄存器里超过八个的放到栈上传递(详见文末参考1)。
    代码中的如下部分被称之为方法头(prologue), 用于保存上一个方法调用栈帧的帧头及预留部分栈空间用于局部变量:

            sub     sp, sp, #32             ; =32
            stp     x29, x30, [sp, #16]     ; 8-byte Folded Spill
            add     x29, sp, #16            ; =16
    

    代码中如下部分被称之为方法尾(epilogue) 用于取出方法头中栈帧信息及方法的返回地址, 并将栈恢复到方法调用前的位置:

            ldp     x29, x30, [sp, #16]     ; 8-byte Folded Reload
            add     sp, sp, #32             ; =32
            ret
    

    调用栈的栈帧(stack frame)可以用来回溯调用栈可以用于生成调用栈。

    1.2 变量和算术表达式

    下一段程序用于输出华氏温度和摄氏温度的值的对应关系, 其运算公式为'C=(5/9)(F-32)', 程序输出如下结果:

    0    -17
    20    -6
    40    4
    60    15
    80    26
    100    37
    120    48
    140    60
    160    71
    180    82
    200    93
    220    104
    240    115
    260    126
    280    137
    300    148
    

    对应的代码如下:

    #include <stdio.h>
    
    /* print Fahrenheit-Celsius table for fahr = 0, 20, ..., 300 */
    main() {
        int fahr, celsius;
        int lower, upper, step;
    
        lower = 0;    /* lower limit of temperature scale */
        upper = 300;  /* upper limit */
        step = 20;     /* step size */
    
        fahr = lower;
        while (fahr <= upper) {
            celsius = 5 * (fahr-32) / 9;
            printf("%d\t%d\n", fahr, celsius);
            fahr = fahr + step;
        }
    }
    

    这段代码的汇编结果如下:

        .section    __TEXT,__text,regular,pure_instructions
        .ios_version_min 11, 1
        .globl    _main
        .p2align    2
    _main:                                  ; @main
    ; BB#0:
        sub    sp, sp, #64             ; =64
        stp    x29, x30, [sp, #48]     ; 8-byte Folded Spill
        add    x29, sp, #48            ; =48
        mov    w8, #20
        mov    w9, #300
        stur    wzr, [x29, #-4]
        stur    wzr, [x29, #-16]
        stur    w9, [x29, #-20]
        str    w8, [sp, #24]
        ldur    w8, [x29, #-16]
        stur    w8, [x29, #-8]
    LBB0_1:                                 ; =>This Inner Loop Header: Depth=1
        ldur    w8, [x29, #-8]
        ldur    w9, [x29, #-20]
        cmp        w8, w9
        b.gt    LBB0_3
    ; BB#2:                                 ;   in Loop: Header=BB0_1 Depth=1
        mov    w8, #5
        ldur    w9, [x29, #-8]
        sub    w9, w9, #32             ; =32
        mul        w8, w8, w9
        mov    w9, #9
        sdiv    w8, w8, w9
        stur    w8, [x29, #-12]
        ldur    w8, [x29, #-8]
        ldur    w9, [x29, #-12]
                                            ; implicit-def: %X0
        mov     x0, x9
        mov     x10, sp
        str    x0, [x10, #8]
                                            ; implicit-def: %X0
        mov     x0, x8
        str        x0, [x10]
        adrp    x0, l_.str@PAGE
        add    x0, x0, l_.str@PAGEOFF
        bl    _printf
        ldur    w8, [x29, #-8]
        ldr    w9, [sp, #24]
        add        w8, w8, w9
        stur    w8, [x29, #-8]
        str    w0, [sp, #20]           ; 4-byte Folded Spill
        b    LBB0_1
    LBB0_3:
        ldur    w0, [x29, #-4]
        ldp    x29, x30, [sp, #48]     ; 8-byte Folded Reload
        add    sp, sp, #64             ; =64
        ret
    
        .section    __TEXT,__cstring,cstring_literals
    l_.str:                                 ; @.str
        .asciz    "%d\t%d\n"
    
    .subsections_via_symbols
    

    这里先介绍一个重点:局部变量是放在栈上的!
    其中的关键点和解释如下:

        sub    sp, sp, #64             ; =64
        stp    x29, x30, [sp, #48]     ; 8-byte Folded Spill
        add    x29, sp, #48            ; =48
    

    prologue部分sp下移64 byte并往sp最顶部的16位存入上一个stack frame的fp和lr剩下48个byte然后把x29(也就是fp)设为栈顶(这个是ARM64的约定)。然后就是局部变量处理:

        mov    w8, #20
        mov    w9, #300
        stur    wzr, [x29, #-4]
        stur    wzr, [x29, #-16]
        stur    w9, [x29, #-20]
        str    w8, [sp, #24]
        ldur    w8, [x29, #-16]
        stur    w8, [x29, #-8]
    

    由于ARM64没有指令可以直接把常数放入内存所以编译器采用了把常数先放入寄存器再从寄存器中存入内存的方式来实现。由于我们在对c代码做汇编时没有指定优化级别默认生成的汇编代码未做优化其代码顺序和c源代码会高度相关。

        int fahr, celsius;
        int lower, upper, step;
    

    这里申明的局部变量会按照顺序在栈上预留空间也就是之前的48byte中的某些部分。这里还隐含了一个局部变量'int return_value;'的声明在最前面因为main方法实际的返回值是int而不是void编译器自动会加上返回值的处理返回值默认是0。
    那么栈剩余的空间和局部变量的对应关系会变成这样:

    x29-4 = sp+44 = return_value = 0
    x29-8 = sp+40 = fahr
    x29-12 = sp+36 = celsius
    x29-16 = sp+32 = lower
    x29-20 = sp+28 = upper
    x29-24 = sp+24 = step
    

    首先是c源代码用到的两个常量20和300分别放入w8和w9寄存器。然后把wzr(零寄存器里面存的是0)的内容分别存入'x29-4'(也就是return_value)和'x29-16'(也就是lower)位置。再然后把w9的内容也就是常量300存入'x29-20'(也就是upper)再然后把w8的内容也就是20存入'sp+24'也就是step中。再然后用ldur指令从'x29-16'也就是lower取出值放入'x29-8'(fahr变量)中完成'fahr = lower'。
    后面开始进入循环:

    LBB0_1:                                 ; =>This Inner Loop Header: Depth=1
        ldur    w8, [x29, #-8]
        ldur    w9, [x29, #-20]
        cmp        w8, w9
        b.gt    LBB0_3
    

    首先是标签LBB0_1处后面的注释已经提到这里是循环头。首先从'x29-8'(fahr)取出值放入w8再从'x29-20'(upper)处取出值放入w9再把x8和x9做比较如果结果是gt(大于)则跳转到标签LBB0_3也就是循环结尾。从C源代码中可以看到这里是做了'fahr<=upper'的判断而汇编代码的现实是如果大于就跳到循环结尾否则继续往下走进入循环体。
    接下来是循环体里的'celsius = 5 * (fahr-32) / 9;'这句:

    ; BB#2:                                 ;   in Loop: Header=BB0_1 Depth=1
        mov    w8, #5
        ldur    w9, [x29, #-8]
        sub    w9, w9, #32             ; =32
        mul        w8, w8, w9
        mov    w9, #9
        sdiv    w8, w8, w9
        stur    w8, [x29, #-12]
    

    首先把数字5放入寄存器w8然后从'x29-8'(fahr)取出值放入w9然后'w9=w9-32'再'w8=w8w9'完成了'5 (fahr-32)'。然后把数字存入w9然后'w8=w8/w9'完成'5 * (fahr-32) / 9',**结果w8放入'x29-12'(celsius)中。
    接下来是'printf'这句:

        ldur    w8, [x29, #-8]
        ldur    w9, [x29, #-12]
                                            ; implicit-def: %X0
        mov     x0, x9
        mov     x10, sp
        str    x0, [x10, #8]
                                            ; implicit-def: %X0
        mov     x0, x8
        str        x0, [x10]
        adrp    x0, l_.str@PAGE
        add    x0, x0, l_.str@PAGEOFF
        bl    _printf
    

    首先取出fahr(放入w8)和celsius(放入w9)把x9(w9和x9是同一个寄存器x是整个64位w是低32位)移入x0。然后把sp移入x10把x0(也就是celsius)的值存入'x10+8'(也就是sp+8)指向的内存中作为printf的第三个参数。然后同样的操作把fahr放入'sp'指向的内存中。接下来'adrp'和'add'两句取出printf的第一个参数(也就是format)放入x0中然后调用'_printf方法'。注意这里的'_printf'也是一个标签它指向了'printf'的代码所在的地址在编译后会被直接替换为一个地址。再注意前面提到方法调用的参数传递一般是依次放在x0-x7然后才是栈上而'printf'从第二参数开始就放在了栈上是因为printf是可变参数方法可变参数部分都是存在栈上的(详见文末参考2)。
    再接下来就是循环体内最后一句:

        ldur    w8, [x29, #-8]
        ldr    w9, [sp, #24]
        add        w8, w8, w9
        stur    w8, [x29, #-8]
        str    w0, [sp, #20]           ; 4-byte Folded Spill
        b    LBB0_1
    

    取出fahr和celsius相加放回fahr然后调回循环头'LBB0_1'处。
    然后就是epilog这里就不多做说明:

    LBB0_3:
        ldur    w0, [x29, #-4]
        ldp    x29, x30, [sp, #48]     ; 8-byte Folded Reload
        add    sp, sp, #64             ; =64
        ret
    

    1.3 for循环

    在1.2节的例子中用的是while循环实际上也可以用for循环来实现:

    #include <stdio.h>
    
    /* print Fahrenheit-Celsius table */
    main() {
        int fahr;
    
        for (fahr = 0; fahr <= 300; fahr = fahr + 20) {
            printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
        }
    }
    

    其汇编实现如下:

        .section    __TEXT,__text,regular,pure_instructions
        .ios_version_min 11, 1
        .section    __TEXT,__literal8,8byte_literals
        .p2align    3
    lCPI0_0:
        .quad    4603179219131243634     ; double 0.55555555555555558
        .section    __TEXT,__text,regular,pure_instructions
        .globl    _main
        .p2align    2
    _main:                                  ; @main
    ; BB#0:
        sub    sp, sp, #48             ; =48
        stp    x29, x30, [sp, #32]     ; 8-byte Folded Spill
        add    x29, sp, #32            ; =32
        stur    wzr, [x29, #-4]
        stur    wzr, [x29, #-8]
    LBB0_1:                                 ; =>This Inner Loop Header: Depth=1
        ldur    w8, [x29, #-8]
        cmp        w8, #300        ; =300
        b.gt    LBB0_4
    ; BB#2:                                 ;   in Loop: Header=BB0_1 Depth=1
        adrp    x8, lCPI0_0@PAGE
        ldr    d0, [x8, lCPI0_0@PAGEOFF]
        ldur    w9, [x29, #-8]
        ldur    w10, [x29, #-8]
        sub    w10, w10, #32           ; =32
        scvtf    d1, w10
        fmul    d0, d0, d1
        mov     x8, sp
        str    d0, [x8, #8]
                                            ; implicit-def: %X0
        mov     x0, x9
        str        x0, [x8]
        adrp    x0, l_.str@PAGE
        add    x0, x0, l_.str@PAGEOFF
        bl    _printf
        stur    w0, [x29, #-12]         ; 4-byte Folded Spill
    ; BB#3:                                 ;   in Loop: Header=BB0_1 Depth=1
        ldur    w8, [x29, #-8]
        add    w8, w8, #20             ; =20
        stur    w8, [x29, #-8]
        b    LBB0_1
    LBB0_4:
        ldur    w0, [x29, #-4]
        ldp    x29, x30, [sp, #32]     ; 8-byte Folded Reload
        add    sp, sp, #48             ; =48
        ret
    
        .section    __TEXT,__cstring,cstring_literals
    l_.str:                                 ; @.str
        .asciz    "%3d %6.1f\n"
    
    .subsections_via_symbols
    

    源代码中有一个常数运算'5.0/9.0'被编译器给优化成了这样:

        .section    __TEXT,__literal8,8byte_literals
        .p2align    3
    lCPI0_0:
        .quad    4603179219131243634     ; double 0.55555555555555558
    

    标签'lCPI0_0'处用'.quad'指令带上了一串数字'4603179219131243634'从后面注释可以看出它是double数字'0.55555555555555558'的内存表现形式的十进制形式。
    前面的'.section'指明这个数字放在'__TEXT,__literal8'节(8byte长度的常量)'.p2align'指明按2的3次方也就是8byte对齐。
    for循环的头部判断是否需要结束循环同样使用的'b.gt'指令:

    LBB0_1:                                 ; =>This Inner Loop Header: Depth=1
        ldur    w8, [x29, #-8]
        cmp        w8, #300        ; =300
        b.gt    LBB0_4
    

    而'fahr=fahr+20'的操作是在循环的尾部做的:

    ; BB#3:                                 ;   in Loop: Header=BB0_1 Depth=1
        ldur    w8, [x29, #-8]
        add    w8, w8, #20             ; =20
        stur    w8, [x29, #-8]
        b    LBB0_1
    

    在代码中用到了d系列寄存器它的长度是一个double的长度也就是16byte和x系列寄存器和w系列寄存器共用类似d系列寄存器是'b,h,s,d,q'也就是'byte, half, single, double, quad'共用的q寄存器有128位d用它的低64位s用它的低32位h用它的低16位b用它的低8位。另外q和v(vector)系列寄存器是共用的这里暂时不对v系列寄存器展开讨论。

    1.4 符号常量

    同样从一个示例开始吧:

    #include <stdio.h>
    
    #define LOWER 0    /* lower limit of table */
    #define UPPER 300  /* upper limit */
    #define STEP  20   /* step size */
    
    /* print Fahrenheit-Celsius table */
    main() {
        int fahr;
    
        for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP)
            printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
    }
    

    上面的代码里有用到宏'LOWER/UPPER/STEP'在编译过程中会被预处理器替换到源代码中。以上代码保存为'f2cforpreprocessor.c'可以通过如下命令进行预处理(preprocessor):

    .....省略头文件的预处理部分.....
    main() {
        int fahr;
    
        for (fahr = 0; fahr <= 300; fahr = fahr + 20)
            printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
    }
    

    '#include'宏把'stdio.h'展开到源代码中使得屏幕上会输出一大堆代码这里我们不多做介绍有兴趣的读者可以自己阅读。而代码中的'LOWER/UPPER/STEP'被分别替换为了常量'0/300/20'。

    1.5 数组

    同样从一段示例代码开始:

    #include <stdio.h>
    
    main()
    {
        int ndigit[10];
    }
    其汇编代码如下:
        .section    __TEXT,__text,regular,pure_instructions
        .ios_version_min 11, 1
        .globl    _main
        .p2align    2
    _main:                                  ; @main
    ; BB#0:
        sub    sp, sp, #80             ; =80
        stp    x29, x30, [sp, #64]     ; 8-byte Folded Spill
        add    x29, sp, #64            ; =64
        adrp    x8, ___stack_chk_guard@GOTPAGE
        ldr    x8, [x8, ___stack_chk_guard@GOTPAGEOFF]
        ldr        x8, [x8]
        adrp    x9, ___stack_chk_guard@GOTPAGE
        ldr    x9, [x9, ___stack_chk_guard@GOTPAGEOFF]
        ldr        x9, [x9]
        stur    x9, [x29, #-8]
        adrp    x9, ___stack_chk_guard@GOTPAGE
        ldr    x9, [x9, ___stack_chk_guard@GOTPAGEOFF]
        ldr        x9, [x9]
        ldur    x10, [x29, #-8]
        cmp        x9, x10
        str    x8, [sp, #8]            ; 8-byte Folded Spill
        b.ne    LBB0_2
    ; BB#1:
        mov    w8, #0
        mov     x0, x8
        ldp    x29, x30, [sp, #64]     ; 8-byte Folded Reload
        add    sp, sp, #80             ; =80
        ret
    LBB0_2:
        bl    ___stack_chk_fail
    
    .subsections_via_symbols
    

    首先是空间开辟:

    ; BB#0:
        sub    sp, sp, #80             ; =80
        stp    x29, x30, [sp, #64]     ; 8-byte Folded Spill
        add    x29, sp, #64            ; =64
    

    栈上开辟了80个byte的空间64byte给局部变量用1.2节中介绍到的局部变量空间对应关系:
    int return_value;
    int ndigit[10];
    总共11个int占44byte空间但却给了64byte空间为什么呢?继续往下看:

        adrp    x8, ___stack_chk_guard@GOTPAGE
        ldr    x8, [x8, ___stack_chk_guard@GOTPAGEOFF]
        ldr        x8, [x8]
        adrp    x9, ___stack_chk_guard@GOTPAGE
        ldr    x9, [x9, ___stack_chk_guard@GOTPAGEOFF]
        ldr        x9, [x9]
        stur    x9, [x29, #-8]
    

    这里取了一个'___stack_chk_guard'的标签里的内容分别放入了x8和x9并且x9存入了'x29-8'里(x29是栈顶)那栈目前的内容就变成了这样:

    x29-4 = sp+60 = return_value = 0
    x29-8 = sp+56 = ___stack_chk_guard
    

    接下来继续看:

        adrp    x9, ___stack_chk_guard@GOTPAGE
        ldr    x9, [x9, ___stack_chk_guard@GOTPAGEOFF]
        ldr        x9, [x9]
        ldur    x10, [x29, #-8]
        cmp        x9, x10
        str    x8, [sp, #8]            ; 8-byte Folded Spill
        b.ne    LBB0_2
    

    又取了一次'___stack_chk_guard'到x9再从'x29-8'里面取出之前存进去的'___stack_chk_guard'值做对比如果不相等则跳转到'LBB0_2':

    LBB0_2:
        bl    ___stack_chk_fail
    

    就调到'___stack_chk_fail'方法然后挂掉了。那么问题来了为什么'___stack_chk_guard'和'___stack_chk_guard'的值会不相等呢?注意力回到'x29-8'如果它的内容因为代码bug或者恶意代码导致内存数据被破坏(数组越界写访问)就会导致失败。这个检查一定程度保护了栈数据的安全。同时倒数第二句里面又在'sp+8'位置也插了个'___stack_chk_guard'进去内存数据就变成了这样:

    x29-4 = sp+60 = return_value = 0
    x29-8 = sp+56 = ___stack_chk_guard
    ...ndigits...
    x29-56 = sp+8 = ___stack_chk_guard
    

    'x29-56 - (x29-8) - 8 = 40'(这里的8是___stack_chk_guard的数据大小)刚好10个整数的长度40byte。

    1.6 方法和参数传递

    前面的小节中讨论的内容都是语句级别的那么方法的表现形式是什么呢?上代码:

    #include <stdio.h>
    
    int power(int m, int n); /* test power function */
    
    int main() {
        return power(2,1);
    }
    
    int power(int base, int n) {
        return base;
    }
    

    该代码的汇编实现如下:

        .section    __TEXT,__text,regular,pure_instructions
        .ios_version_min 11, 1
        .globl    _main
        .p2align    2
    _main:                                  ; @main
    ; BB#0:
        sub    sp, sp, #32             ; =32
        stp    x29, x30, [sp, #16]     ; 8-byte Folded Spill
        add    x29, sp, #16            ; =16
        orr    w0, wzr, #0x2
        orr    w1, wzr, #0x1
        stur    wzr, [x29, #-4]
        bl    _power
        ldp    x29, x30, [sp, #16]     ; 8-byte Folded Reload
        add    sp, sp, #32             ; =32
        ret
    
        .globl    _power
        .p2align    2
    _power:                                 ; @power
    ; BB#0:
        sub    sp, sp, #16             ; =16
        str    w0, [sp, #12]
        str    w1, [sp, #8]
        ldr    w0, [sp, #12]
        add    sp, sp, #16             ; =16
        ret
    
    .subsections_via_symbols
    

    这里除了'_main'标签外代码节里面还多了'_power'标签在要调用'_power'方法的地方可以通过branch系列指令'b/bl/br/blr'(对ARM64指令有兴趣的读者可以参见文末参考3)。来调到对应的方法比如'_main'里面的'bl _power'语句。
    power方法的两个参数2和1分别放入了w0和w1中(x0和x1)和前文提到的calling convention一致(文末参考1、2)。

    1.7 外部变量和作用域

    分别有两个文件extern1.c内容如下:

    #include <stdio.h>
    
    void copy(void);
    char line[1000];
    int main() {
        line[0] = 'a';
        copy();
    }
    

    和extern2.c内容如下:

    void copy()
    {
        extern char line[];
        line[1] = line[0];
    }
    

    他们均能被单独编译为汇编文件'extern1.s':

        .section    __TEXT,__text,regular,pure_instructions
        .ios_version_min 11, 1
        .globl    _main
        .p2align    2
    _main:                                  ; @main
    ; BB#0:
        stp    x29, x30, [sp, #-16]!   ; 8-byte Folded Spill
        mov     x29, sp
        mov    w8, #97
        adrp    x9, _line@GOTPAGE
        ldr    x9, [x9, _line@GOTPAGEOFF]
        strb        w8, [x9]
        bl    _copy
        mov    w8, #0
        mov     x0, x8
        ldp    x29, x30, [sp], #16     ; 8-byte Folded Reload
        ret
    
        .comm    _line,1000,0            ; @line
    
    .subsections_via_symbols
    

    及'extern2.s':

        .section    __TEXT,__text,regular,pure_instructions
        .ios_version_min 11, 1
        .globl    _copy
        .p2align    2
    _copy:                                  ; @copy
    ; BB#0:
        adrp    x8, _line@GOTPAGE
        ldr    x8, [x8, _line@GOTPAGEOFF]
        ldrb        w9, [x8]
        strb    w9, [x8, #1]
        ret
    
    .subsections_via_symbols
    

    在'extern1.s'中'.comm _line,1000,4'会为'_line'标签在全局'__DATA,__common'节中分配一段空间使得在'extern2.s'中被'adrp x8, _line@GOTPAGE'找到并访问。
    而在'extern2.s'中'.globl _copy'会将'_copy'标签暴露在全局使得在'extern1.s'中被'bl _copy'访问到。
    各符号所对应的具体地址会在链接(link)的过程中被确定和关联。执行指令'clang -arch arm64 -O0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.1.sdk extern1.c extern2.c'编译出产物'a.out', 通过otool工具可以查看编译后的产物的信息如'otool -tv a.out'可以查看'__TEXT'段的反汇编:

    $clang -arch arm64 -O0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.1.sdk extern1.c extern2.c
    $otool -tv a.out
    a.out:
    (__TEXT,__text) section
    _main:
    0000000100007f78    stp    x29, x30, [sp, #-0x10]!
    0000000100007f7c    mov     x29, sp
    0000000100007f80    mov    w8, #0x61
    0000000100007f84    adrp    x9, 1 ; 0x100007000
    0000000100007f88    add    x9, x9, #0x0
    0000000100007f8c    strb        w8, [x9]
    0000000100007f90    bl    0x100007fa4
    0000000100007f94    mov    w8, #0x0
    0000000100007f98    mov     x0, x8
    0000000100007f9c    ldp    x29, x30, [sp], #0x10
    0000000100007fa0    ret
    _copy:
    0000000100007fa4    adrp    x8, 1 ; 0x100007000
    0000000100007fa8    add    x8, x8, #0x0
    0000000100007fac    ldrb        w9, [x8]
    0000000100007fb0    strb    w9, [x8, #0x1]
    0000000100007fb4    ret
    

    可以看到copy方法中的line引用以及main方法中对copy的调用都已经被替换为了对应的地址。
    同时可以通过'nm a.out'命令查看可执行文件中的所有符号(标签在编译后我们称之为符号):

    $nm a.out
    0000000100000000 T __mh_execute_header
    0000000100007fa4 T _copy
    0000000100008000 S _line
    0000000100007f78 T _main
                     U dyld_stub_binder
    

    前面用到的'_copy/_line/_main'全部在列里面'T'代表代码节的符号'U'代表未定义'S'代表其他(更多解释参见执行命令'man nm'的结果)。

    参考文献:

    时间: 2017-12-06

    Tags: ios, 阿里技术协会, ADD, ARM, 汇编

    相关文章

      网友评论

          本文标题:入门一

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