ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LLVM嵌入式工具链静态评测:Arm架构模块边界与ABI一致性分析

LLVM嵌入式工具链静态评测:Arm架构模块边界与ABI一致性分析 1. 这不是一次“编译成功就完事”的源码走读为什么静态评测 LLVM Embedded Toolchain for Arm 必须从模块边界开始你有没有试过在 ARM 开发板上跑一个用arm-none-eabi-gcc编译出来的程序结果段错误一闪而过gdb 跟进去发现符号全丢、栈帧错乱、甚至.init_array段压根没被调用或者更糟——你在 CI 流水线上反复触发make check失败报错信息只有一行FAIL: test-suite :: .../memcpy.c但根本看不出是 clang 前端解析错了结构体对齐还是 lld 链接器把.rodata和.text合并时破坏了 MPU 内存区域划分这些不是“环境问题”而是工具链本身在静态层面就埋下的结构性隐患。我过去三年主导过 4 个基于 Cortex-M7/M33 的工业实时控制项目所有固件都强制要求通过 MISRA-C:2012 Rule 20.5禁止动态内存分配和 AUTOSAR BSW 模块接口一致性验证。在这种场景下“能编译”和“能运行”之间隔着一整条信任链前端是否严格遵循 Arm AAPCS ABI 规范处理_Alignas和__attribute__((section))中端优化是否在-O2下误删了volatile访问的寄存器读写序列后端代码生成是否保证ldr pc, [pc, #offset]的跳转绝对地址在 Thumb-2 指令集下不越界这些问题的答案无法靠运行时日志或 gdb 单步调试穷举只能在源码静态结构里定位证据链。这就是为什么标题里强调“静态评测”而非“构建测试”——它不是要证明这个工具链能不能跑通 SPEC CPU2017而是要确认当你在CMakeLists.txt里写下set(CMAKE_C_COMPILER_TARGET armv7m-none-eabi)时背后那套由 Clang、LLVM、Lld、Libc、Compiler-RT 组成的嵌入式工具链其模块职责是否清晰、接口契约是否显式、构建依赖是否可追溯、测试覆盖是否与 Arm 架构特性强绑定。比如llvm/lib/Target/ARM/目录下那个ARMSubtarget.cpp文件它声明的hasV8MBaseline()方法返回true是否真的对应到clang/lib/Basic/Targets/ARM.cpp中getArchFeatures()对ARM::ArchKind::AK_ARMV8M_BASE的判定逻辑这种跨模块的语义一致性才是静态评测的核心战场。而当前社区最大的认知偏差就是把“LLVM Embedded Toolchain for Arm”当成一个黑盒下载包。热词里反复出现的“有没有预编译的llvm”“arm compiler 5.06u7 download”恰恰暴露了开发者对工具链演进路径的陌生——Arm Compiler 5 是基于旧版 ARMCC 的闭源内核而 LLVM Embedded Toolchain 是 Arm 官方主导的开源重构二者 ABI 兼容性为零。当你在 Qt5.5.10 ARM Linux 开发中遇到QMetaObject::activate调用崩溃根源很可能不是 Qt 本身而是你混用了 Arm Compiler 5 编译的系统库和 LLVM 工具链编译的应用二进制导致 vtable 布局错位。静态评测的第一步就是撕开这个黑盒看清每个模块的输入契约、输出承诺、以及它们之间那几条脆弱的胶水层。2. 模块解剖从 llvm-project 仓库的目录树里识别出真正影响嵌入式开发的 7 个关键子系统LLVM 的官方 monorepohttps://github.com/llvm/llvm-project表面看是个巨型代码库但对嵌入式开发者而言90% 的代码与你无关。真正的战场集中在以下七个子系统它们共同构成 Arm 嵌入式工具链的骨架。我按实际开发中遭遇问题的频率排序并标注每个模块的“致命风险点”——即一旦该模块静态结构存在缺陷将直接导致不可调试的底层故障。2.1 Clang 前端ABI 合规性的第一道闸门路径clang/lib/Basic/Targets/ARM.cppclang/lib/CodeGen/TargetInfo.cpp核心职责解析 C/C 源码生成符合 Arm AAPCSARM Architecture Procedure Call Standard的 IR同时处理目标平台特有的属性如__attribute__((naked)),__attribute__((section(.isr_vector)))。致命风险点ABI 特征检测逻辑与后端不一致。例如ARMTargetInfo::getTargetDefines()中定义的__ARM_ARCH_7M__宏必须与llvm/lib/Target/ARM/ARMSubtarget.cpp中getFeatureString()返回的-marcharmv7-m字符串严格映射。我在某次移植 FreeRTOS 到 Cortex-M33 时发现Clang 生成的__attribute__((aligned(16)))结构体在 LLD 链接后.bss段起始地址却是 4 字节对齐——追查发现是ARMTargetInfo::getAlignedAllocSize()返回值硬编码为 8而 Cortex-M33 的 FPU 寄存器保存要求 16 字节对齐。这个 bug 在clang/lib/Basic/Targets/ARM.cpp第 1247 行但修复必须同步更新llvm/lib/Target/ARM/ARMFrameLowering.cpp中的determineFrameLayout()逻辑否则后端会忽略前端的对齐声明。提示静态检查时务必用git grep -n AAPCS clang/扫描所有涉及 AAPCS 的宏定义和条件分支确认#ifdef __ARM_ARCH_7M__和#ifdef __ARM_ARCH_8M_MAIN__的分支覆盖是否完整。缺失任一分支都会导致特定 Arm 架构变体的 ABI 违规。2.2 LLVM 中端优化器的“安全区”边界在哪里路径llvm/lib/Transforms/Scalar/llvm/lib/Target/ARM/ARMISelLowering.cpp核心职责对 Clang 生成的 IR 进行通用优化如 LICM、GVN再通过ARMISelLowering将 IR 映射为 Arm 特定指令如将llvm.memcpy.p0i8.p0i8.i32降级为mov/str序列。致命风险点优化器误判 volatile 语义。Arm 嵌入式开发中volatile uint32_t *REG (uint32_t*)0x400FE000; *REG 0x1;这类寄存器写操作必须保证每次赋值都生成独立的str指令。但 LLVM 的-O2默认启用LoopVectorizePass它可能将连续的*REG i; i;循环向量化为单条strd指令——这在硬件上等同于写入两个寄存器直接触发总线错误。证据链在llvm/lib/Transforms/Vectorize/LoopVectorize.cpp的canVectorizeInstr()函数它对StoreInst的isVolatile()判断存在短路逻辑若 store 指向全局变量且无别名信息就跳过 volatile 检查。解决方案是强制在CMakeLists.txt中添加-mllvm -enable-no-nans-fp-mathfalse并禁用-ffast-math但这属于构建时补丁静态评测必须确认ARMISelLowering::LowerSTORE()是否在isVolatile()为 true 时强制插入ARMISD::ST节点而非通用ISD::STORE。2.3 LLD 链接器链接脚本之外你真正理解.text段如何被切片吗路径lld/ELF/Arch/ARM.cpplld/ELF/LinkerScript.cpp核心职责解析链接脚本如arm-none-eabi.ld将目标文件的 section 合并为可执行段并解决符号重定位如R_ARM_CALL。致命风险点Thumb-2 指令集下的 BL 指令重定位溢出。Arm 的bl指令使用 24 位有符号偏移最大跳转范围 ±16MB。当你的固件代码量超过此限LLD 必须插入 veneer跳板代码。但lld/ELF/Arch/ARM.cpp中needsThunk()的判定逻辑仅检查R_ARM_CALL的距离却忽略了R_ARM_THM_CALLThumb 模式下的 bl 指令——后者使用 22 位偏移范围仅 ±2MB。我在一个 3.2MB 的 Bootloader 项目中发现main()调用uart_init()时LLD 未插入 veneer导致bl指令编码错误CPU 进入 HardFault。静态证据在lld/ELF/Arch/ARM.cpp第 821 行bool needsThunk(const RelocationRef Rel)它对R_ARM_THM_CALL的处理分支缺失。2.4 Compiler-RT那些你以为“理所当然”的内置函数其实全是手写的汇编路径compiler-rt/lib/builtins/arm/compiler-rt/lib/sanitizer_common/核心职责提供__aeabi_memmove、__aeabi_idiv等 AAPCS 标准内置函数以及__ubsan_handle_add_overflow等 sanitizer 支持。致命风险点Thumb-2 模式下__aeabi_memcpy的栈帧破坏。该函数在compiler-rt/lib/builtins/arm/memcpy.S中实现使用push {r4-r7,lr}保存寄存器。但 Thumb-2 的push指令在某些 Cortex-M 内核上如 M0要求栈指针 4 字节对齐而编译器生成的调用者栈帧可能未对齐。静态检查必须确认memcpy.S第 42 行mov r3, sp后是否插入and r3, r3, #0xfffffffc强制对齐——缺失此操作会导致后续ldmia加载错误寄存器值。这不是理论风险而是我在 STM32F030 上实测复现的崩溃。2.5 Libc嵌入式环境下STL 容器的“轻量化”承诺是否兑现路径libcxx/src/libcxx/include/核心职责提供 C 标准库实现但嵌入式版本需禁用异常、RTTI 和动态内存分配。致命风险点std::vector的capacity()计算绕过operator new检查。即使你定义了#define _LIBCPP_NO_EXCEPTIONS和#define _LIBCPP_NO_RTTIlibcxx/src/vector.cpp中__grow_by()函数仍会调用__throw_length_error()——该函数内部隐式依赖operator new。静态证据在libcxx/include/vector第 1523 行__alloc_traits::allocate(__a, __n)它未被#ifdef _LIBCPP_NO_EXCEPTIONS包裹。正确做法是彻底禁用std::vector改用llvm::SmallVector后者在头文件中完全展开无运行时分配。2.6 LLVM Target Backend指令选择器ISel如何决定一条add用adds还是add路径llvm/lib/Target/ARM/ARMInstrInfo.tdllvm/lib/Target/ARM/ARMISelDAGToDAG.cpp核心职责将通用 IR 指令映射为具体 Arm 指令并选择最优指令序列如用movw/movt加载 32 位立即数。致命风险点Cortex-M 系列的ITIf-Then块生成逻辑缺陷。Arm Thumb-2 要求条件执行指令必须包裹在IT块中。ARMISelDAGToDAG::Select()在处理SELECT_CC节点时若分支条件复杂如(a b) (c d)可能生成独立的cmpbne而非it ttaddeqsubne。证据链在llvm/lib/Target/ARM/ARMISelDAGToDAG.cpp第 2185 行if (CondCode ISD::SETNE ...)分支它未覆盖多条件组合场景。这会导致代码体积膨胀 30%且在低功耗模式下因额外分支预测失败增加功耗。2.7 CMake 构建系统-DLLVM_TARGETS_TO_BUILDARM真的只构建 ARM 后端吗路径llvm/CMakeLists.txtllvm/cmake/modules/HandleLLVMOptions.cmake核心职责协调整个工具链的构建流程决定哪些模块被编译、哪些测试被启用。致命风险点LLVM_ENABLE_PROJECTS的隐式依赖泄露。当你设置-DLLVM_ENABLE_PROJECTSclang;lld时CMake 会自动启用compiler-rt但compiler-rt的构建又依赖llvm-config——而llvm-config的--targets-built输出包含X86即使你只构建 ARM。这导致clang的lib/Driver/ToolChains/Clang.cpp中getTripleForArch()方法在解析armv7m-none-eabi时错误地 fallback 到 X86 的默认 ABI。静态证据在llvm/cmake/modules/AddLLVM.cmake第 132 行if(LLVM_TARGETS_TO_BUILD STREQUAL all)它未隔离compiler-rt的 target 依赖。解决方案是手动指定-DCOMPILER_RT_DEFAULT_TARGETSarmv7m-none-eabi。3. 构建证据链如何用 3 个命令从源码中提取出“这个工具链确实为 Arm 构建”的不可辩驳证明很多开发者认为“cmake -G Ninja -DLLVM_TARGETS_TO_BUILDARM .. ninja成功”就是构建证据。这是危险的幻觉。真正的证据必须回答三个问题1编译器是否真的识别 Arm 架构2生成的二进制是否包含 Arm 特定指令3测试用例是否覆盖 Arm 独有的行为下面是我验证过的、可直接复制粘贴的命令链每一步都指向源码中的具体位置。3.1 证据一Clang 的 TargetInfo 是否硬编码 Arm ABI 特征执行命令cd build ./bin/clang --targetarmv7m-none-eabi -E -dM /dev/null | grep -E (ARM|AAPCS|THUMB)预期输出应包含#define __ARM_ARCH_7M__ 1 #define __ARM_ARCH_PROFILE M #define __ARM_EABI__ 1 #define __ARM_PCS_VFP 1 #define __ARM_SIZEOF_WCHAR_T 4 #define __ARMEL__ 1 #define __thumb__ 1 #define __thumb2__ 1这些宏全部源自clang/lib/Basic/Targets/ARM.cpp。例如__ARM_ARCH_7M__在第 1123 行定义Builder.defineMacro(__ARM_ARCH_7M__);而__ARM_PCS_VFP在第 1145 行Builder.defineMacro(__ARM_PCS_VFP);。如果输出缺失__ARM_PCS_VFP说明 Clang 未启用 VFP 调用约定所有浮点运算将通过软件模拟性能暴跌 10 倍。这不是配置问题而是源码中ARMTargetInfo::getTargetDefines()函数的逻辑缺陷。3.2 证据二LLVM IR 是否被正确降级为 Thumb-2 指令编写最小测试文件test.cvoid foo(int *a, int *b) { *a *b 1; }执行命令./bin/clang --targetarmv7m-none-eabi -O2 -S -emit-llvm test.c -o test.ll ./bin/opt -S test.ll | grep -A5 define void foo ./bin/llc -mtriplearmv7m-none-eabi -mcpucortex-m3 test.ll -o test.s grep -E (adds|movs|str|ldr) test.s关键证据在test.s输出foo: ldr r2, [r1] adds r2, r2, #1 str r2, [r0] bx lr其中adds指令带状态更新的加法是 Thumb-2 特有区别于 ARM 模式的add。它的生成逻辑在llvm/lib/Target/ARM/ARMInstrInfo.td第 1287 行def ADDSrr : ARMPseudoInst(outs GPR:$rd), (ins GPR:$rn, GPR:$rm), ...。如果输出是add r2, r2, #1说明llc未启用 Thumb 模式根源在ARMSubtarget::ARMSubtarget()构造函数中ThumbMode标志未被正确设置——该标志由ARMTargetMachine::ARMTargetMachine()传入而后者又依赖ARMTargetInfo::getTargetDefines()的宏定义。3.3 证据三LLD 是否为 Arm 特定重定位生成了正确 fixup创建test.o用arm-none-eabi-gcc生成确保含R_ARM_CALLecho int bar() { return 1; } | arm-none-eabi-gcc -c -x c - -o bar.o echo int foo() { return bar(); } | arm-none-eabi-gcc -c -x c - -o foo.o执行命令./bin/lld -flavor gnu --targetarmv7m-none-eabi bar.o foo.o -o test.elf arm-none-eabi-readelf -r test.elf | grep R_ARM_CALL预期输出00000000 00000017 R_ARM_CALL 00000000 bar这个R_ARM_CALL重定位类型由lld/ELF/Arch/ARM.cpp的getRelocationType()函数返回。查看该函数第 102 行case R_ARM_CALL: return R_ARM_CALL;它直接映射到 ELF 标准类型。但更重要的是lld/ELF/Writer.cpp中writeResult()函数在调用relocate()前会检查Config-EMachine EM_ARM第 1245 行。如果此处为falseLLD 将使用通用重定位逻辑导致R_ARM_CALL被错误解析为R_X86_64_PLT32生成无效二进制。因此readelf -h test.elf的Machine:字段必须为ARM这是 LLD 正确工作的铁证。4. 测试证据为什么make check-all的 98% 通过率对嵌入式开发者毫无意义LLVM 官方的make check-all运行约 8 万个测试用例覆盖 C 标准库、IR 优化、X86 指令生成等。但对 Arm 嵌入式开发者而言其中 95% 的测试与你无关。真正的测试证据必须聚焦于 Arm 架构独有的约束内存模型、异常向量表、特权指令、MPU 配置。以下是我在实际项目中提炼出的、必须人工审查的 5 类测试证据每类都对应源码中的具体文件和断言。4.1 AAPCS ABI 合规性测试clang/test/CodeGen/arm-aapcs.c是唯一可信的 ABI 白皮书该测试文件位于clang/test/CodeGen/arm-aapcs.c它用// CHECK:注释精确描述期望的汇编输出。例如// RUN: %clang_cc1 -triple armv7m-none-eabi -target-feature v7 -emit-llvm %s -o - | FileCheck %s struct S { int a; char b; } __attribute__((packed)); // CHECK: %struct.S type { i32, i8 } // CHECK: store i32 {{.*}}, i32* getelementptr inbounds (%struct.S, %struct.S* %s, i32 0, i32 0)关键证据在于CHECK断言是否匹配 AAPCS 规范。AAPCS 要求packed结构体的成员按自然对齐int4 字节char1 字节但getelementptr的索引必须反映实际内存布局。如果CHECK断言为i32* getelementptr inbounds (%struct.S, %struct.S* %s, i32 0, i32 1)即char b的偏移为 1则证明 Clang 正确实现了 packed 规则。反之若断言为i32* getelementptr inbounds (%struct.S, %struct.S* %s, i32 0, i32 4)说明 Clang 错误地按 4 字节对齐char这将导致硬件寄存器访问错位。我曾在一个 CAN 控制器驱动中因 Clang 的packed实现缺陷导致CAN_TxMessage结构体的Data[8]数组首地址偏移错误发送数据全为 0x00。4.2 Thumb-2 指令集边界测试llvm/test/CodeGen/ARM/thumb2-it-blocks.ll揭示 IT 块生成漏洞该测试位于llvm/test/CodeGen/ARM/thumb2-it-blocks.ll它验证ITIf-Then块的生成逻辑。典型用例; CHECK: ittt ; CHECK-NEXT: addeq ; CHECK-NEXT: subne ; CHECK-NEXT: movs %cond icmp eq i32 %a, %b %val1 add i32 %x, %y %val2 sub i32 %p, %q %res select i1 %cond, i32 %val1, i32 %val2CHECK断言要求生成itttIT Then-Then-Then块而非独立的bne分支。如果测试失败输出为cmp r0, r1 bne .L2 add r2, r3, r4 .L2: sub r2, r5, r6这证明ARMISelDAGToDAG::Select()未触发 IT 块生成根源在llvm/lib/Target/ARM/ARMISelDAGToDAG.cpp第 2185 行的CondCode判定逻辑。此缺陷在 Cortex-M0/M0 上尤为致命因为这些内核无分支预测器bne指令导致流水线清空性能损失达 40%。4.3 MPU 内存保护测试lld/test/ELF/arm/mpu-region.ld验证链接器对 MPU 区域的支持Arm Cortex-M3/M4/M7 的 MPUMemory Protection Unit要求.text、.rodata、.data必须位于不同内存区域且区域大小为 2^n 字节。lld/test/ELF/arm/mpu-region.ld是唯一验证此能力的测试SECTIONS { . 0x00000000; .text ALIGN(0x1000) : { *(.text) } FLASH .rodata ALIGN(0x1000) : { *(.rodata) } FLASH .data ALIGN(0x1000) : { *(.data) } RAM }执行lld --scriptmpu-region.ld test.o后用arm-none-eabi-readelf -l test.elf检查 Program HeadersLOAD off 0x00001000 vaddr 0x00000000 paddr 0x00000000 align 2**12 LOAD off 0x00002000 vaddr 0x20000000 paddr 0x20000000 align 2**12align 2**124KB是 MPU 的最小区域大小。如果align为2**0说明 LLD 忽略了ALIGN(0x1000)指令根源在lld/ELF/LinkerScript.cpp的evaluateExpr()函数未正确解析十六进制常量。此问题将导致 MPU 初始化失败系统无法启动。4.4 Compiler-RT 内置函数测试compiler-rt/test/builtins/Unit/arm/是裸机安全的基石该目录下的memcpy_test.c不是简单验证功能而是测试边界条件// Test memcpy with unaligned src and dst char src[10] {1,2,3,4,5,6,7,8,9,0}; char dst[10]; memcpy(dst, src1, 8); // src1 is unaligned // CHECK: dst[0] 2关键证据是memcpy.S中是否包含ldrb/strb序列处理字节对齐。查看compiler-rt/lib/builtins/arm/memcpy.S第 105 行cmp r2, #3 ble .Lbyte_loop .Lword_loop: ldr r3, [r1], #4 str r3, [r0], #4 subs r2, r2, #4 bhi .Lword_loopsubs r2, r2, #4表明它假设r2长度是 4 的倍数。如果测试用例memcpy(dst, src1, 8)失败说明.Lbyte_loop未被正确调用根源在cmp r2, #3的阈值设置错误——应为cmp r2, #0以处理任意长度。这个 bug 会导致所有非 4 字节对齐的内存拷贝崩溃。4.5 Clang Driver 链接器选择测试clang/test/Driver/arm-linker.cpp确保 LLD 是默认链接器该测试验证clang --targetarmv7m-none-eabi是否自动选择lld而非gcc的ld// RUN: %clang -target armv7m-none-eabi -### %s 21 | FileCheck %s // CHECK: {{.*}}lld{{.*}}如果CHECK匹配失败输出为/usr/bin/arm-none-eabi-gcc -o a.out {{.*}}.o说明 Clang Driver 未集成 LLD。根源在clang/lib/Driver/ToolChains/ARM.cpp的getLinkerPath()函数它应返回getToolChain().getCompilerRTPath()下的lld路径而非硬编码/usr/bin/ld。此缺陷将导致链接时忽略lld/ELF/Arch/ARM.cpp中的 Arm 特定逻辑R_ARM_CALL重定位失效。5. 实操避坑指南从我的 4 个 Arm 项目中总结出的 7 条血泪经验纸上谈兵终觉浅。以下是我踩过的坑、填过的雷、验证过的方案每一条都对应真实项目中的崩溃现场和源码级修复。5.1 经验一永远不要相信--targetarmv7m-none-eabi的“自动推导”在第一个项目中我用clang --targetarmv7m-none-eabi编译但生成的二进制在 Cortex-M3 上进入 HardFault。gdb显示 PC 指向0x00000000即复位向量未正确加载。追查发现Clang 的ARMTargetInfo默认启用__ARM_ARCH_7A__Application Profile而非__ARM_ARCH_7M__Microcontroller Profile。解决方案是显式指定clang --targetarmv7m-none-eabi -marcharmv7-m -mcpucortex-m3 -mfloat-abihard -mfpuvfpv3 ...其中-marcharmv7-m强制 Clang 使用 M-profile 特征它会触发clang/lib/Basic/Targets/ARM.cpp中getArchFeatures()的AK_ARMV7M分支生成正确的__ARM_ARCH_7M__宏。省略-marchClang 会 fallback 到 A-profile导致中断向量表布局错误。5.2 经验二-O2下的volatile优化必须用__attribute__((optimize(O0)))局部禁用第二个项目中一个while(*flag 0);循环被-O2优化为死循环flag地址被缓存。volatile修饰符失效。根源是 LLVM 的LoopAccessAnalysis将flag判定为 loop-invariant。临时方案是给整个函数加__attribute__((optimize(O0)))但治本之策是修改llvm/lib/Analysis/LoopAccessAnalysis.cpp在isSafeToOptimize()中添加对volatile指针的强制否决逻辑。不过更实用的做法是所有硬件寄存器访问函数必须用static inline定义并在函数体内用asm volatile( ::: memory)插入内存屏障。5.3 经验三lld的--gc-sections会删除.init_array必须用--undefined__init_array_start第三个项目的 Bootloader 无法初始化 C 全局对象。readelf -S bootloader.elf显示.init_array段存在但readelf -d bootloader.elf | grep INIT_ARRAY为空。原因是lld的--gc-sections认为.init_array未被引用将其删除。解决方案是在链接脚本中添加SECTIONS { .init_array : { PROVIDE(__init_array_start .); KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*))) KEEP(*(.init_array)) PROVIDE(__init_array_end .); } }并链接时添加--undefined__init_array_start强制 LLD 保留该符号。此逻辑在lld/ELF/Writer.cpp的addSyntheticSections()中实现它依赖Config-UndefinedSymbols列表。5.4 经验四libcxx的std::string在嵌入式中是定时炸弹必须用llvm::StringRef第四个项目中std::string s hello;导致operator new调用失败。libcxx/include/string第 2215 行__imp___string_alloc_base::__allocate()未被#ifdef _LIBCPP_NO_EXCEPTIONS包裹。正确做法是彻底禁用std::string改用llvm::StringRef头文件llvm/ADT/StringRef.h它只存储指针和长度无动态分配。StringRef的构造函数StringRef(const char *data, size_t length)是constexpr可在编译期计算。5.5 经验五clang的#include_next在嵌入式头文件中极易引发循环包含当自定义stdint.h时#include_next stdint.h可能再次包含自身。解决方案是所有自定义头文件必须用#pragma once#ifndef MY_STDINT_H双重防护并在clang/lib/Headers/stdint.h中将#include_next stdint.h替换为#include my_stdint.h的绝对路径。这需要修改clang/lib/Headers/CMakeLists.txt将自定义头文件加入CLANG_HEADER_DIR。5.6 经验六lld的--defsym无法覆盖链接脚本中的PROVIDE必须用--def或修改脚本想用lld --defsym_stack_size0x2000覆盖链接脚本中的PROVIDE(_stack_size 0x1000)但无效。因为PROVIDE是链接脚本指令优先级高于--defsym。正确方案是用--def参数指定一个.def文件其中EXPORTS列表定义_stack_size或直接修改链接脚本将PROVIDE改为__stack_size DEFINED(__stack_size) ? __stack_size : 0x1000;然后用--defsym__stack_size0x2000。5.7 经验七compiler-rt的 __aeabi
返回列表