获取当前芯片平台的相关信息

为了深入了解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;

注意到,relocaddrreloc_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个字节;此外,笔者的自拷贝是在关闭了dcacheicache的条件下执行的(由新增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_RELATIVEu-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_offset0x80028,增量数据r_addend0x843b0;结合此处(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_addendr_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的重定位实现机制相关推荐

  1. Nat. Biotech. | AI、药物重定位和同行评审

    传统的计算分析和机器学习是否可以弥补在信息泛滥的情况下对药物重定位论文进行同行评审的不足? COVID-19的流行改变了科学和临床成果的分享和传播方式.根据最近的一项分析,平均每周有367篇COVID ...

  2. medRxiv | 基于网络的人类冠状病毒的药物重定位

    随着新型冠状病毒(2019-nCoV)感染肺炎疫情持续发展,武汉.全国各地以及全球的疫情牵动着每一个人的心.2020年2月5日medRxiv发表了研究工作"Network-based Dru ...

  3. Nat. Commun | 用于全基因组药物重定位的系统网络算法

    1. 背景 DNA/RNA测序的最新进展实现了通过"精确"定位个性化疾病模块来快速识别新靶标并重新利用已批准的药物治疗异质性疾病.基因组学时代,药物开发已成为高度集成的系统性问题, ...

  4. PE文件结构详解(六)重定位

    前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...

  5. 共享可写节包含重定位_深度探索win32可执行文件格式

    深度探索win32可执行文件格式 Matt Pietrek 翻译:姜庆东 摘要:对可执行文件的深入认识将带你深入到系统深处.如果你知道你的exe/dll里是些什么东东,你就是一个更有知识的程序员.作为 ...

  6. winform 在panel怎么实现锚点定位_5GC支持URLLC解决方案12:以太网PDU会话锚点重定位...

    该解决方案解决了关键问题#3:在UE移动期间增强会话连续性,相较方案11,它"侧重于在保持效率的同时增强会话连续性".为了实现URLLC的低时延,PDU会话锚点和访问节点之间需要物 ...

  7. linux 重定位arm,Arm linxu启动过程分析(一)

    本文着重分析 FS2410 平台 linux-2.6.14 内核启动的详细过程,主要包括: zImage 解压缩阶段. vmlinux 启动汇编阶段. startkernel 到创建第一个进程阶段三个 ...

  8. URL锚点HTML定位技术机制、应用与问题

    by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxinxu.com/wordpress/?p=3591 一.锚点是什 ...

  9. 嵌入式学习(二)——刷机和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 ...

最新文章

  1. 【中文】Joomla1.7扩展介绍之Kunena(强大的论坛)
  2. 使用jquery获取url以及jquery获取url参数的方法
  3. 数据结构-队列之链式队列
  4. 天池 在线编程 区间合并(字符串)
  5. nginx启动报错 :Failed to start The nginx HTTP and reverse prox...er.
  6. Java基础篇:简单介绍一下final
  7. 五子棋c语言策划书活动内容,五子棋比赛活动的策划案
  8. 深度学习——VGG16模型详解
  9. python判断素数程序_python判断素数程序_Python程序检查素数
  10. 抽空写了个小游戏(未完待续)
  11. Subclass in C++ - C++ 中的子类
  12. uni-app获取当前位置并计算出某个地点距离
  13. 你炒的肉丝为何又柴又老又难吃?
  14. linux基本功系列之pwd命令实战
  15. 布里斯托大学计算机科学专业排名,2019上海软科世界一流学科排名计算机科学与工程专业排名布里斯托大学排名第101-150...
  16. Android View(一)——View的基础知识
  17. 【LaTeX公式】LaTeX数学公式的符号表示
  18. Pytorch之Rot旋转
  19. VS2015使用git同步代码
  20. 虚拟化与网络存储技术

热门文章

  1. 从MediaRecord录像中读取H264参数
  2. excel按季度分类汇总_按部门对Excel表格中数据进行分类汇总的方法
  3. PFC2D 5.0基础练习2——根据CAD文件对颗粒分组
  4. Vue - 解决部署到服务器后Element UI图标不显示问题(50错误)
  5. iOS学习之图片放大,滑动浏览
  6. 社交新时代,元宇宙结合游戏应用落地开辟新场景
  7. tkinter库详解
  8. Java 黄金贵金属理财投资系统源码发布
  9. Hoj 3130 Qie-Gao
  10. docker daemon(dockerd) 配置文件 daemon.json