
百篇部落格系列篇.本篇為:
v51.xx 鴻蒙核心源碼分析(ELF格式篇) | 應用程式入口并不是main
加載運作相關篇為:
- v51.xx 鴻蒙核心源碼分析(ELF格式篇) | 應用程式入口并不是main
- v53.xx 鴻蒙核心源碼分析(ELF解析篇) | 你要忘了她姐倆你就不是銀
- v54.xx 鴻蒙核心源碼分析(靜态連結篇) | 完整小項目看透靜态連結過程
- v55.xx 鴻蒙核心源碼分析(重定位篇) | 與國際接軌的對外部發言人
- v56.xx 鴻蒙核心源碼分析(程序映像篇) | ELF是如何被加載運作的?
閱讀之前的說明
先說明,本篇很長,也很枯燥,若不是絕對的技術偏執狂是看不下去的.将通過一段簡單代碼去跟蹤編譯成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
鴻蒙核心源碼分析(ELF格式篇) | 應用程式入口并不是main | 百篇部落格分析HarmonyOS源碼 | v51.04
示例代碼
在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;
}
因在
v50.xx (編譯環境篇) | docker編譯鴻蒙真的很香
篇中已做好了環境映射,是以檔案會同時出現在docker中.編譯生成
ELF
->運作->
readelf -h
檢視
app
頭部資訊.
[email protected]:/home/docker/case_code_100# ls
main.c
[email protected]:/home/docker/case_code_100# gcc -o app main.c
[email protected]:/home/docker/case_code_100# ls
app main.c
[email protected]:/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
-
: 告訴系統如何建立程序映像.用來構造程序映像的目标檔案必須具有程式頭部表,可重定位檔案可以不需要這個表.表描述所有段(Segment)資訊,即Program Header Table
看到的前半部分内容.readelf -l app
-
:段(Segments
)由若幹區(Segment
)組成.是從加載器角度來描述Section
檔案.加載器隻關心ELF
,ELF header
和Program header table
這三部分内容。 在加載階段可以忽略 section header table 來處理程式(是以很多加強手段删除了Segment
)section header table
-
: 是從連結器角度來描述Sections
檔案. 連結器隻關心ELF
,ELF header
以及Sections
這三部分内容。在連結階段,可以忽略Section header table
來處理檔案.program header table
-
:描述區(Section Header Table
)資訊的數組,每個元素對應一個區,通常包含在可重定位檔案中,可執行檔案中為可選(通常包含) 即Section
看到的内容readelf -S app
- 從圖中可以看出
:Segment
(M:N)是多對多的包含關系.Section
是由多個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;
[email protected]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 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1060
Start of program headers: 64 (bytes into file)
Start of section headers: 14784 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
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
解讀
顯示的資訊,就是 ELF header 中描述的所有内容了。這個内容與結構體
LDElf32Ehdr
中的成員變量是一一對應的!
Size of this header: 64 (bytes)
也就是說:ELF header 部分的内容,一共是 64 個位元組。64個位元組碼長啥樣可以用指令
od -Ax -t x1 -N 64 app
看,并對照結構體
LDElf32Ehdr
來了解.
[email protected]:/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
段頭部表内容,先看指令傳回的前半部分:
[email protected]:/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 PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000618 0x0000000000000618 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000225 0x0000000000000225 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000190 0x0000000000000190 R 0x1000
LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x0000000000000260 0x0000000000000268 RW 0x1000
DYNAMIC 0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000358 0x0000000000000358 0x0000000000000358
0x0000000000000044 0x0000000000000044 R 0x4
GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
GNU_EH_FRAME 0x000000000000201c 0x000000000000201c 0x000000000000201c
0x000000000000004c 0x000000000000004c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x0000000000000248 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...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.got .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .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
代碼區位于這個段. 這個段是 'RE’屬性,隻讀可執行的..text
-
包含LOAD4
、.dynamic
、.data
等區, 資料區位于這個段.這個段是 'RW’屬性,可讀可寫..bss
、.data
都是資料區,有何差別呢?.bss
-
它用來存放初始化了的(initailized)全局變量(global)和初始化了的靜态變量(static)..data(ZI data)
-
它用來存放未初始化的(uninitailized)全局變量(global)和未初始化的靜态變量..bss(RW data )
-
段包含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的内容,如下所示.
[email protected]:/home/docker/case_code_100# readelf -S app
There are 31 section headers, starting at offset 0x39c0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000318 00000318
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000000338 00000338
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.gnu.build-i NOTE 0000000000000358 00000358
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 0000037c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 000003a0
0000000000000024 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003c8 000003c8
00000000000000a8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000470 00000470
0000000000000084 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000000004f4 000004f4
000000000000000e 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000508 00000508
0000000000000020 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000000528 00000528
00000000000000d8 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000000600 00000600
0000000000000018 0000000000000018 AI 6 24 8
[12] .init PROGBITS 0000000000001000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000001020 00001020
0000000000000020 0000000000000010 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001040 00001040
0000000000000010 0000000000000010 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001050 00001050
0000000000000010 0000000000000010 AX 0 0 16
[16] .text PROGBITS 0000000000001060 00001060
00000000000001b5 0000000000000000 AX 0 0 16
[17] .fini PROGBITS 0000000000001218 00001218
000000000000000d 0000000000000000 AX 0 0 4
[18] .rodata PROGBITS 0000000000002000 00002000
000000000000001b 0000000000000000 A 0 0 4
[19] .eh_frame_hdr PROGBITS 000000000000201c 0000201c
000000000000004c 0000000000000000 A 0 0 4
[20] .eh_frame PROGBITS 0000000000002068 00002068
0000000000000128 0000000000000000 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000003db8 00002db8
0000000000000008 0000000000000008 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000003dc0 00002dc0
0000000000000008 0000000000000008 WA 0 0 8
[23] .dynamic DYNAMIC 0000000000003dc8 00002dc8
00000000000001f0 0000000000000010 WA 7 0 8
[24] .got PROGBITS 0000000000003fb8 00002fb8
0000000000000048 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000004000 00003000
0000000000000018 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000004018 00003018
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00003018
000000000000002a 0000000000000001 MS 0 0 1
[28] .symtab SYMTAB 0000000000000000 00003048
0000000000000648 0000000000000018 29 46 8
[29] .strtab STRTAB 0000000000000000 00003690
0000000000000216 0000000000000000 0 0 1
[30] .shstrtab STRTAB 0000000000000000 000038a6
000000000000011a 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區的資料:
[email protected]:/home/docker/case_code_100# readelf -x 30 app
Hex dump of section '.shstrtab':
0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
0x00000010 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte
0x00000020 7270002e 6e6f7465 2e676e75 2e70726f rp..note.gnu.pro
0x00000030 70657274 79002e6e 6f74652e 676e752e perty..note.gnu.
0x00000040 6275696c 642d6964 002e6e6f 74652e41 build-id..note.A
0x00000050 42492d74 6167002e 676e752e 68617368 BI-tag..gnu.hash
0x00000060 002e6479 6e73796d 002e6479 6e737472 ..dynsym..dynstr
0x00000070 002e676e 752e7665 7273696f 6e002e67 ..gnu.version..g
0x00000080 6e752e76 65727369 6f6e5f72 002e7265 nu.version_r..re
0x00000090 6c612e64 796e002e 72656c61 2e706c74 la.dyn..rela.plt
0x000000a0 002e696e 6974002e 706c742e 676f7400 ..init..plt.got.
0x000000b0 2e706c74 2e736563 002e7465 7874002e .plt.sec..text..
0x000000c0 66696e69 002e726f 64617461 002e6568 fini..rodata..eh
0x000000d0 5f667261 6d655f68 6472002e 65685f66 _frame_hdr..eh_f
0x000000e0 72616d65 002e696e 69745f61 72726179 rame..init_array
0x000000f0 002e6669 6e695f61 72726179 002e6479 ..fini_array..dy
0x00000100 6e616d69 63002e64 61746100 2e627373 namic..data..bss
0x00000110 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
讀出示例程式中的符号表,如下所示
[email protected]:/home/docker/case_code_100# readelf -s app
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
6: 0000000000000000 0 FUNC WEAK DEFAULT UND [email protected]_2.2.5 (2)
Symbol table '.symtab' contains 67 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000318 0 SECTION LOCAL DEFAULT 1
2: 0000000000000338 0 SECTION LOCAL DEFAULT 2
3: 0000000000000358 0 SECTION LOCAL DEFAULT 3
4: 000000000000037c 0 SECTION LOCAL DEFAULT 4
5: 00000000000003a0 0 SECTION LOCAL DEFAULT 5
6: 00000000000003c8 0 SECTION LOCAL DEFAULT 6
7: 0000000000000470 0 SECTION LOCAL DEFAULT 7
8: 00000000000004f4 0 SECTION LOCAL DEFAULT 8
9: 0000000000000508 0 SECTION LOCAL DEFAULT 9
10: 0000000000000528 0 SECTION LOCAL DEFAULT 10
11: 0000000000000600 0 SECTION LOCAL DEFAULT 11
12: 0000000000001000 0 SECTION LOCAL DEFAULT 12
13: 0000000000001020 0 SECTION LOCAL DEFAULT 13
14: 0000000000001040 0 SECTION LOCAL DEFAULT 14
15: 0000000000001050 0 SECTION LOCAL DEFAULT 15
16: 0000000000001060 0 SECTION LOCAL DEFAULT 16
17: 0000000000001218 0 SECTION LOCAL DEFAULT 17
18: 0000000000002000 0 SECTION LOCAL DEFAULT 18
19: 000000000000201c 0 SECTION LOCAL DEFAULT 19
20: 0000000000002068 0 SECTION LOCAL DEFAULT 20
21: 0000000000003db8 0 SECTION LOCAL DEFAULT 21
22: 0000000000003dc0 0 SECTION LOCAL DEFAULT 22
23: 0000000000003dc8 0 SECTION LOCAL DEFAULT 23
24: 0000000000003fb8 0 SECTION LOCAL DEFAULT 24
25: 0000000000004000 0 SECTION LOCAL DEFAULT 25
26: 0000000000004018 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
29: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
30: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones
31: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
32: 0000000000004018 1 OBJECT LOCAL DEFAULT 26 completed.8060
33: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtors_aux_fin
34: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy
35: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
38: 000000000000218c 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
39: 0000000000000000 0 FILE LOCAL DEFAULT ABS
40: 0000000000003dc0 0 NOTYPE LOCAL DEFAULT 21 __init_array_end
41: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
42: 0000000000003db8 0 NOTYPE LOCAL DEFAULT 21 __init_array_start
43: 000000000000201c 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
44: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
45: 0000000000001000 0 FUNC LOCAL DEFAULT 12 _init
46: 0000000000001210 5 FUNC GLOBAL DEFAULT 16 __libc_csu_fini
47: 0000000000004010 8 OBJECT GLOBAL DEFAULT 25 my_name
48: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
49: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
50: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 25 _edata
51: 0000000000001218 0 FUNC GLOBAL HIDDEN 17 _fini
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]@GLIBC_2.2.5
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]@GLIBC_
54: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
55: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
56: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
57: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
58: 00000000000011a0 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init
59: 0000000000004020 0 NOTYPE GLOBAL DEFAULT 26 _end
60: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start
61: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
62: 0000000000001174 30 FUNC GLOBAL DEFAULT 16 main
63: 0000000000001149 43 FUNC GLOBAL DEFAULT 16 say_hello
64: 0000000000004018 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
65: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
66: 0000000000000000 0 FUNC WEAK DEFAULT UND [email protected]@GLIBC_2.2
在最後位置找到了親切的老朋友
main
和
say_hello
62: 0000000000001174 30 FUNC GLOBAL DEFAULT 16 main
63: 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 00001060
00000000000001b5 0000000000000000 AX 0 0 16
反彙編代碼區
在了解了
String Table
和
Symbol Table
的作用後,通過
objdump
反彙編來了解一下
.text
代碼區:
[email protected]:/home/docker/case_code_100# objdump -j .text -l -C -S app
0000000000001149 <say_hello>:
say_hello():
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 83 ec 10 sub $0x10,%rsp
1155: 48 89 7d f8 mov %rdi,-0x8(%rbp)
1159: 48 8b 45 f8 mov -0x8(%rbp),%rax
115d: 48 89 c6 mov %rax,%rsi
1160: 48 8d 3d 9d 0e 00 00 lea 0xe9d(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
1167: b8 00 00 00 00 mov $0x0,%eax
116c: e8 df fe ff ff callq 1050 <[email protected]>
1171: 90 nop
1172: c9 leaveq
1173: c3 retq
0000000000001174 <main>:
main():
1174: f3 0f 1e fa endbr64
1178: 55 push %rbp
1179: 48 89 e5 mov %rsp,%rbp
117c: 48 8b 05 8d 2e 00 00 mov 0x2e8d(%rip),%rax # 4010 <my_name>
1183: 48 89 c7 mov %rax,%rdi
1186: e8 be ff ff ff callq 1149 <say_hello>
118b: b8 00 00 00 00 mov $0x0,%eax
1190: 5d pop %rbp
1191: c3 retq
1192: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
1199: 00 00 00
119c: 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;
}
[email protected]5e3abe332c5a:/home/docker/case_code_100# ./app
hello, harmony os!
但是!!! 暈,怎麼還有but,西卡西…,上面請大家記住的還有一個地方沒說到
Entry point address: 0x1060 //代碼區 .text 起始位置,即程式運作開始位置
它的位址并不是main函數位置
0x1174
,是
0x1060
!而且代碼區的開始位置是
0x1060
沒錯的.
[16] .text PROGBITS 0000000000001060 00001060
00000000000001b5 0000000000000000 AX 0 0 16
難度
main
不是入口位址? 那
0x1060
上放的是何方神聖,再查符号表發現是
60: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start
從反彙編堆中找到
_start
0000000000001060 <_start>:
_start():
1060: f3 0f 1e fa endbr64
1064: 31 ed xor %ebp,%ebp
1066: 49 89 d1 mov %rdx,%r9
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax
1072: 54 push %rsp
1073: 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 <[email protected]_2.2.5>
108e: f4 hlt
108f: 90 nop
這才看到了
0x1174
的
main
函數.是以真正的說法是:
- 從核心動态加載的視角看,程式運作首個函數并不是
,而是main
._start
- 但從應用程式開發者視角看,
就是啟動函數.main
百篇部落格分析.深挖核心地基
給鴻蒙核心源碼加注釋過程中,整理出以下文章。内容立足源碼,常以生活場景打比方盡可能多的将核心知識點置入某種場景,具有畫面感,容易了解記憶。說别人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆诘屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切.确實有難度,自不量力,但已經出發,回頭已是不可能的了。 😛
與代碼有bug需不斷debug一樣,文章和注解内容會存在不少錯漏之處,請多包涵,但會反複修正,持續更新,
.xx
代表修改的次數,精雕細琢,言簡意赅,力求打造精品内容。
編譯建構 | 基礎工具 | 加載運作 | 程序管理 |
---|---|---|---|
編譯環境篇 編譯過程篇 環境腳本篇 建構工具篇 gn應用篇 忍者ninja篇 | 雙向連結清單篇 位圖管理篇 用棧方式篇 定時器篇 原子操作篇 時間管理篇 | ELF格式篇 ELF解析篇 靜态連結篇 重定位篇 程序映像篇 | 程序管理篇 程序概念篇 Fork篇 特殊程序篇 程序回收篇 信号生産篇 信号消費篇 Shell編輯篇 Shell解析篇 |
程序通訊 | 記憶體管理 | 前因後果 | 任務管理 |
自旋鎖篇 互斥鎖篇 程序通訊篇 信号量篇 事件控制篇 消息隊列篇 | 記憶體配置設定篇 記憶體管理篇 記憶體彙編篇 記憶體映射篇 記憶體規則篇 實體記憶體篇 | 總目錄 排程故事篇 記憶體主奴篇 源碼注釋篇 源碼結構篇 靜态站點篇 | 時鐘任務篇 任務排程篇 任務管理篇 排程隊列篇 排程機制篇 線程概念篇 并發并行篇 系統調用篇 任務切換篇 |
檔案系統 | 硬體架構 | ||
檔案概念篇 檔案系統篇 索引節點篇 挂載目錄篇 根檔案系統 字元裝置篇 VFS篇 檔案句柄篇 管道檔案篇 | 彙編基礎篇 彙編傳參篇 工作模式篇 寄存器篇 異常接管篇 彙編彙總篇 中斷切換篇 中斷概念篇 中斷管理篇 |