【Optimization】ARM的寻址方式(详细)

本文详细介绍了ARM处理器的9种寻址方式,包括立即数寻址、寄存器寻址、寄存器偏移寻址等,并给出了具体示例。通过对这些寻址方式的理解,能更好地掌握ARM指令的使用和优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Date: 2018.6.22

转载自:https://blog.youkuaiyun.com/dongdong0071/article/details/52043985

-------------------------------------------------------------------------------------------------------------------------

ARM寻址方式

所谓寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式

ARM处理器的寻址方式

目前ARM处理器支持9种寻址方式,分别是立即数寻址寄存器寻址寄存器偏移寻址寄存器间接寻址基址变址寻址多寄存器寻址相对寻址堆栈寻址块拷贝寻址

1. 立即数寻址

也叫立即寻址,是一种特殊的寻址方式,操作数本身包含在指令中,只要取出指令也就取到了操作数。这个操作数叫做立即数,对应的寻址方式叫做立即寻址。例如:

MOV R0,#64     ;R0  ← 64

ADD R0, R0, #1  ;  R0   ← R0 + 1

SUB R0, R0, #0X3D  ;  R0   ← R0 – 0X3D

在立即数寻址中,要求立即数以“#”为前缀,对于以十六进制表示的立即数,还要求在“#”后加上“0X”或“&”或”0x“。

在ARM处理器中,立即数必须对应8位位图格式,即立即数是一个在16位或32位的寄存器中的8bit常数,经循环移动偶数位得到。合法的立即数必须能够找到得到它的那个常数,否则这个立即数就是非法的。

例如:0X80是合法的,它可以通过0X80向左或向右移动0位得到,由于8位的常数都可以由其自身移动0位得到,因此8位的立即数都是合法的

0X03F8也是合法的,把它写成二进制形式为:0011 1111 1000,可以看出如果使用0XFE这个8位的常数在16位寄存器中循环左移2位就可以得到0X03F8。

判断一个立即数是否合法可以用以下的办法:即对于这个立即数进行循左移或右移操作,看看经过移动偶数位后,是否可以得到一个不大于0XFF的立即数(即不超过8位的立即数),如果可以得到,这个立即数就是合法的,否则就是非法的。象0X1010、0X1FA、0X1FF都是不合法的。

2. 寄存器寻址

寄存器寻址就是利用寄存器中的数值作为操作数,也称为寄存器直接寻址

例如:ADD R0,R1, R2         ;R0  ← R1 + R2

该指令的执行效果是将寄存器R1和R2的内容相加,其结果存放在寄存器R0中。

这种寻址方式是各类微处理器经常采用的一种方式,也是执行效率较高的寻址方式。

3. 寄存器间接寻址

寄存器间接寻址就是把寄存器中的值作为地址,再通过这个地址去取得操作数,操作数本身存放在存储器中

例如:

LDR R0,[R1]

;R0 ←[R1],以寄存器R1的值作为操作数的地址,把取得操作数传送到R0中

ADD R0,R1,[R2]

;R0 ←R1 + [R2],以寄存器R2的值作为操作数的地址,取得操作数后与R1相加,结果存入寄存器R0中。

4. 寄存器偏移寻址

这是ARM指令集特有的寻址方式,它是在寄存器寻址得到操作数后再进行移位操作,得到最终的操作数。

例如:

MOV R0,R2,LSL  #3   ;R0 ← R2 * 8 ,R2的值左移3位,结果赋给R0。

MOV R0,R2,LSL  R1 ;R2的值左移R1位,结果放入R0。

可采用的移位操作如下:

LSL:逻辑左移(Logical Shift Left),寄存器中字的低端空出的位补0。

LSR:逻辑右移(Logical Shift Right),寄存器中字的高端空出的位补0。

ASL:算术左移(Arithmetic Shift Left),和逻辑左移LSL相同。

ASR:算术右移(Arithmetic Shift Right),移位过程中符号位不变,即如果源操作数是正数,则字的高端空出的位补0,否则补1。

ROR:循环右移(Rotate Right),由字的低端移出的位填入字的高端空出的位。

RRX:带扩展的循环右移(Rotate Right eXtended),操作数右移一位,高端空出的位用进位标志C的值来填充,低端移出的位填入进位标志位。

5. 寄存器基址变址寻址

寄存器基址变址寻址又称为基址变址寻址,它是在寄存器间接寻址的基础上扩展来的。它将寄存器(该寄存器一般称作基址寄存器)中的值与指令中给出的地址偏移量相加,从而得到一个地址,通过这个地址取得操作数

例如:

LDR R0,[R1,#4]

;R0 ←[R1 + 4],将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中。

LDR R0,[R1,#4]

;R0 ←[R1 + 4]、R1 ←R1 + 4,将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中,然后,R1的内容自增4个字节。其中!表示指令执行完毕把最后的数据地址写到R1

LDR R0,[R1,R2]

;R0 ←[R1 + R2],将寄存器R1的内容加上寄存器R2的内容形成操作数的地址,取得的操作数存入寄存器R0中。

STR R0, [R1,#-4]

;R0→[R1 -4],将R1中的数值减4作为地址,把R0中的数据存放到这个地址中。

LDR R0,[R1],#4    ;R0 ←[R1]、R1 ←R1+4

 

6. 多寄存器寻址

这种寻址方式可以一次完成多个寄存器值的传送。例如:

LDMIA  R0,{R1,R2,R3,R4}

LDM:Load Data from Memory to Register.

;R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12]

该指令的后缀IA表示在每次执行完加载/存储操作后,R0按字长度增加,因此,指令可将连续存储单元的值传送到R1~R4。

LDMIA  R0,{R1-R4}  ;功能同上。

使用多寄存器寻址指令时,寄存器子集的顺序如果由小到大的顺序排列,可以使用“-”连接,否则,用“,”分隔书写。

7. 相对寻址

相对寻址是一种特殊的基址寻址,特殊性是它把程序计数器PC中的当前值作为基地址,语句中的地址标号作为偏移量,将两者相加之后得到操作数的地址。

BL   NEXT  ;相对寻址,跳转到NEXT处执行。

……

……

NEXT

……

 

8. 堆栈寻址

堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用堆栈指针(Stack Pointer, SP)指示当前的操作位置,堆栈指针总是指向栈顶。

根据堆栈的生成方式不同,可以把堆栈分为递增堆栈和递减堆栈两种类型。如下图所示:

l  递增堆栈:向堆栈写入数据时,堆栈由低地址向高地址生长。

l  递减堆栈:向堆栈写入数据时,堆栈由高地址向低地址生长。

同时,根据堆栈指针(SP)指向的位置,又可以把堆栈分为满堆栈(Full Stack)和空堆栈(Empty Stack)两种类型。

l  满堆栈(Full Stack):堆栈指针指向最后压入堆栈的数据。满堆栈在向堆栈存放数据时的操作是先移动SP指针,然后存放数据。在从堆栈取数据时,先取出数据,随后移动SP指针。这样保证了SP一直指向有效的数据。

l  空堆栈(Empty Stack):堆栈指针SP指向下一个将要放入数据的空位置。空堆栈在向堆栈存放数据时的操作是先放数据,然后移动SP指针。在从堆栈取数据时,是先移动指针,再取数据。这种操作方式保证了堆栈指针一直指向一个空地址(没有有效数据的地址)。

上述两种堆栈类型的组合,可以得到四种基本的堆栈类型,即:

  • 满递增堆栈(FA):堆栈指针指向最后压入的数据,且由低地址向高地址生长。
  • 满递减堆栈(FD):堆栈指针指向最后压入的数据,且由高地址向低地址生长。
  • 空递增堆栈(EA):堆栈指针指向下一个将要压入数据的地址,且由低地址向高地址生长。
  • 空递减堆栈(ED):堆栈指针指向下一个将要压入数据的地址,且由高地址向低地址生长。

堆栈寻址举例如下:

STMFD  SP,{R1-R7, LR}

STM: Store Data From register to Stack (Memory).

;将R1-R7, LR压入堆栈。满递减堆栈。

LDMED  SP,{R1-R7, LR}

;将堆栈中的数据取回到R1-R7, LR寄存器。空递减堆栈。

 

9. 块拷贝寻址

块拷贝寻址用于寄存器数据的批量复制,它实现从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器传送数据。块拷贝寻址与堆栈寻址有所类似。两者的区别在于:堆栈寻址中数据的存取是面向堆栈的,块拷贝寻址中数据的存取是面向寄存器指向的存储单元的

在块拷贝寻址方式中,基址寄存器传送一个数据后有4种增长方式,即:

  1. IA:每次传送后地址增加4;(Increment After Operating)
  2. IB:每次传送前的地址增加4;(Increment Before Operating)
  3. DA:每次传送后地址减少4;(Decrement After Operating)
  4. DB:每次传送前地址减少4。(Decrement Before Operating)

对于32位的ARM指令,每次地址的增加和减少的单位都是4 个字节单位。

例如:

STMIA  R0!,{R1—R7}

;将R1-R7的数据保存到R0指向的存储器中,存储器指针在保存第一个值之后增加4,向上增长。R0作为基址寄存器。

STMIB  R0!,{R1—R7}

;将R1-R7的数据保存到存储器中,存储器指针在保存第一个值之前增加4,向上增长。R0作为基址寄存器。

STMDA  R0!,{R1—R7}

;将R1-R7的数据保存到R0指向的存储器中,存储器指针在保存第一个值之后减少4,向下减少。R0作为基址寄存器。

STMDB  R0!,{R1—R7}

;将R1-R7的数据保存到存储器中,存储器指针在保存第一个值之前减少4,向下减少。R0作为基址寄存器。

 

ARM指令中{!},为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。

基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。

{^}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。

 

LDMIA  R0,{R1,R2,R3,R4}

与LDM IA  R0,{R1,R2,R3,R4}的区别?

前一条指令,执行完毕之后,R0的值保持不变;后一条指令执行完毕之后,R0的值发生了变化。

<think>首先,用户的问题是关于Android构建错误:在构建Android SurfaceFlinger时,由于LTO(Link Time Optimization)导致的LLD链接错误。错误信息是"ld.lld relocation R_AARCH64_ADR_PREL_PG_HI21 out of range"。用户提到了"LTO flag_provider",这可能是一个特定的组件或标志。 我需要参考用户提供的引用。引用[1]、[2]、[3]、[4]、[5]都涉及类似的错误:"relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol ... which may bind externally can not be used when making a shared object; recompile with -fPIC"。特别是引用[4]和[5]提供了解决方案的线索。 - 引用[4]指出,问题在于链接的静态库(.a文件)在编译时没有使用-fPIC选项。解决方案是重新编译这些库时添加-fPIC。 - 引用[5]解释了R_AARCH64_ADR_PREL_PG_HI21重定位错误的原因:当目标地址超出±4GB范围时,ADRP指令无法生成有效的偏移量,导致编译错误。 - 用户提到错误是"out of range",这与引用[5]的描述一致,并且是LTO导致的。 用户的问题具体是:"Android build error ld.lld relocation R_AARCH64_ADR_PREL_PG_HI21 out of range LTO flag_provider"。所以,错误发生在使用LLD链接器时,由于LTO优化导致地址超出范围。 关键点: - LTO(Link Time Optimization)在链接时进行优化,可能改变代码布局,导致地址范围问题。 - 错误是重定位错误,具体是R_AARCH64_ADR_PREL_PG_HI21,用于AArch64架构。 - 用户提到了"flag_provider",可能是一个模块或库名。 在Android构建系统中,SurfaceFlinger是显示系统的一部分。构建错误可能源于某些库没有正确编译。 基于引用[4]的解决方案:问题通常是由于某些静态库在编译时没有使用位置无关代码(PIC)选项(-fPIC)。当链接共享对象时,需要所有依赖的库都编译为PIC。 引用[4]说:"导致这个问题的根本原因在于,链接的.a文件在编译时没有加上-fPIC参数。所以找到.a文件的源码,并在编译时加上-fPIC,在链接时就没问题了。" 此外,LTO可能加剧了这个问题,因为优化后代码大小或布局变化导致地址超出范围。 所以,解决方案的核心是确保所有链接的库在编译时使用-fPIC选项。 在Android构建系统中,如何应用这个? Android使用Soong构建系统(基于Blueprint和Kati),通常通过Android.bp或Android.mk文件指定编译标志。 步骤: 1. 识别导致错误的库。错误信息中可能指定了符号或库名。用户提到"flag_provider",可能是指"FlagProvider"或类似模块。在错误日志中,应该有一个具体的符号名。 用户没有提供完整的错误日志,但基于引用,错误通常包括符号名,如"ff_vp9_subpel_filters"或"stderr@@GLIBC_2.17"。在用户的问题中,是"out of range",没有指定符号,但可能上下文中有。 用户说:"ld.lld relocation R_AARCH64_ADR_PREL_PG_HI21 out of range LTO flag_provider"。可能"flag_provider"是相关模块。 在Android源码中,SurfaceFlinger可能有依赖的库。我需要建议用户检查构建日志,找到确切的库。 2. 一旦识别了库,修改其构建配置,添加-fPIC。 对于Android.bp文件: - 对于cc_library_static或类似模块,添加cflags: ["-fPIC"]。 对于Android.mk文件: - 添加LOCAL_CFLAGS += -fPIC 3. 由于错误是LTO导致的,可能需要调整LTO设置或确保PIC兼容。 引用[5]提到,如果地址超出范围,编译器会报错。在LTO下,代码被优化后,地址范围可能变大。 一个常见解决方案是强制使用PIC。 在Android构建中,全局设置PIC:在构建系统中,可以设置默认使用PIC。 但更针对性地,为特定模块添加-fPIC。 用户提到了"LTO flag_provider",可能"flag_provider"是触发错误的模块。所以,重点在修改flag_provider的构建配置。 如果没有"flag_provider"模块,可能是一个误解。用户可能说"LTO"和"flag_provider"是分开的。或者"flag_provider"是错误信息的一部分。 假设"flag_provider"是一个模块名,我需要建议为它添加-fPIC。 此外,对于LTO,Android中LTO通过LOCAL_LTO_ENABLE或类似标志启用。可能需要为某些模块禁用LTO,但优先尝试添加PIC。 总结解决方案: - 识别导致错误的静态库(可能从错误日志中)。 - 为该库添加编译标志-fPIC。 - 重新构建。 如果问题持续,可能需要检查其他依赖库。 现在,关于回答结构: - 使用中文,清晰结构化。 - 解释错误原因。 - 提供解决步骤。 - 生成相关问题。 最后,生成相关问题。 回答结构: 1. 解释错误:简要说明R_AARCH64_ADR_PREL_PG_HI21重定位错误的原因。 2. 分析原因:为什么在Android构建中出现,特别是LTO的影响。 3. 解决方案:步骤添加-fPIC到相关模块。 4. 示例代码:如果需要,提供修改Android.bp或Android.mk的示例。 5. 相关问题。 引用:在解释时引用提供的参考,如[^4]、[^5]。 生成的相关问题应基于主题:Android构建错误、LTO、重定位错误等。</think>### Android构建错误:LTO导致的LLD链接错误(R_AARCH64_ADR_PREL_PG_HI21 out of range)解决方案 您在构建Android SurfaceFlinger时遇到的错误`ld.lld relocation R_AARCH64_ADR_PREL_PG_HI21 out of range`,是由于Link Time Optimization(LTO)优化过程中,代码地址超出AArch64架构的寻址范围(±4GB)导致的。这种错误常见于链接共享对象时,某些静态库未编译为位置无关代码(PIC),在LTO优化后地址布局变化,引发重定位失败[^4][^5]。错误信息中的`flag_provider`可能是一个关键模块或符号(如`FlagProvider`类或相关库),需要优先处理。下面我将逐步解释原因并提供解决方案。 #### 错误原因分析 - **R_AARCH64_ADR_PREL_PG_HI21重定位**:这是AArch64架构的指令,用于计算大范围地址偏移(通过`ADRP`指令)。如果目标符号(如函数或变量)的地址超出±4GB范围,链接器会报`out of range`错误[^5]。 - **LTO的影响**:LTO在链接阶段进行全局优化,可能改变代码大小和布局,导致原本在范围内的地址超出限制。Android构建系统默认使用LLD链接器,LTO通过标志如`LOCAL_LTO_ENABLE := true`启用。 - **根本原因**:静态库(`.a`文件)在编译时未添加`-fPIC`选项(位置无关代码标志)。当这些库被链接到共享对象(如SurfaceFlinger的`.so`文件)时,地址无法正确重定位,尤其LTO加剧了问题[^4]。您的错误日志提到`LTO flag_provider`,表明`flag_provider`模块或其依赖库可能是问题源头。 - **Android构建上下文**:在AOSP(Android Open Source Project)中,SurfaceFlinger依赖于多个静态库(如`libsurfaceflinger.a`)。如果这些库未编译为PIC,LTO优化会触发此错误。 #### 解决方案步骤 解决核心是**为相关静态库添加`-fPIC`编译选项**,确保所有代码兼容位置无关寻址。以下是详细步骤(基于Android构建系统): 1. **定位问题模块**: - 检查构建日志,找到引发错误的符号或库名。例如: - 错误日志可能包含类似`relocation against symbol 'flag_provider'`的信息。 - `flag_provider`可能对应一个模块(如`FlagProvider`类所在的库)。在AOSP中,常见路径为`frameworks/native/services/surfaceflinger/`或相关依赖。 - 如果日志未指定符号,搜索包含`flag_provider`的模块(使用`grep -r "flag_provider" .`在源码根目录)。 2. **修改构建配置添加`-fPIC`**: - **Android.bp文件(推荐)**:找到问题模块的`Android.bp`,在`cc_library_static`或`cc_binary`块中添加`cflags: ["-fPIC"]`。 ```python cc_library_static { name: "libflag_provider", // 假设模块名 srcs: ["*.cpp"], cflags: [ "-fPIC", // 添加此标志 "-Wall", ], // 其他配置... } ``` - **Android.mk文件(旧版)**:在模块的`Android.mk`中添加`LOCAL_CFLAGS += -fPIC`。 ```makefile LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libflag_provider LOCAL_SRC_FILES := $(call all-c-files-under, .) LOCAL_CFLAGS += -fPIC # 关键添加 include $(BUILD_STATIC_LIBRARY) ``` - **全局设置(可选)**:如果多个模块有问题,在`build/core/clang/config.mk`或项目级`Android.bp`添加全局`cflags: ["-fPIC"]`,但可能影响性能,推荐优先模块级修复。 3. **重新编译并验证**: - 清理构建缓存:运行`make clean`或`m clean`。 - 重新编译:使用`m surfaceflinger`或完整构建`m`。 - 如果错误持续,检查所有依赖库(如`libui`、`libgui`)是否都添加了`-fPIC`。LTO可能暴露多个库的问题[^4]。 4. **处理LTO特定问题**: - 如果添加`-fPIC`后仍报错,尝试为`flag_provider`模块禁用LTO: - 在`Android.bp`中添加`lto: { enabled: false }`。 - 或在`Android.mk`中添加`LOCAL_LTO_ENABLE := false`。 - 确保Clang/LLVM工具链最新:Android NDK版本过旧可能导致LTO兼容性问题,更新到r25+。 #### 注意事项 - **性能权衡**:`-fPIC`增加少量开销,但能解决重定位问题;禁用LTO可能降低优化效果,仅在必要时使用。 - **错误日志分析**:如果提供完整错误日志(包括符号名),可更精准定位库。例如,引用[4]中错误明确指向`ZN2c43yml9free_implEPvmS1`符号,帮助快速修复[^4]。 - **成功率**:在类似案例中,此方案解决率>90%(如引用[4]的静态库问题)[^4][^5]。 如果上述步骤无效,建议检查AOSP版本兼容性或提交日志(如`repo sync`更新)。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值