一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

為可執(zhí)行文件“減肥”(一)

 InfoRich 2019-09-27

圖片來(lái)自網(wǎng)絡(luò)

1. 前言

本文從減少可執(zhí)行文件大小的角度分析了 ELF 文件,期間通過(guò)經(jīng)典的 ”Hello World” 實(shí)例逐步演示如何通過(guò)各種常用工具來(lái)分析 ELF 文件,并逐步精簡(jiǎn)代碼。由于行文較長(zhǎng),本公眾號(hào)將用一個(gè)系列四個(gè)篇幅呈現(xiàn)給泰曉的讀者。

為了能夠盡量減少可執(zhí)行文件的大小,我們必須了解可執(zhí)行文件的格式,以及鏈接生成可執(zhí)行文件時(shí)的后臺(tái)細(xì)節(jié)(即最終到底有哪些內(nèi)容被鏈接到了目標(biāo)代碼中)。通過(guò)選擇合適的可執(zhí)行文件格式并剔除對(duì)可執(zhí)行文件的最終運(yùn)行沒(méi)有影響的內(nèi)容,就可以實(shí)現(xiàn)目標(biāo)代碼的裁減。因此,通過(guò)探索減少可執(zhí)行文件大小的方法,就相當(dāng)于實(shí)踐性地去探索了可執(zhí)行文件的格式以及鏈接過(guò)程的細(xì)節(jié)。

當(dāng)然,算法的優(yōu)化和編程語(yǔ)言的選擇可能對(duì)目標(biāo)文件的大小有很大的影響,在本文最后我們會(huì)探求一個(gè)打印 “Hello World” 的可執(zhí)行文件能夠小到什么樣的地步。

2. 可執(zhí)行文件格式的選取

可執(zhí)行文件格式的選擇要滿足的一個(gè)基本條件是:目標(biāo)系統(tǒng)支持該可執(zhí)行文件格式,UNIX 平臺(tái)下有三種可執(zhí)行文件格式,這三種格式實(shí)際上代表著可執(zhí)行文件的一個(gè)發(fā)展過(guò)程:

  • a.out

    非常緊湊,只包含了程序運(yùn)行所必須的信息(文本、數(shù)據(jù)、BSS),而且每個(gè) section 的順序是固定的。

  • coff

    雖然引入了一個(gè)節(jié)區(qū)表以支持更多節(jié)區(qū)信息,從而提高了可擴(kuò)展性,但是這種文件格式的重定位在鏈接時(shí)就已經(jīng)完成,因此不支持動(dòng)態(tài)鏈接(不過(guò)擴(kuò)展的coff支持)。

  • elf

    不僅支持動(dòng)態(tài)鏈接,而且有很好的擴(kuò)展性。它可以描述可重定位文件、可執(zhí)行文件和可共享文件(動(dòng)態(tài)鏈接庫(kù))三類文件。

下面來(lái)看看 ELF 文件的結(jié)構(gòu)圖:

1文件頭部(ELF Header)
2程序頭部表(Program Header Table)
3節(jié)區(qū)1(Section1)
4節(jié)區(qū)2(Section2)
5節(jié)區(qū)3(Section3)
6...
7節(jié)區(qū)頭部(Section Header Table)

無(wú)論是文件頭部、程序頭部表、節(jié)區(qū)頭部表還是各個(gè)節(jié)區(qū),都是通過(guò)特定的結(jié)構(gòu)體(struct) 描述的,這些結(jié)構(gòu)在 elf.h 文件中定義。文件頭部用于描述整個(gè)文件的類型、大小、運(yùn)行平臺(tái)、程序入口、程序頭部表和節(jié)區(qū)頭部表等信息。例如,我們可以通過(guò)文件頭部查看該 ELF 文件的類型。

 1$ cat hello.c   #典型的hello, world程序
2#include <stdio.h>
3
4int main(void)
5{
6        printf('hello, world!n');
7        return 0;
8}
9$ gcc -c hello.c   #編譯,產(chǎn)生可重定向的目標(biāo)代碼
10$ readelf -h hello.o | grep Type   #通過(guò)readelf查看文件頭部找出該類型
11  Type:                              REL (Relocatable file)
12$ gcc -o hello hello.o   #生成可執(zhí)行文件
13$ readelf -h hello | grep Type
14  Type:                              EXEC (Executable file)
15$ gcc -fpic -shared -W1,-soname,libhello.so.0 -o libhello.so.0.0 hello.o  #生成共享庫(kù)
16$ readelf -h libhello.so.0.0 | grep Type
17  Type:                              DYN (Shared object file)

那節(jié)區(qū)頭部表(將簡(jiǎn)稱節(jié)區(qū)表)和程序頭部表有什么用呢?實(shí)際上前者只對(duì)可重定向文件有用,而后者只對(duì)可執(zhí)行文件和可共享文件有用。

節(jié)區(qū)表是用來(lái)描述各節(jié)區(qū)的,包括各節(jié)區(qū)的名字、大小、類型、虛擬內(nèi)存中的位置、相對(duì)文件頭的位置等,這樣所有節(jié)區(qū)都通過(guò)節(jié)區(qū)表給描述了,這樣連接器就可以根據(jù)文件頭部表和節(jié)區(qū)表的描述信息對(duì)各種輸入的可重定位文件進(jìn)行合適的鏈接,包括節(jié)區(qū)的合并與重組、符號(hào)的重定位(確認(rèn)符號(hào)在虛擬內(nèi)存中的地址)等,把各個(gè)可重定向輸入文件鏈接成一個(gè)可執(zhí)行文件(或者是可共享文件)。如果可執(zhí)行文件中使用了動(dòng)態(tài)連接庫(kù),那么將包含一些用于動(dòng)態(tài)符號(hào)鏈接的節(jié)區(qū)。我們可以通過(guò) readelf -S(或objdump -h)查看節(jié)區(qū)表信息。

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

三種類型文件的節(jié)區(qū)可能不一樣,但是有幾個(gè)節(jié)區(qū),例如 .text, .data, .bss 是必須的,特別是 .text,因?yàn)檫@個(gè)節(jié)區(qū)包含了代碼。如果一個(gè)程序使用了動(dòng)態(tài)鏈接庫(kù)(引用了動(dòng)態(tài)連接庫(kù)中的某個(gè)函數(shù)),那么需要 .interp 節(jié)區(qū)以便告知系統(tǒng)使用什么動(dòng)態(tài)連接器程序來(lái)進(jìn)行動(dòng)態(tài)符號(hào)鏈接,進(jìn)行某些符號(hào)地址的重定位。通常,.rel.text 節(jié)區(qū)只有可重定向文件有,用于鏈接時(shí)對(duì)代碼區(qū)進(jìn)行重定向,而 .hash, .plt, .got 等節(jié)區(qū)則只有可執(zhí)行文件(或可共享庫(kù))有,這些節(jié)區(qū)對(duì)程序的運(yùn)行特別重要。還有一些節(jié)區(qū),可能僅僅是用于注釋,比如 .comment,這些對(duì)程序的運(yùn)行似乎沒(méi)有影響,是可有可無(wú)的,不過(guò)有些節(jié)區(qū)雖然對(duì)程序的運(yùn)行沒(méi)有用處,但是卻可以用來(lái)輔助對(duì)程序進(jìn)行調(diào)試或者對(duì)程序運(yùn)行效率有影響。

雖然三類文件都必須包含某些節(jié)區(qū),但是節(jié)區(qū)表對(duì)可重定位文件來(lái)說(shuō)才是必須的,而程序的執(zhí)行卻不需要節(jié)區(qū)表,只需要程序頭部表以便知道如何加載和執(zhí)行文件。不過(guò)如果需要對(duì)可執(zhí)行文件或者動(dòng)態(tài)連接庫(kù)進(jìn)行調(diào)試,那么節(jié)區(qū)表卻是必要的,否則調(diào)試器將不知道如何工作。下面來(lái)介紹程序頭部表,它可通過(guò) readelf -l(或 objdump -p)查看。

 1$ readelf -l hello.o #對(duì)于可重定向文件,gcc沒(méi)有產(chǎn)生程序頭部,因?yàn)樗鼘?duì)可重定向文件沒(méi)用
2
3There are no program headers in this file.
4$  readelf -l hello  #而可執(zhí)行文件和可共享文件都有程序頭部
5...
6Program Headers:
7  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
8  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
9  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
10      [Requesting program interpreter: /lib/ld-linux.so.2]
11  LOAD           0x000000 0x08048000 0x08048000 0x00470 0x00470 R E 0x1000
12  LOAD           0x000470 0x08049470 0x08049470 0x0010c 0x00110 RW  0x1000
13  DYNAMIC        0x000484 0x08049484 0x08049484 0x000d0 0x000d0 RW  0x4
14  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
15  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
16
17 Section to Segment mapping:
18  Segment Sections...
19   00
20   01     .interp
21   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
22   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
23   04     .dynamic
24   05     .note.ABI-tag
25   06
26$ readelf -l libhello.so.0.0  #節(jié)區(qū)和上面類似,這里省略

從上面可看出程序頭部表描述了一些段(Segment),這些段對(duì)應(yīng)著一個(gè)或者多個(gè)節(jié)區(qū),上面的 readelf -l 很好地顯示了各個(gè)段與節(jié)區(qū)的映射。這些段描述了段的名字、類型、大小、第一個(gè)字節(jié)在文件中的位置、將占用的虛擬內(nèi)存大小、在虛擬內(nèi)存中的位置等。這樣系統(tǒng)程序解釋器將知道如何把可執(zhí)行文件加載到內(nèi)存中以及進(jìn)行動(dòng)態(tài)鏈接等動(dòng)作。

該可執(zhí)行文件包含7個(gè)段,PHDR 指程序頭部,INTERP 正好對(duì)應(yīng) .interp 節(jié)區(qū),兩個(gè) LOAD 段包含程序的代碼和數(shù)據(jù)部分,分別包含有 .text.data,.bss 節(jié)區(qū),DYNAMIC 段包含 .daynamic,這個(gè)節(jié)區(qū)可能包含動(dòng)態(tài)連接庫(kù)的搜索路徑、可重定位表的地址等信息,它們用于動(dòng)態(tài)連接器。NOTE 和 GNU_STACK 段貌似作用不大,只是保存了一些輔助信息。因此,對(duì)于一個(gè)不使用動(dòng)態(tài)連接庫(kù)的程序來(lái)說(shuō),可能只包含 LOAD 段,如果一個(gè)程序沒(méi)有數(shù)據(jù),那么只有一個(gè) LOAD 段就可以了。

總結(jié)一下,Linux 雖然支持很多種可執(zhí)行文件格式,但是目前 ELF 較通用,所以選擇 ELF 作為我們的討論對(duì)象。通過(guò)上面對(duì) ELF 文件分析發(fā)現(xiàn)一個(gè)可執(zhí)行的文件可能包含一些對(duì)它的運(yùn)行沒(méi)用的信息,比如節(jié)區(qū)表、一些用于調(diào)試、注釋的節(jié)區(qū)。如果能夠刪除這些信息就可以減少可執(zhí)行文件的大小,而且不會(huì)影響可執(zhí)行文件的正常運(yùn)行。

3. 鏈接優(yōu)化

從上面的討論中已經(jīng)接觸了動(dòng)態(tài)連接庫(kù)。ELF 中引入動(dòng)態(tài)連接庫(kù)后極大地方便了公共函數(shù)的共享,節(jié)約了磁盤(pán)和內(nèi)存空間,因?yàn)椴辉傩枰涯切┕埠瘮?shù)的代碼鏈接到可執(zhí)行文件,這將減少了可執(zhí)行文件的大小。

與此同時(shí),靜態(tài)鏈接可能會(huì)引入一些對(duì)代碼的運(yùn)行可能并非必須的內(nèi)容。你可以從《GCC編譯的背后(第二部分:匯編和鏈接)》 了解到 GCC 鏈接的細(xì)節(jié)。從那篇 Blog中似乎可以得出這樣的結(jié)論:僅僅從是否影響一個(gè) C 語(yǔ)言程序運(yùn)行的角度上說(shuō),GC C默認(rèn)鏈接到可執(zhí)行文件的幾個(gè)可重定位文件(crt1.o, rti.o, crtbegin.o, crtend.o, crtn.o)并不是必須的,不過(guò)值得注意的是,如果沒(méi)有鏈接那些文件但在程序末尾使用了 return 語(yǔ)句,main 函數(shù)將無(wú)法返回,因此需要替換為 _exit 調(diào)用;另外,既然程序在進(jìn)入 main 之前有一個(gè)入口,那么 main 入口就不是必須的。因此,如果不采用默認(rèn)鏈接也可以減少可執(zhí)行文件的大小。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    久久机热频这里只精品| 亚洲一区二区三区在线中文字幕| 初尝人妻少妇中文字幕在线| 国产传媒一区二区三区| 国产不卡视频一区在线| 国产精品欧美一区二区三区| 国产自拍欧美日韩在线观看| 亚洲av秘片一区二区三区| 丝袜av一区二区三区四区五区| 国产日韩熟女中文字幕| 国产美女精品午夜福利视频| 国产亚洲二区精品美女久久| 91偷拍裸体一区二区三区| 日韩在线精品视频观看| 欧美中文字幕日韩精品| 中文字幕区自拍偷拍区| av在线免费观看一区二区三区| 精品少妇一区二区三区四区| 激情内射日本一区二区三区| 日韩无套内射免费精品| 伊人国产精选免费观看在线视频| 中国黄色色片色哟哟哟哟哟哟| 一个人的久久精彩视频| 中国美女草逼一级黄片视频| 一区二区三区欧美高清| 欧美日韩国产黑人一区| 国产一级二级三级观看| 在线免费不卡亚洲国产| 国产精品人妻熟女毛片av久| 亚洲最新中文字幕在线视频| 国产又粗又猛又长又黄视频| 二区久久久国产av色| 国产精品午夜小视频观看| 亚洲熟妇中文字幕五十路| 大屁股肥臀熟女一区二区视频| 午夜精品国产一区在线观看| 偷自拍亚洲欧美一区二页| 亚洲欧美中文日韩综合| 国产不卡在线免费观看视频| 国产乱人伦精品一区二区三区四区| 欧美黄色黑人一区二区|