U-Boot的重定位实现机制
获取当前芯片平台的相关信息
为了深入了解ARM 64位芯片架构,笔者为u-boot
添加了archinfo
命令,以获取CPU当前的工作状态等信息。不过在增加完整的64位ARM架构信息查看功能之前,笔者首先增加了简单的获取当前PC指针及栈指针的函数:
diff --git a/arch/arm/lib/arch-info.c b/arch/arm/lib/arch-info.c
new file mode 100644
index 00000000..b9bc9fd0
--- /dev/null
+++ b/arch/arm/lib/arch-info.c
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#include <archinfo.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+ulong archinfo_getpc(ulong * stkp)
+{
+ ulong rval;
+
+ rval = (ulong) __builtin_return_address(0);
+ if (stkp != NULL) {
+ ulong stkval = 0;
+ asm volatile ("\tmov %0, sp\n" : "=r"(stkval));
+ *stkp = stkval;
+ }
+
+ return rval;
+}
diff --git a/cmd/archinfo.c b/cmd/archinfo.c
new file mode 100644
index 00000000..5a474ce4
--- /dev/null
+++ b/cmd/archinfo.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <archinfo.h>
+#include <command.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static int do_archinfo(struct cmd_tbl *cmdtp,
+ int flag, int argc, char *const argv[])
+{
+ ulong pcval, stack = 0;
+
+ pcval = archinfo_getpc(&stack);
+ printf("Current PC: 0x%lx, stack: 0x%lx\n",
+ pcval, stack);
+
+ return 0;
+}
+
+/**************************************************/
+U_BOOT_CMD(
+ archinfo, 3, 1, do_archinfo,
+ "display architectural dependent information",
+ "archinfo"
+);
针对笔者调试使用到的设备,u-boot
镜像的基地址CONFIG_SYS_TEXT_BASE
定义为0x80000
;因镜像文件比较小,获得的PC指针应当与当前的基地址相去不远:
Disassembly of section .text:0000000000080000 <__image_copy_start>:80000: 1400000c b 80030 <reset> 80004: d503201f nop0000000000080008 <_TEXT_BASE>:80008: 00080000 .word 0x000800008000c: 00000000 .word 0x000000000000000000080010 <_end_ofs>:80010: 000850b0 .word 0x000850b080014: 00000000 .word 0x00000000
上面新增代码需要修改相关Makefile才能编译链接成功。加载新的u-boot
镜像后,执行archinfo
命令:
Hit any key to stop autoboot: 0
U-Boot> archinfo
Current PC: 0x3b36492c, stack: 0x3af5bb10
U-Boot>
这个结果与期望结果相去甚远。暂且不关注栈指针的值,通过对u-boot
的反汇编可知,当前的PC指针应当为0x8492c
:
0000000000084918 <do_archinfo>:84918: a9be7bfd stp x29, x30, [sp, #-32]!8491c: 910003fd mov x29, sp 84920: 910083a0 add x0, x29, #0x2084924: f81f8c1f str xzr, [x0, #-8]!84928: 97fff967 bl 82ec4 <archinfo_getpc>8492c: aa0003e1 mov x1, x0
二者相差了0x3b36492c - 0x8492c = 0x3b2e0000
;这个偏量是如何产生的?目前可以肯定u-boot
在启动过程中给自身进行了重定位;不过根据笔者之前的一篇文章(仅是u-boot
的自拷贝),这个重定位到任意地址并执行(Position Independent Execute)对于基地址固定的u-boot
镜像文件来说,是不可能的。笔者以过去的经验犯了一个错误,这个调试结果刷新了笔者的认知。不过,这并不意味着之前的工作白费了:之前已测试,u-boot
必须在加载到基地址处启动才不会有异常。接下来就需要分析u-boot
的重定位功能了,这是一个探究的过程。
u-boot的板级信息查看
u-boot
提供了bdinfo
命令以查看设备相关的板级信息。在笔者的调试设备上,板级信息中恰好存在上面计算得到的偏移量,从而可以从板级信息入手,定位u-boot
重定位的地址如何确定的:
U-Boot> bdinfo
boot_params = 0x0000000000000100
DRAM bank = 0x0000000000000000
-> start = 0x0000000000000000
-> size = 0x000000003b400000
flashstart = 0x0000000000000000
flashsize = 0x0000000000000000
flashoffset = 0x0000000000000000
baudrate = 115200 bps
relocaddr = 0x000000003b360000
reloc off = 0x000000003b2e0000
经一番查找,确定了与u-boot
重定位地址相关的计算的代码如下:
/* common/board_f.c */343 gd->ram_top = gd->ram_base + get_effective_memsize();344 gd->ram_top = board_get_usable_ram_top(gd->mon_len);345 gd->relocaddr = gd->ram_top;...679 #ifdef CONFIG_SYS_TEXT_BASE680 #ifdef ARM681 gd->reloc_off = gd->relocaddr - (unsigned long)__image_copy_start;
注意到,relocaddr
与reloc_off
之差恰好为u-boot
镜像加载的基地址,也是符号__image_copy_start
所在的地址。可见,u-boot
在启动过程中,将自身重定位到了可用内存的顶部ram_top
附近的内存区域。这样做的原因是什么?我想可能是为了方便u-boot
对内存的管理,并可以将新的u-boot
加载到预定义的基地址。不过,为设备配置一个板级参数GD_FLG_SKIP_RELOC
可以跳过多个u-boot
资源的重定位。
u-boot的编译选项
笔者一直认为由-fPIC
和-fPIE
参数编译生成的目标文件,链接成动态库或可执行文件后,在运行时可动态地重定位,从而实现了“位置无关地执行”,即Position Independent Execute。不过笔者犯了经验主义的错误,查看u-boot
的编译命令可以肯定,u-boot
编译的选项同时包含-fno-pic
及-fno-PIE
两个参数:
aarch64-linux-gnu-gcc -Wp,-MD,cmd/.gpio.o.d -nostdinc -isystem /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/../lib/gcc/aarch64-linux-gnu/7.5.0/include -Iinclude -I./arch/arm/include -include ./include/linux/kconfig.h -D__KERNEL__ -D__UBOOT__ -Wall -Wstrict-prototypes -Wno-format-security -fno-builtin -ffreestanding -std=gnu11 -fshort-wchar -fno-strict-aliasing -fno-PIE -Os -fno-stack-protector -fno-delete-null-pointer-checks -Wno-maybe-uninitialized -g -fstack-usage -Wno-format-nonliteral -Wno-unused-but-set-variable -Werror=date-time -D__ARM__ -fno-pic -mstrict-align -ffunction-sections -fdata-sections -fno-common -ffixed-r9 -fno-common -ffixed-x18 -pipe -march=armv8-a -D__LINUX_ARM_ARCH__=8 -I./arch/arm/mach-bcm283x/include -DKBUILD_BASENAME='"gpio"' -DKBUILD_MODNAME='"gpio"' -c -o cmd/gpio.o cmd/gpio.c
u-boot
虽然启用了mmu
,但通常只是一一映射,虚拟地址等同于物理地址。这两个编译参数可以使得编译的目标文件中没有多余的段,如动态库常见的.got
(Global Offset Table)、.got.plt
以及.dynamic
等数据段,方便链接脚本的编写,以及重定位的实现。
u-boot
重定位的实现
为笔者调式设备编译u-boot
,在代码根目录下会生成一个链接脚本,其部分内容如下:
SECTIONS
{. = 0x00000000;. = ALIGN(8);.text :{*(.__image_copy_start)arch/arm/cpu/armv8/start.o (.text*)}.efi_runtime : {__efi_runtime_start = .;*(.text.efi_runtime*)*(.rodata.efi_runtime*)*(.data.efi_runtime*)__efi_runtime_stop = .;}.text_rest :{*(.text*)}. = ALIGN(8);.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }. = ALIGN(8);.data : {*(.data*)}. = ALIGN(8);. = .;. = ALIGN(8);.u_boot_list : {KEEP(*(SORT(.u_boot_list*)));}. = ALIGN(8);.efi_runtime_rel : {__efi_runtime_rel_start = .;*(.rel*.efi_runtime)*(.rel*.efi_runtime.*)__efi_runtime_rel_stop = .;}. = ALIGN(8);.image_copy_end :{*(.__image_copy_end)}. = ALIGN(8);.rel_dyn_start :{*(.__rel_dyn_start)}.rela.dyn : {*(.rela*)}.rel_dyn_end :{*(.__rel_dyn_end)}_end = .;
链接脚本导出了四个重要的符号,分别为:拷贝代码的起始地址__image_copy_start
(实际上为CONFIG_SYS_TEXT_BASE
定义的基地址)、拷贝的终止地址__image_copy_end
、重定位数据的起始地址__rel_dyn_start
和终止地址__rel_dyn_end
。这四个符号在重定位代码实现中被引用,首先,拷贝代码段(.text
)和数据段(.rodata
及.data
):
/* arch/arm/lib/relocate_64.S */adrp x1, __image_copy_start /* x1 <- address bits [31:12] */add x1, x1, :lo12:__image_copy_start/* x1 <- address bits [11:00] */adrp x2, __image_copy_end /* x2 <- address bits [31:12] */add x2, x2, :lo12:__image_copy_end /* x2 <- address bits [11:00] */
copy_loop:ldp x10, x11, [x1], #16 /* copy from source address [x1] */stp x10, x11, [x0], #16 /* copy to target address [x0] */cmp x1, x2 /* until source end address [x2] */b.lo copy_loop
这个拷贝的汇编实现,比笔者的自拷贝效率高一倍,每次复制16个字节;此外,笔者的自拷贝是在关闭了dcache
和icache
的条件下执行的(由新增goraw
命令关闭),可以推测,复制完成之后会有一个刷数据缓存和指令缓存的操作:
/* arch/arm/lib/relocate_64.S */
relocate_done:switch_el x1, 3f, 2f, 1fbl hang
3: mrs x0, sctlr_el3b 0f
2: mrs x0, sctlr_el2b 0f
1: mrs x0, sctlr_el1
0: tbz w0, #2, 5f /* skip flushing cache if disabled */tbz w0, #12, 4f /* skip invalidating i-cache if disabled */ic iallu /* i-cache invalidate all */isb sy
4: ldp x0, x1, [sp, #16]bl __asm_flush_dcache_rangebl __asm_flush_l3_dcache
5: ldp x29, x30, [sp],#32ret
接下来重要的操作是重定位数据的外理了。重定位数据有两种固定的格式,通过man elf
可以得到相关信息。对于64位ELF文件,其定义为:
typedef struct {Elf64_Addr r_offset;uint64_t r_info;
} Elf64_Rel;typedef struct {Elf64_Addr r_offset;uint64_t r_info;int64_t r_addend;
} Elf64_Rela;
重定位数据的处理只考虑了第二种格式,Elf64_Rela
;笔者认为,可能是因为u-boot
是静态链接的,镜像文件中不存在第一种格式的重定位数据;也可能是为了对齐,两种格式的重定位数据大小被设定相同。r_offset
指定了需要重定位的内存偏移地址,该地址的数据在不重定位的情况下不需要修改(那么可以推测,对于静态链接的可执行文件,没有.rela
重定位数据段的情况下,也能够正常运行);r_info
指定了重定位的类型,r_addend
则用于计算相对偏移重定位的一个偏移。u-boot
对重定向数据的处理如下:
/* * Fix .rela.dyn relocations*/adrp x2, __rel_dyn_start /* x2 <- address bits [31:12] */add x2, x2, :lo12:__rel_dyn_start /* x2 <- address bits [11:00] */adrp x3, __rel_dyn_end /* x3 <- address bits [31:12] */add x3, x3, :lo12:__rel_dyn_end /* x3 <- address bits [11:00] */
fixloop:ldp x0, x1, [x2], #16 /* (x0,x1) <- (SRC location, fixup) */ldr x4, [x2], #8 /* x4 <- addend */and x1, x1, #0xffffffffcmp x1, #R_AARCH64_RELATIVEbne fixnext/* relative fix: store addend plus offset at dest location */add x0, x0, x9add x4, x4, x9str x4, [x0]
fixnext:cmp x2, x3b.lo fixloop
宏R_AARCH64_RELATIVE
在u-boot
中被定义为0x403
,在elf.h
中定义相同:
/* /usr/include/elf.h */
#define R_AARCH64_RELATIVE 1027 /* Adjust by program base. */
上面的汇编代码表明,u-boot
只处理程序代码断的偏移;即修改保存于镜像中的函数地址。它会读取r_addend
,加上一个偏移量(由x9
寄存量保存),写入到相应的、新的r_offset
地址处(原先的r_offset
加上偏移量)。通过aarch64-linux-gnu-objdump
的-D
选项反汇编u-boot
可得到:
Disassembly of section .rela.dyn:00000000000f9788 <__image_copy_end>:f9788: 00080028 .word 0x00080028 # r_offsetf978c: 00000000 .word 0x00000000f9790: 00000403 .word 0x00000403 # r_info -> R_AARCH64_RELATIVEf9794: 00000000 .word 0x00000000f9798: 000843b0 .word 0x000843b0 # r_addendf979c: 00000000 .word 0x00000000f97a0: 000810d0 .word 0x000810d0 # r_offsetf97a4: 00000000 .word 0x00000000f97a8: 00000403 .word 0x00000403 # r_info -> R_AARCH64_RELATIVEf97ac: 00000000 .word 0x00000000f97b0: 000810d0 .word 0x000810d0 # r_addendf97b4: 00000000 .word 0x00000000f97b8: 000810d8 .word 0x000810d8 # r_offsetf97bc: 00000000 .word 0x00000000f97c0: 00000403 .word 0x00000403 # r_info -> R_AARCH64_RELATIVEf97c4: 00000000 .word 0x00000000f97c8: 000810d0 .word 0x000810d0 # r_addendf97cc: 00000000 .word 0x00000000
上面的空格及注释是笔者加入的,仅列出了三条重定位结构体Elf64_Rela
。处理第一个重定位数据,其r_offset
为0x80028
,增量数据r_addend
为0x843b0
;结合此处(0x80028
地址处)的数据,可知重定位操作主要是修改了函数的绝对地址:
/* Disassembly of u-boot, u-boot.S */
0000000000080028 <_save_boot_params>:80028: 000843b0 .word 0x000843b08002c: 00000000 .word 0x00000000/* arch/arm/cpu/armv8/start.S */
_save_boot_params:.quad save_boot_params
上面的重定位汇编代码中的x9
若为零,那么r_offset
地址偏移处的数据与r_addend
完全相同,修改r_offset
前后的数据完全相同,那么重定位出就不需要了。这也就证明了上面的推断:若不重定位,u-boot
镜像中不需要.rela
段数据;静态链接的u-boot
自身已是完整的了,但只能在基地址处执行。最后,第二和第三个重定位数据的r_addend
与r_offset
偏移地址处的数据也相同,更加坚定了这种解读的正确性:
/* Disassembly of u-boot, u-boot.S */
00000000000810d0 <efi_runtime_mmio>:810d0: 000810d0 .word 0x000810d0810d4: 00000000 .word 0x00000000810d8: 000810d0 .word 0x000810d0810dc: 00000000 .word 0x00000000/* lib/efi_loader/efi_runtime.c */
LIST_HEAD(efi_runtime_mmio); /* -> 指向自身的空链表(双链) */
本次调试、分析,刷新了笔者的认知,同时也加深了对u-boot
的理解;以后再也不能肯定地说:u-boot
只能运行在基地址偏移处,u-boot
的行为比之前所知的要复杂得多。运行于Linux系统下的应用,在加载动态链接库时,代码和数据的重定位操作远比u-boot
中实现的这一重定位操作复杂,以后再探究。
U-Boot的重定位实现机制相关推荐
- Nat. Biotech. | AI、药物重定位和同行评审
传统的计算分析和机器学习是否可以弥补在信息泛滥的情况下对药物重定位论文进行同行评审的不足? COVID-19的流行改变了科学和临床成果的分享和传播方式.根据最近的一项分析,平均每周有367篇COVID ...
- medRxiv | 基于网络的人类冠状病毒的药物重定位
随着新型冠状病毒(2019-nCoV)感染肺炎疫情持续发展,武汉.全国各地以及全球的疫情牵动着每一个人的心.2020年2月5日medRxiv发表了研究工作"Network-based Dru ...
- Nat. Commun | 用于全基因组药物重定位的系统网络算法
1. 背景 DNA/RNA测序的最新进展实现了通过"精确"定位个性化疾病模块来快速识别新靶标并重新利用已批准的药物治疗异质性疾病.基因组学时代,药物开发已成为高度集成的系统性问题, ...
- PE文件结构详解(六)重定位
前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...
- 共享可写节包含重定位_深度探索win32可执行文件格式
深度探索win32可执行文件格式 Matt Pietrek 翻译:姜庆东 摘要:对可执行文件的深入认识将带你深入到系统深处.如果你知道你的exe/dll里是些什么东东,你就是一个更有知识的程序员.作为 ...
- winform 在panel怎么实现锚点定位_5GC支持URLLC解决方案12:以太网PDU会话锚点重定位...
该解决方案解决了关键问题#3:在UE移动期间增强会话连续性,相较方案11,它"侧重于在保持效率的同时增强会话连续性".为了实现URLLC的低时延,PDU会话锚点和访问节点之间需要物 ...
- linux 重定位arm,Arm linxu启动过程分析(一)
本文着重分析 FS2410 平台 linux-2.6.14 内核启动的详细过程,主要包括: zImage 解压缩阶段. vmlinux 启动汇编阶段. startkernel 到创建第一个进程阶段三个 ...
- URL锚点HTML定位技术机制、应用与问题
by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxinxu.com/wordpress/?p=3591 一.锚点是什 ...
- 嵌入式学习(二)——刷机和led实验(看门狗、c语言、icache、重定位、SDRAM)
目录 一.刷机和裸机实验 1.1 刷机步骤 1.2 交叉编译链 1.2.1 环境变量配置 二.led实验 2.1 实验准备 2.2 实验开始 2.2.1 Makefile 2.2.2 mkv210_i ...
最新文章
- 【中文】Joomla1.7扩展介绍之Kunena(强大的论坛)
- 使用jquery获取url以及jquery获取url参数的方法
- 数据结构-队列之链式队列
- 天池 在线编程 区间合并(字符串)
- nginx启动报错 :Failed to start The nginx HTTP and reverse prox...er.
- Java基础篇:简单介绍一下final
- 五子棋c语言策划书活动内容,五子棋比赛活动的策划案
- 深度学习——VGG16模型详解
- python判断素数程序_python判断素数程序_Python程序检查素数
- 抽空写了个小游戏(未完待续)
- Subclass in C++ - C++ 中的子类
- uni-app获取当前位置并计算出某个地点距离
- 你炒的肉丝为何又柴又老又难吃?
- linux基本功系列之pwd命令实战
- 布里斯托大学计算机科学专业排名,2019上海软科世界一流学科排名计算机科学与工程专业排名布里斯托大学排名第101-150...
- Android View(一)——View的基础知识
- 【LaTeX公式】LaTeX数学公式的符号表示
- Pytorch之Rot旋转
- VS2015使用git同步代码
- 虚拟化与网络存储技术