美文网首页
AT&T汇编-函数实例

AT&T汇编-函数实例

作者: 码无不至 | 来源:发表于2020-02-24 07:03 被阅读0次

    AT&T

    基于x86架构处理器汇编指令一般有2种格式:Intel汇编和AT&T汇编,通过对8086汇编的学习包括对一些常用寄存器以及函数调用非常熟悉,今天我们来学习一下AT&T汇编语言,以及用AT&T汇编语言来探究一些常见代码的执行流程和执行效率。我们通过一张图对比Intel的汇编语法来学习AT&T的基础语法:

    AT&T.png
    通过上面图片对比AT&T汇编与Intel汇编可知,指令后面的操作跟Intel相反,常数前面加$,寄存器前面加%,地址不是用中括号,而是星号标示,指令后面跟的q,l,b均表示操作长度。具体的其它指令请大家自行查阅,前面我们讲了8086的函数汇编调用的完整过程,我们这里来看看AT&T的函数:
    int print(int age)
    {
        int a = 10;
        int b = 11;
        int c = a + b + age;
        return c;
    }
    int main()
    {
        int total = print(10);
        return 0;
    }
    //print函数转为汇编如下:
    pushq  %rbp               //保护rbp
    movq   %rsp, %rbp         //保存rsp
    movl   %edi, -0x4(%rbp)   //从寄存器直接取出函数参数赋值到rbp-0x4,就是age
    movl   $0xa, -0x8(%rbp)   //定义一个局部变量,位置在rbp-0x8,就是a
    movl   $0xb, -0xc(%rbp)   //定义一个局部变量,位置在rbp-0xc,就是b
    movl   -0x8(%rbp), %edi   //将a赋值到edi中
    addl   -0xc(%rbp), %edi   //将b跟a相加,结果在edi中
    addl   -0x4(%rbp), %edi   //将agey跟edi相加,结果在edi中
    movl   %edi, -0x10(%rbp)  //将edi赋值到rbp-0x10中,就是c
    movl   -0x10(%rbp), %eax  //这里把结果放入eax中
    popq   %rbp               //恢复bp
    retq                      //函数返回
    

    从以上可以看出函数调用的流程,你可能觉得不对,局部变量直接用内存空间赋值,没有用rsp类似的指针来保护局部变量,这是因为这个函数里面没有调用其他函数,编译器帮我们优化了。,还有个问题函数的参数我们也是知道需要push到栈的,可是这里直接取了!因为AT&T汇编有%rdi、%rsi、%rdx、%rcx、%r8、%r9、%r10等寄存器用于存放函数参数,只有寄存器不够了,再用push参数的方式,参数也是通过rax返回。

    for循环

    循环遍历的时候我们经常用到,如下:

     for(int i = 10;i < 15;i++)
     {
         printf("%d\n",i);
     }
    

    以上代码我们都知道会输出10,11,12,13,14.我们仔细观察for括号里面也有三句代码,然后又有打印代码,可能凭我们的经验还是能知道它的执行顺序,不过最好的还是看看反汇编代码,更保险也更深刻。将上述代码反汇编如下:

    0x100001e7f <+15>: movl   $0xa, -0x8(%rbp)    //变量等于10
    0x100001e86 <+22>: cmpl   $0xf, -0x8(%rbp)    //比较rbp-0x8的值与15的差值
    0x100001e8a <+26>: jge    0x100001eb2         //大于等于跳转到0x100001eb2,其实就是跳出循环
    ->  0x100001e90 <+32>: movl   -0x8(%rbp), %esi  // 1
    0x100001e93 <+35>: leaq   0x111(%rip), %rdi     // 2
    0x100001e9a <+42>: movb   $0x0, %al             // 3
    0x100001e9c <+44>: callq  0x100001ef0   //以上1 2 3都是为调用printf函数准备
    0x100001ea1 <+49>: movl   %eax, -0xc(%rbp)  //这里对eax以前的值一个备份
    0x100001ea4 <+52>: movl   -0x8(%rbp), %eax //取出rbp-0x8的值
    0x100001ea7 <+55>: addl   $0x1, %eax       //值加1
    0x100001eaa <+58>: movl   %eax, -0x8(%rbp) //然后赋值到rbp-0x8
    0x100001ead <+61>: jmp    0x100001e86 //   跳转到 比较rbp-0x8的值与15的差值的汇编指令处
    0x100001eb2 <+66>: xorl   %eax, %eax  //清空eax
    

    通过以上代码可以非常清楚的看出for循环的执行步骤:
    1.取出i的值,并且与15比较,满足条件就进行打印操作,否则直接执行步骤3。
    2.如果执行了打印,说明依然在循环里面,执行i++后执行步骤1。
    3.跳出循环,执行后面的代码。

    i++与++i

    以前刚学这个i++与++i的时候,大概印象就是i++先加再使用++i则是直接加然后使用,i++不能当成左值,++i能当左值,就是i++不能被赋值,++i可以被赋值。我们先来两个例子:

    int main()
    {
        int i = 10;
        int a = i++;
        return 0;
    }
    
    
    //转成汇编如下:
    pushq  %rbp
    movq   %rsp, %rbp
    xorl   %eax, %eax
    movl   $0x0, -0x4(%rbp)
    movl   $0xa, -0x8(%rbp)
    movl   -0x8(%rbp), %ecx
    movl   %ecx, %edx
    addl   $0x1, %edx
    movl   %edx, -0x8(%rbp)
    movl   %ecx, -0xc(%rbp)
    popq   %rbp
    retq
    
    
    int main()
    {
        int i = 10;
        int a = ++i;
        return 0;
    }
    //转成汇编代码如下:
    pushq  %rbp
    movq   %rsp, %rbp
    xorl   %eax, %eax
    movl   $0x0, -0x4(%rbp)
    movl   $0xa, -0x8(%rbp)
    movl   -0x8(%rbp), %ecx
    addl   $0x1, %ecx
    movl   %ecx, -0x8(%rbp)
    movl   %ecx, -0xc(%rbp)
    popq   %rbp
    retq
    
    

    我们留意到i++有一句汇编指令是ecx赋值到edx,然后edx+1,再把ecx和edx的值赋值到内存中去,其实就是int a = i++,a只是赋值的是i以前值,i++也是执行了加1,然后赋值到内存去。并不是说int a = i++执行完后,i才加1,反汇编查看就是两个寄存器操作,一个寄存器加了,另一个寄存器没加而已,很多地方说产生了临时变量,个人觉得说法不太严谨,从反汇编看并没有产生新的内存空间,但是是汇编多用了一个寄存器。i++和++i的认识,其实单独执行的时候其实没有什么区别,只是在赋值和运算的时候需要区分。比如我们再来看一个有运算的例子:

    int i = 10;
    int a = i++ + ++i + i++;
    //反汇编代码如下:
    movl   -0x8(%rbp), %eax//eax = 10
    movl   %eax, %ecx    //ecx =11
    addl   $0x1, %ecx    //ecx = 11
    movl   %ecx, -0x8(%rbp)
    movl   -0x8(%rbp), %ecx
    addl   $0x1, %ecx   //ecx = 12
    movl   %ecx, -0x8(%rbp)
    addl   %ecx, %eax   //eax = 12 + 10 = 22
    movl   -0x8(%rbp), %ecx
    movl   %ecx, %edx  //ecx = 12 edx = 12
    addl   $0x1, %edx   //edx = 13
    movl   %edx, -0x8(%rbp) //
    addl   %ecx, %eax  //eax = 12 + 22 = 34
    movl   -0x8(%rbp), %eax//eax = 10
    movl   %eax, %ecx    //ecx =11
    addl   $0x1, %ecx    //ecx = 11
    movl   %ecx, -0x8(%rbp)
    movl   -0x8(%rbp), %ecx
    addl   $0x1, %ecx   //ecx = 12
    movl   %ecx, -0x8(%rbp)
    addl   %ecx, %eax   //eax = 12 + 10 = 22
    movl   -0x8(%rbp), %ecx
    movl   %ecx, %edx  //ecx = 12 edx = 12
    addl   $0x1, %edx   //edx = 13
    movl   %edx, -0x8(%rbp) //
    addl   %ecx, %eax  //eax = 12 + 22 = 34
    

    这个汇编代码,能很好解释这句代码的执行流程,运算符是从左到右,最终结果在eax,edx保存i最终的值,注意eax和ecx的变化。

    if else

    我们再来窥探下if与else的执行流程,如下所示:

    int main()
    {
        int a = 100;
        if(a > 200){
            printf("a > 200 %d \n",a);
        }else if (a > 150){
            printf("a > 150 %d \n",a);
        }else if (a > 120){
            printf("a > 120 %d \n",a);
        }else{
            printf("a = other %d \n",a);
        }
        printf("fun end... \n");
        return 0;
    }
    //转成汇编代码如下:
    movl   $0x64, -0x8(%rbp)
    cmpl   $0xc8, -0x8(%rbp)
    jle    0x100001dfc               ; <+60> at main.cpp:109:17
    movl   -0x8(%rbp), %esi
    leaq   0x182(%rip), %rdi         ; "a > 200 %d \n"
    movb   $0x0, %al
    callq  0x100001eb4               ; symbol stub for: printf
    movl   %eax, -0xc(%rbp)
    jmp    0x100001e63               ; <+163> at main.cpp:116:5
    cmpl   $0x96, -0x8(%rbp)
    jle    0x100001e22               ; <+98> at main.cpp:111:17
    movl   -0x8(%rbp), %esi
    leaq   0x169(%rip), %rdi         ; "a > 150 %d \n"
    movb   $0x0, %al
    callq  0x100001eb4               ; symbol stub for: printf
    movl   %eax, -0x10(%rbp)
    jmp    0x100001e5e               ; <+158> at main.cpp
    cmpl   $0x78, -0x8(%rbp)
    jle    0x100001e45               ; <+133> at main.cpp:114:34
    movl   -0x8(%rbp), %esi
    leaq   0x153(%rip), %rdi         ; "a > 120 %d \n"
    movb   $0x0, %al
    callq  0x100001eb4               ; symbol stub for: printf
    movl   %eax, -0x14(%rbp)
    jmp    0x100001e59               ; <+153> at main.cpp
    movl   -0x8(%rbp), %esi
    leaq   0x147(%rip), %rdi         ; "a = other %d \n"
    movb   $0x0, %al
    callq  0x100001eb4               ; symbol stub for: printf
    movl   %eax, -0x18(%rbp)
    jmp    0x100001e5e               ; <+158> at main.cpp
    jmp    0x100001e63               ; <+163> at main.cpp:116:5
    leaq   0x13b(%rip), %rdi         ; "fun end... \n"
    

    仔细一看其实就是逐个比较,一旦发现符合条件的,执行后就不再比较,跳转到if else外面。这个代码指令执行时间跟我们取得符合条件的位置有关。

    switch

    接下来我们来看看switch-break的实现逻辑,我们对比下if else 看看它的区别

    int main()
    {
        int a = 100;
        switch (a) {
            case 98:
                printf("a = %d\n",a);
                break;
            case 99:
                printf("a = %d\n",a);
                break;
            case 100:
                printf("a = %d\n",a);
                break;
            default:
                break;
        }
        printf("fun end... \n");
        return 0;
    }
    
    //转成反汇编代码如下:
    
    0x100001df0 <+0>:   pushq  %rbp
    0x100001df1 <+1>:   movq   %rsp, %rbp
    0x100001df4 <+4>:   subq   $0x30, %rsp
    0x100001df8 <+8>:   movl   $0x0, -0x4(%rbp)
    0x100001dff <+15>:  movl   $0x64, -0x8(%rbp)
    ->  0x100001e06 <+22>:  movl   -0x8(%rbp), %eax
    0x100001e09 <+25>:  movl   %eax, %ecx
    0x100001e0b <+27>:  subl   $0x62, %ecx
    0x100001e0e <+30>:  movl   %eax, -0xc(%rbp)
    0x100001e11 <+33>:  movl   %ecx, -0x10(%rbp)
    0x100001e14 <+36>:  je     0x100001e47               ; <+87> at main.cpp:146:31
    0x100001e1a <+42>:  jmp    0x100001e1f               ; <+47> at main.cpp:144:5
    0x100001e1f <+47>:  movl   -0xc(%rbp), %eax
    0x100001e22 <+50>:  subl   $0x63, %eax
    0x100001e25 <+53>:  movl   %eax, -0x14(%rbp)
    0x100001e28 <+56>:  je     0x100001e60               ; <+112> at main.cpp:149:31
    0x100001e2e <+62>:  jmp    0x100001e33               ; <+67> at main.cpp:144:5
    0x100001e33 <+67>:  movl   -0xc(%rbp), %eax
    0x100001e36 <+70>:  subl   $0x64, %eax
    0x100001e39 <+73>:  movl   %eax, -0x18(%rbp)
    0x100001e3c <+76>:  je     0x100001e79               ; <+137> at main.cpp:152:31
    0x100001e42 <+82>:  jmp    0x100001e92               ; <+162> at main.cpp:155:13
    0x100001e47 <+87>:  movl   -0x8(%rbp), %esi
    0x100001e4a <+90>:  leaq   0x152(%rip), %rdi         ; "a = %d\n"
    0x100001e51 <+97>:  movb   $0x0, %al
    0x100001e53 <+99>:  callq  0x100001ee8               ; symbol stub for: printf
    0x100001e58 <+104>: movl   %eax, -0x1c(%rbp)
    0x100001e5b <+107>: jmp    0x100001e97               ; <+167> at main.cpp:157:5
    0x100001e60 <+112>: movl   -0x8(%rbp), %esi
    0x100001e63 <+115>: leaq   0x139(%rip), %rdi         ; "a = %d\n"
    0x100001e6a <+122>: movb   $0x0, %al
    0x100001e6c <+124>: callq  0x100001ee8               ; symbol stub for: printf
    0x100001e71 <+129>: movl   %eax, -0x20(%rbp)
    0x100001e74 <+132>: jmp    0x100001e97               ; <+167> at main.cpp:157:5
    0x100001e79 <+137>: movl   -0x8(%rbp), %esi
    0x100001e7c <+140>: leaq   0x120(%rip), %rdi         ; "a = %d\n"
    0x100001e83 <+147>: movb   $0x0, %al
    0x100001e85 <+149>: callq  0x100001ee8               ; symbol stub for: printf
    0x100001e8a <+154>: movl   %eax, -0x24(%rbp)
    0x100001e8d <+157>: jmp    0x100001e97               ; <+167> at main.cpp:157:5
    0x100001e92 <+162>: jmp    0x100001e97               ; <+167> at main.cpp:157:5
    0x100001e97 <+167>: leaq   0x10d(%rip), %rdi         ; "fun end... \n"
    0x100001e9e <+174>: movb   $0x0, %al
    0x100001ea0 <+176>: callq  0x100001ee8               ; symbol stub for: printf
    0x100001ea5 <+181>: xorl   %ecx, %ecx
    0x100001ea7 <+183>: movl   %eax, -0x28(%rbp)
    0x100001eaa <+186>: movl   %ecx, %eax
    0x100001eac <+188>: addq   $0x30, %rsp
    0x100001eb0 <+192>: popq   %rbp
    0x100001eb1 <+193>: retq
    

    break跳转指令是先判断跳转地址再跳转,而不是逐个判断,相当于是空间换时间,效率比较高,如果判断条件比较多的情况下,用switch效率更高。今天就介绍到这里,祝进步!

    相关文章

      网友评论

          本文标题:AT&T汇编-函数实例

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