ARM汇编——札记
1 __asm volatile ("dmb sy" ::: "memory")
这是一个用于ARM架构的内联汇编语句,用于实现内存屏障(Memory Barrier
)操作。
-
"dmb sy"
: 是一个内存屏障指令,它确保在此指令之前的所有内存访问操作都执行完毕,并且对后续的内存访问操作可见。 -
"volatile"
: 关键字表明这是一个不会被编译器优化掉的汇编代码。 -
"memory"
: 表示汇编代码可能会访问或修改内存,这告诉编译器不要把汇编代码之前和之后的代码重排序。
这个语句的作用是:
- 确保在执行 "dmb sy" 指令之前,所有之前的内存访问操作都已经完成,并且对后续的内存访问操作是可见的。
- 同时,它也阻止编译器对此汇编指令前后的代码重排序。
这是一个很好的例子,展示了如何在C/C++代码中使用内联汇编来实现更精细的内存控制。
注意:汇编语言的起始表示是__asm
2 ldm
在 ARM 汇编语言中,指令 ldm r1!, {r3, r4, r5, r6, r7, r8, r12, lr}
的意思是从内存中加载多个寄存器。下面是这条指令的详细解释:
2.1 指令结构
-
ldm
:Load Multiple
,表示从内存中加载多个寄存器。 -
r1!
:基地址寄存器 r1,使用了 ! 符号,表示使用基地址寄存器并在操作后更新它。 -
{r3, r4, r5, r6, r7, r8, r12, lr}
:要加载的寄存器列表。 -
指令含义
ldm r1!, {r3, r4, r5, r6, r7, r8, r12, lr}
表示从内存中加载多个寄存器,具体步骤如下:- 加载寄存器:从内存地址 r1 开始,按顺序加载寄存器 r3 到 r12 以及链接寄存器 lr。就是将数据加载到相应的寄存器中
- 更新基地址:加载完成后,将基地址寄存器 r1 增加,以指向加载的最后一个内存位置之后的地址。
-
内存操作
假设内存中从地址 r1 开始有以下内容:
地址 内容
0x1000 0x00000001 -> r3
0x1004 0x00000002 -> r4
0x1008 0x00000003 -> r5
0x100C 0x00000004 -> r6
0x1010 0x00000005 -> r7
0x1014 0x00000006 -> r8
0x1018 0x00000007 -> r12
0x101C 0x00000008 -> lr
执行 ldm r1!, {r3, r4, r5, r6, r7, r8, r12, lr} 后,寄存器和内存将被更新为:
r3 = 0x00000001
r4 = 0x00000002
r5 = 0x00000003
r6 = 0x00000004
r7 = 0x00000005
r8 = 0x00000006
r12 = 0x00000007
lr = 0x00000008
基地址寄存器 r1 将增加 8 * 4 = 32 字节,指向 0x1020。
2.2 总结
指令 `ldm r1!, {r3, r4, r5, r6, r7, r8, r12, lr} 在 ARM 汇编语言中:
从内存地址 r1 开始加载寄存器 r3 到 lr。
每个寄存器占用 4 个字节。
更新基地址寄存器 r1,使其指向加载的最后一个内存位置之后的地址。
这条指令用于快速从内存中恢复多个寄存器的状态,常用于函数返回时恢复保存的上下文。
3 实例汇编代码分析
0xb341f270 <+0>: push {r0, r4, lr} # 将寄存器 r0, r4 和链接寄存器 lr 压入栈中,保存它们的值。
0xb341f274 <+4>: subs r2, r2, #4 # 将寄存器 r2 的值减去 4。
# r2 通常是传递给 memcpy 的第三个参数,表示要复制的字节数。
0xb341f278 <+8>: blt 0xb341f32c <memcpy+188> # 如果r2小于0(即需要复制的字节数小于4),跳转到memcpy函数的偏移量188处。
0xb341f27c <+12>: ands r12, r0, #3 # 将r0源地址与3进行按位与操作,将结果存储在r12中。
# 这个操作用来检查地址是否对齐。
0xb341f280 <+16>: pld [r1] # 使用PLD(预取数据)指令预取r1寄存器指向的地址的数据到缓存中,以提高后续的加载速度。
0xb341f284 <+20>: bne 0xb341f34c <memcpy+220> # 如果r12不为零,跳转到memcpy函数的偏移量220处。
0xb341f288 <+24>: ands r12, r1, #3 # 将r1(目标地址)与3进行按位与操作,将结果存储在r12中,
# 检查目标地址是否对齐。
0xb341f28c <+28>: bne 0xb341f37c <memcpy+268> # 如果 r12 不为零,跳转到 memcpy 函数的偏移量 268 处。
0xb341f290 <+32>: subs r2, r2, #28 # 将 r2 的值减去 28,检查是否有足够的字节进行块复制。
0xb341f294 <+36>: push {r5, r6, r7, r8} # 将寄存器 r5, r6, r7 和 r8 压入栈中,保存它们的值。
0xb341f298 <+40>: blt 0xb341f2d0 <memcpy+96> # 如果 r2 小于 0,跳转到 memcpy 函数的偏移量 96 处。
0xb341f29c <+44>: pld [r1] # 再次预取 r1 指向的内存地址的数据。
0xb341f2a0 <+48>: subs r2, r2, #96 ; 0x60 # 将 r2 的值减去 96。
0xb341f2a4 <+52>: pld [r1, #28] # 预取 r1 加 28 的地址处的数据。
0xb341f2a8 <+56>: blt 0xb341f2b8 <memcpy+72> # 如果 r2 小于 0,跳转到 memcpy 函数的偏移量 72 处。
0xb341f2ac <+60>: pld [r1, #60] ; 0x3c # 预取 r1 加 60 的地址处的数据。
0xb341f2b0 <+64>: pld [r1, #92] ; 0x5c # 预取 r1 加 92 的地址处的数据。
0xb341f2b4 <+68>: pld [r1, #124] ; 0x7c # 预取 r1 加 124 的地址处的数据。
=> 0xb341f2b8 <+72>: ldm r1!, {r3, r4, r5, r6, r7, r8, r12, lr}
- pld: pre load
- blt: branch if less than
- bne: branch not equal
提供的 ARM 汇编代码片段是 memcpy 函数的一部分,这部分代码负责将内存块从一个位置复制到另一个位置。让我们详细解释一下这个片段,特别是与 pc 指向的 memcpy+72 相关的部分:
指令解释
这些指令优化了内存访问,以提高 memcpy 函数的性能。特别是 PLD 指令(预取数据)用于提前加载数据到缓存中,减少后续内存访问的延迟。
memcpy+72 的含义
指令 0xb341f2b8 <+72> 是 memcpy 函数偏移 72 处的指令。根据上面的代码,blt 0xb341f2b8 <memcpy+72> 会在 r2 小于 0 时跳转到这个位置。
这意味着:
当 r2 小于 0 时,程序将跳转到 0xb341f2b8 <memcpy+72>
,继续执行 memcpy 函数偏移 72 处的代码。
总结
0xb341f2b8 <memcpy+72>
处的指令是 memcpy 函数内的某个关键位置,主要是优化内存访问以提高性能。在 memcpy 函数的不同部分,程序会跳转到这个位置来执行特定的内存复制操作。如果你在 GDB 中看到程序计数器 pc 指向这个位置,意味着程序正在执行 memcpy 函数的这个优化部分。