
#星光计划1.0# v55.04 鸿蒙内核源码分析(重定位) 原创 精华
子张问善人之道。子曰:“不践迹,亦不入于室。” 《论语》:先进篇
百篇博客分析.本篇为: (重定位篇) | 与国际接轨的对外发言人
加载运行相关篇为:
- v51.04 鸿蒙内核源码分析(ELF格式) | 应用程序入口并非main
- v53.03 鸿蒙内核源码分析(ELF解析) | 敢忘了她姐俩你就不是银
- v54.04 鸿蒙内核源码分析(静态链接) | 一个小项目看中间过程
- v55.04 鸿蒙内核源码分析(重定位) | 与国际接轨的对外发言人
- v56.05 鸿蒙内核源码分析(进程映像) | 程序是如何被加载运行的
一个程序从源码到被执行,当中经历了3个过程:
- 编译:将.c文件编译成.o文件,不关心.o文件之间的联系.
- 静态链接:将所有.o文件合并成一个.so或.out文件,处理所有.o文件节区在目标文件中的布局.
- 动态链接:将.so或a.out文件加载到内存,处理加载文件在的内存中的布局.
什么是重定位
重定位就是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程。它是实现多道程序在内存中同时运行的基础。重定位有两种,分别是动态重定位与静态重定位。
- 1.静态重定位:即在程序装入内存的过程中完成,是指在程序开始运行前,程序中的各个地址有关的项均已完成重定位,地址变换通常是在装入时一次完成的,以后不再改变,故称为静态重定位。也就是在生成可执行/共享目标文件的同时已完成地址的静态定位,它解决了可执行文件/共享目标文件的内部矛盾.
- 2.动态重定位:它不是在程序装入内存时完成的,而是CPU每次访问内存时 由动态地址变换机构(硬件)自动进行把相对地址转换为绝对地址。动态重定位需要软件和硬件相互配合完成。也就是说可执行文件/共享目标文件的外部矛盾需要外部环境解决,它向外提供了一份入住地球村的外交说明.即本篇最后部分内容.
重定位十种类型
- 重定位有10种类型,在实际中请对号入座,这些类型部分在本篇能见到,如下:
类型 | 公式 | 具体描述 |
---|---|---|
R_X86_64_32 | 公式:S+A S:重定项中VALUE成员所指符号的内存地址 A:被重定位处原值,表示"引用符号的内存地址"与S的偏移 |
全局变量,在不加-fPIC编译生成的.o文件中,每个引用处对应一个R_X86_64_32重定位项,非static全局变量,在不加-fPIC编译生成的.so文件中,每个引用处对应一个R_X86_64_32重定位项. |
R_X86_64_PC32 | 公式:S+A-P S:重定项中VALUE成员所指符号的内存地址 A:被重定位处原值,表示"被重定位处"与"下一条指令"的偏移 P:被重定位处的内存地址 |
非static函数,在不加-fPIC编译生成的.o和.so文件中,每个调用处对应一个R_X86_64_PC32重定位项 |
R_X86_64_PLT32 | 公式:L+A-P L:<重定项中VALUE成员所指符号@plt>的内存地址 A:被重定位处原值,表示"被重定位处"相对于"下一条指令"的偏移 P:被重定位处的内存地址 |
非static函数,在加-fPIC编译生成的.o文件中,每个调用处对应一个R_386_PLT32重定位项. |
R_X86_64_RELATIVE | 公式:B+A B:.so文件加载到内存中的基地址 A:被重定位处原值,表示引用符号在.so文件中的偏移 |
static全局变量,在不加-fPIC编译生成的.so文件中,每个引用处对应一个R_X86_64_RELATIVE重定位项. |
R_X86_64_GOT32 | 公式:G G:引用符号的地址指针,相对于GOT的偏移 |
非static全局变量,在加-fPIC编译生成的.o文件中,每个引用处对应一个R_X86_64_GOT32重定位项 |
R_X86_64_GOTOFF | 公式:S-GOT S:重定项中VALUE成员所指符号的内存地址 GOT:运行时,.got段的结束地址 |
static全局变量,在加-fPIC编译生成的.o文件中,每个引用处对应一个R_X86_64_GOTOFF重定位项. |
R_X86_64_GOLB_DAT | 公式:S S:重定项中VALUE成员所指符号的内存地址 |
非static全局变量,在加-fPIC编译生成的.so文件中,每个引用处对应一个R_X86_64_GOLB_DAT重定位项. |
R_X86_64_COPY | 公式:无 | .out中利用extern引用.so中的变量,每个引用处对应一个R_X86_64_COPY重定位项. |
R_X86_64_JUMP_SLOT | 公式:S(与R_386_GLOB_DAT的公式一样,但对于动态ld,R_386_JMP_SLOT类型与R_386_RELATIVE等价) S:重定项中VALUE成员所指符号的内存地址 |
非static函数,在加-fPIC编译生成的.so文件中,每个调用处对应一个R_X86_64_JMP_SLOT重定位项. |
R_X86_64_GOTPC | 公式:GOT+A-P GOT:运行时,.got段的结束地址 A:被重定位处原值,表示"被重定位处"在机器码中的偏移 P:被重定位处的内存地址 |
全局变量,在加-fPIC编译生成的.o文件中,会额外生成R_X86_64_PC32和R_X86_64_GOTPC重定位项,非static函数,在加-fPIC编译生成的.o文件中,也会额外 生成R_X86_64_PC32和R_X86_64_GOTPC重定位项. |
解读
- fPIC的全称是 Position Independent Code, 用于生成位置无关代码。
objdump命令
objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息.本篇将用它说明静态重定位的实现细节和动态重定位前置条件准备.先整体走读下objdump
命令
objdump -S ./obj/main.o
main.o是个可重定位文件,通过 readelf 可知
解读
- 注意那些
00 00 00 00
部分,这些都是编译器暂时无法确定的内容.肉眼计算下此时OFFSET
偏移位为0x16
,0x21
,即下表内容
objdump -r ./obj/main.o
解读
-
0x16
,0x21
对应的这些值都是 0,也就是说对于编译器不能确定的地址都这设置为空(0x000000),同时编译器都生成一一对应的记录,该记录告诉链接器在进行链接时要修正这条指令中函数的内存地址,并告知是什么重定位类型,要去哪里找数据填充. -
外部全局变量重定位
g_str
,g_int
编译器连g_str在哪个.o文件都不知道,当然更不知道g_str运行时的地址,所以在g.o文件中设置一个重定位,要求后续过程根据"S(g_str内存地址)-A(0x04)",修改main.o镜像中0x21偏移处的值.
-
函数重定位,重定位类型为
R_X86_64_PLT32
同样编译器连``func_int
,
printf`在哪个.o文件都不知道,当然更不知道它们的运行时的地址,所以在main.o文件中设置一个重定位,后续将 修改main.o镜像中3a偏移处的值. -
另一部分数据由本.o自己提供,如下
objdump -sj .rodata ./obj/main.o
解读
-
内部变量重定位.
因为是局部变量,编译器知道数据放在了
.rodata
区,要求后续过程根据 “S(main.o镜像中.rodata的内存地址)-A(0x04)”,修改main.o镜像中0x16偏移处的值.
再分析经过静态链接之后的可执行文件
objdump -S ./bin/weharmony
解读
-
main.o中被重定位的部分不再是
00 00 00 00
都已经有了实际的数据,例如:对应的
# 2050 <_IO_stdin_used+0x50>
地址数据正是.rodata
2050位置的harmony os
-
看main()中的
callq 1188
1188
正是func_str
的入口地址 -
看全局变量
g_str``g_int
对应的链接地址0x4018
和0x4010
由
.data
区提供0x4010
= 0x33 = 51 -
main函数中调用
printf
代码为callq 1050
内容由
.plt.sec
区提供,并反汇编该区为注意
3fd0
,需要运行时环境提供,加载器动态重定位实现. -
总结下来就是 weharmony 已完成了所有.o文件的静态重定位部分, 组合成一个新的可执行文件,其中只还有动态链接部分尚未完成,因为那需要运行时重定位地址.如下:
objdump -R ./bin/weharmony
解读
- 这是
weharmony
对运行时环境提交的一份外交说明,有了它就可以与国际接轨,入住地球村. - 这份说明其他部分很陌生,看个熟悉的
3fd0
,其动态链接重定位类型为R_X86_64_JUMP_SLOT
,它在告诉动态加载器,在运行时环境中找到printf
并完成动态重定位.
百篇博客分析.深挖内核地基
- 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛
- 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
按功能模块:
- 前因后果 >> 总目录 | 调度故事 | 内存主奴 | 源码注释 | 源码结构 | 静态站点 |
- 基础工具 >> 双向链表 | 位图管理 | 用栈方式 | 定时器 | 原子操作 | 时间管理 |
- 加载运行 >> ELF格式 | ELF解析 | 静态链接 | 重定位 | 进程映像 |
- 进程管理 >> 进程管理 | 进程概念 | Fork | 特殊进程 | 进程回收 | 信号生产 | 信号消费 | Shell编辑 | Shell解析 |
- 编译构建 >> 编译环境 | 编译过程 | 环境脚本 | 构建工具 | gn应用 | 忍者ninja |
- 进程通讯 >> 自旋锁 | 互斥锁 | 进程通讯 | 信号量 | 事件控制 | 消息队列 |
- 内存管理 >> 内存分配 | 内存管理 | 内存汇编 | 内存映射 | 内存规则 | 物理内存 |
- 任务管理 >> 时钟任务 | 任务调度 | 任务管理 | 调度队列 | 调度机制 | 线程概念 | 并发并行 | CPU | 系统调用 | 任务切换 |
- 文件系统 >> 文件概念 | 文件系统 | 索引节点 | 挂载目录 | 根文件系统 | 字符设备 | VFS | 文件句柄 | 管道文件 |
- 硬件架构 >> 汇编基础 | 汇编传参 | 工作模式 | 寄存器 | 异常接管 | 汇编汇总 | 中断切换 | 中断概念 | 中断管理 |
百万汉字注解.精读内核源码
四大码仓中文注解 . 定期同步官方代码
鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞则更佳,感谢每一份支持。
