鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析HarmonyOS源码 | v51.04

>> 下载最新.鸿蒙内核源码分析.百篇博客内容.pdf < gitee | github >

在这里插入图片描述

百篇博客系列篇.本篇为:

  • v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51 .c .h .o

阅读之前的说明

先说明,本篇很长,也很枯燥,若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF究竟长了怎样的一副花花肠子,用readelf命令去窥视ELF的全貌,最后用objdump命令反汇编ELF.找到了大家熟悉main函数.
开始之前先说结论:

  • ELF有两个重要概念 Segment(段) 和 Section(区),段比区大,是包含关系,一个段可由多区组成,一个区可被多段所共有.
  • ELF 分四块,其中三块是描述信息(也叫头信息),另一块是内容,放的是所有区的内容.
  • 第一块ELF头定义全局性信息
  • 第二块Segment(段)头,内容描述段的名字,开始位置,类型,偏移,大小及每段由哪些区组成.
  • 第三块内容区,放.text,.data,.bss所有区的内容,有的地方翻译成节,但为了表述严谨,系列篇将统一说代码区,数据区,不再说代码段.
  • 第四块Section(区)头,内容描述区的名字,开始位置,类型,偏移,大小等信息
  • 鸿蒙对EFL的定义在 kernel\extended\dynload\include\los_ld_elf_pri.h文件中
    鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析HarmonyOS源码 | v51.04_第1张图片

示例代码

在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历史

  • 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规范中把ELF文件宽泛地称为"目标文件 (object file)",这与我们平时的理解不同.一般地,我们把经过编译但没有连接的文件(比如Unix/Linux上的.o文件)称为目标文件,而ELF文件仅指连接好的可执行文件;在ELF规范中,所有符合ELF格式规范的都称为ELF文件,也称为目标文件,这两个名字是相同的,而经过编译但没有连接的文件则称为"可重定位文件 (relocatable file)“或"待重定位文件 (relocatable file)”.本文采用与此规范相同的命名方式,所以当提到可重定位文件时,一般可以理解为惯常所说的目标文件;而提到目标文件时,即指各种类型的ELF文件.

ELF格式可以表达四种类型的二进制对象文件(object files):

  • 可重定位文件(relocatable file),用于与其它目标文件进行连接以构建可执行文件或动态链接库.可重定位文件就是常说的目标文件,由源文件编译而成,但还没有连接成可执行文件.在UNIX系统下,一般有扩展名".o".之所以称其为"可重定位",是因为在这些文件中,如果引用到其它目标文件或库文件中定义的符号(变量或者函数)的话,只是给出一个名字,这里还并不知道这个符号在哪里,其具体的地址是什么.需要在连接的过程中,把对这些外部符号的引用重新定位到其真正定义的位置上,所以称目标文件为"可重定位"或者"待重定位"的.
  • 可执行文件(executable file, 例上述示例代码生成的app文件)包含代码和数据,是可以直接运行的程序.其代码和数据都有固定的地址 (或相对于基地址的偏移 ),系统可根据这些地址信息把程序加载到内存执行.
  • 共享目标文件(shared object file),即动态连接库文件.它在以下两种情况下被使用:第一,在连接过程中与其它动态链接库或可重定位文件一起构建新的目标文件;第二,在可执行文件被加载的过程中,被动态链接到新的进程中,成为运行代码的一部分.包含了代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的.
  • 核心转储文件(core dump file,就是core dump文件)

可重定位文件用在编译和链接阶段.可执行文件用在程序运行阶段.共享库则同时用在编译链接和运行阶段.在不同阶段,我们可以用不同视角来理解ELF文件,整体布局如下图所示:

鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析HarmonyOS源码 | v51.04_第2张图片

从上图可见,ELF格式文件整体可分为四大部分:

  • ELF Header: 在文件的开始,描述整个文件的组织.即readelf -h app看到的内容
  • Program Header Table: 告诉系统如何创建进程映像.用来构造进程映像的目标文件必须具有程序头部表,可重定位文件可以不需要这个表.表描述段(Segment)信息的数组,每个元素对应一个段 , 即readelf -l app看到的前半部分内容.
  • Segments and Sections: 段(Segment)由若干区(Section)组成.段在运行时被加载到进程地址空间中,包含在可执行文件中.区是段的组成单元,包含在可执行文件和可重定位文件中 即readelf -l app后半部分内容,readelf -x ** app可查看区具体内容.注意: Segments 是从运行的角度来描述 ELF 文件,Sections 是从链接的角度来描述 ELF 文件.也就是说,在链接阶段,我们可以忽略 program header table 来处理此文件,在运行阶段可以忽略 section header table 来处理此程序(所以很多加固手段删除了section 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;

结构体对应readelf -h app命令内容来理解,就不再贴出来了.

段(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 */
    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区就被第四LOADGNU_RELRO两段所共有.

PHDR,此类型header元素描述了program header table自身的信息.从这里的内容看出,示例程序的program header table在文件中的偏移(Offset)为0x40,即64号字节处.该段映射到进程空间的虚拟地址(VirtAddr)为0x40.PhysAddr暂时不用,其保持和VirtAddr一致.该段占用的文件大小FileSiz0x2d8.运行时占用进程空间内存大小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是有重叠的.

再看命令返回内容的后半部分-段区映射关系

 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)

String Table

从上述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.

这里看到了mainsay_hello字符串,这些是在示例中源码中定义的符号,由此29区是应用自身的String Table,记录了应用程序使用的字符串.这个表就是符号表的一部分.
通过readelf -s命令能拿到全部符号信息.如下

符号表 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/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

在最后位置找到了亲切的老朋友 mainsay_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 TableSymbol 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

这才看到了0x1174main函数.所以真正的说法是:

  • 从内核动态加载的视角看,程序运行首个函数并不是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

百万汉字注解鸿蒙源码,百篇博客深挖内核地基. 原创不易,欢迎转载,但麻烦请注明出处.

你可能感兴趣的:(鸿蒙内核源码分析,内核,操作系统,鸿蒙内核源码分析,百篇博客分析,百万汉字注解)