溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何掌握二進(jìn)制文件

發(fā)布時(shí)間:2021-10-21 14:12:37 來源:億速云 閱讀:126 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“如何掌握二進(jìn)制文件”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

在講述進(jìn)程之前,先來了解一下二進(jìn)制可執(zhí)行文件和目標(biāo)文件。我們知道程序運(yùn)行起來后就有了進(jìn)程,所以了解程序的結(jié)構(gòu)對(duì)于認(rèn)識(shí)操作系統(tǒng)大有好處。先拋三個(gè)問題:編譯器編譯代碼后生成的文件是目標(biāo)文件,目標(biāo)文件里面是什么?可執(zhí)行文件里面又是什么?可執(zhí)行文件與目標(biāo)文件的區(qū)別在哪呢?

這篇文件會(huì)使用如下一個(gè)簡(jiǎn)單的 C 文件進(jìn)行貫穿講解:

#include <stdio.h>

int main(){
    printf("hello");
    return 0;
}

1.編譯過程

代碼要想成為可執(zhí)行文件就需要經(jīng)過編譯。編譯過程分為預(yù)處理、編譯、匯編、鏈接。

如何掌握二進(jìn)制文件

預(yù)處理:主要處理源碼中的預(yù)處理指令,在 C/C++ 中主要指的是“#include”、“#define” 等,它的處理方法是將這些指令所在的位置進(jìn)行內(nèi)容替換,比如 “#include” 就會(huì)把整個(gè)頭文件給引進(jìn)來。

編譯:把上一步預(yù)處理過的文件進(jìn)行詞法分析、語法分析、語義分析&優(yōu)化一系列步驟后生成匯編代碼。這是整個(gè)編譯過程中最難最復(fù)雜的 部分。在現(xiàn)在版本的 GCC 中,預(yù)處理和編譯這兩個(gè)過程已經(jīng)合并為一步。

匯編:把匯編代碼轉(zhuǎn)變?yōu)闄C(jī)器可以執(zhí)行的指令,這是一個(gè)翻譯的過程,有個(gè)匯編指令與機(jī)器指令的對(duì)照表進(jìn)行一一對(duì)應(yīng)。

鏈接:把每個(gè)獨(dú)立編譯的模塊進(jìn)行組裝,這些模塊之間會(huì)有相互引用,鏈接就是讓這些獨(dú)立模塊能夠相互正確引用,形成一個(gè)完整的可執(zhí)行文件。鏈接分為動(dòng)態(tài)鏈接和靜態(tài)鏈接,這兩個(gè)會(huì)在下篇文章進(jìn)行講解。

使用以上那個(gè) C 文件進(jìn)行整個(gè)編譯過程如下:

預(yù)處理:gcc -E hello.c -o hello.i 
編譯:gcc -S hello.i -o hello.s
匯編:gcc -c hello.s -o hello.o
鏈接:ld -static /usr/lib64/crt1.o /usr/lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbeginT.o  -L /usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L /usr/lib -L hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib64/crtn.o

不想這么麻煩的話,直接一行代碼搞定:

gcc hello.c -o hello

2.什么是可執(zhí)行文件

可執(zhí)行文件指的是可以由操作系統(tǒng)進(jìn)行加載執(zhí)行的文件。在不同的系統(tǒng)中,可執(zhí)行文件是不同的。比如在 windows 中可執(zhí)行文件是以 exe 后綴的文件,而在 linux 中可執(zhí)行文件可以是任何后綴的文件,只是需要給文件添加“可執(zhí)行”權(quán)限。注意,在同一種操作系統(tǒng)中如果 CPU 的架構(gòu)不同的話,可執(zhí)行文件是不能通用的,比如:在 linux 中,ARM 架構(gòu)上的可執(zhí)行文件就不能直接在 X86 架構(gòu)執(zhí)行,因?yàn)榭蓤?zhí)行文件內(nèi)通常含有二進(jìn)制編碼的 CPU 指令,而每種 CPU 的指令集都是不一樣的。這種情況下通常需要進(jìn)行交叉編譯:在一個(gè)平臺(tái)上生成另一個(gè)平臺(tái)的可執(zhí)行文件,比如在 X86 平臺(tái)上生成 ARM 的可執(zhí)行文件。

3.可執(zhí)行文件類型

在 windows 系統(tǒng)中可執(zhí)行文件類型為 PE(Portable Executable),在 Linux 系統(tǒng)中可執(zhí)行文件類型為 ELF(Executable Linkable Format)。

在 linux 下可以使用 file 命令查看文件的文件格式。 普通的源碼文件顯示是:

[root@centos7-dev hello]# file hello.c
hello.c: C source, ASCII text

目標(biāo)文件是:

[root@centos7-dev hello]# file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

可執(zhí)行文件是:

[root@centos7-dev hello]# file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=6115b831b9be5d023a87ce84ecd72d44cbfa1548, not stripped

不是只有可執(zhí)行文件可以按照上述這兩種類型進(jìn)行存儲(chǔ),目標(biāo)文件(.obj/.o)可以,鏈接庫(.dll/.so)也可以,甚至在 linux 下的 coredump 也是可執(zhí)行文件格式。目標(biāo)文件既然和可執(zhí)行文件的存儲(chǔ)結(jié)構(gòu)一致,那兩者有什么差異呢?一句話:目標(biāo)文件是尚未進(jìn)行鏈接操作的可執(zhí)行文件。

4.關(guān)于鏈接

為什么會(huì)有鏈接這個(gè)動(dòng)作呢?

先把時(shí)間拉回到打孔條帶的時(shí)代,在計(jì)算機(jī)剛出來的時(shí)候,程序都是寫在條帶上。最開始的程序是一條紙帶,很簡(jiǎn)單很完整。然后隨著時(shí)間的推移,程序越來越多了,條帶也是越來越多。在某個(gè)時(shí)刻有個(gè)程序員想偷懶,他正在寫的 A 程序想使用之前寫好的 B 程序的一個(gè)功能,所以他把 B 條帶上的那段代碼給剪出來然后拼接上了 A 程序。這是最早的鏈接,應(yīng)該也是最早的靜態(tài)鏈接。

在現(xiàn)代的軟件開發(fā)過程中,程序文件的數(shù)量是非常龐大的,往往一個(gè)工程就有上千個(gè)模塊和文件。這些模塊和文件是相互獨(dú)立和依賴的,相互調(diào)用的情況是很常見的。你想想,這樣一個(gè)龐大的項(xiàng)目要編譯出一個(gè)可執(zhí)行文件應(yīng)該怎么做?先對(duì)每個(gè)代碼文件進(jìn)行單獨(dú)編譯得到目標(biāo)文件,然后把這些目標(biāo)文件給捏到一塊去,形成一個(gè)可執(zhí)行文件。而這個(gè)捏到一塊去的過程就是鏈接。

如果沒有這個(gè)鏈接過程,上千模塊的目標(biāo)文件根本沒法正常工作,因?yàn)樗鼈儫o法執(zhí)行。那有人說了:整個(gè)項(xiàng)目工程可以只寫一個(gè)文件呀,只對(duì)這個(gè)文件進(jìn)行編譯就可以不用鏈接了。沒錯(cuò),但那就沒法比較好的進(jìn)行多人協(xié)作開發(fā)了。

5.ELF 結(jié)構(gòu)

由于對(duì) windows 系統(tǒng)的可執(zhí)行文件結(jié)構(gòu)不熟,所以這里拿 linux 系統(tǒng)下的可執(zhí)行文件結(jié)構(gòu) ELF 進(jìn)行分析一下。ELF 里面包含了編譯后的機(jī)器指令和數(shù)據(jù)、符號(hào)表、調(diào)試信息、字符串等,這些不同類型的信息都會(huì)單獨(dú)分開存放在某個(gè)模塊內(nèi),一般稱呼這些模塊為”段“。

首先我們得先了解 ELF 都有哪些段,請(qǐng)看如下資料:

.bss
構(gòu)成程序的內(nèi)存映像的未初始化數(shù)據(jù)。根據(jù)定義,系統(tǒng)在程序開始運(yùn)行時(shí)會(huì)將數(shù)據(jù)初始化為零。如節(jié)類型 SHT_NOBITS 所指明的那樣,此節(jié)不會(huì)占用任何文件空間。

.comment
注釋信息,通常由編譯系統(tǒng)的組件提供。

.data、.data1
構(gòu)成程序的內(nèi)存映像的已初始化數(shù)據(jù)。

.dynamic
動(dòng)態(tài)鏈接信息。

.dynstr
進(jìn)行動(dòng)態(tài)鏈接所需的字符串,通常是表示與符號(hào)表各項(xiàng)關(guān)聯(lián)的名稱的字符串。

.dynsym
動(dòng)態(tài)鏈接符號(hào)表。

.eh_frame_hdr、.eh_frame
用于展開棧的調(diào)用幀信息。

.fini
可執(zhí)行指令,用于構(gòu)成包含此節(jié)的可執(zhí)行文件或共享目標(biāo)文件的單個(gè)終止函數(shù)。

.fini_array
函數(shù)指針數(shù)組,用于構(gòu)成包含此節(jié)的可執(zhí)行文件或共享目標(biāo)文件的單個(gè)終止數(shù)組。

.got
全局偏移表。

.hash
符號(hào)散列表。

.init
可執(zhí)行指令,用于構(gòu)成包含此節(jié)的可執(zhí)行文件或共享目標(biāo)文件的單個(gè)初始化函數(shù)。

.init_array
函數(shù)指針數(shù)組,用于構(gòu)成包含此節(jié)的可執(zhí)行文件或共享目標(biāo)文件的單個(gè)初始化數(shù)組。

.interp
程序的解釋程序的路徑名。

.lbss
特定于 x64 的未初始化的數(shù)據(jù)。此數(shù)據(jù)與 .bss 類似,但用于大小超過 2 GB 的節(jié)。

.ldata、.ldata1
特定于 x64 的已初始化數(shù)據(jù)。此數(shù)據(jù)與 .data 類似,但用于大小超過 2 GB 的節(jié)。

.lrodata、.lrodata1
特定于 x64 的只讀數(shù)據(jù)。此數(shù)據(jù)與 .rodata 類似,但用于大小超過 2 GB 的節(jié)。

.note
注釋節(jié)中說明了該格式的信息。

.plt
過程鏈接表。

.preinit_array
函數(shù)指針數(shù)組,用于構(gòu)成包含此節(jié)的可執(zhí)行文件或共享目標(biāo)文件的單個(gè)預(yù)初始化數(shù)組。

.rela
不適用于特定節(jié)的重定位。此節(jié)的用途之一是用于寄存器重定位。

.relname、.relaname
重定位信息,如重定位節(jié)中所述。如果文件具有包括重定位的可裝入段,則此節(jié)的屬性將包括 SHF_ALLOC 位。否則,該位會(huì)處于禁用狀態(tài)。通常,name 由應(yīng)用重定位的節(jié)提供。因此,.text 的重定位節(jié)的名稱通常為 .rel.text 或 .rela.text。

.rodata、.rodata1
通常構(gòu)成進(jìn)程映像中的非可寫段的只讀數(shù)據(jù)。

.shstrtab
節(jié)名稱。

.strtab
字符串,通常是表示與符號(hào)表各項(xiàng)關(guān)聯(lián)的名稱的字符串。如果文件具有包括符號(hào)字符串表的可裝入段,則此節(jié)的屬性將包括 SHF_ALLOC 位。否則,該位會(huì)處于禁用狀態(tài)。

.symtab
符號(hào)表,如符號(hào)表節(jié)中所述。如果文件具有包括符號(hào)表的可裝入段,則此節(jié)的屬性將包括 SHF_ALLOC 位。否則,該位會(huì)處于禁用狀態(tài)。

.symtab_shndx
此節(jié)包含特殊符號(hào)表的節(jié)索引數(shù)組,如 .symtab 所述。如果關(guān)聯(lián)的符號(hào)表節(jié)包括 SHF_ALLOC 位,則此節(jié)的屬性也將包括該位。否則,該位會(huì)處于禁用狀態(tài)。

.tbss
此節(jié)包含構(gòu)成程序的內(nèi)存映像的未初始化線程局部數(shù)據(jù)。根據(jù)定義,為每個(gè)新執(zhí)行流實(shí)例化數(shù)據(jù)時(shí),系統(tǒng)都會(huì)將數(shù)據(jù)初始化為零。如節(jié)類型 SHT_NOBITS 所指明的那樣,此節(jié)不會(huì)占用任何文件空間。

.tdata、.tdata1
這些節(jié)包含已初始化的線程局部數(shù)據(jù),這些數(shù)據(jù)構(gòu)成程序的內(nèi)存映像。對(duì)于每個(gè)新執(zhí)行流,系統(tǒng)會(huì)對(duì)其內(nèi)容的副本進(jìn)行實(shí)例化。

.text
程序的文本或可執(zhí)行指令。

有了以上資料,我們可以使用 objdump 命令查看 hello 可執(zhí)行文件中都有哪些模塊:

[root@centos7-dev hello]# objdump -h hello

hello:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     0000001c  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000060  00000000004002b8  00000000004002b8  000002b8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       0000003f  0000000000400318  0000000000400318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000008  0000000000400358  0000000000400358  00000358  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000020  0000000000400360  0000000000400360  00000360  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     00000018  0000000000400380  0000000000400380  00000380  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt     00000048  0000000000400398  0000000000400398  00000398  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         0000001a  00000000004003e0  00000000004003e0  000003e0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000040  0000000000400400  0000000000400400  00000400  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         00000182  0000000000400440  0000000000400440  00000440  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .fini         00000009  00000000004005c4  00000000004005c4  000005c4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata       00000016  00000000004005d0  00000000004005d0  000005d0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame_hdr 00000034  00000000004005e8  00000000004005e8  000005e8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame     000000f4  0000000000400620  0000000000400620  00000620  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .init_array   00000008  0000000000600e10  0000000000600e10  00000e10  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 18 .fini_array   00000008  0000000000600e18  0000000000600e18  00000e18  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 19 .jcr          00000008  0000000000600e20  0000000000600e20  00000e20  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      000001d0  0000000000600e28  0000000000600e28  00000e28  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000008  0000000000600ff8  0000000000600ff8  00000ff8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000030  0000000000601000  0000000000601000  00001000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .data         00000004  0000000000601030  0000000000601030  00001030  2**0
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000004  0000000000601034  0000000000601034  00001034  2**0
                  ALLOC
 25 .comment      0000005a  0000000000000000  0000000000000000  00001034  2**0
                  CONTENTS, READONLY

然后再用同樣的命令來查看目標(biāo)文件:

[root@centos7-dev hello]# objdump -h hello.o

hello.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000001a  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  0000005a  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  0000005a  2**0
                  ALLOC
  3 .rodata       00000006  0000000000000000  0000000000000000  0000005a  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002e  0000000000000000  0000000000000000  00000060  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  0000008e  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000038  0000000000000000  0000000000000000  00000090  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

可以發(fā)現(xiàn)目標(biāo)文件內(nèi)部的模塊信息比可執(zhí)行文件少很多,這是因?yàn)樵谀繕?biāo)文件經(jīng)過鏈接過程變成可執(zhí)行文件的過程中進(jìn)行很多處理,比如:地址和空間分配、重定位、符號(hào)決議等。

我們還可以用 objdump 來查看每個(gè)模塊的代碼信息:

objdump -s -d hello

查看符號(hào)表:

objdump -x hello

更多關(guān)于 objdump 命令的使用可以 man objdump 查看。

“如何掌握二進(jìn)制文件”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI