【问题标题】:how does this assembly code get the difference between physical offset and page offset?这个汇编代码如何获得物理偏移和页面偏移之间的差异?
【发布时间】:2017-07-09 09:25:54
【问题描述】:

在研究linux源码的时候,看到很多汇编代码是这样的:

adr r0, 1f
ldmia   r0, {r3-r7}
mvn ip, #0
subs    r3, r0, r3  @ PHYS_OFFSET - PAGE_OFFSET
...
    .align
1:  .long   .
    .long   __pv_table_begin
    .long   __pv_table_end
2:  .long   __pv_phys_pfn_offset
    .long   __pv_offset

首先,它以adr开头,在上面的第一行中,我理解adr r0, 1f的意思是将1:开头的地址保存在r0中。

ldmia r0, {r3-r7} 表示它将从保存在r0(指向1:)中的地址开始加载值到寄存器r3,r4,r5,r6,r7。因此,

r3=.

r4=__pv_table_begin

r5=__pv_table_end

r6=__pv_phys_pfn_offset

r7=__pv_offset.

现在,我不明白的部分: subs r3,r0,r3 我不完全确定r3=. 是什么意思,但我猜r3 最终会包含它自己地址的值。

模糊地说,r0r3 值都是指向同一位置1: 的地址值。但我猜它们的值是不同的,因为一个是物理地址,另一个是虚拟地址。 (

这就是为什么我认为代码试图通过subs r3, r0, r3 来区分这两者。

我不确定我的猜测是否正确,即使如此,我也不知道哪个是物理地址和虚拟地址。此外,评论提到减法会产生物理偏移量和页面偏移量之间的差异。我已经阅读了与内存虚拟化相关的页面,但我无法将这些知识与偏移减法联系起来。

【问题讨论】:

  • 我不是 ARM 专家,但 adr 在运行时计算 1 的地址(即每当该代码被重新定位时),而 gas . 指令是 w.r.t 的偏移量。当前部分(或类似部分,我也不是 GAS 专家)。

标签: assembly memory-management arm virtual-memory


【解决方案1】:

你为什么不试试呢?

hello:
    .long .
    .long 0x11111111
    .long 0x22222222
    .long 0x33333333
    .long 0x44444444
    .long 0x55555555
    .long 0x66666666

.globl TEST
TEST:
    adr r0,hello
    bx lr

链接和反汇编

0000803c <hello>:
    803c:   0000803c    andeq   r8, r0, r12, lsr r0
    8040:   11111111    tstne   r1, r1, lsl r1
    8044:   22222222    eorcs   r2, r2, #536870914  ; 0x20000002
    8048:   33333333    teqcc   r3, #-872415232 ; 0xcc000000
    804c:   44444444    strbmi  r4, [r4], #-1092    ; 0xfffffbbc
    8050:   55555555    ldrbpl  r5, [r5, #-1365]    ; 0xfffffaab
    8054:   66666666    strbtvs r6, [r6], -r6, ror #12

00008058 <TEST>:
    8058:   e24f0024    sub r0, pc, #36 ; 0x24
    805c:   e12fff1e    bx  lr

TEST 如我们预期的那样返回 0x803C。

列表中的第一项可能是您的谜。请注意他们如何使用点快捷方式来指示此处或此地址,因此列表中的第一项是列表开头的地址。其中 r0 已经他们可能已经完成了 mov r3,r0 但可能会燃烧该指令而不是仅加载它并用一条指令燃烧 ram。谁知道...

所以

.globl TEST
TEST:
    adr r0,hello
    ldmia r0,{r3}

    mov r3,r0
    bx lr

返回相同的 0x803C 值。

现在

.globl TEST
TEST:
    adr r0,hello
    ldmia r0,{r3}
    subs r3,r0,r3

    mov r0,r3
    bx lr

正如预期的那样返回零,那么这一切的意义何在?请注意,这整个部分是与位置无关的,对吧?好吧,如果我将链接器更改为认为这是被加载到其他地方怎么办...

MEMORY
{
    ram : ORIGIN = 0xA000, LENGTH = 0x1000000
}

生产

0000a03c <hello>:
    a03c:   0000a03c    andeq   r10, r0, r12, lsr r0
    a040:   11111111    tstne   r1, r1, lsl r1
    a044:   22222222    eorcs   r2, r2, #536870914  ; 0x20000002
    a048:   33333333    teqcc   r3, #-872415232 ; 0xcc000000
    a04c:   44444444    strbmi  r4, [r4], #-1092    ; 0xfffffbbc
    a050:   55555555    ldrbpl  r5, [r5, #-1365]    ; 0xfffffaab
    a054:   66666666    strbtvs r6, [r6], -r6, ror #12

0000a058 <TEST>:
    a058:   e24f0024    sub r0, pc, #36 ; 0x24
    a05c:   e8900008    ldm r0, {r3}
    a060:   e0503003    subs    r3, r0, r3
    a064:   e1a00003    mov r0, r3
    a068:   e12fff1e    bx  lr

但仍然在同一位置执行会给出 0xFFFFE000,即 -0x2000,因为我更改了链接器的方向,如果我将其更改为 0x5000 而不是 0xA000,我会得到 0x3000 作为差异。

所以这段代码的作用是

.long .

是编译时间,adr 是运行时并使用运行时 pc,因此此代码检测表所在的实际内存地址与表所在的编译时间地址之间的差异。如果表中的项目是编译时地址

hello:
    .long .
    .long one
    .long two
    .long three
one:
    .long 0x44444444
two:
    .long 0x55555555
three:
    .long 0x66666666


0000503c <hello>:
    503c:   0000503c    andeq   r5, r0, r12, lsr r0
    5040:   0000504c    andeq   r5, r0, r12, asr #32
    5044:   00005050    andeq   r5, r0, r0, asr r0
    5048:   00005054    andeq   r5, r0, r4, asr r0
0000504c <one>:
    504c:   44444444    strbmi  r4, [r4], #-1092    ; 0xfffffbbc
00005050 <two>:
    5050:   55555555    ldrbpl  r5, [r5, #-1365]    ; 0xfffffaab
00005054 <three>:
    5054:   66666666    strbtvs r6, [r6], -r6, ror #12

然后为了使用这个跳转表或查找表,您需要知道编译地址与运行时地址,以便您可以调整代码中的编译时间地址。

使用物理和页面等术语,我认为页面是错误的,但它可能是虚拟与链接时间(我猜编译也是错误的术语,链接时间与运行时)它仍然是运行时与链接时间是否原因区别在于位置独立性或虚拟化。如果在操作系统上运行,则链接时间和运行时间应该是相同的,因为处理器(adr)至少不能像记录的那样检测到基于 PC 的值,并且 PC 不知道物理来自虚拟,所以不能以这种方式检测到物理,这是关闭的mmu 中的核心边缘。所以我认为物理和页面这两个术语在这里都被错误地使用了,但这只是我的看法。

如果您从编译器选项中删除 -fPIC 并且不使其位置独立代码,我想知道它是否不会打扰所有这些并按原样使用表。

【讨论】:

  • 啊,你说的是linux源码,这不是编译码。他们正在使其位置独立。如果我没记错的话,您可以加载内核,并且至少解压缩真正内核和驱动程序的第一部分是与位置无关的。也许整个事情就是(性能受到多大影响)。
  • 也许作者已经习惯了 x86,其中像 page 这样的术语确实有意义。这可以剪切和粘贴,然后转换为 arm 而无需更改 cmets。
  • .long vs .word 是另一个线索,x86 中的一个词是 arm 16 位 32 位长在 x86 中变化(16 位天长的 C 版本是 32 位,在 32位天它是 32 位,但是对于 64 位,它至少使用一个/一些编译器更改为 64)。尽管这都是 gnu 汇编程序,但由于 gnu 汇编程序习惯与编译器或现实的性质,其本身并没有任何意义。在这种情况下 .word 和 .long 可以互换,我觉得这里选择了 .long 很奇怪,因为 arm 程序员可能会认为 word 不长。
【解决方案2】:

代码中有一些你需要理解的概念。

  1. 代码获得一个“链接”地址,在该地址中解析了绝对寻址。
  2. 运行 (PC) 地址可能不同。

您正在查看的代码是重定位代码,它将使用运行时 PC 地址修复“链接”绝对寻址。

adr r0, 1f
ldmia   r0, {r3-r7}
mvn ip, #0
subs    r3, r0, r3  @ PHYS_OFFSET - PAGE_OFFSET
...
    .align
1:  .long   .
    .long   __pv_table_begin
    .long   __pv_table_end
2:  .long   __pv_phys_pfn_offset
    .long   __pv_offset

adr r0, 1f1: .long . 看起来是一样的。但是,有一个细微的差别。 1: .long . 行将链接地址存储为“1:”本地标签。 adr r0, 1f 将转换为 add r0, pc, #offset,因此运行时地址将放置在 R0 中。 ldmia r0, {r3-r7} 加载了很多值,但 R3 值是本地标签的链接地址。最后subs r3, r0, r3会把运行地址和链接地址的区别放到R3里面;一个修正项。

然后该表是需要应用修复的链接地址列表。这允许“非 PIC”代码在不同的地址运行。

上面的评论似乎很有帮助,

/* __fixup_pv_table - patch the stub instructions with the delta between
 * PHYS_OFFSET and PAGE_OFFSET, which is assumed to be 16MiB aligned and
 * can be expressed by an immediate shifter operand. The stub instruction
 * has a form of '(add|sub) rd, rn, #imm'.
 */

这取决于 Kconfig 值 ARM_PATCH_PHYS_VIRT,它似乎是 needed for the Keystone2 CPU/SOC,根据 early_paging_init 带有物理地址的机器修复。


需要此表的原因仅适用于需要内存物理地址的内核代码,通常用于与 DMA 设备通信,但也广泛用于对分页 OS 性能至关重要的 mm(或虚拟内存管理)代码.在 Linux 中,内联函数只是减去/加上 phys/virt 内核地址之间差异的偏移量; virt_to_physphys_to_virt 等。当在驱动程序/模块中使用此功能时,当编译的物理/虚拟差异与运行映像时发生的差异不同时,需要修复它。内核内存总是有一个连续的映射(虚拟重映射是一个固定的偏移量,仅适用于 kernel 地址)

一些机器位于memory.h,可能有助于了解正在发生的事情。注意评论,

/*
 * Physical vs virtual RAM address space conversion.  These are
 * private definitions which should NOT be used outside memory.h
 * files.  Use virt_to_phys/phys_to_virt/__pa/__va instead.
 *
 * PFNs are used to describe any physical page; this means
 * PFN 0 == physical address 0.
 */

见:

【讨论】:

  • 物理和虚拟都必须打补丁;构建虚拟以运行虚拟并构建物理(PHYS_OFFSET)以运行物理。 Linux 传统上以 3G/1G 拆分执行,因此虚拟运行为 0xc0000000;然而,人们出于不同的原因需要改变这一点。此外,不同 SOC/制造商的物理内存也不同。要在多个 SOC 供应商类型上运行单个二进制文件,'PHYS_OFFSET' 是动态的(嗯,它是 pv_table jazz 中的自修改代码,上面正在修补)。无论如何,内核虚拟到物理的差异对于所有地址都是恒定不变的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 2013-11-27
  • 2013-03-15
  • 2019-07-19
  • 1970-01-01
  • 2014-06-28
相关资源
最近更新 更多