AT&T汇编-函数实例

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效率更高。今天就介绍到这里,祝进步!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 1.地址总线,数据总线,控制总线在哪里,它们有什么作用?答:它们都是cpu连接外部组件的线路。地址总线:地址总线A...
    MagicalGuy阅读 1,449评论 0 1
  • 汇编基础教程 16位和32位的80x86汇编语言的区别 需要注意的是汇编不是一种语言,不同平台有不同的汇编语言对应...
    inwunwe阅读 9,497评论 2 19
  • Return-Oriented-Programming(ROP FTW) Author: Saif El-Sher...
    RealSys阅读 3,315评论 0 2
  • 参考链接 Linux下的汇编 Linux 下用汇编语言编写的代码具有两种不同的形式。第一种是完全的汇编代码,指的是...
    jeepshen阅读 8,320评论 1 9
  • 寄存器 用于解决处理器与内存之前的数据存储效率问题存在的。IA-32平台寄存器核心组有下面几种. 通用寄存器 8个...
    dodomix阅读 1,645评论 0 0