C++背后-Helloworld[1]

来源:互联网 发布:sql注入直接写入一句话 编辑:程序博客网 时间:2024/04/30 10:43

笔者的实验环境:

1. Cygwin:运行在Windows上,如果您还没有此环境,请参考http://www.cygwin.com/进行安装与配置。

2. GCC家族一揽子工具:进行编译/反编译与分析,包括gcc,g++,objdump,as,nm,ld等。

 

好了,开始第一个入门程序。我们毫不例外的使用经典的HelloWorld程序,文件名为helloworld.cpp,内容如下:

 

#include <cstdio>
int main(int argc, char ** argv)
{
    printf("Hello World/r/n");
    return 0;
}

 

运行

   g++ -o helloworld helloworld.cpp

不出意外的话,会在当前目录生成可执行文件helloworld.运行

   ./helloworld

就会看到输出

   Hello World

 

非常简单,对吧!

 

接下来,运行

   g++ -o helloworld helloworld.cpp -save-temps

注意这里加入了一个新的编译选项 -save-temps, 这个选项告诉编译器,“hi,哥们!请把你编译过程的中间文件都保存下来!莫删莫删!”。好了,看看当前目录下面都有什么文件?

    helloworld.o   --这是由helloworld.cpp编译出来的链接前的object文件。

    helloworld.ii    --这是编译器对源代码进行展开后的文件,主要是拷贝引用的其它头文件的内容,对代码中出现的宏进行展开和替换,对常量值进行替换等基本的代码展开操作。

    helloworld.s    --这是在生成object之前的汇编代码文件。

 

查看helloworld.ii,输入

    vim helloworld.ii

就会看到里面包含的内容,这里列出一部分:

# 598 "/usr/include/stdio.h" 3 4
}
# 53 "/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/cstdio" 2 3
# 97 "/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/cstdio" 3
namespace std
{
  using ::FILE;
  using ::fpos_t;

  using ::clearerr;
  using ::fclose;
  using ::feof;
  using ::ferror;
  using ::fflush;
  using ::fgetc;
  using ::fgetpos;
  using ::fgets;
  using ::fopen;
  using ::fprintf;
  using ::fputc;
  using ::fputs;
  using ::fread;
  using ::freopen;
  using ::fscanf;
  using ::fseek;
  using ::fsetpos;
  using ::ftell;
  using ::fwrite;
  using ::getc;
  using ::getchar;
  using ::gets;
  using ::perror;
  using ::printf;
  using ::putc;
  using ::putchar;
  using ::puts;
  using ::remove;
  using ::rename;
  using ::rewind;
  using ::scanf;
  using ::setbuf;
  using ::setvbuf;
  using ::sprintf;
  using ::sscanf;
  using ::tmpfile;
  using ::tmpnam;
  using ::ungetc;
  using ::vfprintf;
  using ::vprintf;
  using ::vsprintf;
}
# 2 "helloworld.cpp" 2

int main(int argc, char ** argv)
{
 printf("Hello World-FengYanshuo./r/n");
 return 0;
}


可以看到,这个文件只是把代码进行了简单扩展;没有什么实质变化。

看文件helloworld.s,输入

       vim helloworld.s

哈哈,看到了吧,这是汇编代码:

 1         .file   "helloworld.cpp"
 2         .def    ___main;        .scl    2;      .type   32;     .endef
 3         .section .rdata,"dr"
 4 LC0:
 5         .ascii "Hello World-FengYanshuo./15/12/0"
 6         .text
 7         .align 2
 8 .globl _main
 9         .def    _main;  .scl    2;      .type   32;     .endef
10 _main:
11         pushl   %ebp
12         movl    %esp, %ebp
13         subl    $8, %esp
14         andl    $-16, %esp
15         movl    $0, %eax
16         addl    $15, %eax
17         addl    $15, %eax
18         shrl    $4, %eax
19         sall    $4, %eax
20         movl    %eax, -4(%ebp)
21         movl    -4(%ebp), %eax
22         call    __alloca
23         call    ___main
24         movl    $LC0, (%esp)
25         call    _printf
26         movl    $0, %eax
27         leave
28         ret
29         .def    _printf;        .scl    2;      .type   32;     .endef

 

第一行,很明显是说,这是有helloworld.cpp生成的或者说对应的源代码是helloworld.cpp.

第二行,.def __main是说有个叫做__main的函数,注意,这个可不是main函数,这是进入main函数前最先调用的函数。

后面,紧接着就是只读数据段,很显然,我们要print出来的字符串就放在里面。

接下来,声明了函数_main,注意这才是真正的main函数;后面从标签_main:开始一直到28行,就是main函数的实现。

第29行声明了函数_printf,其实这就是标准c++库里面printf函数。

很显然,这里面只有main函数的实现,没有__main和_printf的实现,这需要链接器在链接阶段去从其它object中找到所需的函数,然后进行地址分配。

 

好了,重点关注一下main函数的实现:

       pushl %ebp

       movl %esp, %ebp

这两句是典型的GCC编译器使用函数调用入口操作,GCC使用ebp寄存器做基址寄存器,函数入口需要进行圧栈,函数推出需要退栈;后面把esp的内容送入ebp,以后,在函数内部就使用ebp寄存器进行函数内的栈操作来存取函数参数,处理函数内的局部变量等。

第22行,调用__alloca不是很清楚,估计是为栈申请空间之类的;紧接着调用__main函数,然后通过

      movl $LC0,(%esp)

把字符串的地址送给esp寄存器指示的地址;之后调用函数_printf, _printf也要进行圧栈操作,通过esp寄存器获得传入的字符串参数,然后执行输出操作。

      第26行,把数字0送入寄存器eax,之后程序返回;gcc使用eax存储函数的返回值,基本上所有函数都在最后把结果送入eax然后返回。

 

怎么样,通过看编译后的汇编,是不是对编译过程和结果有更深的了解了呢?

 

接下来,看看helloworld.o里面都有什么?helloworld.o实际上是ELF格式的文件,说白了就是可执行可链接文件格式,运行:

      $ objdump.exe -D helloworld.o

helloworld.o:     file format pe-i386


Disassembly of section .text:

00000000 <_main>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 08                sub    $0x8,%esp
   6:   83 e4 f0                and    $0xfffffff0,%esp
   9:   b8 00 00 00 00          mov    $0x0,%eax
   e:   83 c0 0f                add    $0xf,%eax
  11:   83 c0 0f                add    $0xf,%eax
  14:   c1 e8 04                shr    $0x4,%eax
  17:   c1 e0 04                shl    $0x4,%eax
  1a:   89 45 fc                mov    %eax,-0x4(%ebp)
  1d:   8b 45 fc                mov    -0x4(%ebp),%eax
  20:   e8 00 00 00 00          call   25 <_main+0x25>
  25:   e8 00 00 00 00          call   2a <_main+0x2a>
  2a:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
  31:   e8 00 00 00 00          call   36 <_main+0x36>
  36:   b8 00 00 00 00          mov    $0x0,%eax
  3b:   c9                      leave
  3c:   c3                      ret
  3d:   90                      nop
  3e:   90                      nop
  3f:   90                      nop

Disassembly of section .rdata:

00000000 <.rdata>:
   0:   48                      dec    %eax
   1:   65                      gs
   2:   6c                      insb   (%dx),%es:(%edi)
   3:   6c                      insb   (%dx),%es:(%edi)
   4:   6f                      outsl  %ds:(%esi),(%dx)
   5:   20 57 6f                and    %dl,0x6f(%edi)
   8:   72 6c                   jb     76 <_main+0x76>
   a:   64                      fs
   b:   2d 46 65 6e 67          sub    $0x676e6546,%eax
  10:   59                      pop    %ecx
  11:   61                      popa
  12:   6e                      outsb  %ds:(%esi),(%dx)
  13:   73 68                   jae    7d <_main+0x7d>
  15:   75 6f                   jne    86 <_main+0x86>
  17:   2e                      cs
  18:   0d                      .byte 0xd
  19:   0a 00                   or     (%eax),%al
        ...

 

objdump这个工具对ELF文件进行反编译,分析ELF文件中的各个段,方便进行反向分析。可以看到,对于main函数,object文件和helloworld.s里面的几乎一样,只是在只读数据段会有些差别。

 

原创粉丝点击