ARM Cortex-M4裸机C项目链接C++库时未定义符号问题
ARM Cortex-M4裸机项目链接C++库的系统调用未定义引用问题分析
问题背景
为ARM Cortex-M4交叉编译外部C++库/可重链接目标文件时,使用参数:
-fno-unwind-tables -ffunction-sections -fdata-sections -fmessage-length=0 -std=c++11 -fno-rtti -fno-exceptions -fno-threadsafe-statics -Wnon-virtual-dtor -fno-unwind-tables -ffreestanding -fno-non-call-exceptions -fno-use-cxa-atexit -fno-common -r
编译C++包装文件时追加-C参数,随后用以下参数链接库与包装文件:
-static -r -nostartfiles -nolibc -nodefaultlibs -nostdlib
将该产物链接到独立裸机C项目时,出现一系列未定义引用错误:
../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-abort.o): in function `abort': abort.c:(.text.abort+0xa): undefined reference to `_exit' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-signalr.o): in function `_kill_r': signalr.c:(.text._kill_r+0x12): undefined reference to `_kill' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-signalr.o): in function `_getpid_r': signalr.c:(.text._getpid_r+0x0): undefined reference to `_getpid' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-writer.o): in function `_write_r': writer.c:(.text._write_r+0x14): undefined reference to `_write' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-closer.o): in function `_close_r': closer.c:(.text._close_r+0xc): undefined reference to `_close' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-fstatr.o): in function `_fstat_r': fstatr.c:(.text._fstat_r+0x12): undefined reference to `_fstat' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-isattyr.o): in function `_isatty_r': isattyr.c:(.text._isatty_r+0xc): undefined reference to `_isatty' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-lseekr.o): in function `_lseek_r': lseekr.c:(.text._lseek_r+0x14): undefined reference to `_lseek' ../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-readr.o): in function `_read_r': readr.c:(.text._read_r+0x14): undefined reference to `_read'
添加--specs=nosys.specs或-lnosys可解决问题,但需明确根源,避免盲目依赖。
问题根源
这些未定义符号是newlib(ARM交叉编译工具链的libc)可重入标准库函数依赖的底层系统调用桩。
- 当最终链接裸机项目时,链接器会解析所有依赖:如果C++库/包装代码中隐式调用了newlib的可重入函数(如
abort、可重入版IO函数),链接器会从libc.a中提取对应的目标文件(如lib_a-abort.o、lib_a-signalr.o)。 - 这些newlib内部函数依赖
_exit、_kill等底层系统调用——裸机环境没有操作系统提供这些实现,因此触发未定义引用错误。
为什么单独查看C++产物看不到这些符号
你用-r参数生成的是可重链接目标文件(relocatable object),此时链接器仅完成部分符号解析,并未将libc.a中的目标文件合并进来。只有在最终链接裸机项目时,链接器才会根据依赖关系从libc.a中提取所需的.o文件,此时这些依赖的系统调用符号才会暴露出来。因此单独查看C++产物时,无法看到这些来自newlib的符号。
非依赖nosys的解决思路
1. 排查隐式的标准库依赖
使用nm -u <你的C++可重链接文件>查看未定义符号,定位是否有代码触发了newlib函数的引用(如abort、printf等)。比如:
- C++代码中是否有
assert断言失败逻辑,会隐式调用abort; - 是否不小心使用了标准IO、信号相关的功能;
- 内存分配失败时,某些编译器会调用
abort。
2. 实现最小化系统调用桩
不需要依赖nosys,自己实现这些缺失的底层符号,适配裸机环境:
// syscall_stubs.c void _exit(int status) { while (1); // 裸机无操作系统,死循环即可 } int _kill(int pid, int sig) { return -1; // 不支持信号机制,返回错误 } int _getpid(void) { return 1; // 裸机环境固定进程ID为1 } ssize_t _write(int fd, const void *buf, size_t count) { // 如果需要串口输出,可在这里实现UART发送逻辑 // 不需要的话直接返回count表示成功 return count; } int _close(int fd) { return 0; // 空实现,返回成功 } int _fstat(int fd, void *buf) { return 0; // 空实现 } int _isatty(int fd) { return 1; // 默认所有fd都是终端(适配裸机场景) } off_t _lseek(int fd, off_t offset, int whence) { return offset; // 空实现,返回偏移量 } ssize_t _read(int fd, void *buf, size_t count) { return 0; // 无输入设备,返回0 }
将该文件编译为目标文件后,加入到项目链接列表中,即可替代nosys的作用。
3. 优化C++编译参数
追加-fno-builtin参数,禁止编译器生成内置标准库函数调用,减少不必要的依赖;同时确保C++代码完全脱离操作系统相关的标准库功能。
内容的提问来源于stack exchange,提问作者gstukelj
相关产品推荐
相关产品推荐

