从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栈展开相关段。做两处调整即可:- 编译阶段加
-fno-asynchronous-unwind-tables参数,让编译器不生成eh_frame相关内容 - 如果链接后仍残留eh_frame段,直接在链接脚本的
/DISCARD/段中添加规则丢弃该段:/DISCARD/ : { *(.eh_frame) *(.eh_frame_hdr) }
- 编译阶段加
内容的提问来源于stack exchange,提问作者ijustdontgetit
相关产品推荐
相关产品推荐

