0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

什么是ELF文件?ELF文件結(jié)構(gòu)概覽

Linux閱碼場(chǎng) ? 來(lái)源:Linux閱碼場(chǎng) ? 作者:廖威雄 ? 2022-10-20 08:59 ? 次閱讀

1. 背景

長(zhǎng)期以來(lái)只知道 ELF 是一種廣泛使用的文件格式規(guī)范,常指動(dòng)態(tài)庫(kù)、bin等,一直沒(méi)動(dòng)力深入研究。出于業(yè)務(wù)需求,我花了好些天仔細(xì)分析 RTOS Bin 的 Section 和 Symbol 。也是趁著這機(jī)會(huì),查閱大量資料,完善了知識(shí)脈絡(luò)。

但是可以預(yù)見(jiàn),業(yè)務(wù)結(jié)束一段時(shí)間后,由于不常用,我不可避免會(huì)漸漸遺忘。從入門(mén)到遺忘,相信只要經(jīng)歷過(guò)深度學(xué)習(xí)的小伙伴都會(huì)有的痛苦掙扎。為了在將來(lái)需要時(shí)喚醒記憶,我決定通過(guò)這文章打下錨點(diǎn),也希望此文能幫到更多人更快入門(mén)。

2. 基礎(chǔ)概念

2.1 什么是 ELF 文件

ELF 的全稱(chēng)是 Executable and Linking Format,即“可執(zhí)行可連接格式”,通俗來(lái)說(shuō),就是二進(jìn)制程序。ELF 規(guī)定了這二進(jìn)制程序的組織規(guī)范,所有以這規(guī)范組織的文件都叫 ELF 文件。ELF 文件有以下四類(lèi)。

ELF 文件類(lèi)型 示例
可重定位文件(relocatable file) 例如編譯的過(guò)程文件 ".o"
共享目標(biāo)文件(shared object file) 例如 ".so" 庫(kù),以及使用動(dòng)態(tài)鏈接的 bin
可執(zhí)行文件(executable file) 例如靜態(tài)鏈接的文件
core 文件 例如 Linux 上的 coredump 文件

我們通過(guò) file 命令可以識(shí)別出來(lái)

#test.o:gcc-ctest.c-otest.o
#test-static-link.bin:gcc--statictest.c-otest
#test-dynamic-link.bin:gcctest.c-otest

$filetest.otest-static-link.bintest-dynamic-link.bin
test.o:ELF...relocatable,...
test-dynamic-link.bin:ELF...sharedobject,...
test-static-link.bin:ELF...executable,...

除此之外,我們習(xí)慣上叫 ".o" 文件為 目標(biāo)文件(object file),鏈接好的可執(zhí)行文件叫 bin文件

2.2 ELF 文件結(jié)構(gòu)概覽

ELF 文件主要的用途有兩個(gè),

構(gòu)建程序,鏈接成動(dòng)態(tài)庫(kù)或者bin,一般是目標(biāo)文件 ".o"

運(yùn)行程序,一般指鏈接好的 ".so" 或者 "bin"

這個(gè) ELF 文件用作不同用途,文件結(jié)構(gòu)的解析角度就有點(diǎn)不一樣,通俗來(lái)說(shuō),不同用途對(duì)需要哪些數(shù)據(jù)的要求不一樣,例如構(gòu)建(鏈接)時(shí) 節(jié)表頭(Section Table Header)是必須的,但運(yùn)行時(shí)卻是可選的,例如運(yùn)行需要段信息,而鏈接只有節(jié)信息。

c5476f66-5010-11ed-a3b6-dac502259ad0.png

在上圖中,程序頭表(Program Header Table)緊跟在 ELF 文件頭(File Header)之后,節(jié)頭表(Section Header Table)緊跟在節(jié)信息之后,但在實(shí)際的文件中,這個(gè)順序并不是固定的。在 ELF 文件的各個(gè)組成部分中,只有ELF 文件頭的位置是固定的,其它內(nèi)容的位置全都可變。

這里有一個(gè)潛在的概念,很少看到其他文章明確點(diǎn)出來(lái)。Section Header Table 描述了所有節(jié)信息表,而 Program Header Table 其實(shí)描述的是所有的 段信息表

下一章節(jié)會(huì)介紹一些關(guān)鍵字段的含義。在這里引入這張圖,主要為了引出兩個(gè)重要的概念 節(jié)(Seciton)段(Segment)。由上圖可以發(fā)現(xiàn),鏈接視圖有大量的 節(jié)(Section),而執(zhí)行視圖有 段(Segment)。那么什么是段,什么又是節(jié)?

2.3 節(jié)(Section) Vs. 段(Segment)

或許我們聽(tīng)說(shuō)過(guò) bss段,text(代碼)段,或者 data(數(shù)據(jù))段,但其實(shí)我們口頭交流的段更多是泛化的 。為什么呢?

我們隨便拿個(gè) bin,不妨羅列下 程序頭表(Program Header Table):

$readelf-l/bin/ls

ElffiletypeisDYN(Sharedobjectfile)
Entrypoint0x5850
Thereare9programheaders,startingatoffset64

ProgramHeaders:
......

SectiontoSegmentmapping:
......

Program Headers 下面羅列出了所有的段信息,詳細(xì)如下圖所示,共計(jì)有 9 個(gè)段。

ProgramHeaders:
TypeOffsetVirtAddrPhysAddr
FileSizMemSizFlagsAlign
PHDR0x00000000000000400x00000000000000400x0000000000000040
0x00000000000001f80x00000000000001f8RE0x8
INTERP0x00000000000002380x00000000000002380x0000000000000238
0x000000000000001c0x000000000000001cR0x1
[Requestingprograminterpreter:/lib64/ld-linux-x86-64.so.2]
LOAD0x00000000000000000x00000000000000000x0000000000000000
0x000000000001e6e80x000000000001e6e8RE0x200000
LOAD0x000000000001eff00x000000000021eff00x000000000021eff0
0x00000000000012780x0000000000002570RW0x200000
DYNAMIC0x000000000001fa380x000000000021fa380x000000000021fa38
0x00000000000002000x0000000000000200RW0x8
NOTE0x00000000000002540x00000000000002540x0000000000000254
0x00000000000000440x0000000000000044R0x4
GNU_EH_FRAME0x000000000001b1a00x000000000001b1a00x000000000001b1a0
0x00000000000008840x0000000000000884R0x4
GNU_STACK0x00000000000000000x00000000000000000x0000000000000000
0x00000000000000000x0000000000000000RW0x10
GNU_RELRO0x000000000001eff00x000000000021eff00x000000000021eff0
0x00000000000010100x0000000000001010R0x1

Section to Segment mapping 羅列了 各個(gè)段(Segment)包含了哪些節(jié)(Section),是的,段是1個(gè)或者多個(gè)節(jié)的集合,詳細(xì)如下文所示。

SectiontoSegmentmapping:
SegmentSections...
00
01.interp
02.interp.note.ABI-tag.note.gnu.build-id.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.plt.got.text.fini.rodata.eh_frame_hdr.eh_frame
03.init_array.fini_array.data.rel.ro.dynamic.got.data.bss
04.dynamic
05.note.ABI-tag.note.gnu.build-id
06.eh_frame_hdr
07
08.init_array.fini_array.data.rel.ro.dynamic.got

總的來(lái)說(shuō),段是沒(méi)有名字的,但我們往往把包含 text節(jié) 的段叫做 代碼(text)段,把含有 data節(jié) 的段叫做 數(shù)據(jù)(data)段。一定程度上在口述時(shí)習(xí)慣也可以理解為 "段 = 節(jié)",例如 bss段,rodata段,實(shí)際他們指 bss節(jié),rodata節(jié)。甚至有時(shí)候我們說(shuō) text段 就狹義的指 text節(jié),完全不用太糾結(jié)。

3. 關(guān)鍵的文件結(jié)構(gòu)和節(jié)

3.1 一些關(guān)鍵的文件結(jié)構(gòu)

對(duì)一個(gè) ELF 格式的文件,有這么幾個(gè)特殊的結(jié)構(gòu)我們需要關(guān)注的。

ELF 文件頭(File Header):位于文件最開(kāi)始,包含了整個(gè)文件的結(jié)構(gòu)信息,例如是ELF 幻數(shù),是哪種 ELF 文件,程序頭表、節(jié)頭表的地址等。

程序頭表(Program Header Table):描述了所有段的信息

節(jié)頭表(Section Header Table):描述了所有節(jié)的信息

本文不會(huì)解釋結(jié)構(gòu)體每個(gè)元素,而是利用 readelf 工具解讀。如果需要詳細(xì)到每個(gè)字節(jié)的意義,可以查閱章節(jié)1提到的《UnderstandingELF.pdf》

3.1.1 文件頭(File Header)

readelf -h 命令可以查看文件頭信息,例如:

readelf-h/bin/ls
ELFHeader:
Magic:7f454c46020101000000000000000000
Class:ELF64
Data:2'scomplement,littleendian
Version:1(current)
OS/ABI:UNIX-SystemV
ABIVersion:0
Type:DYN(Sharedobjectfile)
Machine:AdvancedMicroDevicesX86-64
Version:0x1
Entrypointaddress:0x5850
Startofprogramheaders:64(bytesintofile)
Startofsectionheaders:132000(bytesintofile)
Flags:0x0
Sizeofthisheader:64(bytes)
Sizeofprogramheaders:56(bytes)
Numberofprogramheaders:9
Sizeofsectionheaders:64(bytes)
Numberofsectionheaders:28
Sectionheaderstringtableindex:27

解讀如下:

字段 含義 備注
Magic 標(biāo)識(shí)是 ELF 文件類(lèi)型的幻數(shù) 值只能為 0x7F + 'E' + 'L' + 'F'
Class 32Bit/64Bit 類(lèi)型 ELF64 / ELF32
Data 小端/大端編碼
Version 文件頭版本
OS/ABI 適用的系統(tǒng) 和 ABI
Type 哪種類(lèi)型的 ELF 文件 DYN:共享目標(biāo)文件,REL:可重定位文件,EXEC:可執(zhí)行文件 [1]
Machine 處理器體系架構(gòu) 常見(jiàn)例如 ARM,X86-64
Version ELF文件的版本
Entry point address 指代程序入口的虛擬地址 一般是 _start(),一系列調(diào)用后才到 main()
Start of program headers 程序頭表在文件的偏移 單位 Byte
Start of section headers 節(jié)頭表在文件的偏移 單位 Byte
Flags 處理器特定的標(biāo)志位
Size of this header ELF 文件頭的大小 單位 Byte
Size of program headers 程序頭表中每一個(gè)表項(xiàng)的大小 單位 Byte
Number of program headers 程序頭表中表項(xiàng)的數(shù)量,理解為有多少個(gè)段
Size of section headers 節(jié)頭表中每個(gè)表項(xiàng)的大小 單位 Byte
Number of section headers 節(jié)頭表中有表項(xiàng)的數(shù)量,理解為有多少個(gè)節(jié)
Section header string table index 節(jié)頭表中與節(jié)名字表相對(duì)應(yīng)的表項(xiàng)索引信息 [2]

[1]. 文件類(lèi)型見(jiàn) 2.1 章節(jié)
[2]. 段沒(méi)名字,但節(jié)是有名字的,節(jié)的名字需要另外保存,對(duì)應(yīng)到 .shstrtab 節(jié)

3.1.2 程序頭表(Program Header Table)

readelf -l 命令可以查看程序表頭的信息。需要注意的是,".o" 文件一般是沒(méi)有程序表頭的。示例如下:

$readelf-l/bin/ls

ElffiletypeisDYN(Sharedobjectfile)
Entrypoint0x5850
Thereare9programheaders,startingatoffset64

ProgramHeaders:
TypeOffsetVirtAddrPhysAddr
FileSizMemSizFlagsAlign
PHDR0x00000000000000400x00000000000000400x0000000000000040
0x00000000000001f80x00000000000001f8RE0x8
INTERP0x00000000000002380x00000000000002380x0000000000000238
0x000000000000001c0x000000000000001cR0x1
[Requestingprograminterpreter:/lib64/ld-linux-x86-64.so.2]
LOAD0x00000000000000000x00000000000000000x0000000000000000
0x000000000001e6e80x000000000001e6e8RE0x200000
LOAD0x000000000001eff00x000000000021eff00x000000000021eff0
0x00000000000012780x0000000000002570RW0x200000
DYNAMIC0x000000000001fa380x000000000021fa380x000000000021fa38
0x00000000000002000x0000000000000200RW0x8
NOTE0x00000000000002540x00000000000002540x0000000000000254
0x00000000000000440x0000000000000044R0x4
GNU_EH_FRAME0x000000000001b1a00x000000000001b1a00x000000000001b1a0
0x00000000000008840x0000000000000884R0x4
GNU_STACK0x00000000000000000x00000000000000000x0000000000000000
0x00000000000000000x0000000000000000RW0x10
GNU_RELRO0x000000000001eff00x000000000021eff00x000000000021eff0
0x00000000000010100x0000000000001010R0x1

SectiontoSegmentmapping:
SegmentSections...
00
01.interp
02.interp.note.ABI-tag.note.gnu.build-id.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.plt.got.text.fini.rodata.eh_frame_hdr.eh_frame
03.init_array.fini_array.data.rel.ro.dynamic.got.data.bss
04.dynamic
05.note.ABI-tag.note.gnu.build-id
06.eh_frame_hdr
07
08.init_array.fini_array.data.rel.ro.dynamic.got

Program Headers 羅列了所有段的信息,含義如下表:

字段 含義 備注
Type 段的類(lèi)型,暗含了如何解析此段的內(nèi)容 [1]
Offset 本段內(nèi)容在文件的位置 單位 Byte
FileSiz 本段內(nèi)容在文件中的大小 單位 Byte
VirtAddr 本段內(nèi)容的開(kāi)始位置在進(jìn)程空間中的虛擬地址
MemSiz 本段內(nèi)容在進(jìn)程空間中的大小 單位 Byte
PhysAddr 本段內(nèi)容的開(kāi)始位置在進(jìn)程空間中的物理地址 由于MMU的存在,物理地址不可知,大多時(shí)候等于虛擬地址
Flags 段的權(quán)限 R/W/E 分別表示 讀/寫(xiě)/可執(zhí)行
Align 大小對(duì)齊信息 [3]

[1]. Type 的取值與含義如下

PHDR:此類(lèi)型的程序頭如果存在的話,它表明的是其自身所在的程序頭表在文件或內(nèi)存中的位置和大小。這樣的段在文件中可以不存在,只有當(dāng)所在程序頭表所覆蓋的段只是整個(gè)程序的一部分時(shí),才會(huì)出現(xiàn)一次這種表項(xiàng),而且這種表項(xiàng)一定出現(xiàn)在其它可裝載段的表項(xiàng)之前。

INTERP:本段指向了一個(gè)以”null”結(jié)尾的字符串,這個(gè)字符串是一個(gè) ELF 解析器的路徑。這種段類(lèi)型只對(duì)可執(zhí)行程序有意義,當(dāng)它出現(xiàn)在共享目標(biāo)文件中時(shí),是一個(gè)無(wú)意義的多余項(xiàng)。在一個(gè) ELF 文件中它最多只能出現(xiàn)一次,而且必須出現(xiàn)在其它可裝載段的表項(xiàng)之前。

LOAD:此類(lèi)型表明本程序頭指向一個(gè)可裝載的段。段的內(nèi)容會(huì)被從文件中拷貝到內(nèi)存中。

DYNAMIC:此類(lèi)型表明本段指明了動(dòng)態(tài)連接的信息。

NOTE:本段指向了一個(gè)以”null”結(jié)尾的字符串,這個(gè)字符串包含一些附加的信息。

[3]. 對(duì)于可裝載的段來(lái)說(shuō),其 虛擬地址 和 文件地址 的值至少要向內(nèi)存頁(yè)面大小對(duì)齊。此數(shù)據(jù)成員指明本段內(nèi)容如何在內(nèi)存和文件中對(duì)齊。如果該值為 0 或 1,表明沒(méi)有對(duì)齊要求;否則,此值應(yīng)該是一個(gè)正整數(shù),并且是 2 的冪次數(shù)。虛擬地址 和 文件地址 在對(duì)此值取模后應(yīng)該相等。

Section to Segment mapping 展示了段包含哪些節(jié),常關(guān)注的是代碼段和數(shù)據(jù)段。下表只是個(gè)典型的例子,一個(gè)實(shí)際的更復(fù)雜的代碼段可能包含更多的節(jié)。

代碼段 數(shù)據(jù)段
.text .data
.rodata .dynamic
.hash .got
.dynsym .bss
.dynstr
.plt
.rel.got

3.1.3 節(jié)頭表(Section Header Table)

readelf -S 命令可以查看節(jié)表頭的信息,示例如下:

$readelf-S/bin/ls
Thereare28sectionheaders,startingatoffset0x203a0:

SectionHeaders:
[Nr]NameTypeAddressOffset
SizeEntSizeFlagsLinkInfoAlign
[0]NULL000000000000000000000000
00000000000000000000000000000000000
[1].interpPROGBITS000000000000023800000238
000000000000001c0000000000000000A001
......
00000000000000180000000000000008AX008
[14].textPROGBITS0000000000003e9000003e90
00000000000124d90000000000000000AX0016
......
00000000000003c80000000000000008WA008
[24].dataPROGBITS000000000022000000020000
00000000000002680000000000000000WA0032
[25].bssNOBITS000000000022028000020268
00000000000012e00000000000000000WA0032
......

KeytoFlags:
W(write),A(alloc),X(execute),M(merge),S(strings),I(info),
L(linkorder),O(extraOSprocessingrequired),G(group),T(TLS),
C(compressed),x(unknown),o(OSspecific),E(exclude),
l(large),p(processorspecific)

字段 含義 備注
Name 本節(jié)的名字
Size 本節(jié)的大小 單位 Byte,如果節(jié)類(lèi)型為 NOBITS,此值仍可能非0,但無(wú)意義
Type 本節(jié)的類(lèi)型 [1]
EntSize 如果節(jié)是表類(lèi)型,則表示表項(xiàng)大小 單位 Byte
Address 如果需要映射到虛擬內(nèi)存,則表示起始地址 單位 Byte
Offset 本節(jié)第一個(gè)字節(jié)所在文件的偏移 單位 Byte
Flags 權(quán)限等屬性信息
Link 可以查閱章節(jié)1提到的《UnderstandingELF.pdf》
Info 可以查閱章節(jié)1提到的《UnderstandingELF.pdf》
Align 大小對(duì)齊信息

[1]. 節(jié)有以下類(lèi)型

NULL:本節(jié)頭是一個(gè)無(wú)效的(非活動(dòng)的)節(jié)頭

PROGBITS:本節(jié)所含有的信息是由程序定義的,本節(jié)內(nèi)容的格式和含義都由程序來(lái)決定。

SYMTAB:所有符號(hào)表調(diào)試信息,strip 可以干掉,不影響運(yùn)行

STRTAB:本節(jié)是字符串表。

RELA:本節(jié)是一個(gè)重定位節(jié)。

HASH/GNU_HASH:本節(jié)包含一張哈希表。

DYNAMIC:本節(jié)包含的是動(dòng)態(tài)連接信息。

NOTE:本節(jié)包含的信息用于以某種方式來(lái)標(biāo)記本文件。

NOBITS:這一節(jié)的內(nèi)容是空的,節(jié)并不占用實(shí)際的空間。

REL:本節(jié)是一個(gè)重定位節(jié)。

DYNSYM:動(dòng)態(tài)鏈接符號(hào)表信息

3.1.4 符號(hào)表(.symtab 節(jié))

符號(hào)表不屬于文件結(jié)構(gòu),但因?yàn)榉浅3S?,也單?dú)拎出來(lái)講。

以一段簡(jiǎn)單的代碼舉例子:

#include

intmain(intargc,char**argv)
{
printf("helloworld");
return0;
}

編譯出 hello 的 bin 之后,我們通過(guò)以下命令查看 .symtab 和 .dynsym

readelf-shello

Symboltable'.symtab'contains67entries:
Num:ValueSizeTypeBindVisNdxName
...
51:00000000000000000FUNCGLOBALDEFAULTUNDprintf@@GLIBC_2.2.5
...
61:00000000000006b039FUNCGLOBALDEFAULT14main
...

字段 含義 備注
Num 編號(hào)
Value 符號(hào)的值 [1]
Size 符號(hào)大小 單位 Byte
Type 符號(hào)類(lèi)型 NOTYPE:無(wú)類(lèi)型,OBJECT:數(shù)據(jù)對(duì)象,例如變量、數(shù)組,F(xiàn)UNC:函數(shù),F(xiàn)ILE:文件符號(hào),SECTION:本符號(hào)與一個(gè)節(jié)相關(guān)聯(lián),用于重定位
Bind 表示符號(hào)的優(yōu)先級(jí)和作用范圍 LOCAL:本地符號(hào),在所屬".o"文件外無(wú)效,GLOBAL:全局符號(hào),WEAK:弱符號(hào),例如__attribute__((weak)) 標(biāo)注的弱函數(shù)
Vis
Ndx 指定相關(guān)聯(lián)的節(jié)[2] 數(shù)字:節(jié)在節(jié)頭表中的索引,ABS:絕對(duì)值常量,常指文件路徑,UND:未定義的,常指需要外部鏈接的函數(shù)、變量等
Name 符號(hào)名字

[1]. 在".o" 中,如果 Ndx 不是 UND,則此值表示字符在所在節(jié)中的偏移量,在可執(zhí)行文件和共享庫(kù)文件中,則表示虛擬地址
[2]. 任何一個(gè)符號(hào)表項(xiàng)的定義都與某一個(gè)“節(jié)”相聯(lián)系,因?yàn)榉?hào)是為節(jié)而定義,在節(jié)中被引用。

更多符號(hào)表的介紹,請(qǐng)看 3.2.6 章節(jié)。

3.2 一些關(guān)鍵的節(jié)

3.2.1 "編譯 + 節(jié)" 與 "鏈接 + 段"

在 《linux 目標(biāo)文件(*.o) bss,data,text,rodata,堆,?!罚╤ttps://blog.csdn.net/sunny04/article/details/40627311) 有張圖非常形象的描述了 text、data、bss 段的作用,如下:

c5570066-5010-11ed-a3b6-dac502259ad0.jpg

從上圖可以看出,代碼放在 text 段,已經(jīng)初始化的變量放在 data 段,未初始化的變量放在 bss 段。詳細(xì)下文再描述,在此章節(jié),我們需要知道一個(gè)概念:

所有變量、函數(shù)等都是一個(gè)符號(hào),還有字符串、固定的數(shù)據(jù)等,在編譯的時(shí)候會(huì)按功能、是否初始化等分類(lèi)放到特定的節(jié)中。

例如 .bss 節(jié)記錄了所有未初始化的變量,.text 節(jié)保存了所有的二進(jìn)制代碼,.rodata 節(jié)保存了所有只讀的數(shù)據(jù)。

所以我們編譯(不含鏈接)出來(lái)的 ".o" 目標(biāo)文件,其實(shí)就是對(duì)單個(gè) ".c/.cpp" 源碼的符號(hào)、數(shù)據(jù)按不同節(jié)分類(lèi)放好。

這是編譯,那么鏈接成可執(zhí)行程序的時(shí)候呢?鏈接雖然涉及多個(gè) ".o",其實(shí)也就把相同的節(jié)合并,然后歸類(lèi)到段中放好。

c562dabc-5010-11ed-a3b6-dac502259ad0.png

此節(jié)也就很粗獷的描述,砍掉了所有枝葉,只是為了更好的更直觀的理解主干,實(shí)際情況肯定更復(fù)雜。

我們編譯、鏈接的時(shí)候,其實(shí)可以主動(dòng)限制某些符號(hào)、數(shù)據(jù)放到哪些節(jié)、哪些段的,也可以自己新建個(gè)節(jié)、段,這屬于另一個(gè)大的課題了。大多時(shí)候我們也用不上,采用默認(rèn)的即可。

下面,我們來(lái)看一些關(guān)鍵的節(jié)及其內(nèi)容。

3.2.2 .text

前文一直有帶出來(lái),.text 節(jié)實(shí)際就是我們說(shuō)的代碼段,保存了一系列的可執(zhí)行二進(jìn)制指令。

.text 節(jié)的內(nèi)容是會(huì)占 ROM 空間,并在運(yùn)行時(shí)拷貝到 RAM。

readelf -x .text 可以以二進(jìn)制 dump 出來(lái),但沒(méi)卵用,誰(shuí)看得懂?此時(shí)可以用 objdump -d -j .text 匯編顯示出來(lái)。

$objdump-d-j.text./main

main:fileformatelf64-x86-64

Disassemblyofsection.text:

......

00000000000006b0
: 6b0:55push%rbp 6b1:4889e5mov%rsp,%rbp 6b4:4883ec10sub$0x10,%rsp 6b8:897dfcmov%edi,-0x4(%rbp) 6bb:488975f0mov%rsi,-0x10(%rbp) 6bf:488d3d9e000000lea0x9e(%rip),%rdi#764<_IO_stdin_used+0x4> 6c6:b800000000mov$0x0,%eax 6cb:e890feffffcallq560 6d0:b800000000mov$0x0,%eax 6d5:c9leaveq 6d6:c3retq 6d7:660f1f84000000nopw0x0(%rax,%rax,1) 6de:0000 ......

上面的 main 匯編僅僅是 printf("hello")。

3.2.3 .bss

.bss 節(jié)主要保存了未初始化的變量,這里的未初始化起始也包括初始化為 0 的變量。通常是指用來(lái)存放程序中未初始化的全局變量和未初始化的局部靜態(tài)變量。

.bss 節(jié)只在運(yùn)行時(shí),才會(huì)從內(nèi)存分配空間,因此近乎不占 ROM 空間。

例如下面示例的兩個(gè)變量都會(huì)保存到 bss。

intg_int1;
intg_int2=0;

對(duì)上述的兩個(gè)變量,我們還是用 objdump -d -j .bss 看看:

$objdump-d-j.bss./main

test:fileformatelf64-x86-64


Disassemblyofsection.bss:

0000000000201010<__bss_start>:
201010:0000add%al,(%rax)
...

0000000000201014:
201014:00000000....

0000000000201018:
...

3.2.4 .data

.data 節(jié)主要保存了已經(jīng)初始化的變量,通常是指用來(lái)存放程序中已初始化的全局變量和已初始化的靜態(tài)變量的一塊內(nèi)存區(qū)域。

既然已經(jīng)初始化,在文件中肯定要記錄初始化的值,因此 .data 節(jié)需要占 ROM 空間的,且相對(duì) .rodata 節(jié)來(lái)說(shuō),.data 節(jié)的內(nèi)容是可變的,因此運(yùn)行前需要拷貝到內(nèi)存中可寫(xiě)的區(qū)間。

objdump 對(duì)查看數(shù)據(jù)類(lèi)型的節(jié)的確方便,我們繼續(xù)用 objdump 看看。

//核心變量:int g_int2 = 1
$objdump-d-j.datatest

test:fileformatelf64-x86-64

Disassemblyofsection.data:

0000000000201000<__data_start>:
...

0000000000201008<__dso_handle>:
201008:0810200000000000.......

0000000000201010:
201010:01000000....

代碼 int g_int2 = 1 對(duì)變量賦了非0的初始值,就不適合放到 .bss 段了,因此放到了 .data 段。從 dump 的結(jié)果也可以看到變量 g_int2 以及值 0x1 。

3.2.5 .rodata

.rodata 節(jié)的數(shù)據(jù)是會(huì)占 ROM 空間的,且(大多時(shí)候)在運(yùn)行時(shí)拷貝到內(nèi)存中。

.rodata 存的是只讀數(shù)據(jù),比如字符串常量,全局const變量 和 #define定義的常量。例如:char *p = "123456", 123456 的字符串就存放在 rodata 節(jié) 中。還有一個(gè)有意思的例子:

printf("thefuncis%s
",__func__);

"the func is %s " 這個(gè)格式化打印字符串以及 __func__ 所指代的函數(shù)名,也是字符串常量,保存到 .rodata 節(jié)中的。

查看字符串類(lèi)型的節(jié),用 readelf 就方便多了,例如以下代碼:

intmain(intargc,char**argv)
{
printf("helloworld");
printf("thisfuncis%s",__func__);
}

執(zhí)行以下命令可以打印字符串?dāng)?shù)據(jù),如果需要看二進(jìn)制數(shù)據(jù),例如整型的值,把 -p 改為 -x 即可。

$readelf-p.rodatamain

Stringdumpofsection'.rodata':
[4]helloworld
[10]thisfuncis%s
[20]main

這里有個(gè)小 Tips,相同的字符串只會(huì)保留 1份,因此下面兩段代碼效果一樣,但保存的 .rodata 數(shù)據(jù)是不一樣的。適當(dāng)?shù)膬?yōu)化可以節(jié)省 ROM 空間。自己琢磨:)。

//代碼1
printf("func%s:valis%d
",__func__,val);
printf("func%s:openfailed
",__func__);

//代碼2
printf("funcmain:valis%d
",val);
printf("funcmain:openfailed
");

3.2.6 .symtab 和 .dynsym

.symtab 和 .dynsym 都是符號(hào)表,例如函數(shù)、變量等都是符號(hào),甚至 .dynsym 是 .symtab 的子集,但是他們的作用不太一樣。

.symtab,俗稱(chēng)的符號(hào)表,記錄了所有符號(hào),不管是自己定義的變量、函數(shù),還是未定義需要?jiǎng)討B(tài)庫(kù)提供實(shí)現(xiàn)的所有符號(hào)。在 ”.o" 鏈接時(shí)必須存在,但鏈接成 bin 后就可去掉節(jié)省空間,例如 strip 。去掉符號(hào)表之后程序運(yùn)行能定位到函數(shù)地址,但再也不知道這函數(shù)名,這也是為什么在程序 crash 時(shí)打印的棧里有時(shí)候只有地址,有時(shí)候有具體函數(shù)名。

.dynsym,動(dòng)態(tài)鏈接才需要的符號(hào)表,即可包括對(duì)外提供調(diào)用的符號(hào),也包括需要外面提供實(shí)現(xiàn)的符號(hào)。.dynsym 在 ".so" 或者動(dòng)態(tài)鏈接的 bin 里是必須的。

.symtab 和 .dynsym 是占 ROM 空間的,.dynsym 會(huì)加載進(jìn)內(nèi)存,但 .symtab 不會(huì)。我們也可以通過(guò) strip 去掉符號(hào)表,然后通過(guò) file 判斷符號(hào)表是否已經(jīng) striped,例如下面的例子:

$gcctest.c-otest
$filetest
test:ELF64-bitLSBsharedobject,...,notstripped
$striptest
test:ELF64-bitLSBsharedobject,...,stripped

4. 利用工具解析 ELF

在上文的示例中頻繁使用 readelf 和 objdump 來(lái)讀取各種頭表和節(jié)內(nèi)容,除了這兩個(gè)之外,還有一個(gè) nm 工具,3者的功能非常相近。吃多嚼不爛,咱們以 readelf 為主,以 objdump 為輔講解如何用工具解析 ELF 。

上文已有示例,本文主要做個(gè)匯總記錄,方便將來(lái)查閱。字段的具體含義,可以查看章節(jié)3中具體的節(jié)解析。

獲取 ELF 文件頭

readelf-h

獲取程序頭表(段表)

readelf-l

獲取節(jié)表(獲取有哪些節(jié))

readelf-S

獲取符號(hào)表(列出函數(shù)、變量符號(hào))

#獲取所有符號(hào)表(含.symtab和.dynsym)
readelf-s

#獲取動(dòng)態(tài)符號(hào)表
readelf--dyn-syms

獲取節(jié)內(nèi)容

#打印節(jié)中的字符串,常用于含字符串類(lèi)型的節(jié),例如.rodata節(jié)
readelf-p

#以二進(jìn)制打印節(jié),常用于非字符串類(lèi)型的節(jié),例如.bss,.data節(jié)
readelf-x

#以匯編打印二進(jìn)制代碼
objdump-d-j

5. ELF 在磁盤(pán) Vs. ELF 加載到內(nèi)存

在 《完全剖析 - Linux虛擬內(nèi)存空間管理》(https://cloud.tencent.com/developer/article/1835295) 一文有張圖畫(huà)出了虛擬內(nèi)存的空間分布情況,如下:

c56da87a-5010-11ed-a3b6-dac502259ad0.png


我們關(guān)注最底下3個(gè)區(qū)間,其實(shí)跟 ELF 的內(nèi)容是能對(duì)應(yīng)上的。用一張新圖來(lái)表示兩者的關(guān)系:

c57abf2e-5010-11ed-a3b6-dac502259ad0.png


可執(zhí)行文件的 代碼段、數(shù)據(jù)段等會(huì)拷貝到內(nèi)存中,BSS 段雖然沒(méi)數(shù)據(jù),但也記錄了有哪些變量,會(huì)拷貝到內(nèi)存可寫(xiě)區(qū)域,而動(dòng)態(tài)庫(kù)是 map 到 mmap 區(qū)的。




審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 處理器
    +關(guān)注

    關(guān)注

    68

    文章

    19286

    瀏覽量

    229852
  • BIN文件
    +關(guān)注

    關(guān)注

    0

    文章

    26

    瀏覽量

    8291
  • elf
    elf
    +關(guān)注

    關(guān)注

    0

    文章

    12

    瀏覽量

    2187
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    在rt-smart操作系統(tǒng)中elf加載運(yùn)行要經(jīng)過(guò)哪些步驟

    ELF 代表 Executable and Linkable Format。它是一種對(duì)可執(zhí)行文件、目標(biāo)文件和庫(kù)使用的文件格式。
    的頭像 發(fā)表于 11-02 09:57 ?1389次閱讀

    如何加載ELF燒錄文件?

    請(qǐng)問(wèn)在如下的界面中,如何加載ELF燒錄文件?
    發(fā)表于 02-01 08:25

    ELF

    elf的相關(guān)文檔資料,有需求的朋友可以參考參考
    發(fā)表于 05-03 10:10 ?11次下載

    嵌入式bin文件elf文件重點(diǎn)

    執(zhí)行raw binary很簡(jiǎn)單,只需要將程序加載到其起始地址,就可以執(zhí)行; 執(zhí)行ELF程序則需要一個(gè)ELF Loader。 現(xiàn)在知道了吧,uboot和Linux kernel啟動(dòng)的時(shí)候是沒(méi)有ELF Loader的,所以燒在fla
    的頭像 發(fā)表于 05-15 11:04 ?9212次閱讀
    嵌入式bin<b class='flag-5'>文件</b>和<b class='flag-5'>elf</b><b class='flag-5'>文件</b>重點(diǎn)

    如何在Atmel Studio 6進(jìn)行ELF文件生成

    如何在Atmel Studio 6進(jìn)行ELF文件生成
    的頭像 發(fā)表于 07-04 09:50 ?4522次閱讀

    ELF文件如何在Atmel Studio 6中生成?

    在Atmel Studio 6中ELF文件的生成
    的頭像 發(fā)表于 07-10 10:11 ?4342次閱讀

    簡(jiǎn)單介紹一下Linux中ELF格式文件

    ELF(Executable and Linkable Format)即可執(zhí)行連接文件格式,是一種比較復(fù)雜的文件格式,但其應(yīng)用廣泛。
    發(fā)表于 04-27 19:09 ?2.7w次閱讀
    簡(jiǎn)單介紹一下Linux中<b class='flag-5'>ELF</b>格式<b class='flag-5'>文件</b>

    ELF相比Hex、Bin文件格式有哪些不同?

    素材來(lái)源:綜合CSDN 編輯整理:strongerHuang 關(guān)于計(jì)算機(jī)的文件有很多種,今天分享一種用于二進(jìn)制文件、可執(zhí)行文件、目標(biāo)代碼、共享庫(kù)和核心轉(zhuǎn)儲(chǔ)格式文件。 一、
    的頭像 發(fā)表于 10-16 09:54 ?5552次閱讀
    <b class='flag-5'>ELF</b>相比Hex、Bin<b class='flag-5'>文件</b>格式有哪些不同?

    如何關(guān)聯(lián)ELF輸出文件并使用vivado對(duì)系統(tǒng)進(jìn)行行為仿真

    本文介紹如何在教程(三)基礎(chǔ)上, 關(guān)聯(lián)ELF輸出文件并使用vivado對(duì)系統(tǒng)進(jìn)行行為仿真。
    的頭像 發(fā)表于 02-08 11:18 ?6361次閱讀
    如何關(guān)聯(lián)<b class='flag-5'>ELF</b>輸出<b class='flag-5'>文件</b>并使用vivado對(duì)系統(tǒng)進(jìn)行行為仿真

    用于讀取ELF格式文件的詳細(xì)信息的命令:readelf命令

    1.功能簡(jiǎn)介 readelf 用于讀取 ELF(Executable and Linkable Format)格式文件的詳細(xì)信息,包括目標(biāo)文件、可執(zhí)行文件、共享目標(biāo)
    的頭像 發(fā)表于 02-09 18:12 ?5085次閱讀
    用于讀取<b class='flag-5'>ELF</b>格式<b class='flag-5'>文件</b>的詳細(xì)信息的命令:readelf命令

    readelf命令:用于讀取ELF格式文件的詳細(xì)信息

    1.功能簡(jiǎn)介 readelf 用于讀取 ELF(Executable and Linkable Format)格式文件的詳細(xì)信息,包括目標(biāo)文件、可執(zhí)行文件、共享目標(biāo)
    的頭像 發(fā)表于 02-02 14:45 ?5399次閱讀
    readelf命令:用于讀取<b class='flag-5'>ELF</b>格式<b class='flag-5'>文件</b>的詳細(xì)信息

    Linux系統(tǒng)如何解析ELF文件

    也就是說(shuō),掌握了 ELF 文件結(jié)構(gòu)和內(nèi)容,是理解編譯、鏈接和程序執(zhí)行的基礎(chǔ)。
    的頭像 發(fā)表于 04-10 16:36 ?3389次閱讀

    ELF文件、鏡像(Image)文件、可執(zhí)行文件、對(duì)象文件詳解

    ELF(Executable and Linking Format)是一個(gè)二進(jìn)制文件規(guī)范。用于定義不同類(lèi)型的對(duì)象文件(Object files)中都放了什么東西、以及都以什么樣的格式去放這些東西。
    的頭像 發(fā)表于 12-30 10:24 ?2331次閱讀

    STM32CubeIDE找不到elf

    庫(kù)(STM32Cube),提供了豐富的開(kāi)發(fā)工具和功能,幫助開(kāi)發(fā)者更快、更方便地開(kāi)發(fā)嵌入式應(yīng)用。 在使用STM32CubeIDE開(kāi)發(fā)STM32應(yīng)用程序時(shí),有時(shí)會(huì)遇到找不到elf文件的問(wèn)題。本文將詳細(xì)介紹在
    的頭像 發(fā)表于 01-02 16:32 ?1830次閱讀