通过例子学习汇编(一)

        话不多说,直接开干!!!

案例一:

        首先实现一个最简单的c语言代码,hello world。

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

int main(){

        func();

        return 0;
}

        使用编译指令进行生成汇编代码:

-O0

        gcc -S -O0 test.c

        .arch armv8-a
        .file   "test.c"
        .text
        .section        .rodata
        .align  3
//上面这段代码的含义是:指定当前源文件的名称为test.c; .text表示后续的这段指令将属于代码段; .section .rodata表示将定义一个名为 .rodata 的段,它通常用于存储只读数据(read-only data)。在这里,.rodata 可能包含程序中使用但不修改的常量数据;.align 3表示将 .rodata 段的起始地址对齐到2的3次方(即8字节)的边界。这样的对齐有助于提高内存访问的效率,因为许多硬件架构要求数据在特定地址上对齐。

.LC0:
        .string "hello world"
        .text
        .align  2
        .global func
        .type   func, %function
//上面这段代码表示:首先定义一个字符串hello world,字节对齐为2的2次方位置;定义一个全局符号func,并表明该func的类型是一个函数。

func:
        stp     x29, x30, [sp, -16]!
        //由于main函数调用了func函数,所以这里保存main的相关数据和指令。具体是:x29保存main的指向自己的局部变量的一块内存的地址,x30保存的是main函数后续指令的地址,以保证func函数返回后能够继续运行main函数。然后将x29,和x30寄存器放入栈中(sp表示栈),因为栈是从搞地址向低地址增长的,所以入栈是减16.
        add     x29, sp, 0
        //把栈sp中的数据复制到寄存器x29中,确保当前函数能够调用局部变量。
        adrp    x0, .LC0
        //adrp=adress of page 表示将.LC0表示的数据所在的页加载到寄存器x0.
        add     x0, x0, :lo12:.LC0
        //将寄存器x0指向的页数据加上偏移量lo12,表示获取.LO0的数据放到寄存器x0.
        bl      puts
        //bl表示调用函数,并返回,调用的函数是puts函数,也就是printf函数。
        nop
        ldp     x29, x30, [sp], 16
        //回复调用者的局部变量内存和指令地址到寄存器x29和x30,并恢复栈sp。
        ret
        .size   func, .-func
        .align  2
        .global main
        .type   main, %function
//

main:
        stp     x29, x30, [sp, -16]!
        add     x29, sp, 0
        bl      func
        mov     w0, 0
        //将常数值 0 移动到通用寄存器 w0 中。在 ARM64 中,通用寄存器 w0 是用于传递返回值的寄存器。
        ldp     x29, x30, [sp], 16
        ret
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
        .section        .note.GNU-stack,"",@progbits

                                                                                          

-O3

        gcc -S -O3 test.c

        .arch armv8-a
        .file   "test.c"
        .text
        .align  2
        .p2align 3,,7
        .global func
        .type   func, %function
func:
        adrp    x0, .LC0
        add     x0, x0, :lo12:.LC0
        b       puts
        .size   func, .-func
        .section        .text.startup,"ax",@progbits
        .align  2
        .p2align 3,,7
        .global main
        .type   main, %function
main:
        stp     x29, x30, [sp, -16]!
        adrp    x0, .LC0
        add     x0, x0, :lo12:.LC0
        add     x29, sp, 0
        bl      puts
        mov     w0, 0
        ldp     x29, x30, [sp], 16
        ret
        .size   main, .-main
        .section        .rodata.str1.8,"aMS",@progbits,1
        .align  3
.LC0:
        .string "hello world"
        .ident  "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
        .section        .note.GNU-stack,"",@progbits
                                                                                   

总结:

        就目前了解的来看,主要是分为几个步骤。首先是将局部变量内存和后续指令保存到栈中,然后进行当前函数的计算,然后返回到调用函数。其中数据的加载分为了两步,先是加载了数据所在的内存页的地址,然后使用偏移量加载该页中的具体数据。

        arm64中的寄存器由32个,即x0-x31,一个是64bit,也就是8byte,所以一次入栈两个寄存器改变的是16个字节。

        在开启-O3级别的优化时,最大的变化是func函数没有了调用者局部变量和后续指令的入栈和出栈指令,并且puts函数的调用只是b puts,不是bl puts了,表示func函数调用结束没有返回了。

        b 是无条件分支指令,它直接跳转到目标地址执行,不保存返回地址。用于无条件地改变程序的执行流程。bl 是带链接的分支指令,它不仅跳转到目标地址执行,还将下一条指令的地址保存到链接寄存器 lr(Link Register)中。这对于函数调用是非常有用的,因为函数执行完毕后可以通过 ret 指令返回到调用者。

        所以-O3优化就是减少了多余的、没有必要的入栈出栈操作,以及调用返回的指令跳转操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值