>> 下载最新.鸿蒙内核源码分析.百篇博客内容.pdf < gitee | github >
百篇博客系列篇.本篇为:
先说明,本篇很长,也很枯燥,若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF
究竟长了怎样的一副花花肠子,用readelf
命令去窥视ELF的全貌,最后用objdump
命令反汇编ELF
.找到了大家熟悉main
函数.
开始之前先说结论:
Segment
(段) 和 Section
(区),段比区大,是包含关系,一个段可由多区组成,一个区可被多段所共有..text
,.data
,.bss
所有区的内容,有的地方翻译成节,但为了表述严谨,系列篇将统一说代码区,数据区,不再说代码段.EFL
的定义在 kernel\extended\dynload\include\los_ld_elf_pri.h
文件中在windows目录E:\harmony\docker\test4harmony
下创建 main.c文件,如下:
#include
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中.编译生成ELF
->运行->readelf -h
查看app
头部信息.
root@5e3abe332c5a:/home/docker/test4harmony# ls
main.c
root@5e3abe332c5a:/home/docker/test4harmony# gcc -o app main.c
root@5e3abe332c5a:/home/docker/test4harmony# ls
app main.c
root@5e3abe332c5a:/home/docker/test4harmony# ./app
hello, harmony os!
root@5e3abe332c5a:/home/docker/test4harmony# 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
这里留意这几个内容,下面会说明,先记住.
Entry point address: 0x1060 //代码区 .text 起始位置,即程序运行开始位置
Number of program headers: 13 //段数量
Number of section headers: 31 //区数量
Section header string table index: 30 //字符串数组索引,该区记录所有区名称
ELF(Executable and Linking Format),即"可执行可连接格式",最初由UNIX系统实验室(UNIX System Laboratories – USL)做为应用程序二进制接口(Application Binary Interface - ABI)的一部分而制定和发布.是鸿蒙的主要可执行文件格式.
ELF的最大特点在于它有比较广泛的适用性,通用的二进制接口定义使之可以平滑地移植到多种不同的操作环境上.这样,不需要为每一种操作系统都定义一套不同的接口,因此减少了软件的重复编码与编译,加强了软件的可移植性.
一下是关于ELF的所有中英名词对照.建议先仔细看一篇再看下面的部分.
可执行可连接格式 : ELF
ELF文件头:ELF header
基地址:base address
动态连接器: dynamic linker
动态连接: dynamic linking
全局偏移量表: global offset 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文件宽泛地称为"目标文件 (object file)",这与我们平时的理解不同.一般地,我们把经过编译但没有连接的文件(比如Unix/Linux上的.o文件)称为目标文件,而ELF文件仅指连接好的可执行文件;在ELF规范中,所有符合ELF格式规范的都称为ELF文件,也称为目标文件,这两个名字是相同的,而经过编译但没有连接的文件则称为"可重定位文件 (relocatable file)“或"待重定位文件 (relocatable file)”.本文采用与此规范相同的命名方式,所以当提到可重定位文件时,一般可以理解为惯常所说的目标文件;而提到目标文件时,即指各种类型的ELF文件.
ELF格式可以表达四种类型的二进制对象文件(object files):
可重定位文件用在编译和链接阶段.可执行文件用在程序运行阶段.共享库则同时用在编译链接和运行阶段.在不同阶段,我们可以用不同视角来理解ELF
文件,整体布局如下图所示:
从上图可见,ELF格式文件整体可分为四大部分:
readelf -h app
看到的内容readelf -l app
看到的前半部分内容.readelf -l app
后半部分内容,readelf -x ** app
可查看区具体内容.注意: Segments
是从运行的角度来描述 ELF
文件,Sections
是从链接的角度来描述 ELF
文件.也就是说,在链接阶段,我们可以忽略 program header table 来处理此文件,在运行阶段可以忽略 section header table 来处理此程序(所以很多加固手段删除了section header table).readelf -S app
看到的内容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;
结构体对应readelf -h app
命令内容来理解,就不再贴出来了.
段(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 */
UINT32 memSize; /* Segment size in memory */
UINT32 flags; /* Segment flags */
UINT32 align; /* Segment alignment */
} LDElf32Phdr;
用readelf -l
查看app
段头部表内容
root@5e3abe332c5a:/home/docker/test4harmony# 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
.........
解读
先看命令返回的前半部分:
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个段
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
元素描述了可加载到进程空间的代码区或数据区:
DYNAMIC
,此类型header
元素描述了动态加载段,其内部通常包含了一个名为.dynamic
的动态加载区.这也是一个数组,每个元素描述了与动态加载相关的各方面信息,将在系列篇(动态加载篇)中介绍.该段是从文件偏移0x2dc8
处开始,长度为0x1f0
,并映射到进程的0x3dc8
.可见该段和上一个段LOAD4 0x2db8
是有重叠的.
再看命令返回内容的后半部分-段区映射关系
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个区.其实在头文件中也已经告诉我们了
Number of section headers: 31 //区数量
通过readelf -S
命令看看示例程序中 section header table的内容,如下所示.
root@5e3abe332c5a:/home/docker/test4harmony# 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)
从上述Section Header Table示例中,我们看到有一种类型为STRTAB
的区(在Section Header Table中的下标为7,29,30).此类区叫做String Table,其作用是集中记录字符串信息,其它区在需要使用字符串的时候,只需要记录字符串起始地址在该String Table表中的偏移即可,而无需包含整个字符串内容.
我们使用readelf -x
读出下标30区的数据:
root@5e3abe332c5a:/home/docker/test4harmony# 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.
再来看下29区的内容,如下所示
root@5e3abe332c5a:/home/docker/test4harmony# readelf -x 29 app
Hex dump of section '.strtab':
0x00000000 00637274 73747566 662e6300 64657265 .crtstuff.c.dere
0x00000010 67697374 65725f74 6d5f636c 6f6e6573 gister_tm_clones
0x00000020 005f5f64 6f5f676c 6f62616c 5f64746f .__do_global_dto
0x00000030 72735f61 75780063 6f6d706c 65746564 rs_aux.completed
0x00000040 2e383036 30005f5f 646f5f67 6c6f6261 .8060.__do_globa
0x00000050 6c5f6474 6f72735f 6175785f 66696e69 l_dtors_aux_fini
0x00000060 5f617272 61795f65 6e747279 00667261 _array_entry.fra
0x00000070 6d655f64 756d6d79 005f5f66 72616d65 me_dummy.__frame
0x00000080 5f64756d 6d795f69 6e69745f 61727261 _dummy_init_arra
0x00000090 795f656e 74727900 6d61696e 2e63005f y_entry.main.c._
0x000000a0 5f465241 4d455f45 4e445f5f 005f5f69 _FRAME_END__.__i
0x000000b0 6e69745f 61727261 795f656e 64005f44 nit_array_end._D
0x000000c0 594e414d 4943005f 5f696e69 745f6172 YNAMIC.__init_ar
0x000000d0 7261795f 73746172 74005f5f 474e555f ray_start.__GNU_
0x000000e0 45485f46 52414d45 5f484452 005f474c EH_FRAME_HDR._GL
0x000000f0 4f42414c 5f4f4646 5345545f 5441424c OBAL_OFFSET_TABL
0x00000100 455f005f 5f6c6962 635f6373 755f6669 E_.__libc_csu_fi
0x00000110 6e69006d 795f6e61 6d65005f 49544d5f ni.my_name._ITM_
0x00000120 64657265 67697374 6572544d 436c6f6e deregisterTMClon
0x00000130 65546162 6c65005f 65646174 61007072 eTable._edata.pr
0x00000140 696e7466 4040474c 4942435f 322e322e intf@@GLIBC_2.2.
0x00000150 35005f5f 6c696263 5f737461 72745f6d 5.__libc_start_m
0x00000160 61696e40 40474c49 42435f32 2e322e35 ain@@GLIBC_2.2.5
0x00000170 005f5f64 6174615f 73746172 74005f5f .__data_start.__
0x00000180 676d6f6e 5f737461 72745f5f 005f5f64 gmon_start__.__d
0x00000190 736f5f68 616e646c 65005f49 4f5f7374 so_handle._IO_st
0x000001a0 64696e5f 75736564 005f5f6c 6962635f din_used.__libc_
0x000001b0 6373755f 696e6974 005f5f62 73735f73 csu_init.__bss_s
0x000001c0 74617274 006d6169 6e007361 795f6865 tart.main.say_he
0x000001d0 6c6c6f00 5f5f544d 435f454e 445f5f00 llo.__TMC_END__.
0x000001e0 5f49544d 5f726567 69737465 72544d43 _ITM_registerTMC
0x000001f0 6c6f6e65 5461626c 65005f5f 6378615f loneTable.__cxa_
0x00000200 66696e61 6c697a65 4040474c 4942435f finalize@@GLIBC_
0x00000210 322e322e 3500 2.2.5.
这里看到了main
、say_hello
字符串,这些是在示例中源码中定义的符号,由此29区是应用自身的String Table,记录了应用程序使用的字符串.这个表就是符号表的一部分.
通过readelf -s
命令能拿到全部符号信息.如下
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/test4harmony# 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 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_registerTMCloneTable
6: 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 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 printf@@GLIBC_2.2.5
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@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 __cxa_finalize@@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
代码区:
root@5e3abe332c5a:/home/docker/test4harmony# objdump -j .text -l -C -S app
0000000000001149 :
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
1171: 90 nop
1172: c9 leaveq
1173: c3 retq
0000000000001174 :
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
1183: 48 89 c7 mov %rax,%rdi
1186: e8 be ff ff ff callq 1149
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
很佩服你还能看到这里,牛逼,牛逼! 看了这么久还记得开头的C代码的样子吗? 再看一遍 : )
#include
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/test4harmony# ./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
1088: ff 15 52 2f 00 00 callq *0x2f52(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
108e: f4 hlt
108f: 90 nop
这才看到了0x1174
的main
函数.所以真正的说法是:
main
,而是_start
.main
就是启动函数.在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆.
说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.
与写代码有bug需不断debug一样,文章和注解内容会反复修正,持续更新,.xx
代表修改的次数,精雕细琢,言简意赅,尽全力打磨精品内容.
>> 下载最新.鸿蒙内核源码分析.百篇博客内容.pdf < gitee | github >
v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51 .c .h .o
v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙看这篇或许真的够了 | 51 .c .h .o
v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 51 .c .h .o
v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51 .c .h .o
v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51 .c .h .o
v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 | 51 .c .h .o
v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51 .c .h .o
v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51 .c .h .o
v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51 .c .h .o
v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51 .c .h .o
v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51 .c .h .o
v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51 .c .h .o
v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51 .c .h .o
v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51 .c .h .o
v37.xx 鸿蒙内核源码分析(系统调用篇) | 开发者永远的口头禅 | 51 .c .h .o
v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51 .c .h .o
v35.xx 鸿蒙内核源码分析(时间管理篇) | 谁是内核基本时间单位 | 51 .c .h .o
v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航 | 51 .c .h .o
v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51 .c .h .o
v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51 .c .h .o
v31.xx 鸿蒙内核源码分析(定时器篇) | 哪个任务的优先级最高 | 51 .c .h .o
v30.xx 鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案 | 51 .c .h .o
v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51 .c .h .o
v28.xx 鸿蒙内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽 | 51 .c .h .o
v27.xx 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 51 .c .h .o
v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立贞节牌坊 | 51 .c .h .o
v25.xx 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 51 .c .h .o
v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51 .c .h .o
v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51 .c .h .o
v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51 .c .h .o
v21.xx 鸿蒙内核源码分析(线程概念篇) | 是谁在不断的折腾CPU | 51 .c .h .o
v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的 | 51 .c .h .o
v19.xx 鸿蒙内核源码分析(位图管理篇) | 谁能一分钱分两半用 | 51 .c .h .o
v18.xx 鸿蒙内核源码分析(源码结构篇) | 内核每个文件的含义 | 51 .c .h .o
v17.xx 鸿蒙内核源码分析(物理内存篇) | 怎么管理物理内存 | 51 .c .h .o
v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51 .c .h .o
v15.xx 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 51 .c .h .o
v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51 .c .h .o
v13.xx 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 51 .c .h .o
v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51 .c .h .o
v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 | 51 .c .h .o
v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51 .c .h .o
v09.xx 鸿蒙内核源码分析(调度故事篇) | 用故事说内核调度过程 | 51 .c .h .o
v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51 .c .h .o
v07.xx 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 51 .c .h .o
v06.xx 鸿蒙内核源码分析(调度队列篇) | 内核有多少个调度队列 | 51 .c .h .o
v05.xx 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 51 .c .h .o
v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 51 .c .h .o
v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁的贡献最大 | 51 .c .h .o
v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51 .c .h .o
v01.xx 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 | 51 .c .h .o
百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto | csdn | harmony | osc >
热爱是所有的理由和答案 - turing
百万汉字注解鸿蒙源码,百篇博客深挖内核地基. 原创不易,欢迎转载,但麻烦请注明出处.