閱讀之前的說明
先說明,本篇很長,也很枯燥,若不是絕對的技術偏執狂是看不下去的.將通過一段簡單代碼去跟蹤編譯成ELF格式后的內容.看看ELF
究竟長了怎樣的一副花花腸子,用readelf
命令去窺視ELF的全貌,最后用objdump
命令反匯編ELF
.找到了大家熟悉main
函數.
開始之前先說結論:
- ELF 分四塊,其中三塊是描述信息(也叫頭信息),另一塊是內容,放的是所有段/區的內容.
-
- ELF頭定義全局性信息
-
- Segment(段)頭,內容描述段的名字,開始位置,類型,偏移,大小及每段由哪些區組成.
-
- 內容區,ELF有兩個重要概念?
Segment
(段) 和?Section
(區),段比區大,二者之間關系如下:
- 每個
Segment
可以包含多個Section
- 每個
Section
可以屬于多個Segment
Segment
之間可以有重合的部分- 拿大家熟知的
.text
,.data
,.bss
舉例,它們都叫區,但它們又屬于LOAD
段.
- 內容區,ELF有兩個重要概念?
-
- Section(區)頭,內容描述區的名字,開始位置,類型,偏移,大小等信息
- ELF一體兩面,面對不同的場景扮演不同的角色,這是理解ELF的關鍵,鏈接器只關注1,3(區),4 的內容,加載器只關注1,2,3(段)的內容
- 鴻蒙對
EFL
的定義在?kernel\extended\dynload\include\los_ld_elf_pri.h
文件中
示例代碼
在windows目錄E:\harmony\docker\case_code_100
下創建 main.c文件,如下:
#include <stdio.h>
void say_hello(char *who)
{printf("hello, %s!\n", who);
}
char *my_name = "harmony os";int main()
{say_hello(my_name);return 0;
}
做好了環境映射,所以文件會同時出現在docker中.編譯生成ELF
->運行->readelf -h
查看app
頭部信息.
root@5e3abe332c5a:/home/docker/case_code_100# ls
main.c
root@5e3abe332c5a:/home/docker/case_code_100# gcc -o app main.c
root@5e3abe332c5a:/home/docker/case_code_100# ls
app main.c
root@5e3abe332c5a:/home/docker/case_code_100# ./app
hello, harmony os!
名正才言順
一下是關于ELF的所有中英名詞對照.建議先仔細看一篇再看系列篇部分.
可執行可連接格式 : ELF(Executable and Linking Format)
ELF文件頭:ELF header
基地址:base address
動態連接器: dynamic linker
動態連接: dynamic linking
全局偏移量表: got(global offset table)
進程鏈接表: plt(Procedure Linkage Table)
哈希表: hash table
初始化函數 : initialization function
連接編輯器 : link editor
目標文件 : object file
函數連接表 : procedure linkage table
程序頭: program header
程序頭表 : program header table
程序解析器 : program interpreter
重定位: relocation
共享目標 : shared object
區(節): section
區(節)頭 : section header
區(節)表: section header table
段 : segment
字符串表 : string table
符號表: symbol table
終止函數 : termination function
ELF歷史
-
ELF(Executable and Linking Format),即"可執行可連接格式",最初由UNIX系統實驗室(UNIX System Laboratories – USL)做為應用程序二進制接口(Application Binary Interface - ABI)的一部分而制定和發布.是鴻蒙的主要可執行文件格式.
-
ELF的最大特點在于它有比較廣泛的適用性,通用的二進制接口定義使之可以平滑地移植到多種不同的操作環境上.這樣,不需要為每一種操作系統都定義一套不同的接口,因此減少了軟件的重復編碼與編譯,加強了軟件的可移植性.
ELF整體布局
ELF規范中把ELF文件寬泛地稱為"目標文件 (object file)",這與我們平時的理解不同.一般地,我們把經過編譯但沒有連接的文件(比如Unix/Linux上的.o文件)稱為目標文件,而ELF文件僅指連接好的可執行文件;在ELF規范中,所有符合ELF格式規范的都稱為ELF文件,也稱為目標文件,這兩個名字是相同的,而經過編譯但沒有連接的文件則稱為"可重定位文件 (relocatable file)“或"待重定位文件 (relocatable file)”.本文采用與此規范相同的命名方式,所以當提到可重定位文件時,一般可以理解為慣常所說的目標文件;而提到目標文件時,即指各種類型的ELF文件.
ELF格式可以表達四種類型的二進制對象文件(object files):
- 可重定位文件(relocatable file),用于與其它目標文件進行連接以構建可執行文件或動態鏈接庫.可重定位文件就是常說的目標文件,由源文件編譯而成,但還沒有連接成可執行文件.在UNIX系統下,一般有擴展名".o".之所以稱其為"可重定位",是因為在這些文件中,如果引用到其它目標文件或庫文件中定義的符號(變量或者函數)的話,只是給出一個名字,這里還并不知道這個符號在哪里,其具體的地址是什么.需要在連接的過程中,把對這些外部符號的引用重新定位到其真正定義的位置上,所以稱目標文件為"可重定位"或者"待重定位"的.
- 可執行文件(executable file)包含代碼和數據,是可以直接運行的程序.其代碼和數據都有固定的地址 (或相對于基地址的偏移 ),系統可根據這些地址信息把程序加載到內存執行.
- 共享目標文件(shared object file),即動態連接庫文件.它在以下兩種情況下被使用:第一,在連接過程中與其它動態鏈接庫或可重定位文件一起構建新的目標文件;第二,在可執行文件被加載的過程中,被動態鏈接到新的進程中,成為運行代碼的一部分.包含了代碼和數據,這些數據是在鏈接時被鏈接器(ld)和運行時動態鏈接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的.
- 核心轉儲文件(core dump file,就是core dump文件)
可重定位文件用在編譯和鏈接階段.
可執行文件用在程序運行階段.
共享庫則同時用在編譯鏈接和運行階段,本篇 app 就是個 DYN,可直接運行.Type: DYN (Shared object file)
在不同階段,我們可以用不同視角來理解ELF
文件,整體布局如下圖所示:
從上圖可見,ELF格式文件整體可分為四大部分:
ELF Header
: 在文件的開始,描述整個文件的組織.即readelf -h app
看到的內容Program Header Table
: 告訴系統如何創建進程映像.用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件可以不需要這個表.表描述所有段(Segment)信息,即readelf -l app
看到的前半部分內容.Segments
:段(Segment
)由若干區(Section
)組成.是從加載器角度來描述?ELF
?文件.加載器只關心?ELF header
,?Program header table
?和?Segment
?這三部分內容。 在加載階段可以忽略 section header table 來處理程序(所以很多加固手段刪除了section header table
)Sections
: 是從鏈接器角度來描述?ELF
?文件. 鏈接器只關心?ELF header
,Sections
?以及?Section header table
?這三部分內容。在鏈接階段,可以忽略?program header table
?來處理文件.Section Header Table
:描述區(Section
)信息的數組,每個元素對應一個區,通常包含在可重定位文件中,可執行文件中為可選(通常包含) 即readelf -S app
看到的內容- 從圖中可以看出?
Segment
:Section
(M:N)是多對多的包含關系.Segment
是由多個Section
組成,Section
也能屬于多個段.
ELF頭信息
ELF
頭部信息對應鴻蒙源碼結構體為?LDElf32Ehdr
, 各字段含義已一一注解,很容易理解.
//kernel\extended\dynload\include\los_ld_elf_pri.h
/* Elf header */
#define LD_EI_NIDENT 16
typedef struct {UINT8 elfIdent[LD_EI_NIDENT]; /* Magic number and other info *///含前16個字節,又可細分成class、data、version等字段,具體含義不用太關心,只需知道前4個字節點包含`ELF`關鍵字,這樣可以判斷當前文件是否是ELF格式UINT16 elfType; /* Object file type *///表示具體ELF類型,可重定位文件/可執行文件/共享庫文件UINT16 elfMachine; /* Architecture *///表示cpu架構UINT32 elfVersion; /* Object file version *///表示文件版本號UINT32 elfEntry; /* Entry point virtual address *///對應`Entry point address`,程序入口函數地址,通過進程虛擬地址空間地址表達UINT32 elfPhoff; /* Program header table file offset *///對應`Start of program headers`,表示program header table在文件內的偏移位置UINT32 elfShoff; /* Section header table file offset *///對應`Start of section headers`,表示section header table在文件內的偏移位置UINT32 elfFlags; /* Processor-specific flags *///表示與CPU處理器架構相關的信息UINT16 elfHeadSize; /* ELF header size in bytes *///對應`Size of this header`,表示本ELF header自身的長度UINT16 elfPhEntSize; /* Program header table entry size *///對應`Size of program headers`,表示program header table中每個元素的大小UINT16 elfPhNum; /* Program header table entry count *///對應`Number of program headers`,表示program header table中元素個數UINT16 elfShEntSize; /* Section header table entry size *///對應`Size of section headers`,表示section header table中每個元素的大小UINT16 elfShNum; /* Section header table entry count *///對應`Number of section headers`,表示section header table中元素的個數UINT16 elfShStrIndex; /* Section header string table index *///對應`Section header string table index`,表示描述各section字符名稱的string table在section header table中的下標
} LDElf32Ehdr;
root@5e3abe332c5a:/home/docker/case_code_100# readelf -h app
ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Shared object file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x1060Start of program headers: 64 (bytes into file)Start of section headers: 14784 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 13Size of section headers: 64 (bytes)Number of section headers: 31Section header string table index: 30
解讀
顯示的信息,就是 ELF header 中描述的所有內容了。這個內容與結構體?LDElf32Ehdr
?中的成員變量是一一對應的!
Size of this header: 64 (bytes)
也就是說:ELF header 部分的內容,一共是 64 個字節。64個字節碼長啥樣可以用命令od -Ax -t x1 -N 64 app
看,并對照結構體LDElf32Ehdr
來理解.
root@5e3abe332c5a:/home/docker/case_code_100/51# od -Ax -t x1 -N 64 app
000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
000010 03 00 3e 00 01 00 00 00 60 10 00 00 00 00 00 00
000020 40 00 00 00 00 00 00 00 c0 39 00 00 00 00 00 00
000030 00 00 00 00 40 00 38 00 0d 00 40 00 1f 00 1e 00
000040
簡單解釋一下命令的幾個選項:
-Ax: 顯示地址的時候,用十六進制來表示。如果使用 -Ad,意思就是用十進制來顯示地址;
-t -x1: 顯示字節碼內容的時候,使用十六進制(x),每次顯示一個字節(1);
-N 64:只需要讀取64個字節;
這里留意這幾個內容,下面會說明,先記住.
Entry point address: 0x1060 //代碼區 .text 起始位置,即程序運行開始位置
Size of program headers: 56 (bytes)//每個段頭大小
Number of program headers: 13 //段數量
Size of section headers: 64 (bytes)//每個區頭大小
Number of section headers: 31 //區數量
Section header string table index: 30 //字符串數組索引,該區記錄所有區名稱
段(Segment)頭信息
段(Segment)信息對應鴻蒙源碼結構體為?LDElf32Phdr
,
//kernel\extended\dynload\include\los_ld_elf_pri.h
/* Program Header */
typedef struct {UINT32 type; /* Segment type */ //段類型UINT32 offset; /* Segment file offset */ //此數據成員給出本段內容在文件中的位置,即段內容的開始位置相對于文件開頭的偏移量.UINT32 vAddr; /* Segment virtual address */ //此數據成員給出本段內容的開始位置在進程空間中的虛擬地址.UINT32 phyAddr; /* Segment physical address */ //此數據成員給出本段內容的開始位置在進程空間中的物理地址.對于目前大多數現代操作系統而言,應用程序中段的物理地址事先是不可知的,所以目前這個成員多數情況下保留不用,或者被操作系統改作它用.UINT32 fileSize; /* Segment size in file */ //此數據成員給出本段內容在文件中的大小,單位是字節,可以是0.UINT32 memSize; /* Segment size in memory */ //此數據成員給出本段內容在內容鏡像中的大小,單位是字節,可以是0.UINT32 flags; /* Segment flags */ //此數據成員給出了本段內容的屬性.UINT32 align; /* Segment alignment */ //對于可裝載的段來說,其p_vaddr和p_offset的值至少要向內存頁面大小對齊.
} LDElf32Phdr;
解讀
用readelf -l
查看app
段頭部表內容,先看命令返回的前半部分:
root@5e3abe332c5a:/home/docker/case_code_100# readelf -l app
Elf file type is DYN (Shared object file)
Entry point 0x1060
There are 13 program headers, starting at offset 64
Program Headers:Type Offset VirtAddr PhysAddrFileSiz MemSiz Flags AlignPHDR 0x0000000000000040 0x0000000000000040 0x00000000000000400x00000000000002d8 0x00000000000002d8 R 0x8INTERP 0x0000000000000318 0x0000000000000318 0x00000000000003180x000000000000001c 0x000000000000001c R 0x1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD 0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000618 0x0000000000000618 R 0x1000LOAD 0x0000000000001000 0x0000000000001000 0x00000000000010000x0000000000000225 0x0000000000000225 R E 0x1000LOAD 0x0000000000002000 0x0000000000002000 0x00000000000020000x0000000000000190 0x0000000000000190 R 0x1000LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db80x0000000000000260 0x0000000000000268 RW 0x1000DYNAMIC 0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc80x00000000000001f0 0x00000000000001f0 RW 0x8NOTE 0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000020 0x0000000000000020 R 0x8NOTE 0x0000000000000358 0x0000000000000358 0x00000000000003580x0000000000000044 0x0000000000000044 R 0x4GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000020 0x0000000000000020 R 0x8GNU_EH_FRAME 0x000000000000201c 0x000000000000201c 0x000000000000201c0x000000000000004c 0x000000000000004c R 0x4GNU_STACK 0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 RW 0x10GNU_RELRO 0x0000000000002db8 0x0000000000003db8 0x0000000000003db80x0000000000000248 0x0000000000000248 R 0x1
數一下一共13個段,其實在ELF頭信息也告訴了我們共13個段
Size of program headers: 56 (bytes)//每個段頭大小
Number of program headers: 13 //段數量
仔細看下這些段的開始地址和大小,發現有些段是重疊的.那是因為一個區可以被多個段所擁有.例如:0x2db8
?對應的?.init_array
區就被第四LOAD
?和?GNU_RELRO
兩段所共有.
PHDR
,此類型header元素描述了program header table自身的信息.從這里的內容看出,示例程序的program header table在文件中的偏移(Offset
)為0x40
,即64號字節處.該段映射到進程空間的虛擬地址(VirtAddr
)為0x40
.PhysAddr
暫時不用,其保持和VirtAddr
一致.該段占用的文件大小FileSiz
為0x2d8
.運行時占用進程空間內存大小MemSiz
也為0x2d8
.Flags
標記表示該段的讀寫權限,這里R
表示只讀,Align
對齊為8,表明本段按8字節對齊.
INTERP
,此類型header元素描述了一個特殊內存段,該段內存記錄了動態加載解析器的訪問路徑字符串.示例程序中,該段內存位于文件偏移0x318
處,即緊跟program header table.映射的進程虛擬地址空間地址為0x318
.文件長度和內存映射長度均為0x1c
,即28個字符,具體內容為/lib64/ld-linux-x86-64.so.2
.段屬性為只讀,并按字節對齊.
LOAD
,此類型header
元素描述了可加載到進程空間的代碼區或數據區:
- 其第二段包含了代碼區,文件內偏移為0x1000,文件大小為0x225,映射到進程地址0x001000處,屬性為只讀可執行(RE),段地址按0x1000(4K)邊界對齊.
- 其第四段包含了數據區,文件內偏移為0x2db8,文件大小為0x260,映射到進程地址0x003db8處,屬性為可讀可寫(RW),段地址也按0x1000(4K)邊界對齊.
DYNAMIC
,此類型header
元素描述了動態加載段,其內部通常包含了一個名為.dynamic
的動態加載區.這也是一個數組,每個元素描述了與動態加載相關的各方面信息,將在系列篇(動態加載篇)中介紹.該段是從文件偏移0x2dc8
處開始,長度為0x1f0
,并映射到進程的0x3dc8
.可見該段和上一個段LOAD4 0x2db8
是有重疊的.
GNU_STACK
,可執行棧,即棧區,在加載段的過程中,當發現存在PT_GNU_STACK,也就是GNU_STACK segment 的存在,如果存在這個這個段的話,看這個段的 flags 是否有可執行權限,來設置對應的值.必須為RW方式.
再看命令返回內容的后半部分-段區映射關系
Section to Segment mapping:Segment Sections...0001 .interp02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt03 .init .plt .plt.got .plt.sec .text .fini04 .rodata .eh_frame_hdr .eh_frame05 .init_array .fini_array .dynamic .got .data .bss06 .dynamic07 .note.gnu.property08 .note.gnu.build-id .note.ABI-tag09 .note.gnu.property10 .eh_frame_hdr1112 .init_array .fini_array .dynamic .got
13個段和31個區的映射關系,右邊其實不止31個區,是因為一個區可以共屬于多個段,例如?.dynamic
?,.interp
,.got
Segment:Section(M:N)是多對多的包含關系.Segment是由多個Section組成,Section也能屬于多個段.這個很重要,說第二遍了.
INTERP
段只包含了.interp
區LOAD2
段包含.interp
、.plt
、.text
等區,.text
代碼區位于這個段. 這個段是 'RE’屬性,只讀可執行的.LOAD4
包含.dynamic
、.data
、.bss
等區, 數據區位于這個段.這個段是 'RW’屬性,可讀可寫.?.data
、.bss
都是數據區,有何區別呢?.data(ZI data)
它用來存放初始化了的(initailized)全局變量(global)和初始化了的靜態變量(static)..bss(RW data )
它用來存放未初始化的(uninitailized)全局變量(global)和未初始化的靜態變量.DYNAMIC
段包含.dynamic
區.
區表
區(section)頭表信息對應鴻蒙源碼結構體為?LDElf32Shdr
,
//kernel\extended\dynload\include\los_ld_elf_pri.h
/* Section header */
typedef struct {UINT32 shName; /* Section name (string tbl index) *///表示每個區的名字UINT32 shType; /* Section type *///表示每個區的功能UINT32 shFlags; /* Section flags *///表示每個區的屬性UINT32 shAddr; /* Section virtual addr at execution *///表示每個區的進程映射地址UINT32 shOffset; /* Section file offset *///表示文件內偏移UINT32 shSize; /* Section size in bytes *///表示區的大小UINT32 shLink; /* Link to another section *///Link和Info記錄不同類型區的相關信息UINT32 shInfo; /* Additional section information *///Link和Info記錄不同類型區的相關信息UINT32 shAddrAlign; /* Section alignment *///表示區的對齊單位UINT32 shEntSize; /* Entry size if section holds table *///表示區中每個元素的大小(如果該區為一個數組的話,否則該值為0)
} LDElf32Shdr;
示例程序共生成31個區.其實在頭文件中也已經告訴我們了
Size of section headers: 64 (bytes)//每個區頭大小
Number of section headers: 31 //區數量
通過readelf -S
命令看看示例程序中 section header table的內容,如下所示.
root@5e3abe332c5a:/home/docker/case_code_100# readelf -S app
There are 31 section headers, starting at offset 0x39c0:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align[ 0] NULL 0000000000000000 000000000000000000000000 0000000000000000 0 0 0[ 1] .interp PROGBITS 0000000000000318 00000318000000000000001c 0000000000000000 A 0 0 1[ 2] .note.gnu.propert NOTE 0000000000000338 000003380000000000000020 0000000000000000 A 0 0 8[ 3] .note.gnu.build-i NOTE 0000000000000358 000003580000000000000024 0000000000000000 A 0 0 4[ 4] .note.ABI-tag NOTE 000000000000037c 0000037c0000000000000020 0000000000000000 A 0 0 4[ 5] .gnu.hash GNU_HASH 00000000000003a0 000003a00000000000000024 0000000000000000 A 6 0 8[ 6] .dynsym DYNSYM 00000000000003c8 000003c800000000000000a8 0000000000000018 A 7 1 8[ 7] .dynstr STRTAB 0000000000000470 000004700000000000000084 0000000000000000 A 0 0 1[ 8] .gnu.version VERSYM 00000000000004f4 000004f4000000000000000e 0000000000000002 A 6 0 2[ 9] .gnu.version_r VERNEED 0000000000000508 000005080000000000000020 0000000000000000 A 7 1 8[10] .rela.dyn RELA 0000000000000528 0000052800000000000000d8 0000000000000018 A 6 0 8[11] .rela.plt RELA 0000000000000600 000006000000000000000018 0000000000000018 AI 6 24 8[12] .init PROGBITS 0000000000001000 00001000000000000000001b 0000000000000000 AX 0 0 4[13] .plt PROGBITS 0000000000001020 000010200000000000000020 0000000000000010 AX 0 0 16[14] .plt.got PROGBITS 0000000000001040 000010400000000000000010 0000000000000010 AX 0 0 16[15] .plt.sec PROGBITS 0000000000001050 000010500000000000000010 0000000000000010 AX 0 0 16[16] .text PROGBITS 0000000000001060 0000106000000000000001b5 0000000000000000 AX 0 0 16[17] .fini PROGBITS 0000000000001218 00001218000000000000000d 0000000000000000 AX 0 0 4[18] .rodata PROGBITS 0000000000002000 00002000000000000000001b 0000000000000000 A 0 0 4[19] .eh_frame_hdr PROGBITS 000000000000201c 0000201c000000000000004c 0000000000000000 A 0 0 4[20] .eh_frame PROGBITS 0000000000002068 000020680000000000000128 0000000000000000 A 0 0 8[21] .init_array INIT_ARRAY 0000000000003db8 00002db80000000000000008 0000000000000008 WA 0 0 8[22] .fini_array FINI_ARRAY 0000000000003dc0 00002dc00000000000000008 0000000000000008 WA 0 0 8[23] .dynamic DYNAMIC 0000000000003dc8 00002dc800000000000001f0 0000000000000010 WA 7 0 8[24] .got PROGBITS 0000000000003fb8 00002fb80000000000000048 0000000000000008 WA 0 0 8[25] .data PROGBITS 0000000000004000 000030000000000000000018 0000000000000000 WA 0 0 8[26] .bss NOBITS 0000000000004018 000030180000000000000008 0000000000000000 WA 0 0 1[27] .comment PROGBITS 0000000000000000 00003018000000000000002a 0000000000000001 MS 0 0 1[28] .symtab SYMTAB 0000000000000000 000030480000000000000648 0000000000000018 29 46 8[29] .strtab STRTAB 0000000000000000 000036900000000000000216 0000000000000000 0 0 1[30] .shstrtab STRTAB 0000000000000000 000038a6000000000000011a 0000000000000000 0 0 1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)
String Table
在 ELF header 的最后 2 個字節是 0x1e 0x00,即30. 它對應結構體中的成員?elfShStrIndex
,意思是這個 ELF 文件中,字符串表是一個普通的 Section,在這個 Section 中,存儲了 ELF 文件中使用到的所有的字符串。
我們使用readelf -x
讀出下標30區的數據:
root@5e3abe332c5a:/home/docker/case_code_100# readelf -x 30 app Hex dump of section '.shstrtab':0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab0x00000010 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte0x00000020 7270002e 6e6f7465 2e676e75 2e70726f rp..note.gnu.pro0x00000030 70657274 79002e6e 6f74652e 676e752e perty..note.gnu.0x00000040 6275696c 642d6964 002e6e6f 74652e41 build-id..note.A0x00000050 42492d74 6167002e 676e752e 68617368 BI-tag..gnu.hash0x00000060 002e6479 6e73796d 002e6479 6e737472 ..dynsym..dynstr0x00000070 002e676e 752e7665 7273696f 6e002e67 ..gnu.version..g0x00000080 6e752e76 65727369 6f6e5f72 002e7265 nu.version_r..re0x00000090 6c612e64 796e002e 72656c61 2e706c74 la.dyn..rela.plt0x000000a0 002e696e 6974002e 706c742e 676f7400 ..init..plt.got.0x000000b0 2e706c74 2e736563 002e7465 7874002e .plt.sec..text..0x000000c0 66696e69 002e726f 64617461 002e6568 fini..rodata..eh0x000000d0 5f667261 6d655f68 6472002e 65685f66 _frame_hdr..eh_f0x000000e0 72616d65 002e696e 69745f61 72726179 rame..init_array0x000000f0 002e6669 6e695f61 72726179 002e6479 ..fini_array..dy0x00000100 6e616d69 63002e64 61746100 2e627373 namic..data..bss0x00000110 002e636f 6d6d656e 7400 ..comment.
可以發現,這里其實是一堆字符串,這些字符串對應的就是各個區的名字.因此section header table中每個元素的Name字段其實是這個string table的索引.為節省空間而做的設計,再回頭看看ELF header中的?elfShStrIndex
,
Section header string table index: 30 //字符串數組索引,該區記錄所有區名稱
它的值正好就是30,指向了當前的string table.
符號表 Symbol Table
Section Header Table中,還有一類SYMTAB
(DYNSYM)區,該區叫符號表.符號表中的每個元素對應一個符號,記錄了每個符號對應的實際數值信息,通常用在重定位過程中或問題定位過程中,進程執行階段并不加載符號表.符號表對應鴻蒙源碼結構體為?LDElf32Sym
.
//kernel\extended\dynload\include\los_ld_elf_pri.h
/* Symbol table */
typedef struct {UINT32 stName; /* Symbol table name (string tbl index) *///表示符號對應的源碼字符串,為對應String Table中的索引UINT32 stValue; /* Symbol table value *///表示符號對應的數值UINT32 stSize; /* Symbol table size *///表示符號對應數值的空間占用大小UINT8 stInfo; /* Symbol table type and binding *///表示符號的相關信息 如符號類型(變量符號、函數符號)UINT8 stOther; /* Symbol table visibility */UINT16 stShndx; /* Section table index *///表示與該符號相關的區的索引,例如函數符號與對應的代碼區相關
} LDElf32Sym;
用readelf -s
讀出示例程序中的符號表,如下所示
root@5e3abe332c5a:/home/docker/case_code_100# readelf -s appSymbol table '.dynsym' contains 7 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)Symbol table '.symtab' contains 67 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND1: 0000000000000318 0 SECTION LOCAL DEFAULT 12: 0000000000000338 0 SECTION LOCAL DEFAULT 23: 0000000000000358 0 SECTION LOCAL DEFAULT 34: 000000000000037c 0 SECTION LOCAL DEFAULT 45: 00000000000003a0 0 SECTION LOCAL DEFAULT 56: 00000000000003c8 0 SECTION LOCAL DEFAULT 67: 0000000000000470 0 SECTION LOCAL DEFAULT 78: 00000000000004f4 0 SECTION LOCAL DEFAULT 89: 0000000000000508 0 SECTION LOCAL DEFAULT 910: 0000000000000528 0 SECTION LOCAL DEFAULT 1011: 0000000000000600 0 SECTION LOCAL DEFAULT 1112: 0000000000001000 0 SECTION LOCAL DEFAULT 1213: 0000000000001020 0 SECTION LOCAL DEFAULT 1314: 0000000000001040 0 SECTION LOCAL DEFAULT 1415: 0000000000001050 0 SECTION LOCAL DEFAULT 1516: 0000000000001060 0 SECTION LOCAL DEFAULT 1617: 0000000000001218 0 SECTION LOCAL DEFAULT 1718: 0000000000002000 0 SECTION LOCAL DEFAULT 1819: 000000000000201c 0 SECTION LOCAL DEFAULT 1920: 0000000000002068 0 SECTION LOCAL DEFAULT 2021: 0000000000003db8 0 SECTION LOCAL DEFAULT 2122: 0000000000003dc0 0 SECTION LOCAL DEFAULT 2223: 0000000000003dc8 0 SECTION LOCAL DEFAULT 2324: 0000000000003fb8 0 SECTION LOCAL DEFAULT 2425: 0000000000004000 0 SECTION LOCAL DEFAULT 2526: 0000000000004018 0 SECTION LOCAL DEFAULT 2627: 0000000000000000 0 SECTION LOCAL DEFAULT 2728: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c29: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones30: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones31: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux32: 0000000000004018 1 OBJECT LOCAL DEFAULT 26 completed.806033: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtors_aux_fin34: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy35: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_init_array_36: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c37: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c38: 000000000000218c 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__39: 0000000000000000 0 FILE LOCAL DEFAULT ABS40: 0000000000003dc0 0 NOTYPE LOCAL DEFAULT 21 __init_array_end41: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC42: 0000000000003db8 0 NOTYPE LOCAL DEFAULT 21 __init_array_start43: 000000000000201c 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR44: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_45: 0000000000001000 0 FUNC LOCAL DEFAULT 12 _init46: 0000000000001210 5 FUNC GLOBAL DEFAULT 16 __libc_csu_fini47: 0000000000004010 8 OBJECT GLOBAL DEFAULT 25 my_name48: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab49: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start50: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 25 _edata51: 0000000000001218 0 FUNC GLOBAL HIDDEN 17 _fini52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.553: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_54: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start55: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__56: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle57: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used58: 00000000000011a0 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init59: 0000000000004020 0 NOTYPE GLOBAL DEFAULT 26 _end60: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start61: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 __bss_start62: 0000000000001174 30 FUNC GLOBAL DEFAULT 16 main63: 0000000000001149 43 FUNC GLOBAL DEFAULT 16 say_hello64: 0000000000004018 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__65: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable66: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2
在最后位置找到了親切的老朋友?main
和say_hello
62: 0000000000001174 30 FUNC GLOBAL DEFAULT 16 main63: 0000000000001149 43 FUNC GLOBAL DEFAULT 16 say_hello
main
函數符號對應的數值為0x1174
,其類型為FUNC
,大小為30字節,對應的代碼區索引為16.
say_hello
函數符號對應數值為0x1149
,其類型為FUNC
,大小為43字節,對應的代碼區索引同為16.
Section Header Table:
[16] .text PROGBITS 0000000000001060 0000106000000000000001b5 0000000000000000 AX 0 0 16
反匯編代碼區
在理解了String Table
和Symbol Table
的作用后,通過objdump
反匯編來理解一下.text
代碼區:
root@5e3abe332c5a:/home/docker/case_code_100# objdump -j .text -l -C -S app0000000000001149 <say_hello>:
say_hello():1149: f3 0f 1e fa endbr64114d: 55 push %rbp114e: 48 89 e5 mov %rsp,%rbp1151: 48 83 ec 10 sub $0x10,%rsp1155: 48 89 7d f8 mov %rdi,-0x8(%rbp)1159: 48 8b 45 f8 mov -0x8(%rbp),%rax115d: 48 89 c6 mov %rax,%rsi1160: 48 8d 3d 9d 0e 00 00 lea 0xe9d(%rip),%rdi # 2004 <_IO_stdin_used+0x4>1167: b8 00 00 00 00 mov $0x0,%eax116c: e8 df fe ff ff callq 1050 <printf@plt>1171: 90 nop1172: c9 leaveq1173: c3 retq0000000000001174 <main>:
main():1174: f3 0f 1e fa endbr641178: 55 push %rbp1179: 48 89 e5 mov %rsp,%rbp117c: 48 8b 05 8d 2e 00 00 mov 0x2e8d(%rip),%rax # 4010 <my_name>1183: 48 89 c7 mov %rax,%rdi1186: e8 be ff ff ff callq 1149 <say_hello>118b: b8 00 00 00 00 mov $0x0,%eax1190: 5d pop %rbp1191: c3 retq1192: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)1199: 00 00 00119c: 0f 1f 40 00 nopl 0x0(%rax)
0x1149
?0x1174
正是say_hello
,main
函數的入口地址.并看到了激動人心的指令
1186: e8 be ff ff ff callq 1149 <say_hello>
很佩服你還能看到這里,牛逼,牛逼! 看了這么久還記得開頭的C代碼的樣子嗎? 再看一遍 : )
#include <stdio.h>
void say_hello(char *who)
{printf("hello, %s!\n", who);
}
char *my_name = "harmony os";
int main()
{say_hello(my_name);return 0;
}
root@5e3abe332c5a:/home/docker/case_code_100# ./app
hello, harmony os!
但是!!! 暈,怎么還有but,西卡西…,上面請大家記住的還有一個地方沒說到
Entry point address: 0x1060 //代碼區 .text 起始位置,即程序運行開始位置
它的地址并不是main函數位置0x1174
,是0x1060
!而且代碼區的開始位置是0x1060
沒錯的.
[16] .text PROGBITS 0000000000001060 0000106000000000000001b5 0000000000000000 AX 0 0 16
難度main
不是入口地址? 那0x1060
上放的是何方神圣,再查符號表發現是
60: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start
從反匯編堆中找到?_start
0000000000001060 <_start>:
_start():1060: f3 0f 1e fa endbr641064: 31 ed xor %ebp,%ebp1066: 49 89 d1 mov %rdx,%r91069: 5e pop %rsi106a: 48 89 e2 mov %rsp,%rdx106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp1071: 50 push %rax1072: 54 push %rsp1073: 4c 8d 05 96 01 00 00 lea 0x196(%rip),%r8 # 1210 <__libc_csu_fini>107a: 48 8d 0d 1f 01 00 00 lea 0x11f(%rip),%rcx # 11a0 <__libc_csu_init>1081: 48 8d 3d ec 00 00 00 lea 0xec(%rip),%rdi # 1174 <main>1088: ff 15 52 2f 00 00 callq *0x2f52(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>108e: f4 hlt108f: 90 nop
這才看到了0x1174
的main
函數.所以真正的說法是:
- 從內核動態加載的視角看,程序運行首個函數并不是
main
,而是_start
. - 但從應用程序開發者視角看,
main
就是啟動函數.
鴻蒙全棧開發全新學習指南
也為了積極培養鴻蒙生態人才,讓大家都能學習到鴻蒙開發最新的技術,針對一些在職人員、0基礎小白、應屆生/計算機專業、鴻蒙愛好者等人群,整理了一套純血版鴻蒙(HarmonyOS Next)全棧開發技術的學習路線【包含了大廠APP實戰項目開發】。
本路線共分為四個階段:
第一階段:鴻蒙初中級開發必備技能
第二階段:鴻蒙南北雙向高工技能基礎:gitee.com/MNxiaona/733GH
第三階段:應用開發中高級就業技術
第四階段:全網首發-工業級南向設備開發就業技術:https://gitee.com/MNxiaona/733GH
《鴻蒙 (Harmony OS)開發學習手冊》(共計892頁)
如何快速入門?
1.基本概念
2.構建第一個ArkTS應用
3.……
開發基礎知識:gitee.com/MNxiaona/733GH
1.應用基礎知識
2.配置文件
3.應用數據管理
4.應用安全管理
5.應用隱私保護
6.三方應用調用管控機制
7.資源分類與訪問
8.學習ArkTS語言
9.……
基于ArkTS 開發
1.Ability開發
2.UI開發
3.公共事件與通知
4.窗口管理
5.媒體
6.安全
7.網絡與鏈接
8.電話服務
9.數據管理
10.后臺任務(Background Task)管理
11.設備管理
12.設備使用信息統計
13.DFX
14.國際化開發
15.折疊屏系列
16.……
鴻蒙開發面試真題(含參考答案):gitee.com/MNxiaona/733GH
鴻蒙入門教學視頻:
美團APP實戰開發教學:gitee.com/MNxiaona/733GH
寫在最后
- 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
- 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
- 關注小編,同時可以期待后續文章ing🚀,不定期分享原創知識。
- 想要獲取更多完整鴻蒙最新學習資源,請移步前往小編:
gitee.com/MNxiaona/733GH