重定向断言失败消息

时间:2018-06-18 18:15:58

标签: c gcc glibc libc weak-linking

我们有一个软件项目,其实时约束主要是用C ++编写的,但是它使用了一些在POSIX操作系统中运行的C库。为了满足实时约束,我们几乎将所有文本日志记录从stderr管道移到了共享内存环缓冲区。

我们现在遇到的问题是,当旧代码或C库调用assert时,消息最终会在stderr中,而不是在我们的环形缓冲区中与其余日志一起。我们想找到一种方法来重定向assert的输出。

我考虑过三种基本方法:

1。)制作我们自己的断言宏 - 基本上,不要使用#include <cassert>,为assert提供我们自己的定义。这可行,但是修补我们使用该调用assert的所有库以包含不同的标题将非常困难。

2。)补丁libc - 修改__assert_fail的libc实现。这样可行,但在实践中会非常尴尬,因为这意味着我们无法在不构建日志记录的情况下构建libc。我们可以这样做,以便在运行时,我们可以将函数指针传递给libc,这是&#34;断言处理程序&#34; - 这是我们可以考虑的事情。问题是,是否存在比此更简单/更少侵入性的解决方案。

3.。)修补libc标题,以便__assert_fail标有__attribute__((weak))。这意味着我们可以在链接时使用自定义实现覆盖它,但如果我们的自定义实现没有链接,那么我们链接到常规libc实现。实际上我希望这个函数已经标有__attribute__((weak)),我很惊讶地发现它显然不是。

我的主要问题是:选项(3)的可能缺点是什么 - 修补libc以便这一行:https://github.com/lattera/glibc/blob/master/assert/assert.h#L67

extern void __assert_fail (const char *__assertion, const char *__file,
               unsigned int __line, const char *__function)
__THROW __attribute__ ((__noreturn__));

也标有__attribute__((weak))

  1. 我有没有理由认为维护人员还没有这样做?
  2. 当我以这种方式修补标头后,当前连接并成功运行libc的任何现有程序如何中断?它不会发生,对吧?
  3. 由于某些原因,在这里使用弱链接符号是否会产生大量的运行时间成本? libc已经是我们的共享库了,我认为动态链接的成本应该淹没系统在加载时必须做的弱分辨率和强分辨率的任何案例分析?
  4. 这里有一种更简单/更优雅的方法吗?
  5. glibc中的某些功能,尤其是strtodmalloc,都标有一个特殊的gcc属性__attribute__((weak))。这是一个链接器指令 - 它告诉gcc这些符号应该标记为&#34;弱符号&#34;,这意味着如果在链接时找到符号的两个版本,那么&#34; strong&#34 ;一个被选中而不是弱者。

    维基百科上描述了这一动机:

      

    Use cases

         

    弱符号可用作提供函数的默认实现的机制,可在链接时由更专业(例如优化)的函数替换。然后将默认实现声明为weak,并且在某些目标上,具有强声明符号的目标文件将添加到链接器命令行。

         

    如果某个库将符号定义为弱符号,则链接该库的程序可以自由地提供强大的符号,例如,用于自定义目的。

         

    弱符号的另一个用例是维护二进制向后兼容性。

    然而,在glibc和musl libc中,在我看来__assert_fail函数(assert.h宏转发)未被标记为弱符号。

    https://github.com/lattera/glibc/blob/master/assert/assert.h

    https://github.com/lattera/glibc/blob/master/assert/assert.c

    https://github.com/cloudius-systems/musl/blob/master/include/assert.h

2 个答案:

答案 0 :(得分:3)

glibc的符号attribute((weak))上不需要__assert_fail。只需在程序中编写自己的__assert_fail实现,链接器使用您的实现,example

#include <stdio.h>
#include <assert.h>

void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function)
{
    fprintf(stderr, "My custom message\n");
    abort();
}


int main()
{
    assert(0);
    printf("Hello World");

    return 0;
}

这是因为当链接器解析符号时,程序已经定义了__assert_fail符号,因此链接器不应该选择libc定义的符号。

如果你真的需要将__assert_fail定义为libc中的弱符号,为什么不只是objcopy --weaken-symbol=__assert_fail /lib/libc.so /lib/libc_with_weak_assert_fail.so。我不认为你需要从源代码重建libc。

答案 1 :(得分:1)

如果我是你,我可能会选择打开一个管道(2)和fdopen(2)'stderr来取得该管道的写入端。我将管道的读端作为主poll(2)循环(或系统中的等效物)的一部分进行服务,并将内容写入环形缓冲区。

这显然处理实际输出的速度较慢,但​​是从你的记录开始,这样的输出很少,所以影响应该是可以忽略的(特别是如果你已经有一个民意调查或选择这个fd可以搭载)。

在我看来,调整libc或依赖工具的副作用可能会在未来中断,并且调试会很麻烦。如果可能的话,我会选择保证安全机制并支付性能价格。