Robust C++: Safety Net by Greg Utas
Greg Utas - 27/May/2020
Greg Utas - 27/May/2020
[SHOWTOGROUPS=4,20]
Keeping a program running when it would otherwise abort.
This article presents a Thread class that prevents exceptions (such as those caused by bad pointers) from forcing a program to exit. It also describes how to capture information that facilitates debugging when such errors occur in software already released to users.
Introduction
Some programs need to keep running even after nasty things happen, such as using an invalid pointer. Servers, other multi-user systems, and real-time games are a few examples. This article describes how to write robust C++ software that does not exit when the usual behavior is to abort. It also discusses how to capture information that facilitates debugging when nasty things occur in software that has been released to users.
sources: https://dumpz.ws/resources/robust-c-safety-net-by-greg-utas.115/
Background
It is assumed that the reader is familiar with C++ exceptions. However, exceptions are not the only thing that robust software needs to deal with. It must also handle Для просмотра ссылки Войдиили Зарегистрируйся, which the operating system raises when something nasty occurs. The header Для просмотра ссылки Войди или Зарегистрируйся defines the following subset of POSIX signals for C/C++:
Using the Code
The code in this article is taken from the Для просмотра ссылки Войдиили Зарегистрируйся (RSC), a large repository that, among other things, provides a framework for developing robust C++ applications. It contains over 225K lines of software organized into static libraries, each in its own namespace. All the code excerpted in this article comes from the namespace NodeBase in the Для просмотра ссылки Войди или Зарегистрируйся directory. NodeBase contains about 55K lines of code that provide base classes for things such as:
An application developed using RSC derives from Thread to implement its threads. Everything described in this article then comes for free—unless the application isn't targeted for Windows, in which case that abstraction layer also has to be implemented.
If you don't want to use RSC, you can copy and modify its source code to meet your needs, subject to the terms of its GPL-3.0 license.
Overview of the Classes
RSC contains many details that are not relevant to this article, so the code that we look at will be excerpted from the relevant classes and functions, but with irrelevant details removed. Many of these Для просмотра ссылки Войдиили Зарегистрируйся are nonetheless important and need to be considered if your approach is to copy and modify RSC software.
We will start by outlining the classes that appear in this article. In most cases, RSC defines each class in a .h of the same name and implements it in a .cpp of the same name. You should therefore be able to easily find the full version of each class in the Для просмотра ссылки Войдиили Зарегистрируйся directory.
Thread
Software that wants to be continuously available must catch all exceptions. A single-threaded application could do this in main. But RSC supports multi-threading, so it does this in a base Thread class from which all other threads derive. Thread has a loop that invokes the application in a try clause that is followed by a series of catch clauses which handle any exception not caught by the application.
SysThread
This is a wrapper for a native thread and is created by Thread's constructor. Much of the implementation is platform-specific.
Daemon
When a Thread is created, it can register a Daemon to recreate the thread after it is forced to exit, which usually occurs when the thread has caused too many exceptions.
Exception
The direct use of <exception> is inappropriate in a system that needs to debug problems in released software. Consequently, RSC defines a virtual Exception class from which all of its exceptions derive. This class's primary responsibility is to capture the running thread's stack when an exception occurs. In this way, the entire chain of function calls that led to the exception will be available to assist in debugging. This is far more useful than the C string returned by std::exception::what, stating something like "invalid string position", which specifies the problem but not where it arose and maybe not even uniquely where it was detected.
SysThreadStack
SysThreadStack is actually a namespace that wraps a handful of functions. The function of most interest is one that actually captures a thread's stack. Exception's constructor invokes this function, and so does a function (Debug::SwLog) whose purpose is to generate a debug log to record a problem that, although unexpected, did not actually result in an exception. All SysThreadStack functions are platform-specific.
SignalException
When a POSIX signal occurs, RSC throws it in a C++ exception so that it can be handled in the usual way, by unwinding the stack and deleting local objects. SignalException, derived from Exception, is used for this purpose. It simply records the signal that occurred and relies on its base class to capture the stack.
PosixSignal
Each signal supported within RSC must create a PosixSignal instance that includes its name (e.g. "SIGSEGV"), numeric value (11), explanation ("Invalid Memory Reference"), and other attributes. The PosixSignal instances for various signals defined by the POSIX standard, including those in <csignal>, are implemented as private members of the simple class SysSignals. The subset of signals supported on the target platform are then instantiated by SysSignals::CreateNativeSignals.
Throwing a SignalException turns out to be a useful way to recover from serious errors. RSC therefore defines signals for internal use in NbSignals.h. An instance of PosixSignal is also associated with each of these:
[/SHOWTOGROUPS]
Keeping a program running when it would otherwise abort.
This article presents a Thread class that prevents exceptions (such as those caused by bad pointers) from forcing a program to exit. It also describes how to capture information that facilitates debugging when such errors occur in software already released to users.
Introduction
Some programs need to keep running even after nasty things happen, such as using an invalid pointer. Servers, other multi-user systems, and real-time games are a few examples. This article describes how to write robust C++ software that does not exit when the usual behavior is to abort. It also discusses how to capture information that facilitates debugging when nasty things occur in software that has been released to users.
sources: https://dumpz.ws/resources/robust-c-safety-net-by-greg-utas.115/
Background
It is assumed that the reader is familiar with C++ exceptions. However, exceptions are not the only thing that robust software needs to deal with. It must also handle Для просмотра ссылки Войди
- SIGINT: interrupt (usually when Ctrl-C is entered)
- SIGILL: illegal instruction (perhaps a stack corruption that affected the instruction pointer)
- SIGFPE: floating point exception (includes dividing by zero)
- Для просмотра ссылки Войди
или Зарегистрируйся: segment violation (using a bad pointer) - SIGTERM: forced termination (usually when the kill command is entered)
- SIGBREAK: break (usually when Ctrl-Break is entered)Для просмотра ссылки Войди
или Зарегистрируйся - SIGABRT: abnormal termination (when abort is invoked by the C++ run-time environment)
Using the Code
The code in this article is taken from the Для просмотра ссылки Войди
- system initialization/reinitialization
- configuration parameters
- multi-threading
- object pooling
- CLI commands
- logging
- debugging tools
An application developed using RSC derives from Thread to implement its threads. Everything described in this article then comes for free—unless the application isn't targeted for Windows, in which case that abstraction layer also has to be implemented.
If you don't want to use RSC, you can copy and modify its source code to meet your needs, subject to the terms of its GPL-3.0 license.
Overview of the Classes
RSC contains many details that are not relevant to this article, so the code that we look at will be excerpted from the relevant classes and functions, but with irrelevant details removed. Many of these Для просмотра ссылки Войди
We will start by outlining the classes that appear in this article. In most cases, RSC defines each class in a .h of the same name and implements it in a .cpp of the same name. You should therefore be able to easily find the full version of each class in the Для просмотра ссылки Войди
Thread
Software that wants to be continuously available must catch all exceptions. A single-threaded application could do this in main. But RSC supports multi-threading, so it does this in a base Thread class from which all other threads derive. Thread has a loop that invokes the application in a try clause that is followed by a series of catch clauses which handle any exception not caught by the application.
SysThread
This is a wrapper for a native thread and is created by Thread's constructor. Much of the implementation is platform-specific.
Daemon
When a Thread is created, it can register a Daemon to recreate the thread after it is forced to exit, which usually occurs when the thread has caused too many exceptions.
Exception
The direct use of <exception> is inappropriate in a system that needs to debug problems in released software. Consequently, RSC defines a virtual Exception class from which all of its exceptions derive. This class's primary responsibility is to capture the running thread's stack when an exception occurs. In this way, the entire chain of function calls that led to the exception will be available to assist in debugging. This is far more useful than the C string returned by std::exception::what, stating something like "invalid string position", which specifies the problem but not where it arose and maybe not even uniquely where it was detected.
SysThreadStack
SysThreadStack is actually a namespace that wraps a handful of functions. The function of most interest is one that actually captures a thread's stack. Exception's constructor invokes this function, and so does a function (Debug::SwLog) whose purpose is to generate a debug log to record a problem that, although unexpected, did not actually result in an exception. All SysThreadStack functions are platform-specific.
SignalException
When a POSIX signal occurs, RSC throws it in a C++ exception so that it can be handled in the usual way, by unwinding the stack and deleting local objects. SignalException, derived from Exception, is used for this purpose. It simply records the signal that occurred and relies on its base class to capture the stack.
PosixSignal
Each signal supported within RSC must create a PosixSignal instance that includes its name (e.g. "SIGSEGV"), numeric value (11), explanation ("Invalid Memory Reference"), and other attributes. The PosixSignal instances for various signals defined by the POSIX standard, including those in <csignal>, are implemented as private members of the simple class SysSignals. The subset of signals supported on the target platform are then instantiated by SysSignals::CreateNativeSignals.
Throwing a SignalException turns out to be a useful way to recover from serious errors. RSC therefore defines signals for internal use in NbSignals.h. An instance of PosixSignal is also associated with each of these:
Код:
// The following signals are proprietary and are used to throw a
// SignalException outside the signal handler.
//
constexpr signal_t SIGNIL = 0; // nil signal (non-error)
constexpr signal_t SIGWRITE = 121; // write to protected memory
constexpr signal_t SIGCLOSE = 122; // exit thread (non-error)
constexpr signal_t SIGYIELD = 123; // ran unpreemptably too long
constexpr signal_t SIGSTACK1 = 124; // stack overflow: attempt recovery
constexpr signal_t SIGSTACK2 = 125; // stack overflow: exit thread
constexpr signal_t SIGPURGE = 126; // thread killed or suicided
constexpr signal_t SIGDELETED = 127; // thread unexpectedly deleted
Последнее редактирование: