ARM汇编——札记

1 __asm volatile ("dmb sy" ::: "memory")

这是一个用于ARM架构的内联汇编语句,用于实现内存屏障(Memory Barrier)操作。

  • "dmb sy": 是一个内存屏障指令,它确保在此指令之前的所有内存访问操作都执行完毕,并且对后续的内存访问操作可见。

  • "volatile": 关键字表明这是一个不会被编译器优化掉的汇编代码。

  • "memory": 表示汇编代码可能会访问或修改内存,这告诉编译器不要把汇编代码之前和之后的代码重排序。

这个语句的作用是:

  1. 确保在执行 "dmb sy" 指令之前,所有之前的内存访问操作都已经完成,并且对后续的内存访问操作是可见的。
  2. 同时,它也阻止编译器对此汇编指令前后的代码重排序。

这是一个很好的例子,展示了如何在C/C++代码中使用内联汇编来实现更精细的内存控制。

注意:汇编语言的起始表示是__asm

2 ldm

在 ARM 汇编语言中,指令 ldm r1!, {r3, r4, r5, r6, r7, r8, r12, lr} 的意思是从内存中加载多个寄存器。下面是这条指令的详细解释:

2.1 指令结构

  • ldmLoad 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 函数的这个优化部分。

posted @ 2024-07-24 17:34  绍荣  阅读(67)  评论(0编辑  收藏  举报