我们有一个具有实时约束的软件项目,主要用 C++ 编写,但使用了许多 C 库,在 POSIX 操作系统中运行。为了满足实时限制,我们已经将几乎所有的文本从stderr
管道中注销并移到共享内存环形缓冲区中。
我们现在遇到的问题是,当旧代码或 C 库调用assert
时,消息最终会stderr
与其余日志一起出现在我们的环形缓冲区中,而不是在我们的环形缓冲区中。我们想找到一种方法来重定向assert
.
这里我考虑了三种基本方法:
1.) 制作我们自己的断言宏——基本上,不要使用#include <cassert>
,给我们自己的定义assert
。这会起作用,但是要修补我们正在使用该调用的所有库assert
以包含不同的标头会非常困难。
2.) 修补 libc - 修改 .libc 的实现__assert_fail
。这会起作用,但在实践中会非常尴尬,因为这意味着我们无法在不构建日志基础设施的情况下构建 libc。我们可以这样做,以便在运行时,我们可以将函数指针传递给libc
“断言处理程序”——这是我们可以考虑的。问题是是否有比这更简单/侵入性更小的解决方案。
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))
?
- 是否有充分的理由我没有想到维护人员还没有这样做?
- 在我以这种方式修补标头后,当前正在链接并成功针对 libc 运行的任何现有程序如何中断?这不可能发生,对吧?
- 出于某种原因在此处使用弱链接符号是否会产生显着的运行时成本?
libc
对我们来说已经是一个共享库,我认为动态链接的成本应该超过系统在加载时必须执行的任何关于弱分辨率与强分辨率的案例分析? - 这里有我没有想到的更简单/更优雅的方法吗?
glibc 中的一些函数,特别是strtod
和malloc
,用特殊的 gcc 属性标记__attribute__((weak))
。这是一个链接器指令——它告诉 gcc 这些符号应该被标记为“弱符号”,这意味着如果在链接时找到符号的两个版本,则选择“强”版本而不是弱版本。
维基百科上描述了这样做的动机:
弱符号可以用作一种机制来提供函数的默认实现,这些函数可以在链接时被更专业的(例如优化的)函数替换。然后将默认实现声明为弱,并且在某些目标上,将具有强声明符号的目标文件添加到链接器命令行。
如果库将符号定义为弱符号,则链接该库的程序可以自由地提供强符号,例如用于定制目的。
弱符号的另一个用例是维护二进制向后兼容性。
但是,在 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