理解一个简单的C程序的汇编

时间:2015-06-09 10:41:16

标签: c gcc assembly gdb reverse-engineering

我正在尝试理解这个简单的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
在foobar之前的主要反汇编

   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标志时,main的一些反汇编是

   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>

我找不到任何关于foobar的电话。

2 个答案:

答案 0 :(得分:1)

这是一个很好的问题。我赞赏你在&#34;偷看引擎盖&#34;,可以这么说。

大量研究已用于编写代码。有时您希望代码快速运行,有时您希望它很小,有时您希望它快速编译。由于编译器研究,编译器可以生成以上述任何方式运行的代码。为了允许用户选择他们想要的那些选项,gcc有command line options that control the level of optimization

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

当您打开-O3标志时,编译器内联 foobar的代码。如您所知,函数调用需要时间,因此,如果函数foobar足够短,编译器将只复制foobar的整个代码而不是调用它,从而无需调用和返回指令。这使得代码更快一点,但它也使代码更大。

考虑一个被称为100次的100指令函数。如果内联此函数,代码大小将急剧增加,因为没有太多额外的速度。如果您设置了较高的优化级别并且所讨论的函数非常小,则编译器仅内联代码。

您可能已经注意到没有任何东西代替foobar功能。它已被&#34;优化&#34;,这意味着编译器完全删除了它。这是因为编译器可以告诉foobar没有做任何有用的事情。也就是说,它没有side effects。在-O0,没有任何优化。在更高的优化级别,gcc开始优化功能,没有副作用,以节省空间。

我几年没有写过x86 assmembly(现在只是手臂),但如果我没记错的话,由于分支预测,repz ret实际上是一种更有效的ret形式。可以找到更多信息here

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

答案 1 :(得分:0)

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

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

以下是在Debian / Sid / amd64上使用GCC 5.1编译的相关列表,使用gcc-5 -O2 -fverbose-asm -S go.c然后使用寻呼机查看生成的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 observable 副作用。然后它删除了对foobar的任何调用,因为它没用。

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

您还可以使用MELT(类似Lisp的域特定语言来扩展GCC)自定义gcc,甚至可以使用 MELT findgimple模式(GCC内部的Gimple表示中的grep种类。)