美文网首页
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汇编-函数实例

    AT&T 基于x86架构处理器汇编指令一般有2种格式:Intel汇编和AT&T汇编,通过对8086汇编的学习包括对...

  • 内联汇编

    AT&T汇编语法 GCC只支持AT&T汇编语法内嵌在C语言中。 Intel和AT&T汇编风格对比: AT&T寻址 ...

  • 6.828 操作系统 lab1: 阅读boot.S

    补充知识 补充1. AT&T汇编 由于内核代码采用的gcc编译器使用AT&T的汇编格式,首先补充下关于AT&T汇编...

  • AT&T Assembly Syntax [ AT&T 汇编语法

    AT&T Assembly Syntax [ AT&T 汇编语法 ] vivek, Mon, 2003-09-01...

  • 汇编at&t

    比较得到数组最大数 写一个比较得到最大数的汇编, 注意: long是4字节 movl, mov运用在不同范围的数字...

  • lab1 二进制炸弹

    汇编与反汇编 汇编与反汇编的区别 phase_1 比较字符串是否相同 二进制炸弹 常见汇编指令详解 AT&T 格式...

  • 寄存器的一点小知识

    汇编语言的种类 Intel:WindowsAT&T:Unix AT&T汇编主要用作iOS模拟器ARM汇编用于iOS...

  • AT&T汇编(2)反汇编

    1、常见代码反汇编 sizeof a++ + a++ + a++ if-else for switch和if效率 ...

  • AT&T汇编(1)基本概念

    1、AT&T汇编 vs Intel汇编 基于x86架构的处理器所使用的汇编指令一般有2种格式a) Intel汇编D...

  • 十七.AT&T汇编

    之前将的都是8086汇编,现在聊一聊AT&T汇编,基本都是相同的汇编就是使用寄存器存储数据和操作内存; ios基本...

网友评论

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

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