gcc常用的编译选项对代码的影响

来源:互联网 发布:塞班贝拉知乎 编辑:程序博客网 时间:2024/05/17 03:03


  原创:alert7(alert7)
  来源:http://xfocus.org/
  gcc常用的编译选项对代码的影响
  by alert7
  2001-12-21
  测试环境 redhat 6.2
  ★ 前言
  本文讨论gcc的一些常用编译选项对代码的影响。当然代码变了,
  它的内存布局也就会变了,随之exploit也就要做相应的变动。
  gcc的编译选项实在太多,本文检了几个最常用的选项。
  ★ 演示程序
  [alert7@redhat62 alert7]$ cat > test.c
  #include
  void hi(void)
  {
  printf("hi");
  }
  int main(int argc, char *argv[])
  {
  hi();
  return 0;
  }
  ★ 一般情况
  [alert7@redhat62 alert7]$ gcc -o test test.c
  [alert7@redhat62 alert7]$ wc -c test
  11773 test
  [alert7@redhat62 alert7]$ gdb -q test
  (gdb) disass main
  Dump of assembler code for function main:
  0x80483e4 : push %ebp
  0x80483e5 : mov %esp,%ebp
  0x80483e7 : call 0x80483d0
  0x80483ec : xor %eax,%eax
  0x80483ee : jmp 0x80483f0
  0x80483f0 : leave
  0x80483f1 : ret
  ....
  End of assembler dump.
  (gdb) disass hi
  Dump of assembler code for function hi:
  0x80483d0 : push %ebp
  0x80483d1 : mov %esp,%ebp
  0x80483d3 : push $0x8048450
  0x80483d8 : call 0x8048308
  0x80483dd : add $0x4,%esp
  0x80483e0 : leave
  0x80483e1 : ret
  0x80483e2 : mov %esi,%esi
  End of assembler dump.
  来看看部分的内存映象
  (内存高址)
  +--------+
  |bffffbc4| argv的地址(即argv[0]的地址)
  0xbffffb84 +--------+
  |00000001| argc的值
  0xbffffb80 +--------+
  |400309cb|main的返回地址
  0xbffffb7c +--------+ <-- 调用main函数前的esp
  |bffffb98| 调用main函数前的ebp
  0xbffffb78 +--------+ <-- main函数的ebp
  |080483ec| hi()的返回地址
  0xbffffb74 +--------+
  |bffffb78| 调用hi()前的esp
  0xbffffb70 +--------+
  |08048450| "hi"的地址
  0xbffffb6c +--------+
  | ...... |
  (内存低址)
  leave 指令所做的操作相当于MOV ESP,EBP 然后 POP EBP
  ret 指令所做的操作相当于POP EIP
  ★ -O 编译选项
  With `-O', the compiler tries to reduce code size and execution time.
  When you specify `-O', the two options `-fthread-jumps' and
  `-fdefer-pop' are turned on
  优化,减少代码大小和执行的时间
  [alert7@redhat62 alert7]$ gcc -O -o test test.c
  [alert7@redhat62 alert7]$ wc -c test
  11757 test
  [alert7@redhat62 alert7]$ gdb -q test
  (gdb) disass main
  Dump of assembler code for function main:
  0x80483d8 : push %ebp
  0x80483d9 : mov %esp,%ebp
  0x80483db : call 0x80483c8
  0x80483e0 : xor %eax,%eax
  0x80483e2 : leave
  0x80483e3 : ret
  0x80483e4 : nop
  ...
  End of assembler dump.
  (gdb) disass hi
  Dump of assembler code for function hi:
  0x80483c8 : push %ebp
  0x80483c9 : mov %esp,%ebp
  0x80483cb : push $0x8048440
  0x80483d0 : call 0x8048308
  0x80483d5 : leave
  0x80483d6 : ret
  0x80483d7 : nop
  End of assembler dump.
  在main()中,把一条jmp指令优化掉了,很显然,这条指令是可以不需要的。
  在hi()中,把add $0x4,%esp优化掉了,这会不会使stack不平衡呢?
  来看看部分的内存映象
  (内存高址)
  +--------+
  |bffffbc4| argv的地址(即argv[0]的地址)
  0xbffffb84 +--------+
  |00000001| argc的值
  0xbffffb80 +--------+
  |400309cb|main的返回地址
  0xbffffb7c +--------+ <-- 调用main函数前的esp
  |bffffb98| 调用main函数前的ebp
  0xbffffb78 +--------+ <-- main函数的ebp
  |080483e0| hi()的返回地址
  0xbffffb74 +--------+
  |bffffb78| 调用hi()前的esp
  0xbffffb70 +--------+
  |08048440| "hi"的地址
  0xbffffb6c +--------+
  | ...... |
  (内存低址)
  leave 指令所做的操作相当于把MOV ESP,EBP 然后 POP EBP
  看到leave指令操作了没有,先把ebp-->esp,再pop ebp,这样即使
  在过程内堆栈的esp,ebp是不平衡的,但只要返回时候碰到leave指令
  就会平衡了,所以把add $0x4,%esp优化掉也是没有问题的。
  ★ -O2 编译选项
  -O2 Optimize even more. Nearly all supported optimizations that do
  not involve a space-speed tradeoff are performed. Loop unrolling
  and function inlining are not done, for example. As compared to -O,
  this option increases both compilation time and the performance of
  the generated code.
  [alert7@redhat62 alert7]$ gcc -O2 -o test test.c
  [alert7@redhat62 alert7]$ wc -c test
  11757 test
  [alert7@redhat62 alert7]$ gdb -q test
  (gdb) disass main
  Dump of assembler code for function main:
  0x80483d8 : push %ebp
  0x80483d9 : mov %esp,%ebp
  0x80483db : call 0x80483c8
  0x80483e0 : xor %eax,%eax
  0x80483e2 : leave
  0x80483e3 : ret
  ...
  0x80483ef : nop
  End of assembler dump.
  (gdb) disass hi
  Dump of assembler code for function hi:
  0x80483c8 : push %ebp
  0x80483c9 : mov %esp,%ebp
  0x80483cb : push $0x8048440
  0x80483d0 : call 0x8048308
  0x80483d5 : leave
  0x80483d6 : ret
  0x80483d7 : nop
  End of assembler dump.
  由于程序比较简单,再优化也没有好优化的了,所以跟-O出来的一样。
  ★ -fomit-frame-pointer 编译选项
  -fomit-frame-pointer
  Don't keep the frame pointer in a register for functions
  that don't need one. This avoids the instructions to save,
  set up and restore frame pointers; it also makes an extra
  register available in many functions. It also makes
  debugging impossible on most machines.
  忽略帧指针。这样在程序就不需要保存,安装,和恢复ebp了。这样ebp也就是一个
  free的register了,在函数中就可以随便使用了。
  [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -o test test.c
  [alert7@redhat62 alert7]$ wc -c test
  11773 test
  [alert7@redhat62 alert7]$ gdb -q test
  (gdb) disass main
  Dump of assembler code for function main:
  0x80483e0 : call 0x80483d0
  0x80483e5 : xor %eax,%eax
  0x80483e7 : jmp 0x80483f0
  0x80483e9 : lea 0x0(%esi,1),%esi
  0x80483f0 : ret
  ....
  End of assembler dump.
  (gdb) disass hi
  Dump of assembler code for function hi:
  0x80483d0 : push $0x8048450
  0x80483d5 : call 0x8048308
  0x80483da : add $0x4,%esp
  0x80483dd : ret
  0x80483de : mov %esi,%esi
  End of assembler dump.
  在main()和hi()中都去掉了以下指令
  push %ebp
  mov %esp,%ebp//这两条指令安装
  leave//这条指令恢复
  来看看部分的内存映象
  (内存高址)
  +--------+
  |bffffbc4| argv的地址(即argv[0]的地址)
  0xbffffb84 +--------+
  |00000001| argc的值
  0xbffffb80 +--------+
  |400309cb|main的返回地址
  0xbffffb7c +--------+
  |080483e5| hi()的返回地址
  0xbffffb78 +--------+
  |08048450| "hi"字符串的地址
  0xbffffb74 +--------+
  | ...... |
  (内存低址)
  没有保存上层执行环境的ebp.
  ★ -fomit-frame-pointer && -O2
  -fomit-frame-pointer编译选项去掉了
  push %ebp
  mov %esp,%ebp//这两条指令安装
  leave//这条指令恢复
  -O2编译选项去掉了
  add $0x4,%esp
  两个加起来会不会这四条指令一起去掉,从而使stack不平衡呢?
  [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -O2 -o test test.c
  [alert7@redhat62 alert7]$ wc -c test
  11741 test
  [alert7@redhat62 alert7]$ gdb -q test
  (gdb) disass main
  Dump of assembler code for function main:
  0x80483d8 : call 0x80483c8
  0x80483dd : xor %eax,%eax
  0x80483df : ret
  End of assembler dump.
  (gdb) disass hi
  Dump of assembler code for function hi:
  0x80483c8 : push $0x8048430
  0x80483cd : call 0x8048308
  0x80483d2 : add $0x4,%esp
  0x80483d5 : ret
  0x80483d6 : mov %esi,%esi
  End of assembler dump.
  来看看部分的内存映象
  (内存高址)
  +--------+
  |bffffbc4| argv的地址(即argv[0]的地址)
  0xbffffb84 +--------+
  |00000001| argc的值
  0xbffffb80 +--------+
  |400309cb|main的返回地址
  0xbffffb7c +--------+
  |080483dd| hi()的返回地址
  0xbffffb78 +--------+
  |08048430| "hi"字符串的地址
  0xbffffb74 +--------+
  | ...... |
  (内存低址)
  此时就没有把add $0x4,%esp优化掉,如果优化掉的话,整个stack就
  会变的不平衡,从而会导致程序出错。
  ★ -fPIC 编译选项
  -fPIC If supported for the target machine, emit position-independent
  code, suitable for dynamic linking,even if branches need large
  displacements.
  产生位置无关代码(PIC),一般创建共享库时用到。
  在x86上,PIC的代码的符号引用都是通过ebx进行操作的。
  [alert7@redhat62 alert7]$ gcc -fPIC -o test test.c
  [alert7@redhat62 alert7]$ wc -c test
  11805 test
  [alert7@redhat62 alert7]$ gdb -q test
  (gdb) disass main
  Dump of assembler code for function main:
  0x80483f8 : push %ebp
  0x80483f9 : mov %esp,%ebp
  0x80483fb : push %ebx
  0x80483fc : call 0x8048401
  0x8048401 : pop %ebx//取得该指令的地址
  0x8048402 : add $0x1093,%ebx//此时ebx里面存放着是GOT表的地址
  0x8048408 : call 0x80483d0
  0x804840d : xor %eax,%eax
  0x804840f : jmp 0x8048411
  0x8048411 : mov 0xfffffffc(%ebp),%ebx
  0x8048414 : leave
  0x8048415 : ret
  ...
  End of assembler dump.
  (gdb) disass hi
  Dump of assembler code for function hi:
  0x80483d0 : push %ebp
  0x80483d1 : mov %esp,%ebp
  0x80483d3 : push %ebx
  0x80483d4 : call 0x80483d9
  0x80483d9 : pop %ebx
  0x80483da : add $0x10bb,%ebx
  0x80483e0 : lea 0xffffefdc(%ebx),%edx
  0x80483e6 : mov %edx,%eax
  0x80483e8 : push %eax
  0x80483e9 : call 0x8048308
  0x80483ee : add $0x4,%esp
  0x80483f1 : mov 0xfffffffc(%ebp),%ebx
  0x80483f4 : leave
  0x80483f5 : ret
  0x80483f6 : mov %esi,%esi
  End of assembler dump.
  来看看部分的内存映象
  (内存高址)
  +--------+
  |bffffbc4| argv的地址(即argv[0]的地址)
  0xbffffb84 +--------+
  |00000001| argc的值
  0xbffffb80 +--------+
  |400309cb|main的返回地址
  0xbffffb7c +--------+ <-- 调用main函数前的esp
  |bffffb98| 调用main函数前的ebp
  0xbffffb78 +--------+ <-- main函数的ebp
  |401081ec| 保存的ebx
  0xbffffb74 +--------+
  |0804840d| (存放过call 0x8048401的下一条指令地址)
  0xbffffb70 +--------+
  |bffffb78| 调用hi()前的esp
  0xbffffb6c +--------+
  |08049494| GOT表地址
  0xbffffb68 +--------+
  |08048470|(存放过call 0x80483d9的下一条指令地址)
  0xbffffb64 +--------+
  | ...... |
  (内存低址)
  ★ -static 编译选项
  -static
  On systems that support dynamic linking, this prevents
  linking with the shared libraries. On other systems,
  this option has no effect.
  把一些函数都静态的编译到程序中,而无需动态链接了。
  [alert7@redhat62 alert7]$ gcc -o test -static test.c
  [alert7@redhat62 alert7]$ wc -c test
  962808 test
  [alert7@redhat62 alert7]$ gdb -q test
  (gdb) disass main
  Dump of assembler code for function main:
  0x80481b4 : push %ebp
  0x80481b5 : mov %esp,%ebp
  0x80481b7 : call 0x80481a0
  0x80481bc : xor %eax,%eax
  0x80481be : jmp 0x80481c0
  0x80481c0 : leave
  0x80481c1 : ret
  ...
  End of assembler dump.
  (gdb) disass hi
  Dump of assembler code for function hi:
  0x80481a0 : push %ebp
  0x80481a1 : mov %esp,%ebp
  0x80481a3 : push $0x8071528
  0x80481a8 : call 0x804865c
  0x80481ad : add $0x4,%esp
  0x80481b0 : leave
  0x80481b1 : ret
  0x80481b2 : mov %esi,%esi
  End of assembler dump.
  [alert7@redhat62 alert7]$ ldd test
  not a dynamic executable
  -static出来的代码已经没有PLT了,GOT虽然有,已经全部为0了。