鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析HarmonyOS源码| v57.01

将 HarmonyOS | 鸿蒙 研究到底 < 国内 | 国外 >

在这里插入图片描述

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

  • v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 51 .c .h .o

一个.c源文件编译的整个过程如图.

鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析HarmonyOS源码| v57.01_第1张图片

编译过程要经过:源文件 --> 预处理 --> 编译(cc1) --> 汇编器(as) --> 链接器(ld) --> 可执行文件(PE/ELF)

GCC

GCC(GNU Compiler Collection,GNU编译器套件),官网:https://gcc.gnu.org/ ,是由 GNU 开发的编程语言编译器

  • GCC源码仓库:https://github.com/gcc-mirror/gcc 有兴趣的可以去阅读源码.
  • GCC用法
    gcc [options] infile
    -o	重定位输入文件位置;
    -E	只对源文件进行预处理,输出.i文件;
    -S	对源文件进行预处理,编译,输出.s文件;
    -c	对源文件进行预处理,编译,汇编,输出.o文件;
    -I	包含头文件路径,如 g++ -Iopencv/include/;
    -L	包含库文件路径,如 g++ -Lopencv/lib/ ;
    -l	链接库文件,如链接lib.so:g++ -llib;
    -shared	编译.so库;
    -fPIC	生成位置无法码;
    -Wall	对代码有问题的地方发出警告;
    -g	在目标文件中嵌入调试信息,便于gdb调试;
    

本篇以 main.c 文件举例,说明白两个问题

  • main.c是怎么编译的? 详细的整个过程是怎样的,是如何一步步走到了可执行文件的.
  • main.c中的代码,数据,栈,堆是如何形成和构建的? 通过变量地址窥视其内存布局.
  • 这两部分内容将掺杂在一起尽量同时说明白,main.c文件它将经历以下变化过程
    main.c --> main.i --> main.s --> main.o --> main

插播 case_code_100

case_code_100 是百篇博客分析过程中用到的案例汇总,序号与博客序号一一对应.仓库地址: https://gitee.com/weharmony/case_code_100,本篇为 57

源文件 | main.c

#include 
#include   
#define HARMONY_OS "hello harmonyos \n"
const int g_const = 10;	//全局常量区
int g_init = 57;	//全局变量
int g_no_init;		//全局变量无初值
static int s_exter_init = 58;//静态外部变量有初值
static int s_exter_no_init;	 //静态外部变量无初值
/**************************************************
* main:通过简单案例窥视编译过程和内存布局
**************************************************/
int main()
{
  static int s_inter_init = 59;//静态内部变量有初值
  static int s_inter_no_init;	 //静态内部变量无初值
  int l_init = 60;			 //局部变量有初值
  int l_no_init;				 //局部变量无初值
  const int l_const = 11;		 //局部常量
  char *heap = (char *)malloc(100);//堆区
  printf(HARMONY_OS);
  //----------------------
  printf("全局常量 g_const:%p\n", &g_const);
  printf("全局外部有初值 g_init:%p\n", &g_init);
  printf("全局外部无初值 g_no_init:%p\n", &g_no_init);
  printf("静态外部有初值 s_exter_init:%p\n", &s_exter_init);
  printf("静态外静无初值 s_exter_no_init:%p\n", &s_exter_no_init);
  printf("静态内部有初值 s_inter_init:%p\n", &s_inter_init);
  printf("静态内部无初值 s_inter_no_init:%p\n", &s_inter_no_init);
  //----------------------
  printf("局部栈区有初值 l_init:%p\n", &l_init);
  printf("局部栈区无初值  l_no_init:%p\n", &l_no_init);
  printf("局部栈区常量  l_const:%p\n", &l_const);
  //----------------------
  printf("堆区地址 heap:%p\n", heap);
  //----------------------
  printf("代码区地址:%p\n", &main);
  return 0;
}

解读

  • 函数具有代表性,有宏,注释,有全局,局部,静态外部,静态内部变量,堆申请.
  • 通过这些值的变化看其中间过程和最后内存布局.

预处理 | main.i

root@5e3abe332c5a:/home/docker/case_code_100/57# gcc -E main.c -o main.i
root@5e3abe332c5a:/home/docker/case_code_100/57# cat main.i
# 1 "main.c"
# 1 ""
# 1 ""
......
typedef __u_char u_char;
typedef __u_short u_short;
extern int printf (const char *__restrict __format, ...);
......
const int g_const = 10;
int g_init = 57;
int g_no_init;
static int s_exter_init = 58;
static int s_exter_no_init;
int main()
{
  static int s_inter_init = 59;
  static int s_inter_no_init;

  int l_init = 60;
  int l_no_init;
  const int l_const = 11;

  char *heap = (char *)malloc(100);

  printf("hello harmonyos \n");

  printf("全局常量 g_const:%p\n", &g_const);
  printf("全局外部有初值 g_init:%p\n", &g_init);
  printf("全局外部无初值 g_no_init:%p\n", &g_no_init);
  printf("静态外部有初值 s_exter_init:%p\n", &s_exter_init);
  printf("静态外静无初值 s_exter_no_init:%p\n", &s_exter_no_init);
  printf("静态内部有初值 s_inter_init:%p\n", &s_inter_init);
  printf("静态内部无初值 s_inter_no_init:%p\n", &s_inter_no_init);

  printf("局部栈区有初值 l_init:%p\n", &l_init);
  printf("局部栈区无初值  l_no_init:%p\n", &l_no_init);
  printf("局部栈区常量  l_const:%p\n", &l_const);

  printf("堆区地址 heap:%p\n", heap);

  printf("代码区地址:%p\n", &main);
  return 0;
}

解读

main.i文件很大,1000多行,此处只列出重要部分,全部代码前往case_code_100查看
预处理过程主要处理那些源代码中以#开始的预处理指令,主要处理规则如下:

  • 将所有的#define删除,并且展开所有的宏定义;
  • 处理所有条件编译指令,如#if,#ifdef等;
  • 处理#include预处理指令,将被包含的文件插入到该预处理指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。
  • 删除所有的注释//和 /**/;
  • 添加行号和文件标识,如# 1 “main.c”,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
  • 保留所有的#pragma编译器指令,因为编译器须要使用它们;
  • 经过预编译后的.i文件不包含任何宏定义,因为所有的宏都已经被展开,并且包含的文件也已经被插入到.i文件中。所以当无法判断宏定义是否正确或头文件包含是否正确使,可以查看预编译后的文件来确定问题。

编译 | main.s

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分,也是最复杂的部分之一。

//编译器: armv7-a clang (trunk)
root@5e3abe332c5a:/home/docker/case_code_100/57# gcc -S main.i -o main.s
root@5e3abe332c5a:/home/docker/case_code_100/57# cat main.s
main:
        push    {r11, lr}     @保存r11和lr寄存器,因为内部有函数调用,lr表示函数的返回地址,所以需要保存
        mov     r11, sp       @保存sp
        sub     sp, sp, #24   @申请栈空间
        mov     r0, #0        @r0 = 0,这个代表 return 0;
        str     r0, [sp]      @栈顶保存 main函数返回值
        str     r0, [r11, #-4]@r0 = 0,即变量l_no_init入栈,优化了指令的顺序
        mov     r0, #60       @r0 = 60,即变量l_init
        str     r0, [r11, #-8]@l_init入栈
        mov     r0, #11       @r0 = 11,即变量l_const
        str     r0, [sp, #8]  @l_const入栈
        mov     r0, #100      @r0=100,为malloc参数
        bl      malloc        @调用malloc
        str     r0, [sp, #4]  @malloc函数返回值入栈
        ldr     r0, .LCPI0_0  @为printf准备参数 "hello harmonyos \n"
        bl      printf        @调用printf("hello harmonyos \n");
        ldr     r0, .LCPI0_1  @准备参数
        ldr     r1, .LCPI0_2  @准备参数
        bl      printf        @调用printf("全局常量 g_const:%p\n", &g_const);
        ldr     r0, .LCPI0_3  @准备参数
        ldr     r1, .LCPI0_4  @准备参数
        bl      printf        @调用printf("全局外部有初值 g_init:%p\n", &g_init);
        ldr     r0, .LCPI0_5  @准备参数
        ldr     r1, .LCPI0_6  @准备参数
        bl      printf        @调用printf("全局外部无初值 g_no_init:%p\n", &g_no_init);
        ldr     r0, .LCPI0_7  @准备参数
        ldr     r1, .LCPI0_8  @准备参数
        bl      printf        @调用printf("静态外部有初值 s_exter_init:%p\n", &s_exter_init);
        ldr     r0, .LCPI0_9  @准备参数
        ldr     r1, .LCPI0_10 @准备参数
        bl      printf        @调用printf("静态外静无初值 s_exter_no_init:%p\n", &s_exter_no_init);
        ldr     r0, .LCPI0_11 @准备参数
        ldr     r1, .LCPI0_12 @准备参数
        bl      printf        @调用printf("静态内部有初值 s_inter_init:%p\n", &s_inter_init);
        ldr     r0, .LCPI0_13 @准备参数
        ldr     r1, .LCPI0_14 @准备参数
        bl      printf        @调用printf("静态内部无初值 s_inter_no_init:%p\n", &s_inter_no_init);
        ldr     r0, .LCPI0_15 @准备参数
        sub     r1, r11, #8   @r1=&l_init
        bl      printf        @调用printf("局部栈区有初值 l_init:%p\n", &l_init);
        ldr     r0, .LCPI0_16 @准备参数
        add     r1, sp, #12   @r1=&l_no_init
        bl      printf        @调用printf("局部栈区无初值  l_no_init:%p\n", &l_no_init);
        ldr     r0, .LCPI0_17 @准备参数
        add     r1, sp, #8    @r1=&l_const
        bl      printf        @调用printf("局部栈区常量  l_const:%p\n", &l_const);
        ldr     r1, [sp, #4]  @r1=heap,即malloc返回值出栈
        ldr     r0, .LCPI0_18 @准备参数
        bl      printf        @调用printf("堆区地址 heap:%p\n", heap);
        ldr     r0, .LCPI0_19 @准备参数
        ldr     r1, .LCPI0_20 @准备参数
        bl      printf        @调用printf("代码区地址:%p\n", &main);
        ldr     r0, [sp]      @即 r0=0,代表main函数的返回值 对应开头的 str     r0, [sp]
        mov     sp, r11       @恢复值,对应开头的  mov     r11, sp
        pop     {r11, lr}     @出栈,对应开头的 push    {r11, lr}
        bx      lr            @退出main,跳到调用main()函数处,返回值保存在r0 | =0
.LCPI0_0:   @以下全部是申请和定义代码中的一个个变量
        .long   .L.str
.LCPI0_1:
        .long   .L.str.1
.LCPI0_2:
        .long   g_const
.LCPI0_3:
        .long   .L.str.2
.LCPI0_4:
        .long   g_init
.LCPI0_5:
        .long   .L.str.3
.LCPI0_6:
        .long   g_no_init
.LCPI0_7:
        .long   .L.str.4
.LCPI0_8:
        .long   s_exter_init
.LCPI0_9:
        .long   .L.str.5
.LCPI0_10:
        .long   _ZL15s_exter_no_init
.LCPI0_11:
        .long   .L.str.6
.LCPI0_12:
        .long   main::s_inter_init
.LCPI0_13:
        .long   .L.str.7
.LCPI0_14:
        .long   _ZZ4mainE15s_inter_no_init
.LCPI0_15:
        .long   .L.str.8
.LCPI0_16:
        .long   .L.str.9
.LCPI0_17:
        .long   .L.str.10
.LCPI0_18:
        .long   .L.str.11
.LCPI0_19:
        .long   .L.str.12
.LCPI0_20:
        .long   main
g_init:
        .long   57                              @ 0x39

g_no_init:
        .long   0                               @ 0x0

main::s_inter_init:
        .long   59                              @ 0x3b

.L.str: 
        .asciz  "hello harmonyos \n"

.L.str.1:
        .asciz  "\345\205\250\345\261\200\345\270\270\351\207\217 g_const\357\274\232%p\n"

g_const:
        .long   10                              @ 0xa

.L.str.2:
        .asciz  "\345\205\250\345\261\200\345\244\226\351\203\250\346\234\211\345\210\235\345\200\274 g_init\357\274\232%p\n"

.L.str.3:
        .asciz  "\345\205\250\345\261\200\345\244\226\351\203\250\346\227\240\345\210\235\345\200\274 g_no_init\357\274\232%p\n"

.L.str.4:
        .asciz  "\351\235\231\346\200\201\345\244\226\351\203\250\346\234\211\345\210\235\345\200\274 s_exter_init\357\274\232%p\n"

s_exter_init:
        .long   58                              @ 0x3a

.L.str.5:
        .asciz  "\351\235\231\346\200\201\345\244\226\351\235\231\346\227\240\345\210\235\345\200\274 s_exter_no_init\357\274\232%p\n"

.L.str.6:
        .asciz  "\351\235\231\346\200\201\345\206\205\351\203\250\346\234\211\345\210\235\345\200\274 s_inter_init\357\274\232%p\n"

.L.str.7:
        .asciz  "\351\235\231\346\200\201\345\206\205\351\203\250\346\227\240\345\210\235\345\200\274 s_inter_no_init\357\274\232%p\n"

.L.str.8:
        .asciz  "\345\261\200\351\203\250\346\240\210\345\214\272\346\234\211\345\210\235\345\200\274 l_init\357\274\232%p\n"

.L.str.9:
        .asciz  "\345\261\200\351\203\250\346\240\210\345\214\272\346\227\240\345\210\235\345\200\274  l_no_init\357\274\232%p\n"

.L.str.10:
        .asciz  "\345\261\200\351\203\250\346\240\210\345\214\272\345\270\270\351\207\217  l_const\357\274\232%p\n"

.L.str.11:
        .asciz  "\345\240\206\345\214\272\345\234\260\345\235\200 heap\357\274\232%p\n"

.L.str.12:
        .asciz  "\344\273\243\347\240\201\345\214\272\345\234\260\345\235\200\357\274\232%p\n"             

解读

  • 汇编代码全部贴出,都已经加上了注释,不要嫌多,忍忍吧.
  • 系列篇到了这里,读上面的汇编应该没什么难度了,不是很清楚的读以下两篇
    • v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51 .c .h .o

    • v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51 .c .h .o

汇编 | main.o

汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
main.o的内容为机器码,不能以文本形式方便的呈现,不过可以利用 objdump -S file 查看源码反汇编

root@5e3abe332c5a:/home/docker/case_code_100/57# gcc -c main.s -o main.o
root@5e3abe332c5a:/home/docker/case_code_100/57#objdump -S main.o
main.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 
: 0: f3 0f 1e fa endbr64 4: 55 push %rbp 5: 48 89 e5 mov %rsp,%rbp 8: 48 83 ec 20 sub $0x20,%rsp c: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 13: 00 00 15: 48 89 45 f8 mov %rax,-0x8(%rbp) 19: 31 c0 xor %eax,%eax 1b: c7 45 e4 3c 00 00 00 movl $0x3c,-0x1c(%rbp) 22: c7 45 ec 0b 00 00 00 movl $0xb,-0x14(%rbp) 29: bf 64 00 00 00 mov $0x64,%edi 2e: e8 00 00 00 00 callq 33 33: 48 89 45 f0 mov %rax,-0x10(%rbp) 37: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 3e 3e: e8 00 00 00 00 callq 43 43: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 4a 4a: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 51 51: b8 00 00 00 00 mov $0x0,%eax 56: e8 00 00 00 00 callq 5b 5b: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 62 62: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 69 69: b8 00 00 00 00 mov $0x0,%eax 6e: e8 00 00 00 00 callq 73 73: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 7a 7a: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 81 81: b8 00 00 00 00 mov $0x0,%eax 86: e8 00 00 00 00 callq 8b 8b: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 92 92: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 99 99: b8 00 00 00 00 mov $0x0,%eax 9e: e8 00 00 00 00 callq a3 a3: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # aa aa: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b1 b1: b8 00 00 00 00 mov $0x0,%eax b6: e8 00 00 00 00 callq bb bb: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # c2 c2: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # c9 c9: b8 00 00 00 00 mov $0x0,%eax ce: e8 00 00 00 00 callq d3 d3: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # da da: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # e1 e1: b8 00 00 00 00 mov $0x0,%eax e6: e8 00 00 00 00 callq eb eb: 48 8d 45 e4 lea -0x1c(%rbp),%rax ef: 48 89 c6 mov %rax,%rsi f2: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # f9 f9: b8 00 00 00 00 mov $0x0,%eax fe: e8 00 00 00 00 callq 103 103: 48 8d 45 e8 lea -0x18(%rbp),%rax 107: 48 89 c6 mov %rax,%rsi 10a: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 111 111: b8 00 00 00 00 mov $0x0,%eax 116: e8 00 00 00 00 callq 11b 11b: 48 8d 45 ec lea -0x14(%rbp),%rax 11f: 48 89 c6 mov %rax,%rsi 122: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 129 129: b8 00 00 00 00 mov $0x0,%eax 12e: e8 00 00 00 00 callq 133 133: 48 8b 45 f0 mov -0x10(%rbp),%rax 137: 48 89 c6 mov %rax,%rsi 13a: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 141 141: b8 00 00 00 00 mov $0x0,%eax 146: e8 00 00 00 00 callq 14b 14b: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 152 152: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 159 159: b8 00 00 00 00 mov $0x0,%eax 15e: e8 00 00 00 00 callq 163 163: b8 00 00 00 00 mov $0x0,%eax 168: 48 8b 55 f8 mov -0x8(%rbp),%rdx 16c: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx 173: 00 00 175: 74 05 je 17c 177: e8 00 00 00 00 callq 17c 17c: c9 leaveq 17d: c3 retq

解读

  • 此时的main.o是个REL (Relocatable file)重定位文件,关于重定位可前往翻看
    • v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51 .c .h .o

链接 | main

链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。

root@5e3abe332c5a:/home/docker/case_code_100/57# gcc main.o -o main
root@5e3abe332c5a:/home/docker/case_code_100/57# readelf -lW  main
Elf file type is DYN (Shared object file)
Entry point 0x10c0
There are 13 program headers, starting at offset 64
Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x0002d8 0x0002d8 R   0x8
  INTERP         0x000318 0x0000000000000318 0x0000000000000318 0x00001c 0x00001c R   0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x0006c8 0x0006c8 R   0x1000
  LOAD           0x001000 0x0000000000001000 0x0000000000001000 0x0003b5 0x0003b5 R E 0x1000
  LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x000338 0x000338 R   0x1000
  LOAD           0x002da0 0x0000000000003da0 0x0000000000003da0 0x00027c 0x000290 RW  0x1000
  DYNAMIC        0x002db0 0x0000000000003db0 0x0000000000003db0 0x0001f0 0x0001f0 RW  0x8
  NOTE           0x000338 0x0000000000000338 0x0000000000000338 0x000020 0x000020 R   0x8
  NOTE           0x000358 0x0000000000000358 0x0000000000000358 0x000044 0x000044 R   0x4
  GNU_PROPERTY   0x000338 0x0000000000000338 0x0000000000000338 0x000020 0x000020 R   0x8
  GNU_EH_FRAME   0x0021e8 0x00000000000021e8 0x00000000000021e8 0x000044 0x000044 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x002da0 0x0000000000003da0 0x0000000000003da0 0x000260 0x000260 R   0x1

 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
root@5e3abe332c5a:/home/docker/case_code_100/57# readelf -SW  main
There are 31 section headers, starting at offset 0x3b10:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1
  [ 2] .note.gnu.property NOTE            0000000000000338 000338 000020 00   A  0   0  8
  [ 3] .note.gnu.build-id NOTE            0000000000000358 000358 000024 00   A  0   0  4
  [ 4] .note.ABI-tag     NOTE            000000000000037c 00037c 000020 00   A  0   0  4
  [ 5] .gnu.hash         GNU_HASH        00000000000003a0 0003a0 000024 00   A  6   0  8
  [ 6] .dynsym           DYNSYM          00000000000003c8 0003c8 0000f0 18   A  7   1  8
  [ 7] .dynstr           STRTAB          00000000000004b8 0004b8 0000ab 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          0000000000000564 000564 000014 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         0000000000000578 000578 000030 00   A  7   1  8
  [10] .rela.dyn         RELA            00000000000005a8 0005a8 0000c0 18   A  6   0  8
  [11] .rela.plt         RELA            0000000000000668 000668 000060 18  AI  6  24  8
  [12] .init             PROGBITS        0000000000001000 001000 00001b 00  AX  0   0  4
  [13] .plt              PROGBITS        0000000000001020 001020 000050 10  AX  0   0 16
  [14] .plt.got          PROGBITS        0000000000001070 001070 000010 10  AX  0   0 16
  [15] .plt.sec          PROGBITS        0000000000001080 001080 000040 10  AX  0   0 16
  [16] .text             PROGBITS        00000000000010c0 0010c0 0002e5 00  AX  0   0 16
  [17] .fini             PROGBITS        00000000000013a8 0013a8 00000d 00  AX  0   0  4
  [18] .rodata           PROGBITS        0000000000002000 002000 0001e8 00   A  0   0  8
  [19] .eh_frame_hdr     PROGBITS        00000000000021e8 0021e8 000044 00   A  0   0  4
  [20] .eh_frame         PROGBITS        0000000000002230 002230 000108 00   A  0   0  8
  [21] .init_array       INIT_ARRAY      0000000000003da0 002da0 000008 08  WA  0   0  8
  [22] .fini_array       FINI_ARRAY      0000000000003da8 002da8 000008 08  WA  0   0  8
  [23] .dynamic          DYNAMIC         0000000000003db0 002db0 0001f0 10  WA  7   0  8
  [24] .got              PROGBITS        0000000000003fa0 002fa0 000060 08  WA  0   0  8
  [25] .data             PROGBITS        0000000000004000 003000 00001c 00  WA  0   0  8
  [26] .bss              NOBITS          000000000000401c 00301c 000014 00  WA  0   0  4
  [27] .comment          PROGBITS        0000000000000000 00301c 00002a 01  MS  0   0  1
  [28] .symtab           SYMTAB          0000000000000000 003048 000708 18     29  50  8
  [29] .strtab           STRTAB          0000000000000000 003750 0002a3 00      0   0  1
  [30] .shstrtab         STRTAB          0000000000000000 0039f3 00011a 00      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)

解读

  • 区头表位置的顺序将是加载到内存中映像的顺序,即虚拟地址的大小顺序,可以看出 .text < .rodata < .data < .bss
    • v54.xx 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 51 .c .h .o

运行 | ./main

root@5e3abe332c5a:/home/docker/case_code_100/57# ./main
hello harmonyos 
全局常量 g_const:0x5599b1a00008
全局外部有初值 g_init:0x5599b1a02010
全局外部无初值 g_no_init:0x5599b1a02028
静态外部有初值 s_exter_init:0x5599b1a02014
静态外静无初值 s_exter_no_init:0x5599b1a02020
静态内部有初值 s_inter_init:0x5599b1a02018
静态内部无初值 s_inter_no_init:0x5599b1a02024
局部栈区有初值 l_init:0x7ffda7f4dc94
局部栈区无初值  l_no_init:0x7ffda7f4dc98
局部栈区常量  l_const:0x7ffda7f4dc9c
堆区地址 heap:0x5599b2f522a0
代码区地址:0x5599b19ff1a9 

解读

  • 栈区地址最高 0x7ffda7******,局部变量是放在栈中的,这些局部变量地址大小为 l_init< l_no_init < l_const, 这和变量定义的方向是一致的,从而佐证了其用栈方式为递减满栈.越是在前面的变量内存的虚拟地址越小.这个在
    • v01.xx 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 | 51 .c .h .o
    • v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地由谁提供 | 51 .c .h .o
      都已说过,请自行翻看.
  • 代码区.text地址最低 0x5599b1******,代码区第二个LOAD加载段,其flag为(R/E)
  • 全局地址顺序是 g_no_init(.bss) > g_init(.data) > g_const(rodata),刚好和三个区的地址范围吻合.
  • 关于静态变量,看地址的顺序 s_exter_init < s_inter_init < s_exter_no_init < s_inter_no_init ,说明前两个因为有初始值都放在了.data区,后两个都放到了.bss.
  • 对于同样在.bss区的三个变量地址顺序是 s_exter_no_init(2020) < s_inter_no_init(2024) < g_no_init(2028)
  • 对于同样在.data区的三个变量地址顺序是 g_init(2010) < s_exter_init(2014) < s_inter_init(2018)
  • 从地址上看.bss,.data挨在一起的,因为实际的ELF区分布上它们也确实是挨在一起的
    [25] .data             PROGBITS        0000000000004000 003000 00001c 00  WA  0   0  8
    [26] .bss              NOBITS          000000000000401c 00301c 000014 00  WA  0   0  4
    
  • 堆区在中间位置 0x5599b2******,并且可以发现在 .bss(0x5599b1a0****).heap(0x5599b2f5****)区之间还有大量的虚拟地址没有被使用
  • ELF如何被加载运行可翻看
    • v56.xx 鸿蒙内核源码分析(进程映像篇) | ELF是如何被加载运行的? | 51 .c .h .o

问题

细心的可能会发现了一个问题,s_inter_init(2018),s_exter_no_init(2020)这两个地址之间只相差两个字节,但是int变量是4个字节,这是为什么呢?

百篇博客.往期回顾

在加注过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 

与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,.xx代表修改的次数,精雕细琢,言简意赅,力求打造精品内容。

  • v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 51 .c .h .o

  • v56.xx 鸿蒙内核源码分析(进程映像篇) | ELF是如何被加载运行的? | 51 .c .h .o

  • v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51 .c .h .o

  • v54.xx 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 51 .c .h .o

  • v53.xx 鸿蒙内核源码分析(ELF解析篇) | 你要忘了她姐俩你就不是银 | 51 .c .h .o

  • v52.xx 鸿蒙内核源码分析(静态站点篇) | 五一哪也没去就干了这事 | 51 .c .h .o

  • 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

关于 51 .c .h .o

看系列篇文章会常看到 51 .c .h .o,希望这对大家阅读不会造成影响.
分别对应以下四个站点的首个字符,感谢这些站点一直以来对系列篇的支持和推荐,尤其是 oschina gitee ,很喜欢它的界面风格,简洁大方,让人感觉到开源的伟大!

  • 51cto
  • csdn
  • harmony
  • oschina

而巧合的是.c .h .o是C语言的头/源/目标文件,这就很有意思了,冥冥之中似有天数,将这四个宝贝以这种方式融合在一起. 51 .c .h .o , 我要CHO ,嗯嗯,hin 顺口 : )

百万汉字注解.百篇博客分析

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto | csdn | harmony | osc >

关注不迷路.代码即人生

鸿蒙内核源码分析

热爱是所有的理由和答案 - turing

原创不易,欢迎转载,但麻烦请注明出处.

你可能感兴趣的:(鸿蒙内核源码分析,内核,c++,操作系统,编译器,鸿蒙)