arm ldr/ld/数据加载系列指令和adr指令

本文详细解析了ARM指令集中的ldrb、ldr、ldrex指令以及不同的内存访问模式,包括偏移、预索引和后索引模式。通过实例展示了它们在C和汇编混合编程中的应用,强调了原子操作在ldrex指令中的作用,并介绍了LDMIA和LDMIB指令的使用。


ldrb指令

1. 语法

armv7手册语法:

LDRB{<c>}{<q>} <Rt>, [<Rn> {, #+/-<imm>}] Offset: index==TRUE, wback==FALSE
LDRB{<c>}{<q>} <Rt>, [<Rn>, #+/-<imm>]! Pre-indexed: index==TRUE, wback==TRUE
LDRB{<c>}{<q>} <Rt>, [<Rn>], #+/-<imm> Post-indexed: index==FALSE, wback==TRUE

<Rt> The destination register.
<Rn> The base register. The SP can be used. For PC use see LDRB (literal) on page A8-418.
+/- Is + or omitted if the immediate offset is to be added to the base register value (add == TRUE), or – if
it is to be subtracted (add == FALSE). #0 and #-0 generate different instructions.
<imm> The immediate offset used for forming the address. For the offset addressing syntax, <imm> can be
omitted, meaning an offset of 0. Any value in the range 0-4095 is permitted.

 功能描述:

Load Register Byte (immediate) calculates an address from a base register value and an immediate offset, loads a
byte from memory, zero-extends it to form a 32-bit word, and writes it to a register. It can use offset, post-indexed,
or pre-indexed addressing. For information about memory accesses see Memory accesses on page A8-291.

核心:从某个地址加载一个字节(Byte)到寄存器 Rt中,根据memory access方式不同分为:

  • Offset addressing
  • Pre-indexed addressing
  • Post-indexed addressing

2. Memory access mode

Offset addressing:

 Offset addressing:

The offset value is applied to an address obtained from the base register. The result is used as the address for the memory access. The value of the base register is unchanged. The assembly language syntax for this mode is:    [<Rn>, <offset>]

Pre-indexed addressing :

Pre-indexed addressing
The offset value is applied to an address obtained from the base register. The result is used as the address for the memory access, and written back into the base register. The assembly language syntax for this mode is: [<Rn>, <offset>]!

Post-indexed addressing :

Post-indexed addressing
The address obtained from the base register is used, unchanged, as the address for the memory access. The offset value is applied to the address, and written back into the base register The assembly language syntax for this mode is: [<Rn>], <offset>

3. 详细说明

  • Offset addressing:[Rn, offset]

        说明:最终访问内存的地址 = Rn+offset,这种操作后Rn的值不会改变

  • Pre-indexed addressing:[ Rn, offset] !

        说明:最终访问内存的地址 = Rn+offset,这种操作后Rn的值 = Rn+offset,注意加载的值来自地址:Rn + offset的值(更新后),类似于++i。       

  • Post-indexed addressing :[Rn],offset

        说明:最终访问内存的地址 = Rn,这种操作后Rn的值 = Rn+offset,跟pre-indexed的核心区别在于加载的值来自地址:Rn(更新前前),类似于i++

4. 实验验证:

通过实现一个c调用汇编的程序代码验证ldrb功能:

c程序:arm.c

#include<stdio.h>                                                                                                                                          
#include<string.h>
#include<stdlib.h>

extern void asm_strcpy(const char *src, char *dest);

int main(){
    const char *s = "Hello World";
    char tmp[32];
    memset(tmp, 0, sizeof(tmp)/sizeof(char));
    asm_strcpy(s, tmp);
    printf("-----%s------\n", tmp);
}
~  

汇编程序:ldrb.S

.globl asm_strcpy                                                                                                                                          
asm_strcpy:
loop:
    ldrb    r4, [r0, #1]!
    cmp     r4,#0
    beq     over
    strb    r4,[r1], #1
    b       loop

over:
    mov     pc,lr

.type asm_strcpy,%function

编译:arm-linux-androideabi-gcc -o ldrb ldrb.S arm.c  -pie -fPIE 

运行:

D:\tmp>adb shell ldrb
-----ello World------

可以看到由于采用了[r0, #1] pre-indexed的memory access方式,r4寄存器加载的值来自于地址:r0 + 1。如果改成ldrb r4, [r0], #1,可正常输出Hello Wold,如下:

D:\tmp>adb shell ldrb
-----Hello World------

ldr指令

1.语法

Load Register (immediate) calculates an address from a base register value and an immediate offset, loads a word
from memory, and writes it to a register. It can use offset, post-indexed, or pre-indexed addressing. For information
about memory accesses see Memory accesses on page A8-291.

于ldrb的区别在于一次加载一个word长度的数据。arm32位中即32 bit的数据。

举例:

1. ldr     r0, =0x00000001    @加载立即数1到寄存器r0,= 意思对变量取地址

2. 
.globl get_int
get_int:
    ldr     r0, =abc        @将0x10数值加载r0寄存器
    mov     pc,lr

.long       abc                                                                                                                                            
.equ        abc, 0x10

1)LDR R0,[R1,R2,LSL #2]!

将存储器地址为R1+R2*4的字数据读入到寄存器R0中,并将新地址R1+R2*4写入R1

2)LDR R0, [R1, R2, LSL #2]

将存储器地址为R1+R2*4的字数据读入到寄存器R0中。

ldrex指令

Load Register Exclusive calculates an address from a base register value and an immediate offset, loads a word from
memory, writes it to a register and:
• if the address has the Shared Memory attribute, marks the physical address as exclusive access for the
executing processor in a global monitor
• causes the executing processor to indicate an active exclusive access in the local monitor.

ldrex跟ldr核心区别实现原子操作,如内核的atomic_cmpxchg就是基于ldrex和strex指令实现,具体可以参考如下文章:

宋宝华:关于ARM Linux原子操作的实现_linux阅码场的技术博客_51CTO博客

 LDR伪指令

用于加载一个32位立即数或地址值到寄存器,【有点像MOV,但不是访存】!!!
语法:LDR{condition} register,=[expression | label-expression]

.global _start
_start:
    //list地址存入r0寄存器,注意这就不是pic了
	ldr r0, =list	
	ldr r1, [r0]
	
.data
list:
	.word 1,2,3

LDMIA

格式:

LDM{<c>}{<q>} <Rn>{!}, <registers>

说明:

Load Multiple Increment After (Load Multiple Full Descending) loads multiple registers from consecutive memory locations using an address from a base register. The consecutive memory locations start at this address, and the address just above the highest of those locations can optionally be written back to the base register. The registers loaded can include the PC, causing a branch to a loaded address.

根据官方文档描述LDMIA是LD = Load, M:Multiple, I : Increment A; “!”代表Rn寄存器会随着数据加载而增加。IA类似c语言的i++,即先加载完第一个数据到寄存器列表,再执行“r0++”。

举例:

.global _start
_start:
	ldr r0, =list	
	ldr r1, [r0]
	
    //r0存储list地址
	adr r0, list
    //从r0寄存器指向的开始加载,r2 = 1, r3 =2
	ldmia r0!, {r2, r3}

list:
	.word 1,2,3
	

LDMIB

理解了LDMIA,LIDMIB就简单了,B代表Before,解Increment Before,类似c语言的++i,具体到该指令就是先把Rn寄存器地址增加,然后在加载,即“++Rn",还是用上面代码举例:

.global _start
_start:
	ldr r0, =list	
	ldr r1, [r0]
	
	adr r0, list
    //r2 = 2, r3 = 3,因为先将r0寄存地址增加了,即所谓的Increment Before (load)
	ldmib r0!, {r2, r3}

list:
	.word 1,2,3

有IB/IA,同样也有DB/BA(Decrement),意义就很好理解了,不再代码举例。

ADR指令

参考:ADR (PC-relative)----获取同节中标号所代表的内存地址_ASMARM的博客-优快云博客

<think>我们正在解决一个在编译WebP库时遇到的问题,具体是在链接`lossless_enc.o`时出现的LDR/STR指令8字节对齐错误。这个错误是由于`_arm64_lo12`重定位导致的。根据引用[2]引用[5]中关于ARM64汇编指令的说明,我们知道LDR/STR指令在访问内存时要求地址对齐,特别是对于8字节数据(如64位寄存器)需要8字节对齐。 问题分析: 在ARM64架构中,使用LDR/STR指令加载或存储64位数据(使用X寄存器)时,目标地址必须是8字节对齐的。如果地址不对齐,则会导致对齐错误。而`_arm64_lo12`重定位是用于生成地址的低12位(相对于页的偏移),如果这个重定位得到的地址不是8字节对齐的,那么在使用LDR/STR指令时就会出错。 解决方案: 1. 检查代码中涉及内存访问的地方,确保数据结构的地址对齐。特别是那些通过`_arm64_lo12`重定位访问的全局变量或静态变量,应该确保这些变量在内存中是8字节对齐的。 2. 在汇编代码中,可以使用`.align`指令来确保数据对齐。在C/C++代码中,可以使用编译器属性来指定对齐,例如:`__attribute__((aligned(8)))`。 3. 检查链接脚本,确保相关的段(如.data, .bss等)的起始地址是8字节对齐的,并且每个需要8字节对齐的变量都被正确对齐。 具体到WebP库中的`lossless_enc.o`,我们需要查看该模块的源代码,找出可能未对齐的变量。通常,这个问题可能出现在使用了64位指针或64位整型变量的地方。 步骤: 1. 定位错误:从编译错误信息中定位到具体的LDR/STR指令,以及它试图访问的地址符号。 2. 检查符号定义:在源代码中找到该符号(全局变量或静态变量),检查其定义是否保证了8字节对齐。 3. 修改代码:如果没有对齐,则添加对齐属性。 例如,假设错误信息指向了一个全局变量`my_var`,那么在定义时应该这样: ```c uint64_t my_var __attribute__((aligned(8))); ``` 或者对于结构体: ```c struct my_struct { ... } __attribute__((aligned(8))); ``` 4. 如果问题出现在汇编代码中,则需要在变量定义前添加对齐指令: ```assembly .align 3 // 2^3 = 8字节对齐 my_var: .word 0 ``` 5. 重新编译并测试。 另外,引用[3]中提到了一个编译失败的情况,可能与工具链有关。因此,我们还需要确保使用的工具链(如NDK)支持正确的对齐要求,并且编译选项正确。 如果问题仍然存在,可能需要检查是否在代码中使用了指针运算导致了地址不对齐,或者是否在结构体中使用了不对齐的成员(如在一个本身未对齐的结构体中包含64位成员)。 由于问题具体发生在`lossless_enc.o`,我们可以重点检查WebP库中`lossless_enc.c`文件中的相关变量。 注意:在ARM64中,栈指针(SP)在函数调用时必须保持16字节对齐,但这里我们处理的是数据变量的对齐问题。 根据引用[4]中提到的异常向量表设置,我们知道ARM64对对齐要求很严格,不对齐会导致异常。因此,确保数据对齐至关重要。 如果问题是由于WebP库本身的代码导致的,可能需要向WebP社区提交补丁。但首先我们可以尝试在本地修复。 示例修复: 假设在`lossless_enc.c`中有一个全局数组: ```c static uint64_t some_array[10]; ``` 我们可以修改为: ```c static uint64_t some_array[10] __attribute__((aligned(8))); ``` 或者,如果这个数组是在结构体内部,则可能需要对齐整个结构体。 如果问题出现在动态分配的内存上(例如通过malloc),则需要注意,malloc通常返回的地址是满足最大基本类型对齐的,但在ARM64上,malloc返回的地址至少是16字节对齐的(因为最大对齐要求通常是16字节),所以通常不会出现问题。但如果是自己实现的内存分配器,则需要确保对齐。 总结步骤: 1. 确定错误发生的具体位置(哪个符号)。 2. 在代码中找到该符号的定义。 3. 确保该符号有8字节对齐(使用属性或修改定义)。 4. 重新编译。 如果错误发生在多个地方,可能需要逐个修复。或者,如果发现是某个结构体定义的问题,修复该结构体即可。 最后,由于问题是由`_arm64_lo12`重定位引起的,我们也可以考虑修改链接脚本,确保相关段的对齐,但这通常由标准链接脚本保证,所以优先修改代码。 如果问题仍然无法解决,可能需要查看具体的重定位记录,这需要分析目标文件(.o)的重定位信息。可以使用`objdump`或`readelf`工具查看重定位信息,检查哪些重定位要求地址必须是8字节对齐的(如R_AARCH64_ADR_PREL_LO21, R_AARCH64_ADD_ABS_LO12_NC等),然后找到对应的符号并确保其对齐。 希望这些步骤能帮助解决问题。</think>### 解决 LDR/STR 8 字节对齐错误问题 在 ARM64 架构中,LDR/STR 指令要求目标地址必须 **8 字节对齐**(64 位对齐)。当 `_arm64_lo12` 重定位生成的地址未满足此对齐要求时,会导致编译错误。以下是系统级解决方案: #### 1. **检查数据对齐声明** - 在 C 代码中,为涉及内存访问的全局变量或结构体显式添加 `__attribute__((aligned(8)))`: ```c // 示例:强制 8 字节对齐 uint64_t critical_data[10] __attribute__((aligned(8))); typedef struct { uint32_t a; uint64_t b; // 64 位成员需对齐 } __attribute__((aligned(8))) AlignedStruct; ``` #### 2. **修改链接脚本(Linker Script)** - 在链接脚本中确保关键段(如 `.data` 或自定义段)的起始地址按 8 字节对齐: ```ld .data : { /* 确保段起始地址 8 字节对齐 */ . = ALIGN(8); *(.data .data.*) } ``` #### 3. **汇编级手动对齐** - 在汇编文件中使用 `.align 3`($2^3=8$ 字节)指令强制对齐: ```assembly .section .data .align 3 ; 8 字节对齐 critical_symbol: .quad 0x123456789ABCDEF0 ``` #### 4. **检查内存访问指令** - 确保 LDR/STR 指令的地址寄存器满足对齐要求(引用[2][5]): ```assembly ; 正确做法:基址寄存器 (Xs) 需 8 字节对齐 ldr x0, [x1, #offset] ; x1 的值 + offset 必须 8 字节对齐 ; 错误示例:地址未对齐时可能触发异常 ldr x0, [x1, #3] ; 地址 = x1+3 (非对齐) ``` #### 5. **异常处理(内核级)** - ARM64 严格对齐检查会触发 `SIGBUS` 或进入对齐异常处理(引用[4]): ```c // 内核异常向量表处理(示例) el0_da: // Data Abort 处理 mrs x25, esr_el1 lsr x24, x25, #ESR_ELx_EC_SHIFT cmp x24, #ESR_ELx_EC_DABT_LOW b.eq handle_data_abort // 跳转到自定义处理函数 ``` #### 验证步骤 1. 使用 `objdump -d lossless_enc.o` 检查 LDR/STR 指令的地址偏移量是否为 8 的倍数。 2. 通过 `readelf -s lossless_enc.o` 确认符号地址的对齐属性。 3. 在 QEMU 或 FVP 模型(引用[1])中启用对齐检查调试。 > **关键点**:`_arm64_lo12` 重定位仅生成地址的低 12 位,必须确保基地址本身已 8 字节对齐,否则 `基地址 + 低12位偏移` 仍可能不对齐。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值