繁体   English   中英

了解简单C程序的汇编

[英]Understanding assembly of a simple C program

我试图了解此简单C程序的汇编。

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
void foobar(char *a){
    char c = a[0];
}
int main(){
    int fd = open("file.txt", O_RDONLY);
        char buf1[100]="\0";
    char buf[100];
    int aa=0,b=1,c=2,d=3,f=2,g=3;
    read(fd,buf1,104);
    if(strlen(buf1) > 100){

    }else{
        strcpy(buf,buf1);
    }
    //strcpy(buf,buf1);
    foobar(buf1);
}

我得到的使用gdb的可执行文件反汇编为foobar反汇编。

   0x000000000040067d <+0>: push   rbp
   0x000000000040067e <+1>: mov    rbp,rsp
   0x0000000000400681 <+4>: mov    QWORD PTR [rbp-0x18],rdi
   0x0000000000400685 <+8>: mov    rax,QWORD PTR [rbp-0x18]
   0x0000000000400689 <+12>:    movzx  eax,BYTE PTR [rax]
   0x000000000040068c <+15>:    mov    BYTE PTR [rbp-0x1],al
   0x000000000040068f <+18>:    pop    rbp

foob​​ar之前的主要拆卸

   0x0000000000400784 <+243>:   lea    rax,[rbp-0xf0]
   0x000000000040078b <+250>:   mov    rdi,rax
   0x000000000040078e <+253>:   call   0x40067d <foobar>
   0x0000000000400793 <+258>:   mov    rbx,QWORD PTR [rbp-0x18]
   0x0000000000400797 <+262>:   xor    rbx,QWORD PTR fs:0x28
   0x00000000004007a0 <+271>:   je     0x4007a7 <main+278>
   0x0000000000400690 <+19>:    ret   

现在,我有一个关于foobar拆卸的问题

0x0000000000400681 <+4>:    mov    QWORD PTR [rbp-0x18],rdi
0x0000000000400685 <+8>:    mov    rax,QWORD PTR [rbp-0x18]

不会的指示

mov rax, rdi

将完成上述两个指令所需的工作。 为什么要为rdi使用额外的内存位置rbp - 0x18 与通过引用有关吗?

编辑:我想问的另一个问题是,为什么foobar函数正在访问不在foobar框架中的内容(rbp - 0x18)

我的gcc版本是gcc(Ubuntu 4.8.2-19ubuntu1)4.8.2

编辑:编译时使用-O1 -O2和-O3优化标志后,foobar程序集更改为

   0x0000000000400670 <+0>: repz ret 

并且在使用-O3标志时,主要的一些反汇编是

   0x0000000000400551 <+81>:    rep stos QWORD PTR es:[rdi],rax
   0x0000000000400554 <+84>:    mov    DWORD PTR [rdi],0x0
   0x000000000040055a <+90>:    mov    cl,0x64
   0x000000000040055c <+92>:    mov    edi,r8d
   0x000000000040055f <+95>:    call   0x4004b0 <__read_chk@plt>
   0x0000000000400564 <+100>:   mov    rdx,QWORD PTR [rsp+0x68]
   0x0000000000400569 <+105>:   xor    rdx,QWORD PTR fs:0x28
   0x0000000000400572 <+114>:   jne    0x400579 <main+121>
   0x0000000000400574 <+116>:   add    rsp,0x78
   0x0000000000400578 <+120>:   ret    
   0x0000000000400579 <+121>:   call   0x4004c0 <__stack_chk_fail@plt>

我在main中找不到对foobar的任何调用。

这是一个很好的问题。 可以这么说,我赞扬您“在引擎盖下偷看”。

大量研究已进入编译代码。 有时您希望代码快速运行,有时希望代码很小,有时又希望快速编译。 多亏了编译器的研究,编译器可以生成以上述任何一种方式运行的代码。 为了允许用户选择他们想要的这些选项中的哪一个,gcc具有控制优化级别的命令行选项

默认情况下,gcc使用-O0,它不会对代码进行太多优化,而是专注于最快的编译时间。 因此,有时您会发现效率低下的指令序列。


当您打开-O3标志时,编译器将内 foob​​ar的代码。 如您所知,函数调用需要花费时间,因此,如果函数foobar足够短,则编译器将只复制foobar的整个代码而不是对其进行调用,从而消除了对调用和ret指令的需要。 这使代码的处理速度更快,但同时也使其变得更大。

考虑一个称为100次的100条指令的函数。 如果内联此函数,则代码大小将急剧增加,而不会增加太多速度。 仅当您设置了较高的优化级别并且所考虑的函数很小时,编译器才内联代码。

您可能已经注意到,没有什么可以代替foobar函数。 它已被“优化”,这意味着编译器将其完全删除。 这是因为编译器可以告诉foobar没有做任何有用的事情。 也就是说,它没有副作用 在-O0,没有任何优化。 在更高的优化级别上,gcc开始优化功能,而没有副作用以节省空间。

几年来我还没有写过x86程序集(如今只是武装起来),但是如果我repz ret话,由于分支的repz retrepz ret实际上是一种更有效的ret形式。 更多信息可以在这里找到。

我现在必须去睡觉。 如果您还有问题,我会在稍后回复:)。

正如一些人评论的那样,您应该进行一些优化,例如至少使用gcc -O1 (最好是gcc -O2 )进行gcc -O2

如果专门使用GCC进行编译,我建议也传递-fverbose-asm因为这会在生成的汇编文件中发出有用的生成注释。

这是相关的清单,使用gcc-5 -O2 -fverbose-asm -S go.c在Debian / Sid / amd64上使用GCC 5.1进行编译,然后使用go.s器查看生成的go.s汇编器文件:

        .section        .text.unlikely,"ax",@progbits
.LCOLDB0:
        .text
.LHOTB0:
        .p2align 4,,15
        .globl  foobar
        .type   foobar, @function
foobar:
.LFB25:
        .cfi_startproc
        rep ret
        .cfi_endproc
.LFE25:
        .size   foobar, .-foobar
        .section        .text.unlikely
.LCOLDE0:
        .text
.LHOTE0:
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC1:
        .string "file.txt"
        .section        .rodata
.LC2:
        .string ""
        .string ""
        .zero   98
        .section        .text.unlikely
.LCOLDB3:
        .section        .text.startup,"ax",@progbits
.LHOTB3:
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB26:
        .cfi_startproc
        subq    $120, %rsp      #,
        .cfi_def_cfa_offset 128
        xorl    %esi, %esi      #
        movl    $.LC1, %edi     #,
        xorl    %eax, %eax      #
        call    open    #
        movl    %eax, %r8d      #, fd
        movzwl  .LC2(%rip), %eax        #, tmp92
        leaq    8(%rsp), %rdi   #, tmp93
        movl    $11, %ecx       #, tmp95
        movl    $104, %edx      #,
        movq    %rsp, %rsi      #,
        movl    $0, 4(%rsp)     #, buf1
        movw    %ax, (%rsp)     # tmp92, buf1
        xorl    %eax, %eax      #
        movw    %ax, 2(%rsp)    #, buf1
        xorl    %eax, %eax      # tmp94
        rep stosq
        movl    $0, (%rdi)      #, buf1
        movl    %r8d, %edi      # fd,
        call    read    #
        movq    %rsp, %rax      #, D.3346
.L3:
        movl    (%rax), %edx    #* D.3346, tmp100
        addq    $4, %rax        #, D.3346
        leal    -16843009(%rdx), %ecx   #, tmp99
        notl    %edx    # tmp100
        andl    %edx, %ecx      # tmp100, tmp99
        andl    $-2139062144, %ecx      #, tmp99
        je      .L3     #,
        xorl    %eax, %eax      #
        addq    $120, %rsp      #,
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
.LFE26:
        .size   main, .-main

内联编译器调用foobar优化它的身体空(因为你的源代码有没有明显的副作用foobar )。 然后它删除了对foobar所有调用,因为它没有用。

您可以尝试使用-fdump-tree-all进行编译。 您将获得数百个转储文件,对应于生成它们的许多GCC优化过程。

您还可以使用MELT (一种类似于Lisp的领域特定语言来扩展GCC)自定义gcc ,甚至可以使用MELTfindgimple模式(GCC内部的Gimple表示形式的grep )搜索一些Gimple或Tree模式。 )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM