[ 永遠的UNIX::UNIX技術資料的寶庫 ]   GB | BIG5

首頁 > 編程技術 > C/C++ > 正文
gcc常用的編譯選項對代碼的影響
本文出自:http://xfocus.org 作者:alert7 (2002-08-06 06:02:00)
測試環境 redhat 6.2

★ 前言

本文討論gcc的一些常用編譯選項對代碼的影響。當然代碼變了,
它的內存布局也就會變了,隨之exploit也就要做相應的變動。
gcc的編譯選項實在太多,本文檢了幾個最常用的選項。


★ 演示程序

[alert7@redhat62 alert7]$ cat > test.c
#include <stdio.h>
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 <main>:       push   %ebp
0x80483e5 <main+1>:     mov    %esp,%ebp
0x80483e7 <main+3>:     call   0x80483d0 <hi>
0x80483ec <main+8>:     xor    %eax,%eax
0x80483ee <main+10>:    jmp    0x80483f0 <main+12>
0x80483f0 <main+12>:    leave
0x80483f1 <main+13>:    ret
....
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483d0 <hi>:        push   %ebp
0x80483d1 <hi+1>:       mov    %esp,%ebp
0x80483d3 <hi+3>:       push   $0x8048450
0x80483d8 <hi+8>:       call   0x8048308 <printf>
0x80483dd <hi+13>:      add    $0x4,%esp
0x80483e0 <hi+16>:      leave
0x80483e1 <hi+17>:      ret
0x80483e2 <hi+18>:      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 <main>:       push   %ebp
0x80483d9 <main+1>:     mov    %esp,%ebp
0x80483db <main+3>:     call   0x80483c8 <hi>
0x80483e0 <main+8>:     xor    %eax,%eax
0x80483e2 <main+10>:    leave
0x80483e3 <main+11>:    ret
0x80483e4 <main+12>:    nop
...
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483c8 <hi>:        push   %ebp
0x80483c9 <hi+1>:       mov    %esp,%ebp
0x80483cb <hi+3>:       push   $0x8048440
0x80483d0 <hi+8>:       call   0x8048308 <printf>
0x80483d5 <hi+13>:      leave
0x80483d6 <hi+14>:      ret
0x80483d7 <hi+15>:      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 <main>:       push   %ebp
0x80483d9 <main+1>:     mov    %esp,%ebp
0x80483db <main+3>:     call   0x80483c8 <hi>
0x80483e0 <main+8>:     xor    %eax,%eax
0x80483e2 <main+10>:    leave
0x80483e3 <main+11>:    ret
...
0x80483ef <main+23>:    nop
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483c8 <hi>:        push   %ebp
0x80483c9 <hi+1>:       mov    %esp,%ebp
0x80483cb <hi+3>:       push   $0x8048440
0x80483d0 <hi+8>:       call   0x8048308 <printf>
0x80483d5 <hi+13>:      leave
0x80483d6 <hi+14>:      ret
0x80483d7 <hi+15>:      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 <main>:       call   0x80483d0 <hi>
0x80483e5 <main+5>:     xor    %eax,%eax
0x80483e7 <main+7>:     jmp    0x80483f0 <main+16>
0x80483e9 <main+9>:     lea    0x0(%esi,1),%esi
0x80483f0 <main+16>:    ret
....
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483d0 <hi>:        push   $0x8048450
0x80483d5 <hi+5>:       call   0x8048308 <printf>
0x80483da <hi+10>:      add    $0x4,%esp
0x80483dd <hi+13>:      ret
0x80483de <hi+14>:      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 <main>:       call   0x80483c8 <hi>
0x80483dd <main+5>:     xor    %eax,%eax
0x80483df <main+7>:     ret
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483c8 <hi>:        push   $0x8048430
0x80483cd <hi+5>:       call   0x8048308 <printf>
0x80483d2 <hi+10>:      add    $0x4,%esp
0x80483d5 <hi+13>:      ret
0x80483d6 <hi+14>:      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 <main>:       push   %ebp
0x80483f9 <main+1>:     mov    %esp,%ebp
0x80483fb <main+3>:     push   %ebx
0x80483fc <main+4>:     call   0x8048401 <main+9>
0x8048401 <main+9>:     pop    %ebx//取得該指令的地址
0x8048402 <main+10>:    add    $0x1093,%ebx//此時ebx裡面存放著是GOT表的地址
0x8048408 <main+16>:    call   0x80483d0 <hi>
0x804840d <main+21>:    xor    %eax,%eax
0x804840f <main+23>:    jmp    0x8048411 <main+25>
0x8048411 <main+25>:    mov    0xfffffffc(%ebp),%ebx
0x8048414 <main+28>:    leave
0x8048415 <main+29>:    ret
...
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483d0 <hi>:        push   %ebp
0x80483d1 <hi+1>:       mov    %esp,%ebp
0x80483d3 <hi+3>:       push   %ebx
0x80483d4 <hi+4>:       call   0x80483d9 <hi+9>
0x80483d9 <hi+9>:       pop    %ebx
0x80483da <hi+10>:      add    $0x10bb,%ebx
0x80483e0 <hi+16>:      lea    0xffffefdc(%ebx),%edx
0x80483e6 <hi+22>:      mov    %edx,%eax
0x80483e8 <hi+24>:      push   %eax
0x80483e9 <hi+25>:      call   0x8048308 <printf>
0x80483ee <hi+30>:      add    $0x4,%esp
0x80483f1 <hi+33>:      mov    0xfffffffc(%ebp),%ebx
0x80483f4 <hi+36>:      leave
0x80483f5 <hi+37>:      ret
0x80483f6 <hi+38>:      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 <main>:       push   %ebp
0x80481b5 <main+1>:     mov    %esp,%ebp
0x80481b7 <main+3>:     call   0x80481a0 <hi>
0x80481bc <main+8>:     xor    %eax,%eax
0x80481be <main+10>:    jmp    0x80481c0 <main+12>
0x80481c0 <main+12>:    leave
0x80481c1 <main+13>:    ret
...
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80481a0 <hi>:        push   %ebp
0x80481a1 <hi+1>:       mov    %esp,%ebp
0x80481a3 <hi+3>:       push   $0x8071528
0x80481a8 <hi+8>:       call   0x804865c <printf>
0x80481ad <hi+13>:      add    $0x4,%esp
0x80481b0 <hi+16>:      leave
0x80481b1 <hi+17>:      ret
0x80481b2 <hi+18>:      mov    %esi,%esi
End of assembler dump.
[alert7@redhat62 alert7]$ ldd test
        not a dynamic executable
-static出來的代碼已經沒有PLT了,GOT雖然有,已經全部為0了。


★ 小節

拋磚引玉般簡單的實例描述了下gcc常用的編譯選項對代碼的影響。
不正之處,還請斧正。謝謝。   (http://www.fanqiang.com)
    進入【UNIX論壇

相關文章
關gcc/egcs/pgcc (2002-01-29 20:22:21)
用gcc 編譯,為何有不同的結果 (關socket函數)? (2001-08-29 10:00:00)
在Solaris系統中安裝GCC編譯器 (2001-06-25 18:08:00)
GCC HOWTO中譯版V0.2 --- 8. 與發展人士聯絡,9. 結語 (2001-06-22 13:04:00)
GCC HOWTO中譯版V0.2 --- 7. 動態載入 (2001-06-22 12:10:01)
GCC HOWTO中譯版V0.2 --- 6. 連結 (2001-06-22 11:00:00)
GCC HOWTO中譯版V0.2 --- 5. 除錯與監管 (2001-06-22 10:08:00)
GCC HOWTO中譯版V0.2 --- 4. 移植程式與編譯程式 (2001-06-22 09:04:00)
GCC HOWTO中譯版V0.2 --- 3. GCC的安裝與GCC的設定 (2001-06-22 08:10:00)
GCC HOWTO中譯版V0.2 --- 2. 東東在哪兒? (2001-06-22 07:00:00)

===更多相關===
 

★  樊強制作 歡迎分享  ★