2

We have a software project with real-time constraints largely written in C++ but making use of a number of C libraries, running in a POSIX operating system. To satisfy real-time constraints, we have moved almost all of our text logging off of stderr pipe and into shared memory ring buffers.

The problem we have now is that when old code, or a C library, calls assert, the message ends up in stderr and not in our ring buffers with the rest of the logs. We'd like to find a way to redirect the output of assert.

There are three basic approaches here that I have considered:

1.) Make our own assert macro -- basically, don't use #include <cassert>, give our own definition for assert. This would work but it would be prohibitively difficult to patch all of the libraries that we are using that call assert to include a different header.

2.) Patch libc -- modify the libc implementation of __assert_fail. This would work, but it would be really awkward in practice because this would mean that we can't build libc without building our logging infra. We could make it so that at run-time, we can pass a function pointer to libc that is the "assert handler" -- that's something that we could consider. The question is if there is a simpler / less intrusive solution than this.

3.) Patch libc header so that __assert_fail is marked with __attribute__((weak)). This means that we can override it at link-time with a custom implementation, but if our custom implementation isn't linked in, then we link to the regular libc implementation. Actually I was hoping that this function already would be marked with __attribute__((weak)) and I was surprised to find that it isn't apparently.

My main question is: What are the possible downsides of option (3) -- patching libc so that this line: 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__));

is marked with __attribute__((weak)) as well ?

  1. Is there a good reason I didn't think of that the maintainers didn't already do this?
  2. How could any existing program that is currently linking and running successfully against libc break after I patch the header in this way? It can't happen, right?
  3. Is there a significant run-time cost to using weak-linking symbols here for some reason? libc is already a shared library for us, and I would think the cost of dynamic linking should swamp any case analysis regarding weak vs. strong resolution that the system has to do at load time?
  4. Is there a simpler / more elegant approach here that I didn't think of?

Some functions in glibc, particularly, strtod and malloc, are marked with a special gcc attribute __attribute__((weak)). This is a linker directive -- it tells gcc that these symbols should be marked as "weak symbols", which means that if two versions of the symbol are found at link time, the "strong" one is chosen over the weak one.

The motivation for this is described on wikipedia:

Use cases

Weak symbols can be used as a mechanism to provide default implementations of functions that can be replaced by more specialized (e.g. optimized) ones at link-time. The default implementation is then declared as weak, and, on certain targets, object files with strongly declared symbols are added to the linker command line.

If a library defines a symbol as weak, a program that links that library is free to provide a strong one for, say, customization purposes.

Another use case for weak symbols is the maintenance of binary backward compatibility.

However, in both glibc and musl libc, it appears to me that the __assert_fail function (to which the assert.h macro forwards) is not marked as a weak symbol.

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

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • One downside of option 3 could be that you are editing a standard header file, which means it will be a maintenance/procedural headache for the duration of the life of the software you are creating, until it is frozen into its final executable. – ryyker Jun 18 '18 at 18:22
  • @ryyker yeah, normally this isn't even an option for most projects, but because we build our whole OS from source when we deploy, in our case we can consider patching libc. Just because we could in principle doesn't mean we should or that I want to though. – Chris Beck Jun 18 '18 at 18:25
  • In any case, if @Kamil's answer pans out, it appears you have your solution! – ryyker Jun 18 '18 at 18:27
  • for what I know, C uses the first definition found, and ignores any subsequent ones - this behavior should be enough for you to shadow any implementation. weak changes this, making latter definitions overwrite a weakly defined symbol. strong... forces overwrite of previously defined? not sure. better go read the docs... – Andreas Jun 18 '18 at 20:00

2 Answers2

4

You don't need attribute((weak)) on symbol __assert_fail from glibc. Just write your own implementation of __assert_fail in your program, and the linker should use your implementation, for 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;
}

That's because when resolving symbols by the linker the __assert_fail symbol will already be defined by your program, so the linker shouldn't pick the symbol defined by libc.

If you really need __assert_fail to be defined as a weak symbol inside libc, why not just objcopy --weaken-symbol=__assert_fail /lib/libc.so /lib/libc_with_weak_assert_fail.so. I don't think you need to rebuild libc from sources for that.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • For my understanding: If this is the case, then what's the point of marking any libc symbols with weak linking? Is it only to support people who are statically linking libc? – Chris Beck Jun 18 '18 at 18:35
  • 1
    I don't know. It is fun searching git blame tho. Most symbols were weak in [1996](https://github.com/lattera/glibc/commit/f65fd747b440ae2d8a7481ecc50e668c5e4d0cc9#diff-6db84199a16f98c62bd9dd3a696f6babR3336). Then in [2004](https://github.com/lattera/glibc/commit/eba19d2be7678d6ec4f448919d05a89c0e8294cc#diff-6db84199a16f98c62bd9dd3a696f6babL5442) most of the symbols in malloc.c were marked strong cause `Use strong_alias instead of weak_alias wherever possible` (dunno why it's not possible in some cases). Probably we would need to ask some libc developers. – KamilCuk Jun 18 '18 at 19:24
  • 2
    @kamil: iiuc, a symbol will first be searched inside the library its reference is found in, and then in the link arguments in order. If the symbol found is weak, the search continues in case a strong symbol is found. That allows a library to choose between protecting its internal symbols from external hijacking or allowing its symbols to be customization points. – rici Jun 18 '18 at 23:06
  • 1
    @ChrisBeck You may want to also redefine `__assert_perror_fail()`, which is called by `assert_perror()` macro. This isn't very common, but GLIBC itself does call it. – Employed Russian Jun 19 '18 at 03:21
1

If I were you, I would probably opt for opening a pipe(2) and fdopen(2)'ing stderr to take the write end of that pipe. I'd service the read end of the pipe as part of the main poll(2) loop (or whatever the equivalent is in your system) and write the contents to the ring buffer.

This is obviously slower to handle actual output, but from your write-up, such output is rare, so the impact ought to be negligable (especially if you already have a poll or select this fd can piggyback on).

It seems to me that tweaking libc or relying on side-effects of the tools might break in the future and will be a pain to debug. I'd go for the guaranteed-safe mechanism and pay the performance price if at all possible.

One Guy Hacking
  • 1,176
  • 11
  • 16