重定向断言失败消息 我们有一个软件项目,它的实时约束主要是用C++编写的,但是利用了许多C库,运行在POSIX操作系统中。为了满足实时限制,我们将几乎所有的文本从stderr管道注销,并转移到共享内存环缓冲区中
我们现在遇到的问题是,当旧代码或C库调用重定向断言失败消息 我们有一个软件项目,它的实时约束主要是用C++编写的,但是利用了许多C库,运行在POSIX操作系统中。为了满足实时限制,我们将几乎所有的文本从stderr管道注销,并转移到共享内存环缓冲区中,c,gcc,glibc,libc,weak-linking,C,Gcc,Glibc,Libc,Weak Linking,我们现在遇到的问题是,当旧代码或C库调用assert时,消息最终会出现在stderr中,而不是与其余日志一起出现在环形缓冲区中。我们想找到一种方法来重定向assert的输出 我在这里考虑了三种基本方法: 1.)制作我们自己的断言宏——基本上,不要使用#include,为断言给出我们自己的定义。这会起作用,但要修补我们正在使用的所有库(调用assert以包含不同的头),这将非常困难 2.)补丁libc——修改\uu assert\u fail的libc实现。这是可行的,但在实践中会非常尴尬,因为这
assert
时,消息最终会出现在stderr
中,而不是与其余日志一起出现在环形缓冲区中。我们想找到一种方法来重定向assert
的输出
我在这里考虑了三种基本方法:
1.)制作我们自己的断言宏——基本上,不要使用#include
,为断言给出我们自己的定义。这会起作用,但要修补我们正在使用的所有库(调用assert
以包含不同的头),这将非常困难
2.)补丁libc——修改\uu assert\u fail
的libc实现。这是可行的,但在实践中会非常尴尬,因为这意味着如果不构建日志基础设施,我们就无法构建libc。在运行时,我们可以将函数指针传递给<代码> LBC >,即“断言处理程序”——这是我们可以考虑的事情。问题是是否有比这更简单/更少干扰的解决方案
3.)修补libc头,使\uu断言\u失败
标记为\uu属性((弱))
。这意味着我们可以在链接时用自定义实现覆盖它,但是如果我们的自定义实现没有链接进来,那么我们就链接到常规的libc实现。实际上,我希望这个函数已经被标记为\uuuuu属性(弱))
,我惊讶地发现它显然不是
我的主要问题是:选项(3)可能的缺点是什么——修补libc以使这一行:
是否也用\uuuuuu属性((弱))
标记
我没有想到维护人员没有这样做,有什么好的理由吗
在我以这种方式修补头文件后,当前正在链接并成功运行libc的任何现有程序如何中断?这不可能发生,对吧
出于某种原因,在这里使用弱链接符号是否会带来巨大的运行时成本libc
对于我们来说已经是一个共享库,我认为动态链接的成本应该会淹没任何关于系统在加载时必须执行的弱分辨率与强分辨率的案例分析
这里有没有一个我没有想到的更简单/更优雅的方法
glibc中的一些函数,特别是strtod和malloc中的一些函数,用一个特殊的gcc属性\uuuuu属性((弱))
标记。这是一个链接器指令——它告诉gcc这些符号应该被标记为“弱符号”,这意味着如果在链接时发现两个版本的符号,则选择“强”符号而不是弱符号
维基百科上描述了其动机:
弱符号可用作一种机制,以提供函数的默认实现,这些函数可以在链接时被更专业(如优化)的函数替换。然后将默认实现声明为弱实现,并且在某些目标上,将具有强声明符号的对象文件添加到链接器命令行
如果某个库将某个符号定义为弱符号,则链接该库的程序可以自由地提供强符号,例如,用于自定义目的
弱符号的另一个用例是二进制向后兼容性的维护
但是,在glibc和musl libc中,我认为\uuu assert\u fail
函数(向其发出assert.h
宏)未标记为弱符号
您不需要glibc提供的符号\uu assert\u fail
上的属性((弱))
。只需在程序中编写自己的uuu assert_ufail实现,链接器应使用您的实现,用于:
#包括
#包括
无效断言失败(常量字符*断言,常量字符*文件,无符号整数行,常量字符*函数)
{
fprintf(stderr,“我的自定义消息”);
中止();
}
int main()
{
断言(0);
printf(“你好世界”);
返回0;
}
这是因为当链接器解析符号时,\uu assert\u fail
符号已经由程序定义,因此链接器不应该选择libc定义的符号
如果您确实需要在libc中将uuu assert_u定义为弱符号,为什么不将
objcopy--弱化符号=uu assert_ufail/lib/libc.so/lib/libc_u与u-weak_assert_fail.so。我认为您不需要为此从源代码重建libc。如果我是您,我可能会选择打开一个管道(2)和fdopen(2)ing stderr以获取该管道的写入端。我会将管道的读取端作为主轮询(2)循环的一部分(或系统中的任何等效循环)提供服务,并将内容写入环形缓冲区
这显然比处理实际输出要慢,但从你的记录来看,这样的输出很少,所以影响应该是可以忽略的(特别是如果你已经有了一个民意调查或选择了这个fd,你就可以利用它)
在我看来,调整libc或依赖这些工具的副作用可能会在将来崩溃,这将是调试的痛苦。如果可能的话,我会选择有保证的安全机制,并支付性能价格。选项3的一个缺点可能是您正在编辑一个标准头文件,这意味着在您所创建的软件的整个生命周期内,这将是一个维护/程序方面的难题,直到它被冻结到最终的可执行文件中。@Ryker是的,通常这甚至不是大多数项目的选项,但因为我们构建了
extern void __assert_fail (const char *__assertion, const char *__file,
unsigned int __line, const char *__function)
__THROW __attribute__ ((__noreturn__));
#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;
}