该汇编代码如何区分物理偏移量和页面偏移量?
how does this assembly code get the difference between physical offset and page offset?
在研究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
最终会包含它自己的地址值。
含糊地说,r0
和r3
值都是指向同一个位置1:
的地址值。但我猜它们的值是不同的,因为一个是物理地址,另一个是虚拟地址。 (<- 这纯粹是我的猜测)。
这就是为什么我认为代码以某种方式试图通过 subs r3, r0, r3
来区分这两者。
我不确定我的猜测是否正确,即便如此,我也不知道哪个是物理地址和虚拟地址。此外,评论提到减法将产生物理偏移量和页面偏移量之间的差异。我已经阅读了与内存虚拟化相关的页面,但我无法将这些知识与此偏移减法相关联。
你为什么不试试呢?
hello:
.long .
.long 0x11111111
.long 0x22222222
.long 0x33333333
.long 0x44444444
.long 0x55555555
.long 0x66666666
.globl TEST
TEST:
adr r0,hello
bx lr
link编辑和反汇编
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 returns 0x803C 正如我们所料。
虽然列表中的第一项可能是您的谜。请注意他们如何使用点快捷方式来指示此处或此地址,因此列表中的第一项是列表开头的地址。 which r0 already they could have just done a mov r3,r0 but may burning that instruction vs just loading it and burning ram with a instruction.谁知道...
所以
.globl TEST
TEST:
adr r0,hello
ldmia r0,{r3}
mov r3,r0
bx lr
其中 returns 相同的 0x803C 值。
现在
.globl TEST
TEST:
adr r0,hello
ldmia r0,{r3}
subs r3,r0,r3
mov r0,r3
bx lr
正如预期的那样 returns 为零,那么这一切的意义何在?请注意,整个部分与位置无关,对吗?好吧,如果我改变我的 linker 认为这是在其他地方加载的...
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,因为我改变了我的方向 linker 如果我将它更改为 0x5000 而不是 0xA000,我得到 0x3000 作为差异。
所以这段代码所做的是
.long .
是编译时 adr 是运行时并使用运行时 pc 所以这段代码检测 table 所在的实际内存地址与 table 所在的编译时地址之间的差异.如果 table 中的项目是编译时地址
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
然后为了使用此跳转table或查找table,您需要知道编译地址与运行时地址,以便您可以在代码中调整编译时地址。
使用物理和页面之类的术语,我认为页面是错误的,但它可能是虚拟的 vs link 时间(我猜编译也是错误的术语,link 时间 vs 运行时)它仍然是运行时 vs link 时间,无论差异的原因是位置独立性还是虚拟化。如果 运行 在操作系统上 link 时间和运行时间应该是相同的物理无法检测到这种方式作为处理器 (adr) 至少如文档所示看到基于 PC 的值并且 PC 不知道物理来自虚拟,即位于 mmu 核心的边缘。所以我认为物理和页面这两个术语在这里使用不正确,但这只是我的意见。
如果您从编译器选项中删除 -fPIC 并且不使其位置独立代码,我想知道它是否不会打扰所有这些而只是按原样使用 table。
代码中有些概念你需要理解。
- 代码获取一个 'link' 地址,其中解析了绝对寻址。
- 运行 (PC) 地址可能不同。
您正在查看的代码是重定位代码,它将用 运行 时间 PC 地址修复 'link' 绝对寻址。
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, 1f
和1: .long .
好像是一样的。但是,有一个细微的差别。 1: .long .
行将存储一个 link 地址作为 '1:' 本地标签。 adr r0, 1f
将转换为 add r0, pc, #offset
,因此 运行 时间地址将放在 R0 中。 ldmia r0, {r3-r7}
加载了很多值,但是R3的值是本地标签的link地址。最后,subs r3, r0, r3
会将运行地址和link地址的差分放在R3中;修正项。
然后 table 是需要应用 fix-up 的 link 地址列表。这允许 'non-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 根据 early_paging_init
机器 fix-ups 似乎是 needed for the Keystone2 CPU/SOC用于物理地址。
之所以需要这个 table 是因为内核代码需要内存的物理地址,通常是为了与 DMA 设备通信,但也相当广泛地通过 mm(或虚拟内存管理)代码进行通信,它是性能对分页至关重要 OS。在 Linux 中,内联函数只是 subtracts/adds phys/virt 内核地址之间差异的偏移量; virt_to_phys
、phys_to_virt
等。当此功能用于 driver/module 时,编译后的 phys/virtual 差异与图片是 运行。总是有一个连续的内核内存映射(虚拟重映射是一个固定的偏移量,只针对内核地址)
一些机器位于 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.
*/
参见:
- Getting a label to a register
- Relocation in ARM assembler
- Assemblers and Loader,作者:大卫所罗门
- GOLD blog 作者 Ian Lance Taylor 关于新的 GNU 链接器和 linker 问题。
在研究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
最终会包含它自己的地址值。
含糊地说,r0
和r3
值都是指向同一个位置1:
的地址值。但我猜它们的值是不同的,因为一个是物理地址,另一个是虚拟地址。 (<- 这纯粹是我的猜测)。
这就是为什么我认为代码以某种方式试图通过 subs r3, r0, r3
来区分这两者。
我不确定我的猜测是否正确,即便如此,我也不知道哪个是物理地址和虚拟地址。此外,评论提到减法将产生物理偏移量和页面偏移量之间的差异。我已经阅读了与内存虚拟化相关的页面,但我无法将这些知识与此偏移减法相关联。
你为什么不试试呢?
hello:
.long .
.long 0x11111111
.long 0x22222222
.long 0x33333333
.long 0x44444444
.long 0x55555555
.long 0x66666666
.globl TEST
TEST:
adr r0,hello
bx lr
link编辑和反汇编
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 returns 0x803C 正如我们所料。
虽然列表中的第一项可能是您的谜。请注意他们如何使用点快捷方式来指示此处或此地址,因此列表中的第一项是列表开头的地址。 which r0 already they could have just done a mov r3,r0 but may burning that instruction vs just loading it and burning ram with a instruction.谁知道...
所以
.globl TEST
TEST:
adr r0,hello
ldmia r0,{r3}
mov r3,r0
bx lr
其中 returns 相同的 0x803C 值。
现在
.globl TEST
TEST:
adr r0,hello
ldmia r0,{r3}
subs r3,r0,r3
mov r0,r3
bx lr
正如预期的那样 returns 为零,那么这一切的意义何在?请注意,整个部分与位置无关,对吗?好吧,如果我改变我的 linker 认为这是在其他地方加载的...
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,因为我改变了我的方向 linker 如果我将它更改为 0x5000 而不是 0xA000,我得到 0x3000 作为差异。
所以这段代码所做的是
.long .
是编译时 adr 是运行时并使用运行时 pc 所以这段代码检测 table 所在的实际内存地址与 table 所在的编译时地址之间的差异.如果 table 中的项目是编译时地址
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
然后为了使用此跳转table或查找table,您需要知道编译地址与运行时地址,以便您可以在代码中调整编译时地址。
使用物理和页面之类的术语,我认为页面是错误的,但它可能是虚拟的 vs link 时间(我猜编译也是错误的术语,link 时间 vs 运行时)它仍然是运行时 vs link 时间,无论差异的原因是位置独立性还是虚拟化。如果 运行 在操作系统上 link 时间和运行时间应该是相同的物理无法检测到这种方式作为处理器 (adr) 至少如文档所示看到基于 PC 的值并且 PC 不知道物理来自虚拟,即位于 mmu 核心的边缘。所以我认为物理和页面这两个术语在这里使用不正确,但这只是我的意见。
如果您从编译器选项中删除 -fPIC 并且不使其位置独立代码,我想知道它是否不会打扰所有这些而只是按原样使用 table。
代码中有些概念你需要理解。
- 代码获取一个 'link' 地址,其中解析了绝对寻址。
- 运行 (PC) 地址可能不同。
您正在查看的代码是重定位代码,它将用 运行 时间 PC 地址修复 'link' 绝对寻址。
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, 1f
和1: .long .
好像是一样的。但是,有一个细微的差别。 1: .long .
行将存储一个 link 地址作为 '1:' 本地标签。 adr r0, 1f
将转换为 add r0, pc, #offset
,因此 运行 时间地址将放在 R0 中。 ldmia r0, {r3-r7}
加载了很多值,但是R3的值是本地标签的link地址。最后,subs r3, r0, r3
会将运行地址和link地址的差分放在R3中;修正项。
然后 table 是需要应用 fix-up 的 link 地址列表。这允许 'non-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 根据 early_paging_init
机器 fix-ups 似乎是 needed for the Keystone2 CPU/SOC用于物理地址。
之所以需要这个 table 是因为内核代码需要内存的物理地址,通常是为了与 DMA 设备通信,但也相当广泛地通过 mm(或虚拟内存管理)代码进行通信,它是性能对分页至关重要 OS。在 Linux 中,内联函数只是 subtracts/adds phys/virt 内核地址之间差异的偏移量; virt_to_phys
、phys_to_virt
等。当此功能用于 driver/module 时,编译后的 phys/virtual 差异与图片是 运行。总是有一个连续的内核内存映射(虚拟重映射是一个固定的偏移量,只针对内核地址)
一些机器位于 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.
*/
参见:
- Getting a label to a register
- Relocation in ARM assembler
- Assemblers and Loader,作者:大卫所罗门
- GOLD blog 作者 Ian Lance Taylor 关于新的 GNU 链接器和 linker 问题。