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

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

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

深入了解 ELF每個結構的細節(jié)

Linux閱碼場 ? 來源:Linux閱碼場 ? 作者:Linux閱碼場 ? 2022-10-20 09:04 ? 次閱讀

1. 背景

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

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

內(nèi)容:翻譯 ELF(v1.2) 通用規(guī)范,深入了解 ELF 每個結構的細節(jié),并在最后提供一個實例協(xié)助理解。

2. 基礎概念

2.1 什么是 ELF 文件

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

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

我們通過 file 命令可以識別出來

#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,...

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

2.2 ELF 文件結構概覽

ELF 文件主要的用途有兩個,

構建程序,鏈接成動態(tài)庫或者bin,一般是目標文件 ".o"

運行程序,一般指鏈接好的 ".so" 或者 "bin"

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

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

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

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

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

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

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

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

$readelf-l/bin/ls

ElffiletypeisDYN(Sharedobjectfile)
Entrypoint0x5850
Thereare9programheaders,startingatoffset64

ProgramHeaders:
......

SectiontoSegmentmapping:
......

Program Headers 下面羅列出了所有的段信息,詳細如下圖所示,共計有 9 個段。

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 羅列了 各個段(Segment)包含了哪些節(jié)(Section),是的,段是1個或者多個節(jié)的集合,詳細如下文所示。

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

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

3. 關鍵的文件結構和節(jié)

3.1 一些關鍵的文件結構

對一個 ELF 格式的文件,有這么幾個特殊的結構我們需要關注的。

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

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

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

本文不會解釋結構體每個元素,而是利用 readelf 工具解讀。如果需要詳細到每個字節(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 標識是 ELF 文件類型的幻數(shù) 值只能為 0x7F + 'E' + 'L' + 'F'
Class 32Bit/64Bit 類型 ELF64 / ELF32
Data 小端/大端編碼
Version 文件頭版本
OS/ABI 適用的系統(tǒng) 和 ABI
Type 哪種類型的 ELF 文件 DYN:共享目標文件,REL:可重定位文件,EXEC:可執(zhí)行文件 [1]
Machine 處理器體系架構 常見例如 ARM,X86-64
Version ELF文件的版本
Entry point address 指代程序入口的虛擬地址 一般是 _start(),一系列調(diào)用后才到 main()
Start of program headers 程序頭表在文件的偏移 單位 Byte
Start of section headers 節(jié)頭表在文件的偏移 單位 Byte
Flags 處理器特定的標志位
Size of this header ELF 文件頭的大小 單位 Byte
Size of program headers 程序頭表中每一個表項的大小 單位 Byte
Number of program headers 程序頭表中表項的數(shù)量,理解為有多少個段
Size of section headers 節(jié)頭表中每個表項的大小 單位 Byte
Number of section headers 節(jié)頭表中有表項的數(shù)量,理解為有多少個節(jié)
Section header string table index 節(jié)頭表中與節(jié)名字表相對應的表項索引信息 [2]

[1]. 文件類型見 2.1 章節(jié)
[2]. 段沒名字,但節(jié)是有名字的,節(jié)的名字需要另外保存,對應到 .shstrtab 節(jié)

3.1.2 程序頭表(Program Header Table)

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

$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 段的類型,暗含了如何解析此段的內(nèi)容 [1]
Offset 本段內(nèi)容在文件的位置 單位 Byte
FileSiz 本段內(nèi)容在文件中的大小 單位 Byte
VirtAddr 本段內(nèi)容的開始位置在進程空間中的虛擬地址
MemSiz 本段內(nèi)容在進程空間中的大小 單位 Byte
PhysAddr 本段內(nèi)容的開始位置在進程空間中的物理地址 由于MMU的存在,物理地址不可知,大多時候等于虛擬地址
Flags 段的權限 R/W/E 分別表示 讀/寫/可執(zhí)行
Align 大小對齊信息 [3]

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

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

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

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

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

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

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

Section to Segment mapping 展示了段包含哪些節(jié),常關注的是代碼段和數(shù)據(jù)段。下表只是個典型的例子,一個實際的更復雜的代碼段可能包含更多的節(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é)類型為 NOBITS,此值仍可能非0,但無意義
Type 本節(jié)的類型 [1]
EntSize 如果節(jié)是表類型,則表示表項大小 單位 Byte
Address 如果需要映射到虛擬內(nèi)存,則表示起始地址 單位 Byte
Offset 本節(jié)第一個字節(jié)所在文件的偏移 單位 Byte
Flags 權限等屬性信息
Link 可以查閱章節(jié)1提到的《UnderstandingELF.pdf》
Info 可以查閱章節(jié)1提到的《UnderstandingELF.pdf》
Align 大小對齊信息

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

NULL:本節(jié)頭是一個無效的(非活動的)節(jié)頭

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

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

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

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

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

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

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

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

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

DYNSYM:動態(tài)鏈接符號表信息

3.1.4 符號表(.symtab 節(jié))

符號表不屬于文件結構,但因為非常常用,也單獨拎出來講。

以一段簡單的代碼舉例子:

#include

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

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

readelf-shello

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

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

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

更多符號表的介紹,請看 3.2.6 章節(jié)。

3.2 一些關鍵的節(jié)

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

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

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

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

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

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

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

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

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

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

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

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

3.2.2 .text

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

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

readelf -x .text 可以以二進制 dump 出來,但沒卵用,誰看得懂?此時可以用 objdump -d -j .text 匯編顯示出來。

$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 的變量。通常是指用來存放程序中未初始化的全局變量和未初始化的局部靜態(tài)變量。

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

例如下面示例的兩個變量都會保存到 bss。

intg_int1;
intg_int2=0;

對上述的兩個變量,我們還是用 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)初始化的變量,通常是指用來存放程序中已初始化的全局變量和已初始化的靜態(tài)變量的一塊內(nèi)存區(qū)域。

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

objdump 對查看數(shù)據(jù)類型的節(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 對變量賦了非0的初始值,就不適合放到 .bss 段了,因此放到了 .data 段。從 dump 的結果也可以看到變量 g_int2 以及值 0x1 。

3.2.5 .rodata

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

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

printf("thefuncis%s
",__func__);

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

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

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

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

$readelf-p.rodatamain

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

這里有個小 Tips,相同的字符串只會保留 1份,因此下面兩段代碼效果一樣,但保存的 .rodata 數(shù)據(jù)是不一樣的。適當?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 都是符號表,例如函數(shù)、變量等都是符號,甚至 .dynsym 是 .symtab 的子集,但是他們的作用不太一樣。

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

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

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

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

4. 利用工具解析 ELF

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

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

獲取 ELF 文件頭

readelf-h

獲取程序頭表(段表)

readelf-l

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

readelf-S

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

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

#獲取動態(tài)符號表
readelf--dyn-syms

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

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

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

#以匯編打印二進制代碼
objdump-d-j

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

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

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

我們關注最底下3個區(qū)間,其實跟 ELF 的內(nèi)容是能對應上的。用一張新圖來表示兩者的關系:

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

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

審核編輯:彭靜
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 程序
    +關注

    關注

    117

    文章

    3787

    瀏覽量

    81038
  • 代碼
    +關注

    關注

    30

    文章

    4788

    瀏覽量

    68601
  • 深度學習
    +關注

    關注

    73

    文章

    5503

    瀏覽量

    121157
  • elf
    elf
    +關注

    關注

    0

    文章

    12

    瀏覽量

    2185

原文標題:搞懂 ELF - 從入門到遺忘

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關推薦

    深入了解示波器

    深入了解示波器
    發(fā)表于 11-14 22:32

    專家開講:深入了解電池技術 ──Part 1

    。筆者不會一一詳細介紹所有的電池技術,只選擇一些常見或是值得認識的;而在接下來的專欄里,筆者將開始介紹電池分類、常見規(guī)格以及專業(yè)術語,如果你有特別想知道的電池技術,歡迎留言!擴展閱讀:專家開講:深入了解
    發(fā)表于 08-18 09:33

    專家開講:深入了解電池技術──Part 3

    資深工程師 Ivan Cowie 的「深入了解電池技術」專欄Part 3來啰!這次要介紹的是鉛酸電池(lead-acidbatteries)技術。鉛酸電池是在1859年由法國物理學家Gaston
    發(fā)表于 08-18 09:37

    單片機的深入了解!

    項目名稱:單片機的深入了解!項目是否開源:否申請開發(fā)板數(shù)量:1 塊申請人團隊介紹:我們團隊由五個人組成,我們打算開始著手單片機的程序改編,設計一些比較特殊新穎的東西!希望給以支持!
    發(fā)表于 10-12 20:00

    深入了解LabVIEW FPGA資料分享

    深入了解LabVIEW FPGA
    發(fā)表于 05-27 08:35

    深入了解單片機匯編重要嗎?

    不學匯編,只用C語言,能不能深入了解單片機?
    發(fā)表于 07-21 10:38

    深入了解主動電掃描陣列(AESA)雷達系統(tǒng)

    深入了解主動電掃描陣列(AESA)雷達系統(tǒng)
    發(fā)表于 05-24 06:51

    示波器的深入了解

    示波器的深入了解 引言自然界運行著各種形式的正弦波,比如海浪、地震、聲波、爆破、空氣中傳播的聲音,或者身體運轉(zhuǎn)的自然節(jié)律。物理世界里,能
    發(fā)表于 11-04 11:53 ?52次下載
    示波器的<b class='flag-5'>深入了解</b>

    深入了解示波器入門手冊

    深入了解示波器入門手冊
    發(fā)表于 03-27 17:43 ?241次下載
    <b class='flag-5'>深入了解</b>示波器入門手冊

    深入了解電路噪聲的那些事

    模擬電子的相關知識學習教材資料——深入了解電路噪聲的那些事
    發(fā)表于 09-27 15:19 ?0次下載

    深入了解電感與磁珠的異同

    模擬電子的相關知識學習教材資料——深入了解電感與磁珠的異同
    發(fā)表于 09-27 15:19 ?0次下載

    了解IC內(nèi)部結構嗎本文帶你深入了解

    本文檔的主要內(nèi)容詳細介紹的是IC內(nèi)部結構了解IC內(nèi)部結構嗎本文帶你深入了解
    的頭像 發(fā)表于 03-09 11:33 ?1.1w次閱讀
    你<b class='flag-5'>了解</b>IC內(nèi)部<b class='flag-5'>結構</b>嗎本文帶你<b class='flag-5'>深入了解</b>

    帶你深入了解示波器

    帶你深入了解示波器
    發(fā)表于 02-07 14:26 ?19次下載

    深入了解安全光柵

    深入了解安全光柵
    的頭像 發(fā)表于 06-25 13:53 ?1218次閱讀
    <b class='flag-5'>深入了解</b>安全光柵

    深入了解 GaN 技術

    深入了解 GaN 技術
    的頭像 發(fā)表于 12-06 17:28 ?6159次閱讀
    <b class='flag-5'>深入了解</b> GaN 技術