尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

ARM64编译错误:relocation R_AARCH64_ADR_PREL_PG_HI21的成因与解决方案

ARM64编译错误:relocation R_AARCH64_ADR_PREL_PG_HI21的成因与解决方案 1. 项目概述一个典型的ARM64链接错误如果你在ARM64架构比如树莓派4、AWS Graviton实例或者国产的飞腾、鲲鹏服务器上编译或运行程序特别是涉及到动态链接库.so文件或者某些静态库的混合链接时很可能撞上这个让人头疼的报错relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol ‘stderrGLIBC_2.17’ which may bind extern。这个错误信息看起来像天书但它背后指向的是一个在ARM64平台上非常经典且关键的编译链接问题——位置无关代码PIC的缺失。简单来说这个错误是链接器ld在“抱怨”它试图将一个程序或库的代码段与一个名为stderr标准错误流的全局符号进行绑定这个符号来自GNU C库glibc的2.17版本。但在ARM64的指令集和内存模型下链接器发现当前正在处理的代码可能来自某个.o目标文件或.a静态库在编译时没有生成“位置无关”的指令导致它无法安全地计算出这个全局符号在最终内存布局中的准确地址。R_AARCH64_ADR_PREL_PG_HI21是一种特定类型的“重定位”指令用于计算大范围地址偏移但它要求代码本身是位置无关的否则在动态链接或创建共享库时就会失败。这个问题不仅限于stderr你可能会看到stdout、stdin、__stack_chk_guard栈保护符或者其他任何GLIBC提供的全局符号。它通常在你尝试做这几件事时爆发1将一个非PIC编译的静态库链接进一个动态库.so2在ARM64上直接编译生成动态库3某些跨架构的Docker镜像构建或容器运行过程这解释了为什么网络热词里会出现failed to register layer: applylayer exit status 1这类容器错误底层很可能就是链接或加载失败。接下来我会带你彻底拆解这个错误的成因、原理并给出从编译器选项到构建系统配置的一整套解决方案。2. 错误深度解析从指令集到链接器要真正理解这个错误我们不能停留在“加个-fPIC参数”的表面操作而是得深入到ARM64的指令集特性和Linux动态链接的机制里去看。2.1 核心概念什么是重定位Relocation当编译器将你的C/C源代码变成机器码时代码中所有对函数和变量的引用比如调用printf、使用stderr在编译阶段都是“未决”的。编译器会先用一个占位符标记这些位置。链接器的核心工作之一就是处理这些占位符用真实的地址去填充它们这个过程就叫“重定位”。在x86-64架构上由于指令集相对“宽松”有很多寻址模式可以选择重定位类型繁多且灵活。但ARM64为了追求更高的能效和精简指令集设计得非常规整和固定每条指令的长度都是32位这导致用于加载地址的指令模式也相对固定。R_AARCH64_ADR_PREL_PG_HI21就是其中一种专门用于进行“页相对寻址”的重定位类型。2.2 关键重定位类型R_AARCH64_ADR_PREL_PG_HI21这个名字可以拆解开来理解R_AARCH64: 表明这是ARM64架构特有的重定位类型。ADR: 表示这是一条“地址计算”指令ARM64的adrp指令。PREL: 代表“相对”PC-relative即地址是相对于当前程序计数器PC的值来计算的。PG_HI21: 表示这个重定位用于计算目标地址所在“内存页”的高21位偏移。ARM64的adrp指令非常强大它可以将一个目标符号的地址比如stderr分解为两部分一个“页”地址通常是4KB对齐的和一个页内的偏移。adrp指令本身负责计算出目标符号所在页的基地址存入一个寄存器然后后续再用一条add或ldr指令加上页内偏移就能得到完整的符号地址。这种“分两步走”的策略使得ARM64可以用固定的32位指令访问整个64位的地址空间。R_AARCH64_ADR_PREL_PG_HI21这个重定位记录就是告诉链接器“请帮我计算stderr这个符号的页地址部分并填充到这条adrp指令里。”2.3 冲突根源位置无关代码PIC的强制要求动态链接库.so文件有一个核心特性它可以在进程的虚拟地址空间中被加载到任意位置由动态链接器ld.so决定。因此动态库内部所有的代码和数据的地址引用都必须是“位置无关”的。也就是说无论这个.so文件被加载到0x0000地址还是0xFFFF地址它内部的代码都能正确运行因为它不依赖绝对的硬编码地址而是使用基于PC程序计数器的相对地址来计算。-fPICPosition Independent Code这个GCC/Clang编译选项就是指示编译器生成位置无关代码的开关。当开启-fPIC后编译器会对全局数据的访问通过一个叫做“全局偏移表GOT”的中间结构来间接完成。代码中不直接存放数据的绝对地址而是存放GOT条目的相对地址运行时通过GOT表查到真实地址。对函数的调用通过“过程链接表PLT”进行延迟绑定。确保生成的指令特别是像adrp这样的地址加载指令所使用的重定位类型是“可重定位的”即R_AARCH64_ADR_PREL_PG_HI21这种类型它计算的是相对偏移而不是绝对地址。那么错误是怎么发生的链接器报错说“can not be used when making a shared object”直指矛盾核心你正在尝试创建一个共享对象动态库但链接器发现它需要处理的一个目标文件比如libcrypto.a中的armcap.o里包含了对stderr这类全局符号的R_AARCH64_ADR_PREL_PG_HI21重定位。然而这个目标文件在编译时没有使用-fPIC选项。非PIC代码虽然也可能使用adrp指令但其背后的假设和重定位属性与PIC代码不同。当链接器尝试将非PIC的代码片段整合到一个必须位置无关的共享库中时它无法保证这些重定位能在任意加载地址下正确解析于是果断拒绝并报错。2.4 符号版本绑定GLIBC_2.17的含义错误信息中的stderrGLIBC_2.17不仅仅是一个符号名它还包含了“符号版本”信息。这是glibc用于维护二进制兼容性的一个重要机制。表示这是该符号的默认版本。GLIBC_2.17指明了这个stderr符号的定义来自glibc 2.17版本引入的ABI应用程序二进制接口。链接器和动态链接器会利用这个信息确保程序在运行时链接到正确版本的库函数避免因glibc升级导致的行为不一致或崩溃。在这个错误上下文中它只是告诉我们冲突的符号具体是哪个版本的问题的根源并不在于版本不匹配而在于前面所述的PIC缺失。3. 解决方案全攻略从单次编译到构建系统理解了原理解决方案就清晰了。核心原则就一条确保所有最终要参与到动态库.so链接中的代码无论是你自己写的还是第三方静态库都必须以位置无关代码PIC的形式编译。3.1 基础解决方案为编译单元添加-fPIC标志这是最直接的方法。在你调用编译器gcc/clang的命令行中加入-fPIC选项。# 编译单个源文件为目标文件.o准备用于链接成动态库 aarch64-linux-gnu-gcc -c my_source.c -o my_source.o -fPIC # 直接编译并链接生成动态库 aarch64-linux-gnu-gcc -shared -fPIC my_source1.c my_source2.c -o libmylib.so # 对于C项目同样使用-fPIC aarch64-linux-gnu-g -c -fPIC my_class.cpp -o my_class.o实操心得-fPIC通常与-shared生成共享库选项一同使用但记住-fPIC作用于编译阶段而-shared作用于链接阶段。即使你最终链接成可执行文件如果其中包含了需要位置无关的代码比如某些插件机制也可能需要-fPIC。3.2 处理第三方静态库重新编译是根本你遇到的错误很可能不是你的代码引起的而是你链接的某个第三方静态库如libcrypto.a来自OpenSSL在编译时没有启用-fPIC。对于这种情况你有几个选择首选方案从源码重新编译该库这是最彻底的方法。下载第三方库的源代码在其配置configure或构建cmake, make过程中显式指定-fPIC编译标志。# 以OpenSSL为例通过Configure脚本传递编译器参数 ./Configure linux-aarch64 -fPIC --prefix/usr/local make sudo make install # 对于使用CMake的库通常可以通过设置CMAKE_POSITION_INDEPENDENT_CODE变量 cmake -DCMAKE_POSITION_INDEPENDENT_CODEON -DCMAKE_C_FLAGS-fPIC ..注意有些库的构建系统可能会自动处理PIC但对于ARM64上的动态库链接手动确保总是更稳妥。查找预编译的PIC版本一些发行版或软件源会同时提供静态库和动态库。如果可能直接链接对应的动态库.so文件而非静态库.a文件因为动态库本身必然是PIC的。例如使用-lcrypto而不是/usr/lib/libcrypto.a。不推荐强制链接极端情况下你可以使用链接器的-Wl,--whole-archive和-no-pie等选项尝试绕过检查但这可能导致运行时崩溃仅作为临时诊断手段切勿用于生产环境。3.3 构建系统集成CMake与Autotools配置现代项目很少直接手写gcc命令更多的是通过构建系统管理。在CMake中启用PIC在项目的CMakeLists.txt中最优雅的方式是设置CMAKE_POSITION_INDEPENDENT_CODE全局变量。# 为当前目录及所有子目录下的目标启用PIC set(CMAKE_POSITION_INDEPENDENT_CODE ON) # 或者仅为特定的库目标启用 add_library(mylib SHARED src.cpp) set_target_properties(mylib PROPERTIES POSITION_INDEPENDENT_CODE ON)如果第三方库通过add_subdirectory或FetchContent引入这个设置通常也会对其生效。在Autotoolsconfigure/make中启用PIC通常通过向CFLAGS和CXXFLAGS环境变量传递-fPIC来实现。./configure CFLAGS-fPIC -O2 CXXFLAGS-fPIC -O2 make在Makefile中手动管理CFLAGS -fPIC CXXFLAGS -fPIC libmylib.so: $(OBJS) $(CC) -shared $(OBJS) -o $3.4 与容器化Docker相关的特殊场景网络热词中提到的failed to register layer: applylayer exit status 1错误经常发生在构建多架构Docker镜像尤其是使用docker buildx或在ARM64宿主机上运行x86镜像时。其底层原因之一可能就是镜像中的某个二进制程序或库在构建时未正确处理ARM64的PIC要求导致在容器启动的早期阶段如文件系统层应用链接或加载失败。解决方案确保基础镜像匹配在Dockerfile中使用明确支持ARM64且版本正确的基础镜像例如arm64v8/ubuntu:22.04而非通用的ubuntu:22.04。在Dockerfile中显式指定-fPIC如果你在容器内编译软件务必在RUN指令的编译命令中加入-fPIC。FROM arm64v8/ubuntu:22.04 RUN apt-get update apt-get install -y gcc make libssl-dev RUN cd /tmp \ wget https://.../some-lib.tar.gz \ tar -xzf some-lib.tar.gz \ cd some-lib \ ./configure CFLAGS-fPIC \ # 关键在这里 make make install使用多阶段构建在第一阶段builder阶段用-fPIC编译所有依赖然后将编译好的PIC版本的库复制到最终的运行时镜像中。4. 诊断与调试进阶技巧当问题复杂时你需要更强大的工具来定位到底是哪个.o文件或.a库出了问题。4.1 使用readelf和objdump检查目标文件readelf和objdump是分析ELF格式文件的利器。# 1. 检查一个.o或.a文件是否包含非PIC的重定位 aarch64-linux-gnu-readelf -r non_pic_object.o | grep R_AARCH64_ADR_PREL_PG_HI21 # 2. 查看目标文件的完整重定位表了解所有需要重定位的符号 aarch64-linux-gnu-objdump -r non_pic_object.o # 3. 检查动态库.so本身的重定位信息 aarch64-linux-gnu-readelf -d libmylib.so # 查看动态段 aarch64-linux-gnu-objdump -T libmylib.so # 查看动态符号表如果在一个静态库.a文件中的某个.o成员里发现了大量的、针对全局数据如stderr,stdout的R_AARCH64_ADR_PREL_PG_HI21重定位而该库又计划被链接进动态库那它就是嫌疑犯。4.2 分析编译和链接命令如果你使用的是像Make或CMake这样复杂的构建系统有时-fPIC标志可能因为变量覆盖、条件判断等原因未能正确传递。你可以通过以下方式查看实际执行的命令# 对于Make通常使用 make V1 或 make VERBOSE1 make clean make V1 # 对于CMake在构建时指定VERBOSE cmake -B build -DCMAKE_VERBOSE_MAKEFILE:BOOLON .. cd build make # 或者在CMake构建目录中直接使用 make VERBOSE1 make VERBOSE1在输出的海量命令中聚焦于编译gcc -c ...和链接gcc -shared ...或ld ...的那几行确认-fPIC是否出现。4.3 理解链接器脚本与--emit-relocs对于极度复杂的情况你可能需要查看链接器生成的最终重定位信息。使用-Wl,--emit-relocs链接器选项可以让链接器在最终输出文件可执行文件或动态库中保留所有重定位节。这通常用于高级调试或某些安全加固技术如CFI但也能帮你看到最终合并后的重定位情况。aarch64-linux-gnu-gcc -shared -fPIC -Wl,--emit-relocs -o libtest.so source.c aarch64-linux-gnu-readelf -r libtest.so请注意--emit-relocs会增加输出文件的大小不应在生产环境中使用。5. 常见问题排查与避坑指南在实际操作中你可能会遇到一些变体或相关的问题。这里整理了一个速查表。问题现象可能原因解决方案编译静态库时加-fPIC警告“无用”静态库.a只是一组.o的打包PIC对静态库本身无意义但对.o文件有意义。链接器检查的是.o文件。确保生成静态库的每个.o文件都用了-fPIC编译。静态库的构建命令如ar rcs lib.a *.o本身不需要-fPIC。错误变成R_AARCH64_ADR_PREL_PG_HI21 against symbol \__stack_chk_guardGLIBC_2.17本质相同符号变成了栈保护符。GCC默认启用的栈保护机制-fstack-protector会引用这个符号。同样为编译单元添加-fPIC。如果问题出现在第三方库可能需要重新编译该库并确保其构建系统传递了-fPIC给GCC。在x86-64上编译正常交叉编译到ARM64出错x86-64对PIC的要求不如ARM64严格一些在x86上能勉强工作的非PIC代码在ARM64上会立刻暴露。为交叉编译工具链明确指定-fPIC。检查交叉编译的配置脚本如./configure --hostaarch64-linux-gnu确保CFLAGS/CXXFLAGS包含-fPIC。使用-static链接静态可执行文件时也报错如果你在链接一个完全静态的可执行文件所有库都静态链接理论上不需要PIC。但错误可能源于你试图将非PIC的代码与PIC的代码或某些特殊启动文件混合。尝试使用-static-pie替代-static或者检查是否无意中链接了某些为动态链接准备的目标文件。清理构建目录并确保所有依赖库的编译选项一致。CMake中CMAKE_POSITION_INDEPENDENT_CODE设置了但无效1. 设置得太晚在add_library之后。2. 第三方库通过find_package引入其导入的目标属性未覆盖。3. 编译器不支持或默认行为不同。1. 将set(CMAKE_POSITION_INDEPENDENT_CODE ON)放在CMakeLists.txt的最顶部或至少在任何add_library/add_executable之前。2. 对导入的目标手动设置set_target_properties(ThirdParty::Lib PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ON)。3. 检查CMake输出的编译命令确认。最后的个人体会ARM64生态正在快速发展从移动设备到服务器其重要性日益凸显。-fPIC这个在x86世界可能偶尔被忽略的选项在ARM64上成为了一个“硬约束”。养成在构建共享库和涉及动态链接的组件时无条件添加-fPIC的习惯能为你省去大量跨平台移植时的调试时间。尤其是在混合了开源第三方依赖的项目中在项目初期就统一好PIC的编译策略相当于为项目的可移植性买了一份重要的保险。当你看到relocation R_AARCH64_ADR_PREL_PG_HI21这个错误时不要再感到困惑它只是一个提醒你“嘿这里的代码还没有做好在内存中自由移动的准备”而你的任务就是用-fPIC这把钥匙为它解开束缚。
返回列表