9

We have a third-party library that was written without multithreading or exception handling in mind. Our main executable is multithreaded and uses exceptions.

The third-party library uses exit() to abort the program for serious problems (like "driver not initialized" or "file not found"). Calling exit() in a multithreaded application is not allowed, as it does not shut down threads correctly. In addition, I really don't want to ever exit the main application, as it is a server application, and in many cases, there are proactive things that the main program can do to recover from the error.

I would like to essentially replace the system provided exit(int status) function with my own function, ie

class exit_exception : public runtime_error 
{
    public: exit_exception(int status) 
      : runtime_error("exit called with status " + to_string(status)) {}      
};

extern "C" void exit(int status) {
    throw  exit_exception(status);
}

and catch the exception in my code. It seems to work, but this is obviously a hack and not the way nature intended exit() to be used. What am I doing wrong without knowing?

edit

Many have suggested I put this in a separate process, but that would defeat many things. The third-party library does very high speed data transfer that needs to be in the main application process because it lives in the same virtual memory space and does not use malloc to allocate memory from the FPGA coprocessor that is controller. This code is close to the "iron" and is squeezing every bit of bandwidth out of the memory and PCIe busses.

edit 2

My program can still return status codes to the OS with the return value from int main(), which does not ultimately call exit(). Otherwise I would be in real trouble.

Community
  • 1
  • 1
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
  • The trouble with that is that a) your replacement has surprising behavior and b) the third-party library may have `exit` or its behavior baked in in myriad ways (not only by the compiler, but also by the programmer). – Deduplicator Jul 31 '14 at 21:29
  • 9
    If the library was written without multithreading in mind, it seems doubtful that overriding `exit()` would be sufficient to make it well-behaved in a multithreaded environment. – NPE Jul 31 '14 at 21:33
  • 1
    Also, since return from main is equivalent to calling `exit`, it is quite possible that the implementation literally calls `exit` upon returning from main. Which would break with the `exit` replacement. – celtschk Jul 31 '14 at 21:34
  • @celtschk - I thought the same thing, but my code exits normally. This code only has to run on this unique hardware, so portability is not an issue. – Mark Lakata Jul 31 '14 at 21:40
  • 1
    I understand that overriding exit() is not sufficient to make it well-behaved, but it is better than nothing. This is the real world. I can't change third-party library. – Mark Lakata Jul 31 '14 at 21:41
  • 2
    You might get some of the functionality you are seeking by using `atexit()`. I have not used that sufficiently to know if you can ultimately escape from exiting, but at least you can monitor an orderly shutdown and even trigger a relaunch. Not a perfect solution, but might be useful. – Logicrat Jul 31 '14 at 21:42
  • @Logicrat - I thought of that, but I did not see a way of preventing `exit()` from happening. – Mark Lakata Jul 31 '14 at 21:44
  • 7
    The only good solution is isolating that 3rd party lib in its own process. – Deduplicator Jul 31 '14 at 21:47
  • I came here to make the same point as @Deduplicator. Use a different process. Isolate your own process from the bad code. (There, now it's been suggested twice.) – asveikau Jul 31 '14 at 21:48
  • Dynamically take the address of `exit`. Overwrite that address with machine language instructions to pop the stack and return or jump to the address of your choice. I feel that the Good Practice Police will hunt me down for making such a suggestion, but it's a thing I would consider after all rational alternatives had been exhausted. – Logicrat Jul 31 '14 at 22:06
  • @Logicrat - that's basically what I did. I changed the address of exit and threw an exception which will unwind the stack. – Mark Lakata Jul 31 '14 at 22:09
  • 1
    @MarkLakata Nice. That's meatball programming at its best. Sometimes nothing else will do. – Logicrat Jul 31 '14 at 22:10
  • 3
    @MarkLakata: Shared memory should take care of letting the hardware transfer directly into the main application's memory space, while still preventing the library from damaging anything else in the main application. Your assumption that a separate process will ruin performance has led you to an XY problem. – Ben Voigt Jul 31 '14 at 22:27
  • @BenVoigt - shared memory is most likely not an option. The third party library maps memory from an FPGA coprocessor to the virtual memory of the host process. The memory is not allocated using malloc. Using `shm_open` and `mmap` only gives a hint to the position of the virtual memory, and I really doubt it will work out of the box. It might though, and I understand your point. – Mark Lakata Jul 31 '14 at 23:27
  • 1
    @Mark: In that case perhaps you can cause the data area to be mapped into the main application also (doesn't need to be at the same address) which will cause sharing without actually having an shm pseudo-file, while keeping mapping for control areas and all the library state isolated to the sandbox process. – Ben Voigt Jul 31 '14 at 23:34
  • 1
    @Logicrat - Meatball programming... I just learned a new term. :) – Mark Lakata Jul 31 '14 at 23:35
  • 2
    @Mark: In particular, I think starting the sandbox process using `clone(CLONE_VM)` might be helpful. – Ben Voigt Jul 31 '14 at 23:47
  • @BenVoigt - thanks for the tip regarding CLONE_VM. I think in the current situation, I will use the thrown exception as it works better than just calling `exit()` and that's all I need now, but if I were to start over again, I would considering `clone(CLONE_VM)`. – Mark Lakata Aug 01 '14 at 18:03
  • 1
    Replacing `exit()` with a version that throws exceptions is not a good idea. For example this could cause the library to leak resources. If the program were to exit such leaks are inconsequential under a typical operating system. However, in a long running server process that continues to run after 'recovering' from the error it is likely to cause reliability problems that are much harder to diagnose and correct. By pursuing this path you're going to incur significant technical debt which will come back to haunt you unless the project gets thrown away. – bames53 May 18 '15 at 21:46
  • In fact it seems to me that replacing `exit()` with a version that throws exceptions is nearly as bad a mistake as designing a library that uses `exit()` to 'handle' errors. – bames53 May 18 '15 at 21:48
  • The original code works, but there's a bug with gcc. On x64 before 4.6, -fexceptions or -funwind-tables needs to be added when building any C code so that the stack unwinding can happen. You can see the [details here](https://bugzilla.redhat.com/show_bug.cgi?id=1222110#c5). – Dave Johansen Mar 27 '16 at 21:52
  • @MarkLakata any idea how to do this in Visual C++ – rboy Mar 11 '23 at 16:50

3 Answers3

1

This is just an idea, but you could use a similar approach as i did when i needed to wrap memcpy to use some different version, take a look at my answer here.

So you could build a replacement for the exit() function that does nothing, or do some cleanup. It's just an idea and i have not tried it, but it could help you to solve your problem.

Community
  • 1
  • 1
Ortwin Angermeier
  • 5,957
  • 2
  • 34
  • 34
  • This looks interesting and I wanted to try it (but I didn't know how to do it, so this might be useful for future use), but what I have right now works and looks cleaner. I'm more concerned about what might be bad about my approach. – Mark Lakata Jul 31 '14 at 21:51
1

The biggest and most obvious risk is resource leakage. If the library thinks its error handling strategy is to dive out of the nearest window there's alway a risk that throwing that exception isn't going to result in well organised release of memory and system resources.

However I notice you mention it doesn't allocate memory with malloc() if that means you have to provide it with all its resources as buffers and the like then maybe by some miraculous accident it is safely unwindable!

If that fails all I can suggest is contact the supplier and persuade them that they should join the rest of us in 21st programming paradigms.

PS: Throwing an exception out of an atexit() handler causes termination of C++ programs. So adding a throwing handler is not an option. It's also not an option if some other library inserts an 'atexit()' handler after yours.

Persixty
  • 8,165
  • 2
  • 13
  • 35
0

You can try leaving atexit() handler with longjmp. Though the behavior is undefined :) The other way is to run lib in a separate process, bridged with any IPC. Harder, tedious, but safer. Or, you can try to scan binary image and hook any exit() invocations. I know about MS detours and mhook on windows. Though I know none on linux, unfortunately.

SevenBits
  • 2,836
  • 1
  • 20
  • 33
Target-san
  • 451
  • 3
  • 11
  • Is this better than my exception method? I'm all for making things better, but this sounds even more hacky and risky. – Mark Lakata Jul 31 '14 at 22:07
  • 1
    @MarkLakata: `longjmp` and throwing an exception are pretty much equivalent. The advantage of this answer is that `atexit()` bit -- you don't have to mess with dynamically modifying code, and you can return to the original behavior of `exit` by letting the handler conditionally return normally. – Ben Voigt Jul 31 '14 at 22:25