You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从g++切换到clang++时出现undefined symbol等链接错误

问题背景

尝试将项目从g编译切换为clang编译时,出现undefined reference(未定义引用)错误,使用的编译链接命令如下:

clang++ -v <long list of .o files> -I$(SRC_DIR) -I$(SRC_DIR)/third_party/gemmlowp -I$(SRC_DIR)/third_party/flatbuffers/include -I$(SRC_DIR)/third_party/ruy -Isrc/third_party/kissfft -I$(SOC_SOFTWARE_DIR)/include -I$(SOC_SOFTWARE_DIR)/libc -I$(LIBC_DIR)/include -I$(LITEX_HW_DIR) -I(VEX_SRC_DIR) -ffunction-sections -fdata-sections -fno-common -fomit-frame-pointer -ffreestanding -Wno-error -Wsign-compare -Wdouble-promotion -Wshadow -Wunused-variable -Wno-missing-field-initializers -Wunused-function -Wno-uninitialized -Wno-deprecated-register -Wno-format -Wswitch -Wvla -DTF_LITE_STATIC_MEMORY -DTF_LITE_USE_GLOBAL_CMATH_FUNCTIONS -DTF_LITE_USE_GLOBAL_MIN -DTF_LITE_USE_GLOBAL_MAX -DTF_LITE_DISABLE_X86_NEON -g -O3 -fno-builtin --target=riscv32 --sysroot=/opt/riscv32/riscv32-unknown-elf --gcc-toolchain=/opt/riscv32 -menable-experimental-extensions -fuse-ld=lld -std=c++11 -fstrict-aliasing -fno-rtti -fno-exceptions -fno-threadsafe-statics -fmessage-length=0 -Wall -Wextra -Wstrict-aliasing -Wno-unused-parameter -L$(BUILD_DIR)/ld -L$(SOC_SOFTWARE_DIR)/include/generated -L$(SOC_SOFTWARE_DIR)/libbase -lbase -L$(SOC_SOFTWARE_DIR)/libc -lc -L$(SOC_SOFTWARE_DIR)/libc/newlib -lm -L$(SOC_SOFTWARE_DIR)/libcompiler_rt -lcompiler_rt -nostartfiles -Wl,--gc-sections -Wl,--fatal-warnings -Wl,--no-warn-mismatch -Wl,--script=$(BUILD_DIR)/ld/linker.ld -Wl,--build-id=none -Wl,-Map=software.map -o software.elf

使用g执行完全相同的命令时,项目可正常编译并按预期运行,切换为clang执行时出现如下报错:

>>> referenced by base.c:34 (<BUILD_DIR>/src/base.c:34)
>>>               src/base.o:(isr)

ld.lld: error: undefined symbol: uart_init
>>> referenced by irq.h:19 (<VEX_SRC_DIR>/irq.h:19)
>>>               src/base.o:(init_runtime)

ld.lld: error: undefined symbol: getpid
>>> referenced by signal.c:159 (<LIBC_DIR>/signal/signal.c:159)
>>>               libc_signal_signal.c.o:(raise) in archive <SOC_SOFTWARE_DIR>/libc/libc.a

ld.lld: error: undefined symbol: kill
>>> referenced by signal.c:160 (<LIBC_DIR>/signal/signal.c:160)
>>>               libc_signal_signal.c.o:(raise) in archive <SOC_SOFTWARE_DIR>/libc/libc.a

ld.lld: error: undefined symbol: stdin
>>> referenced by getchar.c:40 (<LIBC_DIR>/tinystdio/getchar.c:40)
>>>               libc_tinystdio_getchar.c.o:(getchar) in archive <SOC_SOFTWARE_DIR>/libc/libc.a
>>> referenced by getchar.c:40 (<LIBC_DIR>/tinystdio/getchar.c:40)
>>>               libc_tinystdio_getchar.c.o:(getchar) in archive <SOC_SOFTWARE_DIR>/libc/libc.a

ld.lld: error: undefined symbol: stdout
>>> referenced by printf.c:42 (<LIBC_DIR>/tinystdio/printf.c:42)
>>>               libc_tinystdio_printf.c.o:(printf) in archive <SOC_SOFTWARE_DIR>/libc/libc.a
>>> referenced by printf.c:37 (<LIBC_DIR>/tinystdio/printf.c:37)
>>>               libc_tinystdio_printf.c.o:(printf) in archive <SOC_SOFTWARE_DIR>/libc/libc.a
>>> referenced by putchar.c:39 (<LIBC_DIR>/tinystdio/putchar.c:39)
>>>               libc_tinystdio_putchar.c.o:(putchar) in archive <SOC_SOFTWARE_DIR>/libc/libc.a
>>> referenced 3 more times

ld.lld: error: undefined symbol: __errno
>>> referenced by sys_exit.c
>>>               sys_exit.o:(_exit) in archive /opt/riscv32/riscv32-unknown-elf/lib/libgloss.a

ld.lld: error: section .eh_frame load address range overlaps with .data
>>> .eh_frame range is [0x400A29F0, 0x400A31F3]
>>> .data range is [0x400A29F0, 0x400A318F]
clang-15: error: ld command failed with exit code 1 (use -v to see invocation)

核心疑问:clang与g在库构建、链接配置逻辑上存在哪些容易被忽略的差异点,需要做哪些适配调整才能解决上述报错?

适配方案

两类编译器和对应的默认链接器存在三个核心差异,按以下步骤调整即可解决问题:

  • 修正静态库链接顺序。g默认使用的GNU ld会对静态库做多遍递归回溯搜索,即使库顺序摆放错误,也会反复查找未定义符号;但当前使用的lld是单遍顺序扫描,库必须放在引用它的目标文件、其他依赖库的后面,顺序错误就会触发未定义引用。
    调整库的排列逻辑:所有业务目标文件、自定义静态库(如包含uart_init实现的库、自行实现的系统调用桩目标文件)放在最前面,之后按依赖层级从高到低摆放,即被依赖的库放在更靠后的位置:-lbase -lc -lm -lgloss -lcompiler_rt。如果不确定顺序,可以给正常工作的g
    命令加-v参数,查看g++实际传递给链接器的完整库列表和排列顺序,直接照搬该顺序即可。
  • 补全符号兼容适配。g链接时会隐式补充libgcc等底层支撑库、自动处理部分符号别名,clang搭配lld时不会自动完成这些适配。针对报错中的__errno符号,在errno实现代码中补充弱别名即可:
    int* __errno_location(void);
    __attribute__((weak, alias("__errno_location"))) int* __errno(void);
    
    针对stdin/stdout/getpid/kill这类未定义符号,确认对应实现的目标文件/静态库已经放在libc的前面,保证lld扫描时能找到。
  • 解决.eh_frame段重叠问题。lld对自定义链接脚本的段解析规则和GNU ld存在差异,且当前已开启-fno-exceptions -fno-rtti,完全不需要eh_frame栈展开相关段。做两处调整即可:
    1. 编译阶段加-fno-asynchronous-unwind-tables参数,让编译器不生成eh_frame相关内容
    2. 如果链接后仍残留eh_frame段,直接在链接脚本的/DISCARD/段中添加规则丢弃该段:
      /DISCARD/ : {
        *(.eh_frame)
        *(.eh_frame_hdr)
      }
      

内容的提问来源于stack exchange,提问作者ijustdontgetit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 20:09:34