【问题标题】:How this relocation of u-boot works hereu-boot 的这种重定位如何在这里工作
【发布时间】:2014-01-09 18:27:03
【问题描述】:

我正在尝试理解部分代码,它表示 U-boot 在 RAM 中的重定位,下面是代码

   #ifndef  CONFIG_SKIP_RELOCATE_UBOOT
   relocate:
     adr r0,_start      /*r0 <--- Current posistion of code8*/
     ldr r1,_TEXT_BASE  /* test if we run from flash or Ram /*
     cmp r0,r1
     beq stack_setup
     ldr r2,_armboot_start   
     ldr r3,_bss_start
     sub r2,r3,r2
     add r2,r0,r2
   copy_loop:
     ldmia r0!,{r3-r10}
     stmia r1!,{r3-r10}
     cmp   r0,r2
     ble   cop_loop
     #endif /*CONFIG_SKIP_RELOCATE_UBOOT/*

现在可以告诉我这是怎么回事吗??我们如何测试我们的 u-boot 是从 RAM 还是 Flash 运行??

我在 ARM 平台上。

【问题讨论】:

  • 只需阅读代码。它正在比较您是从闪存运行此代码还是在 ram 中运行它的副本。如果从闪存运行,则复制并重试,如果从 ram 运行,则继续下一步(堆栈设置)
  • @dwelch 谢谢回复,所以 r0 有 FLASH 位置而 r1 有 RAM 位置??
  • 我认为区别在于 adr 与 ldr。一个是将 pc 作为数据(以及地址)的一部分,另一个则不是。这是关键的区别。
  • Relocation in assembly 的可能重复项; adr rX,_labelbl xxx 在获取 PC 相关信息方面基本上是等效的。 Maybe also interesting/related.

标签: assembly arm u-boot


【解决方案1】:

这是一个非常简单的例子,

.globl _start
_start:

    adr r0,_start
    ldr r1,_TEXT_BASE

...

_TEXT_BASE: .word _start

组装、链接然后拆卸时:

00008000 <_start>:
    8000:   e24f0008    sub r0, pc, #8
    8004:   e59f101c    ldr r1, [pc, #28]   ; 8028 <_TEXT_BASE>
...
00008028 <_TEXT_BASE>:
    8028:   00008000    andeq   r8, r0, r0

这就是你的答案。 adr 指令基于您的电脑在执行时包含 0x8008 的假设。 ldr 将提取一个链接时间值,无论您身在何处,该值都是相同的。

例如,如果这段代码实际上位于地址 0x20000000,那么当第一条指令(adr 是伪指令,在反汇编中它是 8 的子指令)时,adr 被执行,现在你得到一个 0x20000008-8 = 0x20000000,您将其与 0x8000 进行比较,它们不匹配。如果您在 0x8000 处运行代码,则 0x8008-8 = 0x8000 并且两者匹配。

只需阅读代码并查找 adr 指令(或执行我所做的并尝试并检查编译器/工具的输出,和/或在硬件上运行它,如果没有显示答案)。

编辑:

使用具有各种前缀的 gnu 工具,但是这段代码很简单,几乎不需要关心。 arm-none-eabi- 或 arm-none-linux-gnueabi-。

假设程序集文件名为 foo.s

arm-none-eabi-as foo.s -o foo.o
arm-none-eabi-ld -T memmap foo.o -o foo.elf
arm-none-eabi-objdump -D foo.elf

我所说的 memmap 是链接器脚本,在这种情况下您可以使用命令行 -Ttext=0x8000。而且我喜欢为我的交叉编译二进制文件使用 .elf 扩展名,但不是每个人都这样做。

00008000 <_start>:
    8000:   e24f0008    sub r0, pc, #8
    8004:   e59f101c    ldr r1, [pc, #28]   ; 8028 <_TEXT_BASE>
...
00008028 <_TEXT_BASE>:
    8028:   00008000    andeq   r8, r0, r0

_start 是一个 gnu 工具,链接器需要/希望这个标签知道代码的入口点在哪里。所以它不是真正的 uboot 东西,虽然 uboot 可能也关心,但它绝对是 gnu 链接器的东西。

0x8000 对于基于 ARM 的 linux 程序作为入口点并不是一个不常见的地址,你会看到 linux 作为一个内核从这样的地址开始,但是你可以设置你的系统和二进制文件真的很随意。

这里发生的事情并没有真正的 ARM 特定或魔法。在任何平台上都有相同的想法,您只需提出正确的说明即可。

arm 中的程序计数器在前两条指令,这是一条 32 位的 arm 指令,因此执行指令时的程序计数器假定为该指令的地址加 8。由于这个 adr 是 _start 处的第一条指令这意味着该指令在链接/编译时位于地址 0x8000,因此从 0x8008 到 0x8000 指令被编码为 r0 = pc-8;另一个信息是链接器在标签_TEXT_BASE 之后提供_start 地址。所以另一个步骤是将该值加载到 r1 中。

这仅在您假设代码实际存在于闪存中并且处理器在地址 0x8000 处看到该指令的情况下才有效。 0x8000 和 0x8000 比较是相等的,所以程序在 ram 中复制自己,然后它会跳转到这个副本所在的开头,这次当它通过代码的副本时,一个寄存器包含一些地址除了 0x8000 之外,比较失败,如果您从闪存运行原始版本或从 ram 运行副本,这只是一个检测器。目的是制作副本并运行基于 ram 的副本。需要采取其他预防措施来确保代码可以在两个地址上运行(位置无关代码)。

如果您碰巧知道闪存地址是 0x00008000,并且碰巧知道 RAM 地址是 0x20000000,那么您可以改为将 pc 与 0x10000 进行比较,或者将 pc 的值与 0xFF000000 相比较

p>
and r0,pc,#0xFF000000
beq stack_setup

但我认为这是更通用的代码,因此使用了额外的说明和精确的比较。

同样,这种类型的技巧相当普遍,检测闪存或内存副本等。所使用的确切指令以及如何让链接器为您填充内容是特定于目标的。

在这种情况下,闪存可能位于某个非零地址,而 0x8000 是 ram 副本。我不知道。

【讨论】:

  • 感谢@dwelch 让事情变得清晰和简单,我对组装知之甚少,在 x86 上也是如此。我理解的是标签 _start,它是 u-boot 映像的入口点,有一个地址0008000(在我的平台上会有所不同)。现在第一个 inst adr r0,_start 将存储到从 0008000 开始的某个偏移量? “board/kb9202configs.mk”中定义的_TEXT_BASE是为了什么目的。我也想知道你是怎么做这个反汇编的,这样我也可以在我的平台上做同样的事情。
  • 我个人的看法是,如果你想在这个级别的引导加载程序上工作,即使你的汇编语言技能不强,你也需要知道如何编译、汇编和链接。如果您已为目标系统正确链接,为了进行调试,最简单的方法是反汇编并查看最终结果。因此,通过扩展,我会争辩说你学习如何让你的工具反汇编你所链接的东西......从那里你可以重复我上面所做的事情来弄清楚发生了什么。
  • 感谢@dwelch 对我的善意。我会接受您的建议并继续努力。也感谢您提供详细的答案。
猜你喜欢
  • 1970-01-01
  • 2013-05-19
  • 1970-01-01
  • 2013-08-04
  • 2015-06-08
  • 2018-03-18
  • 2017-01-28
  • 1970-01-01
  • 2021-01-10
相关资源
最近更新 更多