I am currently writing a dynamic linker for my libc project. In the course of doing so, I have the problem that I kind of want two different error handling policies to apply to certain functions within the linker, depending on whether they are running during the initial linking of the application or later at runtime:
If the code is running during the initial link, I want to print the error message to stderr, note that there has been an error, and then resume operation at a sensible place. For example, if one symbol could not be found (and it was needed for a relocation), I want to continue with the next relocation. But then, once everything is done, I just exit if there has been any error.
But on the other hand, if we are inside of dlopen() or dlsym(), I want to abort everything at the first error, write the error message into a string buffer to be returned in dlerror().
My programming language is C, so exception handling is not an option. It wouldn't help anyway, since what I am looking for here is resume semantics, and C++ exceptions don't have those.
The reason I want those is because I think there is value in providing as many errors as possible during the initial link, rather than letting the user waste their time fixing one issue, when an insurmountable hurdle is lurking a little bit further away. Imagine a program not finding two library files, and one the user knows how to provide, and the other they don't.
As far as I can tell, my options are:
- Using longjmp(). That's what musl and glibc are doing. I personally really don't like longjmp(), because it is the only standard C function with non-local control flow. Its mere presence makes understanding a codebase harder. So I would really rather not.
- Duplicating the code. Have a version of the code that continues on error, and another version that aborts. That avoids the issue of overgeneralization, but it comes at the cost of writing significant pieces of code (in particular, the library loader and the relocation processor) twice.
- Passing the policy as a parameter. That is probably the least stinky option, but it does make my functions more complicated than they have to be. And they already are quite complicated.
- As stated before, musl and glibc use longjmp() to jump out of the error handling function back to the runtime function that was called, and then attempt to clean up any state that was left unclean. glibc dresses it up in something that looks like an exception if you squint a little, but there's no hiding the red flag that is longjmp().
- dietlibc and mlibc both always bail on first error, so there's no difference in control flow between load time and runtime. The error message goes into a buffer, and the caller then decides whether to print it to stderr or to a buffer.
- newlib doesn't have a dynlinker, it just leaves everything to Windows.

