eCos编译Synthethic Target程序时无法解析__sprintf_chk的解决办法

时间:2021-11-14 00:36:17


mingdu.zheng <at> gmail <dot> com

 

官方已解决此问题:

详见:http://hg-pub.ecoscentric.com/ecos/rev/a1df75458e13

问题描述:

在Xubuntu 12.04下编译eCos Synthetic Target的测试程序(通过eCos图形配置工具菜单Build >> Tests),当编译cxxsupp测试程序时出现未解析符号“__sprintf_chk”。
 

错误输出:

gcc -L/home/pangu/ecos/conf/synth_default_install/lib -Ttarget.ld -o /home/pangu/ecos/conf/synth_default_install/tests/infra/current/tests/cxxsupp tests/cxxsupp.o -g -nostdlib -Wl,-static -Wl,--fatal-warnings 
make[1]:正在离开目录 `/home/pangu/ecos/conf/synth_default_build/infra/current'
/usr/lib/gcc/i686-linux-gnu/4.6/libsupc++.a(cp-demangle.o): In function `.L741':
make:离开目录“/home/pangu/ecos/conf/synth_default_build”
(.text+0x4088): undefined reference to `__sprintf_chk'
/usr/lib/gcc/i686-linux-gnu/4.6/libsupc++.a(cp-demangle.o): In function `.L747':
(.text+0x45c3): undefined reference to `__sprintf_chk'
/usr/lib/gcc/i686-linux-gnu/4.6/libsupc++.a(cp-demangle.o): In function `.L747':
(.text+0x473b): undefined reference to `__sprintf_chk'
/usr/lib/gcc/i686-linux-gnu/4.6/libsupc++.a(cp-demangle.o): In function `.L747':
(.text+0x4833): undefined reference to `__sprintf_chk'
/usr/lib/gcc/i686-linux-gnu/4.6/libsupc++.a(cp-demangle.o): In function `d_print_mod_list':
(.text+0x62e9): undefined reference to `__sprintf_chk'
collect2: ld 返回 1
make[1]: *** [/home/pangu/ecos/conf/synth_default_install/tests/infra/current/tests/cxxsupp] 错误 1
make: *** [tests] 错误 2

 

解决办法:

在packages\hal\synth\arch\<version>\src\synth_entry.c文件中追加以下代码。

 

 

#include <stdarg.h>
#include <limits.h>
#include <stdio.h>


// __chk_fail -- terminate a function in case of buffer overflow
// copy from gcc-4.8.1/libssp/ssp.c
void __chk_fail (void)
{
CYG_FAIL("Buffer overflow detected, aborting");
diag_printf("Application error: buffer overflow detected.\n");
cyg_hal_sys_exit(1);
for(;;);
}


// __sprintf_chk -- convert formatted output, with stack checking
// copy from gcc-4.8.1/libssp/sprintf-chk.c
int __sprintf_chk (char *s, int flags __attribute__((unused)),
size_t slen, const char *format, ...)
{
va_list arg;
int done;


va_start (arg, format);
if (slen > (size_t) INT_MAX)
done = vsprintf (s, format, arg);
else
{
done = vsnprintf (s, slen, format, arg);
if (done >= 0 && (size_t) done >= slen)
__chk_fail ();
}
va_end (arg);
return done;
}

 

问题原因:

1、Xubuntu 12.04,以及其它最近发行的Linux版本默认开启了GCC的堆栈保护特性,因此sprintf函数调用将会被替换为__sprintf_chk函数调用,__sprintf_chk除了实现sprintf的功能外还将检查堆栈是否溢出。
2、new操作符的实现是由编译器提供的(libsupc++.a),编译eCos Synthetic Target时使用的是Linux下的本地编译器,因此libsupc++.a是在打开堆栈保护特性的情况下编译的,也就是说如果new操作符引用或者间接引用了sprintf函数,那么实际引用的将是__sprintf_chk。
3、new操作符在分配内存失败时需要抛出异常,异常机制经过一系列的函数调用,最终会调用cp-demangle.o文件内的__cxa_demangle函数逆向解析C++名称包装,例如将_Znwj解析为operator new(unsigned int),__cxa_demangle引用了sprintf进行字符串格式化,而在开启GCC堆栈保护特性的情况下,sprintf自动被替换为__sprintf_chk,在Xubuntu中,__sprintf_chk函数由glibc提供,而eCos Synthetic Target虽然编译成Linux下的一个进程,但是使用的仍然是eCos自带的C库,而不是Linux中的C库,而eCos中的C库并没有实现__sprintf_chk函数,因此在链接过程中产生无法解析的符号“__sprintf_chk”,解决办法是实现该函数。

 

 

 

深入解析:

编译器将new和delete操作符的相关代码打包到libsupc++.a静态库(new_op.o和del_op.o),因此使用了new和delete操作符的程序将引用该静态库内的相关代码,根据C++标准要求,new操作符在内存分配失败是将抛出异常,从底层实现角度就是将会调用__cxa_throw函数,而__cxa_throw函数引用了__cxxabiv1::__terminate_handler,这是一个函数指针,该函数指针指向__gnu_cxx::__verbose_terminate_handler函数,该函数引用了cp-demangle.o文件内的__cxa_demangle函数,__cxa_demangle函数引用了__sprintf_chk,使用nm工具输出.o文件符号可以非常清楚地看到函数间可能的调用关系,下面是nm工具的输出,已删除无关的符号,T字母标识的是该.o文件定义的符号,U字母标识的是该.o文件引用的符号。

 

 

 

new_op.o:
00000000 T _Znwj(operator new(unsigned int))
U __cxa_throw

eh_throw.o:
U _ZN10__cxxabiv119__terminate_handlerE(__cxxabiv1::__terminate_handler)
00000000 T __cxa_throw


eh_term_handler.o:
00000000 D _ZN10__cxxabiv119__terminate_handlerE
U _ZN9__gnu_cxx27__verbose_terminate_handlerEv(__gnu_cxx::__verbose_terminate_handler())


vterminate.o:
00000000 T _ZN9__gnu_cxx27__verbose_terminate_handlerEv
U __cxa_demangle


cp-demangle.o:
00006e20 T __cxa_demangle
U __sprintf_chk

具体的实现以及调用层次关系可以阅读libsupc++的源代码,位于gcc源代码的libstdc++-v3\libsupc++目录下。

 

补充说明:

cxxsupp是eCos用来测试C++支持的测试程序,其中使用了new和delete操作符,如果这个测试通不过意味着在应用中不能够使用new和delete操作符。默认情况下new和delete操作符的实现代码由编译器提供,即使是eCos也是如此,除非进行显式地重定义覆盖编译器的默认实现。
除了libsupc++.a之外,根据需要还将链接libgcc.a、libgcc_eh.a,这两个静态包同样是由编译器提供的。