ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

在 HarmonyOS 上从源码构建 GCC 16(gcc/g++ + libstdc++ + libsanitizer):完整记录

2026/8/31 18:25:22 拓冰建站 浏览量
在 HarmonyOS 上从源码构建 GCC 16(gcc/g++ + libstdc++ + libsanitizer):完整记录 在 HarmonyOS 上从源码构建 GCC 16gcc/g libstdc libsanitizer完整记录注本文涉及的所有工作以及本文的编写全部由 AI 完成。这么繁琐的事情当然是要交给 AI 了。本文记录在 MateBook Pro SHarmonyOS 7aarch64musl上用 Harmonybrew 的binutils 已自建的clang-22作为宿主编译器从releases/gcc-16分支把GCC 16 的c/c编译器以及libgcc/libstdc全部构建出来安装到~/Installed/gcc-16最终做到g foo.cpp -o foo零参数编译、跑通 C23 与import std;的全过程。与 LLVM 那份记录一样绝大多数坑来自「HarmonyOS 看起来像 Linux、又不完全是」uname -s返回HarmonyOS、/tmp只读、~/挂在 hmdfs分布式文件系统行为怪异、ELF 必须签名、OHOS 头文件是给 clang 写的。0. 结论先说安装位置/storage/Users/currentUser/Installed/gcc-16目标 tripleaarch64-linux-muslGCC 的 musl 动态链接器正是/lib/ld-musl-aarch64.so.1与鸿蒙运行时一致宿主编译器~/Installed/clang-22/bin/clang{,}你们之前编的 LLVM 22汇编器brew 的binutils里的 GNUas链接器编译期用 GNUldbinutils 自动签名 wrapper安装后的 gcc 默认链接器仍是 SDK 的ld.lld自动--code-sign最终用法g hello.cpp -o hello—— 零参数sysroot/-I/-L/-rpath 全部 bakedg -stdc23 foo.cpp -o foo—— C23 特性std::println、std::expected、std::ranges等g -stdc23 -fmodules --compile-std-module foo.cpp -o foo——import std;1. 关键前提机器是 aarch644 核内存 23G可用约 7G 50G swap。运行时/lib/ld-musl-aarch64.so.1就是 musl 的加载器它自己同时扮演libc.so所以系统里看不到单独的libc.soNEEDED libc.so会被加载器自解析。头文件/链接库都在 ohos-sdk 的 sysroot 里~/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/native/sysrootlibc 头在usr/includearch 相关库在usr/lib/aarch64-linux-ohos。坑 1~/挂在 hmdfs 上/tmp只读。构建目录必须放到非 hmdfs 的挂载点上。$(brew --cache)落在/data/storage/el2/base/haps/entry/files/Homebrew真正的 hmfs行为正常。本文所有构建都在$(brew --cache)/manual-builds/gcc-build-16下进行。2. 构建命令CACHE$(brew--cache)# /data/storage/el2/.../HomebrewBUILD$CACHE/manual-builds/gcc-build-16SYS/storage/Users/currentUser/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/native/sysrootSDK/storage/Users/currentUser/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/nativeBP/storage/Users/currentUser/.harmonybrew/opt/binutilsSRC/storage/Users/currentUser/ProjectSources/gcc# 1) 让 GCC 在多架构目录里找到 crt/libcGCC 的多架构目录名是 aarch64-linux-muslln-sfnaarch64-linux-ohos$SYS/usr/lib/aarch64-linux-muslln-sfnaarch64-linux-ohos$SYS/usr/include/aarch64-linux-musl# 注意是 - ohos不是 - .# 2) 编译期工具目录as/ar/nm 等用 binutilsld 指向「GNU ld 签名」wrappermkdir-p$BUILD/toolsfortinas ar nm ranlib objdump objcopy strip readelf;doln-sfn$BP/bin/$t$BUILD/tools/$t;done# 见 §4ld 用 GNU ldbfd因为 lld 会拒绝 clang 生成的一种 misaligned 重定位# 此处先放一个 wrapperGNU ld 链接后自动 binary-sign-tool 自签名cat$BUILD/tools/ld-real-wrapperEOF #!/bin/sh GNULD/storage/Users/currentUser/.harmonybrew/opt/binutils/bin/ld SIGN/storage/Users/currentUser/.harmonybrew/bin/binary-sign-tool out; reloc0; prev_o0 for a in $; do [ $prev_o -eq 1 ] { out$a; prev_o0; } case $a in -r) reloc1;; -o) prev_o1;; --output*) out${a#--output};; -o*) out${a#-o};; esac done $GNULD $; rc$? [ $rc -eq 0 ] [ $reloc -eq 0 ] [ -n $out ] [ -f $out ] \ $SIGN sign -selfSign 1 -inFile $out -outFile $out /dev/null 21 exit $rc EOFchmodx$BUILD/tools/ld-real-wrapperln-sfn$BUILD/tools/ld-real-wrapper$BUILD/tools/ldln-sfn$BUILD/tools/ld-real-wrapper$BUILD/tools/ld.lld# 3) configureout-of-treebuild 目录必须在 hmfs 上mkdir-p$BUILD/tmp;exportTMPDIR$BUILD/tmpTMP$BUILD/tmpTEMP$BUILD/tmpcd$BUILDenvPATH$BUILD/tools:$PATHCC$BP/../opt/binutils/bin/as\CC/storage/Users/currentUser/Installed/clang-22/bin/clang\CXX/storage/Users/currentUser/Installed/clang-22/bin/clang\AR$BP/bin/arRANLIB$BP/bin/ranlibNM$BP/bin/nmOBJDUMP$BP/bin/objdump\CPPFLAGS-D_GNU_SOURCE\$SRC/configure\--prefix/storage/Users/currentUser/Installed/gcc-16\--with-sysroot$SYS\--buildaarch64-linux-musl--hostaarch64-linux-musl--targetaarch64-linux-musl\--enable-languagesc,c\--disable-multilib --enable-multiarch --disable-bootstrap --enable-checkingrelease\--disable-nls\--disable-libsanitizer --disable-libquadmath --disable-libssp\--disable-libgomp --disable-libitm --disable-libvtv\--with-as$BP/bin/as--with-ld$SDK/llvm/bin/ld.lld# 4) 时间戳修正gmp/mpfr/mpc/isl/gettext 都是从 tarball 解压的时间戳乱# make 会去跑 aclocal-1.16没装重新生成 autotools 文件。# 把「生成文件」统一改成比「源文件」新、比 build 输出旧。fordingmp-6.3.0 mpfr-4.2.2 mpc-1.3.1 isl-0.24 gettext-0.22;dofind$SRC/$d-typef-exectouch-d2026-08-01 00:00:00{}2/dev/nullfind$SRC/$d-typef\(-nameaclocal.m4-o-nameconfigure-o-nameMakefile.in-o-nameconfig.h.in\)\-exectouch-d2026-08-15 00:00:00{}2/dev/nulldone# 5) 用「追加 -fPIC」的 clang wrapper 当宿主编译器见 §5 的 PIC 坑cat$BUILD/tools/clangEOF #!/bin/sh exec /storage/Users/currentUser/Installed/clang-22/bin/clang $ -fPIC EOFcat$BUILD/tools/clangEOF #!/bin/sh exec /storage/Users/currentUser/Installed/clang-22/bin/clang $ -fPIC EOFchmodx$BUILD/tools/clang$BUILD/tools/clang# 重新 configure把 wrapper 当作 CC/CXX 烘焙进 MakefileenvPATH$BUILD/tools:$PATH\CC$BUILD/tools/clangCXX$BUILD/tools/clang\AR$BP/bin/arRANLIB$BP/bin/ranlibNM$BP/bin/nmOBJDUMP$BP/bin/objdump\CPPFLAGS-D_GNU_SOURCE\$SRC/configure[……参数同上……]# 6) buildtarget 库需要一个头文件把 clang 的 __availability__ 属性消掉cat$BUILD/noavail.hEOF /* Neutralize Clang-only __availability__ attribute for GCC. */ #define __availability__(...) EOFmake-j4all\CFLAGS_FOR_TARGET-g -O2 -include$BUILD/noavail.h -DHAVE_DECL_BASENAME1\CXXFLAGS_FOR_TARGET-g -O2 -include$BUILD/noavail.h -DHAVE_DECL_BASENAME1# 7) installmakeinstall\CFLAGS_FOR_TARGET-g -O2 -include$BUILD/noavail.h -DHAVE_DECL_BASENAME1\CXXFLAGS_FOR_TARGET-g -O2 -include$BUILD/noavail.h -DHAVE_DECL_BASENAME1说明上面为「讲清原理」写得很啰嗦实际一条条跑即可。构建时间4 核大约 2~3 小时不含各种踩坑重试。3. 安装后的「零参数」收尾specs 文件GCC 会自动加载$prefix/lib/gcc/target/version/specs。我们在~/Installed/gcc-16/lib/gcc/aarch64-linux-musl/16.2.1/specs里放*cc1_options: -D__availability__(...) -fPIC *libgcc: -lgcc -lgcc_eh *link: -rpath /storage/Users/currentUser/Installed/gcc-16/lib64 -rpath /storage/Users/currentUser/Installed/gcc-16/lib四条各自的作用条目作用-D__availability__(...)把 OHOS 头文件里的 clang 专用__attribute__((__availability__(ohos, introduced12.0.0)))消成空否则 GCC 会报too many decimal points in number-fPIC让 GCC 生成 PIC 代码全局变量走 GOT避免 §6 的stdoutCOPY 重定位截断-lgcc -lgcc_ehmusl 目标不装共享 libgcc_s默认-lgcc -lgcc_s_asneeded会缺_Unwind_Resume改成静态libgcc_eh.a-rpath ...让运行时找得到lib64/libstdc.so.6等等价于你们 clang 里的-Wl,-rpath注意specs 里每个*xxx:条目之间要留一个空行否则 GCC 会把后面的*yyy:当参数去执行。4. 坑clang 生成的R_AARCH64_LDST64_ABS_LO12_NC与 lld 不兼容clang 在 aarch64 上非 PIC 模式访问局部字符串常量会生成R_AARCH64_LDST64_ABS_LO12_NC重定位且目标地址可能只有 4 字节对齐lld直接报improper alignment for relocation ... is not aligned to 8 bytes。GNU ldbfd不检查这个对齐但非 PIC 访问动态符号stdout时会报relocation truncated to fit外部符号不能用绝对寻址。结论宿主编译 GCC 自身时必须让 clang 生成 PIC 代码外部符号走 GOT。GCC 的 Makefile 会追加-fno-PIE它会覆盖-fPIC所以不能只把-fPIC塞进CFLAGS会排在-fno-PIE之前。正解是 §2 里的clang wrapper在参数列表最后追加-fPIC让它始终是最后一个 PIC 相关选项。5. 坑target 库需要-D_GNU_SOURCE/__availability__/basenameOHOS 的strings.h把ffs藏在_XOPEN_SOURCE || _GNU_SOURCE || _BSD_SOURCE后面且 OHOS 头文件不像原生 musl 那样自动 includefeatures.h。所以宿主 ISL 的configure 会报No ffs implementation found。解法configure 时加CPPFLAGS-D_GNU_SOURCE。OHOS 头文件大量使用 clang 专用__attribute__((__availability__(ohos, introduced12.0.0)))288 个头文件、6670 处。GCC 解析不了12.0.0。解法target 库编译时-include noavail.h把__availability__(...)定义成空。-D_GNU_SOURCE会让系统的basename声明暴露出来与 libiberty.h 里的basename冲突conflicting types for basename。解法target 编译时加-DHAVE_DECL_BASENAME1让 libiberty.h 跳过自己的声明。6. 坑stdout的 COPY 重定位只有 4 字节 → 运行时段错误现象printf隐式 stdout正常但fwrite(..., stdout)/fprintf(stdout, ...)段错误。原因是 OHOS sysroot 的 stublibc.so里stdout符号大小是4 字节GCC 生成R_AARCH64_COPY时只拷 4 字节而真实FILE *是 8 字节指针被截断。clang 为什么没事clang 默认 PIEstdout走 GOTR_AARCH64_GLOB_DAT不 COPY。解法让 gcc 默认也生成 PIC见 §3 的-fPIC全局变量走 GOT不再 COPY。7.import std;用法与 hmdfs 的坑编译import std;g-stdc23-fmodules--compile-std-module foo.cpp-ofoo--compile-std-module会让驱动把bits/stdc.h、bits/std.cc、bits/std.compat.cc三个模块单元先编译成gcm.cache/std.gcm等首次较慢之后按 gcm 缓存复用。编译产物运行需要 libstdc已由 §3 的-rpath解决。hmdfs 坑gcm.cache/std.gcm是 GCC 用 mmap 写的大文件约 30MB。在~/hmdfs 分布式文件系统上这个写会失败得到 0 字节的std.gcm~报imports must be built before being imported。在$(brew --cache)这类 hmfs挂载点上则正常。所以要import std;时在非 hmdfs 目录里编译或把gcm.cache软链到 hmfs 上的目录。8. 踩坑速查现象原因解法config.status: cant create ./confXXXXXX/subs1.awk: Permission denied~/挂 hmdfsumask 077建的目录带 setgid 位沙箱拒绝写入构建目录挪到$(brew --cache)hmfsconfigure: error: No ffs implementation foundOHOS 头文件把ffs藏在 feature 宏后面CPPFLAGS-D_GNU_SOURCEaclocal-1.16: inaccessiblegettext tarball 时间戳乱触发 autotools 重生成统一 touch 生成文件的时间戳§2 步骤 4too many decimal points in numberOHOS 头用 clang 的__availability__(ohos, introduced12.0.0)-D__availability__(...)或-include noavail.h链接报undefined symbol: _Unwind_Resumemusl 不装共享 libgcc_s默认 spec 缺 unwinderspecs 里*libgcc: -lgcc -lgcc_ehconflicting types for basename-D_GNU_SOURCE暴露系统basename与 libiberty 冲突-DHAVE_DECL_BASENAME1运行报Error loading shared library libstdc.so.6运行时找不到lib64/libstdc.so.6specs 里加-rpath $prefix/lib64fwrite(stdout)段错误printf却正常stdoutCOPY 重定位只有 4 字节默认-fPIC走 GOTclang 编 GCC 时 lld 报improper alignment ... LDST64_ABS_LO12_NCclang 非 PIC 代码 lld 严格检查用「追加 -fPIC」的 clang wrapper链接用 GNU ldimport std;报imports must be built before being importedstd.gcm~0 字节hmdfs 上 mmap 写大 gcm 失败在 hmfs 目录编译或软链 gcm.cache 到 hmfs9. 验证结果$ ~/Installed/gcc-16/bin/gcc hello.c -o h ./h # hello-gcc-16-C $ ~/Installed/gcc-16/bin/g hello.cpp -o h ./h # 零参数vector 正常 $ ~/Installed/gcc-16/bin/g -stdc23 p.cpp -o p ./p # std::println / std::expected / std::ranges $ ~/Installed/gcc-16/bin/g -stdc23 -fmodules --compile-std-module m.cpp -o m ./m # import std; std::println ranges::fold_leftimport std;最小示例importstd;automain()-int{std::println(import std works: {},42);std::vectorintv{1,2,3,4};std::println(fold sum {},std::ranges::fold_left(v,0,std::plus{}));}10. 构建 sanitizerslibsanitizer前面 §2 的 configure 里我显式关了--disable-libsanitizer所以要补编 sanitizer运行时只需重新 configure 去掉这个开关再单独编 libsanitizer 这个 target库不用重编整个 GCC但见下面的「注意」。# 1) 重新 configure去掉 --disable-libsanitizer其余参数与 §2 完全相同# CC/CXX 仍是 tools/clang{,}追加 -fPIC 的 wrapper$SRC/configure\--prefix... --with-sysroot$SYS\--buildaarch64-linux-musl--hostaarch64-linux-musl--targetaarch64-linux-musl\--enable-languagesc,c --disable-multilib --enable-multiarch\--disable-bootstrap --enable-checkingrelease --disable-nls\--disable-libquadmath --disable-libssp --disable-libgomp --disable-libitm --disable-libvtv\--with-as$BP/bin/as--with-ld$SDK/llvm/bin/ld.lld# 注意没有 --disable-libsanitizer# 2) 单独编 装 libsanitizer带 OHOS 头文件冲突的 guard见下TFLAGS-g -O2 -include$BUILD/noavail.h -DHAVE_DECL_BASENAME1 -D_LINUX_SYSINFO_H -D_UAPI_LINUX_SOCKET_Hmake-j4all-target-libsanitizerCFLAGS_FOR_TARGET$TFLAGSCXXFLAGS_FOR_TARGET$TFLAGSmakeinstall-target-libsanitizerCFLAGS_FOR_TARGET$TFLAGSCXXFLAGS_FOR_TARGET$TFLAGS注意重新 configure 会触发一次级联重编host 库 gcc 自身 各 target 库都重来一遍因为 top 的 config.status 变了。而且中途会撞 autoconf 的 cache 一致性检查CFLAGS has changed since the previous run把$BUILD/aarch64-linux-musl/*/config.cache清掉即可。代价约 1.5 小时属于重新 configure 的固有成本。10.1 坑struct sysinfo/struct sockaddr_storage重定义libsanitizer 是 LLVM compiler-rt 的移植sanitizer_platform_limits_posix.cpp会同时includesys/sysinfo.h/sys/socket.h和linux/sysctl.h间接拉进linux/sysinfo.h/linux/socket.h。OHOS 头文件bionic 风格里这两套同名结构体会冲突error: redefinition of struct sysinfo error: redefinition of struct sockaddr_storage解法给 target 编译加两个 include guard跳过冲突的 linux 头-D_LINUX_SYSINFO_H -D_UAPI_LINUX_SOCKET_H这正是你在 LLVM compiler-rt 里撞到、最后靠关掉 sanitizer 绕开的那类问题。10.2 实测结果Sanitizer命令结果UBSan-fsanitizeundefined✅ 可用实测检出signed integer overflow 堆栈TSan-fsanitizethread✅ 可用实测检出 data raceASan-fsanitizeaddress⚠️ 编译/链接成功运行时挂见 10.3LSan随 ASan⚠️ 同上HWASan-fsanitizehwaddress⚠️ 同上装好的运行时在$prefix/lib64/libasan.so.8、libubsan.so.1、liblsan.so.0、libtsan.so.2、libhwasan.so.0。10.3 ASan/LSan/HWASan 运行时失败39-bit VA现象pidError: heap size 40000002000 exceeds max user virtual address 7fffffffff pidERROR: AddressSanitizer failed to allocate ... at address 0x040000000000 (error code: 22)还有一条 ASan 特有的、更早报的ASan runtime does not come first in initial library list; you should either link runtime to your application or manually preload it with LD_PRELOAD.两条的根因does not come firstmusl 的dl_iterate_phdr顺序里 libc 排在 libasan 之前而 ASan 这个检查是按 glibc 的 DSO 顺序写的。可先用ASAN_OPTIONSverify_asan_link_order0跳过这个检查。39-bit VA这台机器的内核是CONFIG_ARM64_VA_BITS39用户 VA 上限 512GB 0x7fffffffff。但 GCC 的 libsanitizer 对「非 Android 的 aarch64」用的是旧的 48-bit 假设libsanitizer/asan/asan_allocator.hkAllocatorSize 0x400000000004TB而 Android 分支用的是0x2000000000128G注释明说 “Android needs to support39, 42 and 48 bit VMA”libsanitizer/asan/asan_mapping.haarch64 用固定 shadow offset0x1000000000136编译器侧gcc/config/aarch64/aarch64.cc的aarch64_asan_shadow_offset返回136。39-bit VA 不是鸿蒙独有——它是 ARM64 Linux 的标准内核配置项Android 手机大量在用。LLVM/compiler-rt 早就用「128G 分配器 动态 shadow」适配了 39/42/48 位GCC 的libsanitizer 这个快照在 aarch64 非 Android 路径还没跟上。修法方向未做把asan_allocator.h里非 Android aarch64 的kAllocatorSize改成0x2000000000128G配VeryCompactSizeClassMap必要时再切动态 shadow且要保证编译器侧 shadow offset 与运行时一致需重编 libasan 编译器。这是一个可提给GCCcomponentlibsanitizer的合理 bugaarch64 Linux 在 39-bit VA 下 ASan 运行时失败而 Android 分支已处理。未完待续ASan 的 39-bit VA 布局补丁、std.compat、import std;在 hmdfs 上的进一步适配思路与上文一致。