目录
问题现象:
问题原因
问题机理
问题现象:
客户现场加载out文件出现如下问题:
打印“Relocation of type ‘R_AARCH64_ADR_PREL_PG_HI22…..’”,明确是ARDP指令引起的问题
问题原因
ARDP的寻址范围是±4GB范围,加载的位置已经超过这个位置范围,便会报如
问题机理
ARDP指令说明:
ADRP指令使用一个21位的立即数(immediate)来指定偏移量,这个偏移量是以4KB页面大小为单位计算的。因此,ADRP指令可以表示的偏移范围是:
超出范围的处理
如果目标地址超出了±4GB范围,编译器将无法使用ADRP指令生成有效的偏移量。在这种情况下,编译阶段会产生错误。具体错误信息可能因编译器和工具链而异,但通常会出现类似于“out of range”或“cannot generate relocation”之类的错误信息。
链接时错误:如果链接器在处理重定位时发现符号地址超出了 ADRP 能够表示的范围,它可能会报错。这种情况下,链接器可能会报告一个错误,提示重定位类型 R_AARCH64_ADR_PREL_PG_HI21 无法应用,因为目标地址超出了 ADRP 指令能够访问的范围。
运行时错误:如果链接器没有检测到这个问题,或者在编译时使用了某些特殊的选项来绕过检查,那么在运行时,程序可能会尝试执行一个超出范围的地址访问。这将导致未定义行为,可能表现为程序崩溃或产生错误消息。
常见错误信息
在使用ADRP指令加载超出±4GB范围的地址时,常见的编译器错误信息包括:
“relocation truncated to fit”
“cannot generate relocation”
“immediate offset out of range”
这些错误表明目标地址超出了ADRP指令能够处理的范围,编译器无法生成有效的机器码。
解决方案
在out文件编译的时候添加-mcmodel=large参数。
使用 -mcmodel=large 时指令生成的变化
在大内存模型下,编译器会选择能够处理更大地址空间的指令来确保正确访问数据。例如:
在小内存模型下,编译器可能会生成如下代码:
ADRP X0, label
ADD X0, X0, :lo12:label
在大内存模型下,编译器可能会生成如下代码:
LDR X0, =label
在大内存模型下,使用LDR指令可以直接从内存中加载绝对地址,而不受±2GB的限制。这是因为在-mcmodel=large模式下,编译器假定需要访问的地址可能超出ADRP指令的范围。
说明(LDR和ADRP区别)
在ARMv8架构中,ADRP 和 LDR 指令用于不同的目的和场景,尽管它们都与内存地址操作有关。以下是它们的主要区别:
ADRP 指令
ADRP 指令是“Add with Relocation Page”的缩写,用于生成一个页面对齐的地址。它将当前的PC(程序计数器)与一个相对偏移相加,并将结果存储在目标寄存器中。主要用于生成基地址,在加载较大的数据或访问静态变量时特别有用。
- 语法: ADRP Xd, label
- 功能: 将标签(label)的页基地址加载到寄存器 Xd 中。标签必须在同一页面(通常为4KB)或相邻页面中。
- 用途: 常用于PIC(位置无关代码)和地址计算,如访问全局变量或静态数据。
例如:
ADRP X0, label ; 将label的页基地址加载到X0
LDR 指令
LDR 指令是“Load Register”的缩写,用于从内存中加载数据到寄存器中。LDR 可以直接加载数据或通过基址和偏移量来访问内存。
- 语法: LDR Xt, [Xn, #offset]
- 功能: 从地址 [Xn + offset] 处加载数据到寄存器 Xt 中。
- 用途: 直接访问内存中的数据,用于加载全局变量、数组元素或从内存中读取任意数据。
例如:
LDR X1, [X0, #offset] ; 从X0 + offset处加载数据到X1
示例说明
结合 ADRP 和 LDR 指令,我们可以看到一个完整的地址加载过程。例如,加载一个全局变量的地址:
ADRP X0, label ; 加载label所在页的页基地址到X0
LDR X1, [X0, #offset] ; 从X0 + offset处加载实际数据到X1
在这个例子中,ADRP 用于生成一个接近目标地址的基址(页对齐的基址),然后 LDR 用于加载该地址的实际数据。
总结
ADRP 用于生成页基地址,特别适用于需要生成基址的情况。
LDR 用于从内存加载数据,直接与内存地址打交道。(因虚拟地址需MMU配置完成才能使用)
两者结合使用时,能够有效地访问大数据或静态变量,在编写高效和位置无关代码时尤为重要。