I am running into a weird issue at work where after updating from RHEL 7 (linux kernel 3.10.0, GCC 4.8.5) to RHEL 8 (linux kernel 4.18.0, GCC 8.3.1), our enums have started to cause problems while destructing. From my best diagnosis in gdb, it is trying to call the destructor on the same static object more than once (once for each lib that instantiates the enums and is used to build the executable in question) and segfaulting on the second attempt, as the object has already been destroyed.
Here is the backtrace:
#0 0x0000000000000000 in ?? ()
#1 0x00007ffff3c91b6f in __tcf_2 () at /sourcepath/ExampleEnum.H:106
#2 0x00007ffff68ae3c7 in __cxa_finalize () from /lib64/libc.so.6
#3 0x00007ffff3c33c87 in __do_global_dtors_aux () from /libpath/lib64/libsecond_lib.so
#4 0x00007fffffff9c10 in ?? ()
#5 0x00007ffff7de42a6 in _dl_fini () from /lib64/ld-linux-x86-64.so.2
This is the second time it reaches that line of ExampleEnum.H in __tcf_2, a function related to static destruction. The first time is no problem.
Here is the structure of the enums:
#ifndef _EXAMPLEENUM_H
#define _EXAMPLEENUM_H
#include "OurString.H"
#define EXAMPLEENUM_SOURCE_LIST(enum) \
enum(THIS_EXAMPLE_ENUM, "THIS_EXAMPLE", "", false),\
enum(ExampleEnumMax, "ExampleEnumMax", "error", false)
#define NAME_GENERATOR(name, guiname, description, p4) name
#define GUI_NAME_STR_GENERATOR(name, guiname, description, p4) guiname
class Example {
public:
enum Enum {
EXAMPLEENUM_SOURCE_LIST(NAME_GENERATOR)
};
static const int NUM_FIELDS = ExampleEnumMax + 1;
static const char* names[NUM_FIELDS];
};
typedef Example::Enum ExampleEnum
extern const OurString ExampleEnum_GuiName[Example::ExampleEnumMax + 1];
#ifdef CONSTRUCT_ENUM_STRINGS
const OurString ExampleEnum_GuiName[Example::ExampleEnumMax + 1] = {
EXAMPLEENUM_SOURCE_LIST(GUI_NAME_STR_GENERATOR)
};
#endif
#endif
And then in the libs where it is used, this names.C is compiled into the lib:
#define CONSTRUCT_ENUM_STRINGS 1
#include <enumpath/ExampleEnum.H>
#undef CONSTRUCT_ENUM_STRINGS
const char* Example::names[Example::NUM_FIELDS] = {
EXAMPLEENUM_SOURCE_LIST(GUI_NAME_STR_GENERATOR)
};
We have a band-aid solution that basically just covers up the problem, ie calling _exit(0)
at the end of main() skips all destructors, including static destructors which pose the problem so it doesn't segfault. However, obviously we want to fix the way our enums are handled such that we can run all necessary destructors (and no more than necessary) without segfaulting.
Is there anything obviously wrong with our enums? They have been working through several kernel/gcc versions and have only recently posed a problem.
Is there likely to be anything wrong with how they are used in the libs? This problem only occurs when an executable is compiled with multiple libs that use the same enum, which is unfortunately quite often. Is there some strict tree of import dependency structure we could keep to to fix this?
Why did it work up until we updated the OS?
EDIT:
Concerns about OurString's destructor have been raised, I didn't include it because it was trivial:
~OurString() throw () {}
ALSO: a little more debugging and going through a version compiled by GCC 4.8.5 that doesn't segfault shows me that __tcf_2
is entered twice there too, so my theory about improperly calling the destructor multiple times is wrong, and it looks like @PaulMcKenzie's theory of static initialization order is likely.
Thanks in advance!