子畏于匡,颜渊后。子曰:“吾以女为死矣。”曰:“子在,回何敢死?” 《论语》:先进篇
百篇博客系列篇.本篇为:
v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视编译全过程
编译构建相关篇为:
- v50.03 鸿蒙内核源码分析(编译环境) | 编译鸿蒙防掉坑指南
- v57.02 鸿蒙内核源码分析(编译过程) | 简单案例窥视编译全过程
- v58.03 鸿蒙内核源码分析(环境脚本) | 编译鸿蒙原来如此简单
- v59.04 鸿蒙内核源码分析(构建工具) | 顺瓜摸藤调试鸿蒙构建过程
- v60.04 鸿蒙内核源码分析(gn应用) | gn语法及在鸿蒙的使用
- v61.03 鸿蒙内核源码分析(忍者ninja) | 都忍者了能不快吗
一个.c源文件编译的整个过程如图.
编译过程要经过:源文件 --> 预处理 --> 编译(cc1) --> 汇编器(as) --> 链接器(ld) --> 可执行文件(PE/ELF)
GCC
GCC(GNU Compiler Collection,GNU编译器套件),官网:https://gcc.gnu.org/ ,是由 GNU 开发的编程语言编译器
- GCC源码仓库:https://github.com/gcc-mirror/gcc 有兴趣的可以去阅读源码.
GCC用法
本篇以 main.c 文件举例,说明白两个问题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.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一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
按功能模块:
基础工具 | 加载运行 | 进程管理 | 编译构建 |
---|---|---|---|
双向链表 位图管理 用栈方式 定时器 原子操作 时间管理 |
ELF格式 ELF解析 静态链接 重定位 进程映像 |
进程管理 进程概念 Fork 特殊进程 进程回收 信号生产 信号消费 Shell编辑 Shell解析 |
编译环境 编译过程 环境脚本 构建工具 gn应用 忍者ninja |
进程通讯 | 内存管理 | 前因后果 | 任务管理 |
自旋锁 互斥锁 进程通讯 信号量 事件控制 消息队列 |
内存分配 内存管理 内存汇编 内存映射 内存规则 物理内存 |
总目录 调度故事 内存主奴 源码注释 源码结构 静态站点 |
时钟任务 任务调度 任务管理 调度队列 调度机制 线程概念 并发并行 CPU 系统调用 任务切换 |
文件系统 | 硬件架构 | ||
文件概念 文件系统 索引节点 挂载目录 根文件系统 字符设备 VFS 文件句柄 管道文件 |
汇编基础 汇编传参 工作模式 寄存器 异常接管 汇编汇总 中断切换 中断概念 中断管理 |
百万汉字注解.精读内核源码
四大码仓中文注解 . 定期同步官方代码
鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞更好,感谢每一份支持。