这个汇编代码如何获得物理偏移和页面偏移之间的差异?

时间:2017-02-20 07:33:28

标签: assembly memory-management arm virtual-memory

在研究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来解决这两者之间的差异。

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

2 个答案:

答案 0 :(得分: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

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

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

如果从编译器选项中删除-fPIC而不使其与位置无关的代码,我想知道它是否不会打扰所有这些并只是按原样使用该表。

答案 1 :(得分:1)

您需要在代码中理解一些概念。

  1. 代码获得了一个&#39;链接&#39;解决绝对寻址的地址。
  2. 运行(PC)地址可能不同。
  3. 您正在查看的代码是重定位代码,该代码将修复&#39;链接&#39;运行时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 .行会将链接地址存储为&#39; 1:&#39;当地标签。 adr r0, 1f将转换为add r0, pc, #offset,因此运行时地址将放在R0中。 ldmia r0, {r3-r7}加载了许多值,但R3值是本地标签的链接地址。最后,subs r3, r0, r3将对R3中的运行地址和链接地址进行区分;修正词。

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

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

    /* __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 ,对于物理地址的机器修复,每early_paging_init needed for the Keystone2 CPU/SOC似乎是memory.h

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

    某些机器位于Getting a label to a register,可能对理解正在发生的事情有所帮助。注意评论,

    /*
     * 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.
     */
    

    请参阅: