青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

興海北路

---男兒仗劍自橫行
<2008年3月>
2425262728291
2345678
9101112131415
16171819202122
23242526272829
303112345

統計

  • 隨筆 - 85
  • 文章 - 0
  • 評論 - 17
  • 引用 - 0

常用鏈接

留言簿(6)

隨筆分類

隨筆檔案

收藏夾

全是知識啊

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

將你的可執行文件“減肥”
by falcon <zhangjinw@gmail.com>
2008-02-23

    這篇blog從減少可執行文件大小的角度分析了ELF文件,期間通過經典的"Hello World"實例逐步演示如何通過各種常用工具來分析ELF文件,并逐步精簡代碼。
   
    為了能夠盡量減少可執行文件的大小,我們必須了解可執行文件的格式,以及鏈接生成可執行文件時的后臺細節(即最終到底有哪些內容被鏈接到了目標代碼中)。 通過選擇合適的可執行文件格式并剔除對可執行文件的最終運行沒有影響的內容,就可以實現目標代碼的裁減。因此,通過探索減少可執行文件大小的方法,就相當 于實踐性地去探索了可執行文件的格式以及鏈接過程的細節。

    當然,算法的優化和編程語言的選擇可能對目標文件的大小有很大的影響,在這篇blog的后面我們會跟參考資料[1]的作者那樣去探求一個打印“Hello World”的可執行文件能夠小到什么樣的地步。

1、可執行文件格式的選取
   
    可執行文件格式的選擇要滿足的一個基本條件是:目標系統支持該可執行文件格式,資料[2]分析和比較了UNIX平臺下的三種可執行文件格式,這三種格式實際上代表著可執行文件的一個發展過程。
    a.out文件格式非常緊湊,只包含了程序運行所必須的信息(文本、數據、BSS),而且每個 section的順序是固定的。
    coff文件格式雖然引入了一個節區表以支持更多節區信息,從而提高了可擴展性,但是這種文件格式的重定位在鏈接時就已經完成,因此不支持動態鏈接(不過擴展的coff支持)。
    elf文件格式不僅動態鏈接,而且有很好的擴展性。它可以描述可重定位文件、可執行文件和可共享文件(動態鏈接庫)三類文件。
    下面來看看ELF文件的結構圖。

    文件頭部(ELF Header)
    程序頭部表(Program Header Table)
    節區1(Section1)
    節區2(Section2)
    節區3(Section3)
    ...
    節區頭部(Section Header Table)
   
    無論是文件頭部、程序頭部表、節區頭部表還是各個節區,都是通過特定的結構體(struct)描述的,這些結構在elf.h文件中定義。文件頭部用于描述 整個文件的類型、大小、運行平臺、程序入口、程序頭部表和節區頭部表等信息。例如,我們可以通過文件頭部查看該ELF文件的類型。
Quote:

$ cat hello.c   #典型的hello, world程序
#include <stdio.h>

int main(void)
{
        printf("hello, world!\n");
        return 0;
}
$ gcc -c hello.c   #編譯,產生可重定向的目標代碼
$ readelf -h hello.o | grep Type   #通過readelf查看文件頭部找出該類型
  Type:                              REL (Relocatable file)
$ gcc -o hello hello.o   #生成可執行文件
$ readelf -h hello | grep Type
  Type:                              EXEC (Executable file)
$ gcc -fpic -shared -W1,-soname,libhello.so.0 -o libhello.so.0.0 hello.o  #生成共享庫
$ readelf -h libhello.so.0.0 | grep Type
  Type:                              DYN (Shared object file)


    那節區頭部表(將簡稱節區表)和程序頭部表有什么用呢?實際上前者只對可重定向文件有用,而后者只對可執行文件和可共享文件有用。
    節區表是用來描述各節區的,包括各節區的名字、大小、類型、虛擬內存中的位置、相對文件頭的位置等,這樣所有節區都通過節區表給描述了,這樣連接器就可以 根據文件頭部表和節區表的描述信息對各種輸入的可重定位文件進行合適的鏈接,包括節區的合并與重組、符號的重定位(確認符號在虛擬內存中的地址)等,把各 個可重定向輸入文件鏈接成一個可執行文件(或者是可共享文件)。如果可執行文件中使用了動態連接庫,那么將包含一些用于動態符號鏈接的節區。我們可以通過 readelf -S(或objdump -h)查看節區表信息。
Quote:

$ readelf -S hello  #可執行文件、可共享庫、可重定位文件默認都生成有節區表
...
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048114 000114 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048128 000128 000020 00   A  0   0  4
  [ 3] .hash             HASH            08048148 000148 000028 04   A  5   0  4
...
    [ 7] .gnu.version      VERSYM          0804822a 00022a 00000a 02   A  5   0  2
...
  [11] .init             PROGBITS        08048274 000274 000030 00  AX  0   0  4
...
  [13] .text             PROGBITS        080482f0 0002f0 000148 00  AX  0   0 16
  [14] .fini             PROGBITS        08048438 000438 00001c 00  AX  0   0  4
...


    三種類型文件的節區(各個常見節區的作用請參考資料[11])可能不一樣,但是有幾個節區,例如.text, .data, .bss是必須的,特別是.text,因為這個節區包含了代碼。如果一個程序使用了動態鏈接庫(引用了動態連接庫中的某個函數),那么需要.interp 節區以便告知系統使用什么動態連接器程序來進行動態符號鏈接,進行某些符號地址的重定位。通常,.rel.text節區只有可重定向文件有,用于鏈接時對 代碼區進行重定向,而.hash,.plt,.got等節區則只有可執行文件(或可共享庫)有,這些節區對程序的運行特別重要。還有一些節區,可能僅僅是 用于注釋,比如.comment,這些對程序的運行似乎沒有影響,是可有可無的,不過有些節區雖然對程序的運行沒有用處,但是卻可以用來輔助對程序進行調 試或者對程序運行效率有影響。
    雖然三類文件都必須包含某些節區,但是節區表對可重定位文件來說才是必須的,而程序的執行卻不需要節區表,只需要程序頭部表以便知道如何加載和執行文件。 不過如果需要對可執行文件或者動態連接庫進行調試,那么節區表卻是必要的,否則調試器將不知道如何工作。下面來介紹程序頭部表,它可通過readelf -l(或objdump -p)查看。
Quote:

$ readelf -l hello.o #對于可重定向文件,gcc沒有產生程序頭部,因為它對可重定向文件沒用

There are no program headers in this file.
$  readelf -l hello  #而可執行文件和可共享文件都有程序頭部
...
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x00470 0x00470 R E 0x1000
  LOAD           0x000470 0x08049470 0x08049470 0x0010c 0x00110 RW  0x1000
  DYNAMIC        0x000484 0x08049484 0x08049484 0x000d0 0x000d0 RW  0x4
  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00    
   01     .interp
   02     .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag
   06    
$  readelf -l libhello.so.0.0  #節區和上面類似,這里省略


    從上面可看出程序頭部表描述了一些段(Segment),這些段對應著一個或者多個節區,上面的readelf -l很好地顯示了各個段與節區的映射。這些段描述了段的名字、類型、大小、第一個字節在文件中的位置、將占用的虛擬內存大小、在虛擬內存中的位置等。這樣 系統程序解釋器將知道如何把可執行文件加載到內存中以及進行動態鏈接等動作。
    該可執行文件包含7個段,PHDR指程序頭部,INTERP正好對應.interp節區,兩個LOAD段包含程序的代碼和數據部分,分別包含有.text 和.data,.bss節區,DYNAMIC段包含.daynamic,這個節區可能包含動態連接庫的搜索路徑、可重定位表的地址等信息,它們用于動態連 接器。NOTE和GNU_STACK段貌似作用不大,只是保存了一些輔助信息。因此,對于一個不使用動態連接庫的程序來說,可能只包含LOAD段,如果一 個程序沒有數據,那么只有一個LOAD段就可以了。

    總結一下,Linux雖然支持很多種可執行文件格式,但是目前ELF較通用,所以選擇ELF作為我們的討論對象。通過上面對ELF文件分析發現一個可執行 的文件可能包含一些對它的運行沒用的信息,比如節區表、一些用于調試、注釋的節區。如果能夠刪除這些信息就可以減少可執行文件的大小,而且不會影響可執行 文件的正常運行。

2、鏈接優化

    從上面的討論中已經接觸了動態連接庫。ELF中引入動態連接庫后極大地方便了公共函數的共享,節約了磁盤和內存空間,因為不再需要把那些公共函數的代碼鏈接到可執行文件,這將減少了可執行文件的大小。
    與此同時,靜態鏈接可能會引入一些對代碼的運行可能并非必須的內容。你可以從《GCC編譯的背后(第二部分:匯編和鏈接)》 了 解到GCC鏈接的細節。從那篇Blog中似乎可以得出這樣的結論:僅僅從是否影響一個C語言程序運行的角度上說,GCC默認鏈接到可執行文件的幾個可重定 位文件(crt1.o,rti.o,crtbegin.o,crtend.o,crtn.o)并不是必須的,不過值得注意的是,如果沒有鏈接那些文件但在 程序末尾使用了return語句,main函數將無法返回,因此需要替換為_exit調用;另外,既然程序在進入main之前有一個入口,那么main入 口就不是必須的。因此,如果不采用默認鏈接也可以減少可執行文件的大小。

3、可執行文件“減肥”實例

    這里主要是根據上面兩點來介紹如何減少一個可執行文件的大小。以"Hello World"為例。
    首先來看看默認編譯產生的Hello World的可執行文件大小。

  • 系統默認編譯

    代碼同上,下面是一組演示,
    Quote:

    $ uname -r   #先查看內核版本和gcc版本,以便和你的結果比較
    2.6.22-14-generic
    $ gcc --version
    gcc (GCC) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)
    ...
    $ gcc -o hello hello.c   #默認編譯
    $ wc -c hello   #產生一個大小為6442字節的可執行文件
    6442 hello



  • 不采用默認編譯

        可以考慮編輯時就把return 0替換成_exit(0)并包含定義該函數的unistd.h頭文件。下面是從GCC編譯的背后(第二部分:匯編和鏈接)》總結出的Makefile文件。



    Code:

    [Ctrl+A Select All]


        把上面的代碼復制到一個Makefile文件中,并利用它來編譯hello.c。
    Quote:

    $ make   #編譯
    sed -i -e '/#include[ "<]*unistd.h[ ">]*/d;' -i -e '1i #include <unistd.h>' -i -e 's/return 0;/_exit(0);/' hello.c
    cc -S   hello.c
    sed -i -e 's/main/_start/g' hello.s
    cc -c hello.s
    ld -dynamic-linker /lib/ld-linux.so.2 -L /usr/lib/ -lc   -o hello hello.o
    $ ./hello   #這個也是可以正常工作的
    Hello World
    $ wc -c hello   #但是大小減少了4382個字節,減少了將近70%
    2060 hello  
    $ echo "6442-2060" | bc
    4382
    $ echo "(6442-2060)/6442" | bc -l
    .68022353306426575597


        對于一個比較小的程序,能夠減少將近70%“沒用的”代碼。至于一個大一點的程序(這個代碼是[1]的作者寫的一個小工具,我們后面會使用它)再看看效果。
    Quote:

    $ gcc -o sstrip sstrip.c   #默認編譯的情況
    $ wc -c sstrip
    10912 sstrip
    $ sed -i -e "s/hello/sstrip/g" Makefile  #把Makefile中的hello替換成sstrip
    $ make clean      #清除默認編譯的sstrip
    $ make            #用我們的Makefile編譯
    $ wc -c sstrip
    6589 sstrip
    $ echo "10912-6589" | bc -l   #再比較大小,減少的代碼還是4323個字節,減幅40%
    4323
    $ echo "(10912-6589)/10912" | bc -l
    .39616935483870967741


        通過這兩個簡單的實驗,我們發現,能夠減少掉4000個字節左右,相當于4k左右。

  • 刪除對程序運行沒有影響的節區

        使用上述Makefile來編譯程序,不鏈接那些對程序運行沒有多大影響的文件,實際上也相當于刪除了一些“沒用”的節區,可以通過下列演示看出這個實質。

    Quote:

    $ sed -i -e "s/sstrip/hello/g" Makefile  #先看看用Makefile編譯的結果,替換回hello
    $ make clean
    $ make
    $ readelf -l hello | grep "0[0-9]\ \ "
       00    
       01     .interp
       02     .interp .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.plt .plt .text .rodata
       03     .dynamic .got.plt
       04     .dynamic
       05    
    $ make clean
    $ gcc -o hello hello.c
    $ readelf -l hello | grep "0[0-9]\ \ "
       00    
       01     .interp
       02     .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
       03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
       04     .dynamic
       05     .note.ABI-tag
       06


        通過比較發現使用自定義的Makefile文件,少了這么多節區:.bss .ctors .data .dtors .eh_frame .fini .gnu.hash .got .init .jcr .note.ABI-tag .rel.dyn。
        再看看還有哪些節區可以刪除呢?通過之前的分析發現有些節區是必須的,那.hash?.gnu.version?呢,通過strip -R(或objcop -R)刪除這些節區試試。

    Quote:

    $ wc -c hello   #查看大小,以便比較
    2060
    $ time ./hello    #我們比較一下一些節區對執行時間可能存在的影響
    Hello World

    real    0m0.001s
    user    0m0.000s
    sys     0m0.000s
    $ strip -R .hash hello   #刪除.hash節區
    $ wc -c hello           
    1448 hello
    $ echo "2060-1448" | bc   #減少了612字節
    612
    $ time ./hello           #發現執行時間長了一些(實際上也可能是進程調度的問題)
    Hello World

    real    0m0.006s
    user    0m0.000s
    sys     0m0.000s
    $ strip -R .gnu.version hello   #刪除.gnu.version還是可以工作
    $ wc -c hello
    1396 hello
    $ echo "1448-1396" | bc      #又減少了52字節
    52
    $ time ./hello
    Hello World

    real    0m0.130s
    user    0m0.004s
    sys     0m0.000s
    $ strip -R .gnu.version_r hello   #刪除.gnu.version_r就不工作了
    $ time ./hello
    ./hello: error while loading shared libraries: ./hello: unsupported version 0 of Verneed record


        通過刪除各個節區可以查看哪些節區對程序來說是必須的,不過有些節區雖然并不影響程序的運行卻可能會影響程序的執行效率,這個可以上面的運行時間看出個大概。
        通過刪除兩個“沒用”的節區,我們又減少了52+612,即664字節

  • 刪除可執行文件的節區表

        用普通的工具沒有辦法刪除節區表,但是參考資料[1]的作者已經寫了這樣一個工具。你可以從這里http://www.muppetlabs.com/~breadbox/software/elfkickers.html下載到那個工具,即我們上面作為一個演示例子的sstrip,它是該作者寫的一序列工具ELFkickers中的一個。下載以后,編譯,并復制到/usr/bin下,下面用它來刪除節區表。

    Quote:

    $ sstrip hello      #刪除ELF可執行文件的節區表
    $ ./hello           #還是可以正常運行,說明節區表對可執行文件的運行沒有任何影響
    Hello World
    $ wc -c hello       #大小只剩下708個字節了
    708 hello
    $ echo "1396-708" | bc  #又減少了688個字節。
    688


        通過刪除節區表又把可執行文件減少了688字節。現在回頭看看相對于gcc默認產生的可執行文件,通過刪除一些節區和節區表到底減少了多少字節?減幅達到了多少?
    Quote:

    $ echo "6442-708" | bc   #
    5734
    $ echo "(6442-708)/6442" | bc -l
    .89009624340266997826


        減少了5734多字節,減幅將近90%,這說明:對于一個簡短的hello.c程序而言,gcc引入了將近90%的對程序運行沒有影響的數據。雖然通過刪 除節區和節區表,使得最終的文件只有708字節,但是打印一個"Hello World"真的需要這么多字節么?
        事實上未必,因為:
        1、打印一段Hello World字符串,我們無須調用printf,也就無須包含動態連接庫,因此.interp,.dynamic等節區又可以去掉。為什么?我們可以直接使用系統調用(sys_write)來打印字符串。
        2、另外,我們無須把Hello World字符串存放到可執行文件中?而是讓用戶把它當作參數輸入。
        下面,繼續進行可執行文件的“減肥”。

    4、用匯編語言來重寫"Hello World"

        先來看看gcc默認產生的匯編代碼情況。通過gcc的-S選項可得到匯編代碼。
    Quote:

    $ cat hello.c  #這個是使用_exit和printf函數的版本
    #include <stdio.h>      /* printf */
    #include <unistd.h>     /* _exit */

    int main()
    {
            printf("Hello World\n");
            _exit(0);
    }
    $ gcc -S hello.c    #生成匯編
    $ cat hello.s       #這里是匯編代碼
            .file   "hello.c"
            .section        .rodata
    .LC0:
            .string "Hello World"
            .text
    .globl main
            .type   main, @function
    main:
            leal    4(%esp), %ecx
            andl    $-16, %esp
            pushl   -4(%ecx)
            pushl   %ebp
            movl    %esp, %ebp
            pushl   %ecx
            subl    $4, %esp
            movl    $.LC0, (%esp)
            call    puts
            movl    $0, (%esp)
            call    _exit
            .size   main, .-main
            .ident  "GCC: (GNU) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)"
            .section        .note.GNU-stack,"",@progbits   
    $ gcc -o hello hello.s   #看看默認產生的代碼大小
    $ wc -c hello
    6523 hello


        現在對匯編代碼(hello.s)進行簡單的處理得到,


    Code:

    [Ctrl+A Select All]


        再編譯看看,
    Quote:

    $ gcc -o hello.o hello.s
    $ wc -c hello
    6443 hello
    $ echo "6523-6443" | bc   #僅僅減少了80個字節
    80


        如果不采用默認編譯呢并且刪除掉對程序運行沒有影響的節區和節區表呢?
    Quote:

    $ sed -i -e "s/main/_start/g" hello.s   #因為沒有初始化,所以得直接進入代碼,替換main為_start
    $ as -o  hello.o hello.s
    $ ld -o hello hello.o --dynamic-linker /lib/ld-linux.so.2 -L /usr/lib -lc
    $ ./hello
    hello world!
    $ wc -c hello
    1812 hello
    $ echo "6443-1812" | bc -l   #和之前的實驗類似,也減少了4k左右
    4631
    $ readelf -l hello | grep "\ [0-9][0-9]\ "
       00    
       01     .interp
       02     .interp .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.plt .plt .text
       03     .dynamic .got.plt
       04     .dynamic
    $ strip -R .hash hello
    $ strip -R .gnu.version hello
    $ wc -c hello
    1200 hello
    $ sstrip hello
    $ wc -c hello  #這個結果比之前的708(在刪除所有垃圾信息以后)個字節少了708-676,即32個字節
    676 hello
    $ ./hello
    Hello World


        容易發現這32字節可能跟節區.rodata有關系,因為剛才在鏈接完以后查看節區信息時,并沒有.rodata節區。
        前面提到,實際上還可以不用動態連接庫中的printf函數,也不用直接調用_exit,而是在匯編里頭使用系統調用,這樣就可以去掉和動態連接庫關聯的內容。(如果想了解如何在匯編中使用系統調用,請參考資料[9])。使用系統調用重寫以后得到如下代碼,



    Code:

    [Ctrl+A Select All]


        現在編譯就不再需要動態鏈接器ld-linux.so了,也不再需要鏈接任何庫。
    Quote:

    $ as -o hello.o hello.s
    $ ld -o hello hello.o
    $ readelf -l hello

    Elf file type is EXEC (Executable file)
    Entry point 0x8048062
    There are 1 program headers, starting at offset 52

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      LOAD           0x000000 0x08048000 0x08048000 0x0007b 0x0007b R E 0x1000

     Section to Segment mapping:
      Segment Sections...
       00     .text
    $ sstrip hello
    $ ./hello           #完全可以正常工作
    Hello World
    $ wc -c hello
    123 hello
    $ echo "676-123" | bc   #相對于之前,已經只需要123個字節了,又減少了553個字節
    553


        可以看到效果很明顯,只剩下一個LOAD段,它對應.text節區。
        不過是否還有辦法呢?把Hello World作為參數輸入,而不是硬編碼在文件中。所以如果處理參數的代碼少于Hello World字符串的長度,那么就可以達到減少目標文件大小的目的。
        先來看一個能夠打印程序參數的匯編語言程序,它來自參考資料[9]。


    Code:

    [Ctrl+A Select All]


        編譯看看效果,
    Quote:

    $ as -o args.o args.s
    $ ld -o args args.o
    $ ./args "Hello World"  #能夠打印輸入的字符串,不錯
    ./args
    Hello World
    $ sstrip args
    $ wc -c args           #處理以后只剩下130字節
    130 args


        可以看到,這個程序可以接收用戶輸入的參數并打印出來,不過得到的可執行文件為130字節,比之前的123個字節還多了7個字節,看看還有改進么?分析上面的代碼后,發現,原來的代碼有些地方可能進行優化,優化后得到如下代碼。



    Code:

    [Ctrl+A Select All]


        再測試(記得先重新匯編、鏈接并刪除沒用的節區和節區表)。
    Quote:

    $ wc -c hello
    124 hello


        現在只有124個字節,不過還是比123個字節多一個,還有什么優化的辦法么?
        先來看看目前hello的功能,感覺不太符合要求,因為只需要打印Hello World,所以不必處理所有的參數,僅僅需要接收并打印一個參數就可以。這樣的話,把jmp vnext(2字節)這個循環去掉,然后在第一個pop %ecx語句之前加一個pop %ecx(1字節)語句就可以。

    Quote:

    .global _start
    _start:
            popl %ecx
            popl %ecx        #彈出argc[0]的地址
            popl %ecx        #彈出argv[1]的地址
            test %ecx, %ecx
            jz exit
            movl %ecx, %ebx
            xorl %edx, %edx
    strlen:                  
            movb (%ebx), %al
            inc %edx
            inc %ebx
            test %al, %al
            jnz strlen
            movb $10, -1(%ebx)
            xorl %eax, %eax
            movb $4, %al
            xorl %ebx, %ebx
            incl %ebx
            int $0x80
    exit:
            xorl %eax, %eax
            movl %eax, %ebx
            incl %eax
            int $0x80


        現在剛好123字節,和原來那個代碼大小一樣,不過仔細分析,還是有減少代碼的余地:因為在這個代碼中,用了一段額外的代碼計算字符串的長度,實際上如果 僅僅需要打印Hello World,那么字符串的長度是固定的,即12。所以這段代碼可去掉,與此同時測試字符串是否為空也就沒有必要(不過可能影響代碼健壯性!),當然,為了 能夠在打印字符串后就換行,在串的末尾需要加一個回車($10)并且設置字符串的長度為12+1,即13,



    Code:

    [Ctrl+A Select All]


        再看看效果,
    Quote:

    $ wc -c hello
    111 hello


        現在只剩下111字節,比剛才少了12字節。貌似到了極限?還有措施么?
        還有,仔細分析發現:系統調用sys_exit和sys_write都用到了eax和ebx寄存器,它們之間剛好有那么一點巧合:
        1、sys_exit調用時,eax需要設置為1,ebx需要設置為0。
        2、sys_write調用時,ebx剛好是1。
        因此,如果在sys_exit調用之前,先把ebx復制到eax中,再對ebx減一,則可減少兩個字節。
        不過,因為標準輸入、標準輸出和標注錯誤都指向終端,如果往標準輸入寫入一些東西,它還是會輸出到標準輸出上,所以在上述代碼中如果在sys_write 之前ebx設置為0,那么也可正常往屏幕上打印Hell World,這樣的話,sys_exit調用前就沒必要修改ebx,而僅需把eax設置為1,這樣就可減少3個字節。



    Code:

    [Ctrl+A Select All]


        看看效果,  
    Quote:

    $ wc -c hello
    108 hello


        現在看一下純粹的指令還有多少?
    Quote:

    $ readelf -h hello | grep Size
      Size of this header:               52 (bytes)
      Size of program headers:           32 (bytes)
      Size of section headers:           0 (bytes)
    $  echo "108-52-32" | bc
    24


        純粹的指令只有24個字節了,還有辦法再減少目標文件的大小么?如果看了參考資料[1],看樣子你又要蠢蠢欲動了:這24個字節是否可以插入到文件頭部或程序頭部?如果可以那是否意味著還可減少可執行文件的大小呢?現在來比較一下這三部分的十六進制內容。

    Quote:

    $  hexdump -C hello -n 52     #文件頭(52bytes)
    00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
    00000010  02 00 03 00 01 00 00 00  54 80 04 08 34 00 00 00  |........T...4...|
    00000020  00 00 00 00 00 00 00 00  34 00 20 00 01 00 00 00  |........4. .....|
    00000030  00 00 00 00                                       |....|
    00000034
    $ hexdump -C hello -s 52 -n 32    #程序頭(32bytes)
    00000034  01 00 00 00 00 00 00 00  00 80 04 08 00 80 04 08  |................|
    00000044  6c 00 00 00 6c 00 00 00  05 00 00 00 00 10 00 00  |l...l...........|
    00000054
    $ hexdump -C hello -s 84          #實際代碼部分(24bytes)
    00000054  59 59 59 c6 41 0c 0a 31  d2 b2 0d 31 c0 b0 04 31  |YYY.A..1...1...1|
    00000064  db cd 80 31 c0 40 cd 80                           |...1.@..|
    0000006c

     
        從上面結果發現ELF文件頭部和程序頭部還有好些空洞(0),是否可以通過引入跳轉指令把24個字節分散放入到那些空洞里或者是直接覆蓋掉那些系統并不關 心的內容?抑或是把代碼壓縮以后放入可執行文件中,并在其中實現一個解壓縮算法?還可以是通過一些代碼覆蓋率測試工具(gcov,prof)對你的代碼進 行優化?這個作為我們共同的練習吧!
        由于時間關系,這里不再進一步討論,如果想進一步研究,請閱讀參考資料[1],它更深層次地討論了ELF文件,特別是Linux系統對ELF文件頭部和程序頭部的解析。  
        到這里,關于可執行文件的討論暫且結束,最后來一段小小的總結,那就是我們設法去減少可執行文件大小的意義?
        實際上,通過這樣一個討論深入到了很多技術的細節,包括可執行文件的格式、目標代碼鏈接的過程、Linux下匯編語言開發等。與此同時,可執行文件大小的 減少本身對嵌入式系統非常有用,如果刪除那些對程序運行沒有影響的節區和節區表將減少目標系統的大小,適應嵌入式系統資源受限的需求。除此之外,動態連接 庫中的很多函數可能不會被使用到,因此也可以通過某種方式剔除[8][10]。
        或許,你還會發現更多有趣的意義,歡迎給我發送郵件,一起討論。

    參考資料:

    [1] A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux
    http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
    [2] UNIX/LINUX 平臺可執行文件格式分析
    http://blog.chinaunix.net/u/19881/showart_215242.html
    [3] C/C++程序編譯步驟詳解
    http://www.xxlinux.com/linux/article/development/soft/20070424/8267.html
    [4] The Linux GCC HOW TO
    http://www.faqs.org/docs/Linux-HOWTO/GCC-HOWTO.html
    [5] ELF: From The Programmer's Perspective
    http://linux.jinr.ru/usoft/WWW/www_debian.org/Documentation/elf/elf.html
    [6] Understanding ELF using readelf and objdump
    http://www.linuxforums.org/misc/understanding_elf_using_readelf_and_objdump.html
    [7] Dissecting shared libraries
    http://www.ibm.com/developerworks/linux/library/l-shlibs.html
    [8] 嵌入式Linux小型化技術
    http://www.gexin.com.cn/UploadFile/document2008119102415.pdf
    [9] Linux匯編語言開發指南
    http://www.ibm.com/developerworks/cn/linux/l-assembly/index.html
    [10] Library Optimizer
    http://sourceforge.net/projects/libraryopt
    [11] ELF file format and ABI
    http://www.x86.org/ftp/manuals/tools/elf.pdf
    http://www.muppetlabs.com/~breadbox/software/ELF.txt
    (北大OS實驗室)http://162.105.203.48/web/gaikuang/submission/TN05.ELF.Format.Summary.pdf
    (alert7 大牛翻譯)http://www.xfocus.net/articles/200105/174.html
  • posted on 2008-03-14 15:33 隨意門 閱讀(1973) 評論(0)  編輯 收藏 引用

    青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲精品国产精品国自产在线| 国产日韩亚洲| 洋洋av久久久久久久一区| 亚洲国产精品成人久久综合一区| 美女精品一区| 亚洲国产日韩一区二区| 日韩午夜av| 午夜精品理论片| 久久久久久亚洲精品不卡4k岛国| 老司机精品久久| 欧美日韩二区三区| 国产欧美一区二区精品忘忧草 | 国产精品免费一区二区三区在线观看| 国产精品人人爽人人做我的可爱| 国产精品最新自拍| 亚洲国产成人porn| 中文av一区特黄| 久久久久久久久蜜桃| 亚洲国产高清一区| 亚洲一区二区三区乱码aⅴ蜜桃女| 久久精品国产v日韩v亚洲 | 国产欧美视频在线观看| 亚洲欧洲日本在线| 欧美一级欧美一级在线播放| 欧美大片在线看| 亚洲永久字幕| 欧美大片免费久久精品三p| 国产精品免费一区二区三区在线观看| 黄色精品网站| 午夜精品久久久久久久久久久久久 | 欧美激情亚洲另类| 一本色道久久99精品综合| 欧美一区观看| 欧美亚韩一区| 亚洲美女av网站| 久久一区精品| 亚洲女同精品视频| 欧美精品一区二区三区高清aⅴ| 国产精品美女视频网站| 亚洲精品乱码久久久久久| 欧美一区二区三区免费观看视频 | 激情欧美一区二区三区在线观看| 中文日韩电影网站| 亚洲国产裸拍裸体视频在线观看乱了中文 | 亚洲国产天堂网精品网站| 亚洲欧美在线播放| 亚洲黄色在线看| 久久久噜噜噜久噜久久| 国产一区二区三区日韩欧美| 午夜精品久久久久久久99黑人| 99av国产精品欲麻豆| 欧美日韩成人一区| 欧美精品电影在线| 亚洲欧美日韩国产中文| 亚洲精选在线| 欧美大片免费| 蜜桃精品久久久久久久免费影院| 激情久久久久久久| 久久综合中文字幕| 久久久夜精品| 亚洲激情电影中文字幕| 欧美国产第二页| 免费试看一区| 一本色道久久综合亚洲精品不| 亚洲高清资源| 欧美精品一区在线发布| 亚洲欧美日本精品| 国产精品久久久久久久久久妞妞| 亚洲男人第一av网站| 一个色综合导航| 国产精品欧美经典| 欧美专区福利在线| 久久成人免费电影| 亚洲电影免费观看高清完整版在线观看| 久久亚洲私人国产精品va| 久久久久青草大香线综合精品| 一区在线视频观看| 亚洲国产精品毛片| 国产精品高潮呻吟久久av无限| 欧美一区二区三区电影在线观看| 久久九九全国免费精品观看| 亚洲国产美女久久久久| 亚洲免费精品| 国产伪娘ts一区| 欧美激情黄色片| 欧美视频在线观看一区| 欧美一区二区在线观看| 另类酷文…触手系列精品集v1小说| 亚洲精品免费网站| 午夜久久tv| 亚洲另类在线视频| 亚洲欧美日韩国产一区二区| 亚洲欧洲精品一区二区| 亚洲欧美视频一区| 亚洲麻豆国产自偷在线| 午夜一级久久| 在线亚洲观看| 久久青草欧美一区二区三区| 亚洲视频成人| 欧美一区二区三区播放老司机| 最新高清无码专区| 亚洲日本一区二区| 国产欧美一区二区在线观看| 蜜桃视频一区| 国产精品日韩二区| 欧美黑人在线播放| 国产伦精品一区二区三区四区免费| 免费日韩av| 国产精品三级视频| 亚洲成在人线av| 国产亚洲欧美一级| 亚洲美女av黄| 一区免费观看| 午夜精品视频在线观看| 欧美在线视频网站| 亚洲专区免费| 欧美成人精品在线播放| 久久免费99精品久久久久久| 欧美日本簧片| 91久久夜色精品国产九色| 国产在线观看91精品一区| 亚洲精品偷拍| 激情视频一区二区| 欧美专区在线播放| 午夜国产一区| 国产精品扒开腿爽爽爽视频| 亚洲区免费影片| 亚洲免费av电影| 久久久精品国产一区二区三区| 欧美精品一卡| 亚洲欧洲在线播放| 亚洲福利一区| 久久av一区二区三区| 欧美在线免费视频| 国产精品欧美精品| 一区二区不卡在线视频 午夜欧美不卡在 | 欧美日韩国产成人高清视频| 欧美高清不卡在线| **性色生活片久久毛片| 美日韩丰满少妇在线观看| 免费成人av在线| 亚洲国产另类精品专区| 美女主播视频一区| 亚洲国产经典视频| 亚洲看片一区| 欧美三级网页| 先锋资源久久| 久久先锋影音| 亚洲人精品午夜| 欧美三级网址| 午夜天堂精品久久久久| 久久综合色一综合色88| 亚洲丁香婷深爱综合| 今天的高清视频免费播放成人| 久久久久se| 亚洲电影免费观看高清完整版在线观看 | 国产精品黄色| 欧美一区在线看| 欧美成人a视频| 国语自产精品视频在线看一大j8 | 亚洲美女毛片| 中文日韩在线| 国产日韩欧美二区| 久久综合色88| 亚洲午夜精品福利| 免费欧美网站| 在线亚洲+欧美+日本专区| 国产一区在线观看视频| 欧美日韩www| 久久成人久久爱| 99精品视频免费观看视频| 久久国产精品一区二区三区四区| 亚洲欧美99| 亚洲第一中文字幕在线观看| 午夜免费电影一区在线观看| 尤物99国产成人精品视频| 欧美日韩在线第一页| 久久九九全国免费精品观看| 99精品免费| 亚洲丰满少妇videoshd| 久久福利资源站| 亚洲女同精品视频| 亚洲精品社区| 精品成人在线视频| 国产精品一区免费视频| 欧美精品成人一区二区在线观看| 久久成人羞羞网站| 亚洲一区二区视频| 亚洲黄色免费电影| 久久亚洲精品伦理| 亚洲一区免费观看| 亚洲精品1区2区| 黄色成人免费观看| 国产日韩在线视频| 国产精品美女| 国产精品盗摄久久久| 欧美精品一区二区在线观看| 久久麻豆一区二区| 久久大逼视频| 久久av在线看|