天天看點

鴻蒙核心源碼分析(ELF格式篇) | 應用程式入口并不是main | 百篇部落格分析HarmonyOS源碼 | v51.04

鴻蒙核心源碼分析(ELF格式篇) | 應用程式入口并不是main | 百篇部落格分析HarmonyOS源碼 | v51.04

百篇部落格系列篇.本篇為:

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 分四塊,其中三塊是描述資訊(也叫頭資訊),另一塊是内容,放的是所有段/區的内容.
    1. ELF頭定義全局性資訊
    1. Segment(段)頭,内容描述段的名字,開始位置,類型,偏移,大小及每段由哪些區組成.
    1. 内容區,ELF有兩個重要概念

      Segment

      (段) 和

      Section

      (區),段比區大,二者之間關系如下:
    • 每個

      Segment

      可以包含多個

      Section

    • 每個

      Section

      可以屬于多個

      Segment

    • Segment

      之間可以有重合的部分
    • 拿大家熟知的

      .text

      .data

      .bss

      舉例,它們都叫區,但它們又屬于

      LOAD

      段.
    1. 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格式篇) | 應用程式入口并不是main | 百篇部落格分析HarmonyOS源碼 | v51.04

從上圖可見,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;
[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

    等區,

    .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的内容,如下所示.

[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篇

檔案句柄篇

管道檔案篇

彙編基礎篇

彙編傳參篇

工作模式篇

寄存器篇

異常接管篇

彙編彙總篇

中斷切換篇

中斷概念篇

中斷管理篇

繼續閱讀