C++11: Compatiability with pthreads, static class function, and -pg flag - c++

I stumbled into a weird bug involving C++11, pthreads, and the -pg flag. It seems that my threads are getting stuck on a C++ library routine line mcount.c file when it invokes a static function in any of my classes.
Sleeping
Awakened
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7bc6148 in pthread_join (threadid=140737333020416, thread_return=0x7fffffffe4f8)
at pthread_join.c:89
89 pthread_join.c: No such file or directory.
(gdb) info threads
Id Target Id Frame
17 Thread 0x7fffef3cd700 (LWP 6152) "test.o" __mcount_internal (frompc=4198422, selfpc=4206354)
at mcount.c:72
16 Thread 0x7fffefbce700 (LWP 6151) "test.o" __mcount_internal (frompc=4211225, selfpc=4212043)
at mcount.c:72
15 Thread 0x7ffff03cf700 (LWP 6150) "test.o" __mcount_internal (frompc=4211225, selfpc=4212043)
at mcount.c:72
......
at mcount.c:72
3 Thread 0x7ffff63db700 (LWP 6138) "test.o" __mcount_internal (frompc=4206451, selfpc=4211201)
at mcount.c:72
2 Thread 0x7ffff6bdc700 (LWP 6136) "test.o" __mcount_internal (frompc=4206732, selfpc=4211201)
at mcount.c:72
* 1 Thread 0x7ffff7fd6740 (LWP 6135) "test.o" 0x00007ffff7bc6148 in pthread_join (
threadid=140737333020416, thread_return=0x7fffffffe4f8) at pthread_join.c:89
(gdb) thread 17
[Switching to thread 17 (Thread 0x7fffef3cd700 (LWP 6152))]
#0 __mcount_internal (frompc=4198422, selfpc=4206354) at mcount.c:72
72 mcount.c: No such file or directory.
(gdb) bt
#0 __mcount_internal (frompc=4198422, selfpc=4206354) at mcount.c:72
#1 0x00007ffff71d0b94 in mcount () at ../sysdeps/x86_64/_mcount.S:48
#2 0x00007ffff7ff7030 in ?? ()
#3 0x000000000000001a in ?? ()
#4 0x0000000008800191 in ?? ()
#5 0x000000000000001a in ?? ()
#6 0x00007ffff7ff7030 in ?? ()
#7 0x0000000000000005 in ?? ()
#8 0x0000000000000040 in ?? ()
#9 0x0000000000402f12 in Helper::remove (vec=0x8800191, pos=0, p=0x5) at Helpers.hpp:100
The threads should all exit after main thread prints "Awakened" but they dont, and when I interrupt the program, they are all in the mcount.c file. Which seems be called in between my call to Helper::remove and the initialization of the function variables in the Helper::Remove.
Indicated by
#9 0x0000000000402f12 in Helper::remove (vec=0x8800191, pos=0, p=0x5) at Helpers.hpp:100
which should hold the values (vec=0x7ffff7ff7030, pos=26, p=0x8800191), the last variable makes me wonder if I am some how overwriting the stack. (these values were retrieved from stack frame #10).
Line 100 in Helpers.hpp is simply the function declaration:
static bool remove(WFVector *vec, int pos, void *p){
Can anyone explain why the inclusion of the -pg flag causes threads to get stuck in the static function?
Code compiled with: g++-4.7 -DDEBUG=1 -g -pg -std=c++0x -mcx16 -m64 tester.cpp -o test.o -I /usr/include/boost -lpthread
and testing with GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04

gprof is known to not support multi-threaded applications. See if this workaround solves the problem. Most people simply use another profiling tool anyways.
I personally prefer Linux's built-in perf. Searching for "gprof threads" will give plenty of results from SO, with various suggestions for profiling tools.

Removing the -pg flag fixes the error. It took me a long time to figure it out and it was only dumb luck that I figured it out. So I am posting this bug in case anyone else runs into this issue.

Related

Why is ThreadSanitizer (TSAN) reporting data race on Glib::signal_idle().connect_once

Contents of hello.cpp
#include <gtkmm.h>
void RunInMain()
{
printf("RunInMain\n");
}
void ThreadFunc()
{
printf("ThreadFunc\n");
Glib::signal_idle().connect_once(std::bind(&RunInMain));
}
int main()
{
Gtk::Main kit(0, NULL);
Gtk::Window window;
window.set_title("hello world");
Glib::Thread* pThread = Glib::Thread::create(&ThreadFunc);
kit.run(window);
pThread->join();
return(0);
}
Compile with:
g++ `pkg-config gtkmm-2.4 --cflags --libs` hello.cpp -Wno-deprecated-declarations -fsanitize=thread
This is the error from TSAN when executing the resulting a.out file:
WARNING: ThreadSanitizer: data race (pid=153699)
Write of size 8 at 0x7b5000006f90 by thread T1:
#0 memset <null> (libtsan.so.0+0x37abf)
#1 g_slice_alloc0 <null> (libglib-2.0.so.0+0x71412)
#2 sigc::pointer_functor0<void>::operator()() const <null> (a.out+0x402835)
#3 sigc::adaptor_functor<sigc::pointer_functor0<void> >::operator()() const <null> (a.out+0x402606)
#4 sigc::internal::slot_call0<void (*)(), void>::call_it(sigc::internal::slot_rep*) <null> (a.out+0x4021d0)
#5 call_thread_entry_slot /usr/include/sigc++-2.0/sigc++/functors/slot.h:535 (libglibmm-2.4.so.1+0x5d889)
Previous write of size 8 at 0x7b5000006f90 by main thread:
#0 posix_memalign <null> (libtsan.so.0+0x3061d)
#1 allocator_memalign ../glib/gslice.c:1411 (libglib-2.0.so.0+0x706b8)
#2 allocator_add_slab ../glib/gslice.c:1283 (libglib-2.0.so.0+0x706b8)
#3 slab_allocator_alloc_chunk ../glib/gslice.c:1329 (libglib-2.0.so.0+0x706b8)
#4 __libc_start_main ../csu/libc-start.c:308 (libc.so.6+0x27041)
Location is heap block of size 496 at 0x7b5000006e00 allocated by main thread:
#0 posix_memalign <null> (libtsan.so.0+0x3061d)
#1 allocator_memalign ../glib/gslice.c:1411 (libglib-2.0.so.0+0x706b8)
#2 allocator_add_slab ../glib/gslice.c:1283 (libglib-2.0.so.0+0x706b8)
#3 slab_allocator_alloc_chunk ../glib/gslice.c:1329 (libglib-2.0.so.0+0x706b8)
#4 __libc_start_main ../csu/libc-start.c:308 (libc.so.6+0x27041)
Thread T1 (tid=153701, running) created by main thread at:
#0 pthread_create <null> (libtsan.so.0+0x5ec29)
#1 g_system_thread_new ../glib/gthread-posix.c:1308 (libglib-2.0.so.0+0xa0ea0)
#2 __libc_start_main ../csu/libc-start.c:308 (libc.so.6+0x27041)
SUMMARY: ThreadSanitizer: data race (/lib64/libtsan.so.0+0x37abf) in memset
The code runs as expected (I get all of the prints) but I don't understand why I'm getting the TSAN data race warning. If I comment out the Glib::signal_idle().connect_once line, there is no TSAN error. From what I've read, that function is supposed to be safe to call from any thread. Is TSAN reporting a false positive here or is there a real data race?
Fedora 31 linux
g++ 10.0.1
glibmm24-2.64.2-1
gtkmm24-2.24.5-9
libtsan-10.2.1-9
From TSAN wiki:
TSAN generally requires all code to be compiled with -fsanitize=thread. If some code (e.g. dynamic libraries) is not compiled with the flag, it can lead to false positive race reports, false negative race reports and/or missed stack frames in reports depending on the nature of non-instrumented code.
If you are using glib from distribution repository (e.g.: sudo apt get install libglib2.0-dev), the number of false positive reports will depend on how the library was built - number of warnings will vary from distro to distro. In order to get proper TSAN report, one should compile all used shared libraries by hand with -fsanitize=thread. In particular glib should be compiled by hand, because it contains various thread-related APIs.
Compile glib with TSAN (for Debian 11.5 "bullseye"):
# clone TAG 2.66.8 (TAG should match glib version on the host)
git clone --depth=1 --branch=2.66.8 https://github.com/GNOME/glib.git
cd glib
CFLAGS="-O2 -g -fsanitize=thread" meson build
ninja -C build
# add TSAN-enabled glib libraries to lib search path
export LD_LIBRARY_PATH=$PWD/build/gio:$PWD/build/glib:$PWD/build/gmodule:$PWD/build/gobject:$PWD/build/gthread
Before running your project, make sure that it links with freshly compiled glib libraries (all glib libraries if used, i.e.: libglib, libgio, libgmodule, libgobject, libgthread) with ldd a.out.

GDB not reacting Ctrl-C when after fork() and setsid()

I'm debugging a daemon and after it forks and child calls setsid() the execution cannot be interrupted by pressing Ctrl-C.
Here is a simple example:
// test.c
#include <unistd.h>
#include <stdio.h>
int main()
{
int i = 0;
if(fork())
{
printf("Parent\n");
return 1;
}
printf("Child\n");
setsid();
for(;;)
i++;
return 0;
}
Steps to reproduce:
gcc -g -o test test.c
gdb ./test
In gdb shell:
set follow-fork-mode child (because by default gdb follows parent process)
run
press Ctrl-C
And nothing happens. Even if I send SIGINT to the process, it is hanging. I have to kill gdb to stop this.
But I can interrupt loop if I send SIGINT before pressing Ctrl-C. If I set breakpoint inside loop, process stops on it fine.
If I comment out one of "fork()" or "setsid()", everything works as expected.
Some workarounds I know are:
Run process in foreground for debugging purpose
Add some sleep in child process after it daemonizes. And attach gdb to it
But maybe someone knows better solution or can explain, why this is happening.
Thanks in advance
Linux 4.12.10-1 x86_64
gcc 7.1.1
GNU gdb (GDB) 8.0
UPD 2017-09-11:
Some more research (here stat <process> stands for grep State /proc/<pid of process>/status)
Scenario 1 (send SIGINT before pressing Ctrl-C, in this case everything work as expected)
$ stat gdb
State: S (sleeping)
$ stat test
State: R (running)
$ killall -SIGINT test
$ stat gdb
State: S (sleeping)
$ stat test
State: t (tracing stop)
Scenario 2 (send SIGINT after pressing Ctrl-C, in this case gdb seems to be hanging)
$ stat gdb
State: R (running)
$ stat test
State: R (running)
$ killall -SIGINT test
$ stat gdb
State: R (running)
$ stat test
State: t (tracing stop)
$ grep TracerPid /proc/`pidof test`/status
TracerPid: 26533
Backtrace of gdb:
#0 0x00007fbf7bb316c0 in __write_nocancel () from /usr/lib/libpthread.so.0
#1 0x00005625e2cc090c in serial_event_set(serial_event*) ()
#2 <signal handler called>
#3 0x00007fbf7aae3ba7 in kill () from /usr/lib/libc.so.6
#4 0x00005625e2cf908b in default_target_pass_ctrlc(target_ops*) ()
#5 0x00005625e2d0b3a4 in maybe_quit() ()
#6 0x00005625e2c24e92 in invoke_async_signal_handlers() ()
#7 0x00005625e2c259e6 in start_event_loop() ()
#8 0x00005625e2c7d33b in captured_command_loop(void*) ()
#9 0x00005625e2c27c55 in catch_errors(int (*)(void*), void*, char const*, return_mask) ()
#10 0x00005625e2c7e63f in gdb_main(captured_main_args*) ()
#11 0x00005625e2a62bec in main ()

Why gdb is not working for this simple hello world program?

Code (m1.cpp):
#include <iostream>
using namespace std;
int main (int argc, char *argv[])
{
cout << "running m1" << endl;
return 0;
}
GDB Version: GNU gdb (GDB) 7.6.2
Built using: g++ -g m1.cpp
Command line history:
(gdb) b main
Breakpoint 1 at 0x40087b: file m1.cpp, line 6.
(gdb) r
Starting program: .../a.out
Program received signal SIGSEGV, Segmentation fault.
0x00002aaaaaac16a0 in strcmp () from /lib64/ld-linux-x86-64.so.2
(gdb) c
Continuing.
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb)
When I run without setting any breakpoints, it runs without errors.
As requested:
(gdb) bt
#0 strcmp () from /lib64/ld-linux-x86-64.so.2
#1 in check_match.12104 () from /lib64/ld-linux-x86-64.so.2
#2 in do_lookup_x () from /lib64/ld-linux-x86-64.so.2
#3 in _dl_lookup_symbol_x () from /lib64/ld-linux-x86-64.so.2
#4 in _dl_relocate_object () from /lib64/ld-linux-x86-64.so.2
#5 in dl_main () from /lib64/ld-linux-x86-64.so.2
#6 in _dl_sysdep_start () from /lib64/ld-linux-x86-64.so.2
#7 in _dl_start () from /lib64/ld-linux-x86-64.so.2
#8 in _start () from /lib64/ld-linux-x86-64.so.2
#9 in ?? ()
I was able to replicate the OP's observed behavior (using the same compile and getting the same backtrace). The behavior was persistent across a range GDBs and GCCs. I noticed that the symptom goes away when I unset SHELL. In my normal environment I use tcsh (version 1.15.00). If SHELL is set, then (I believe) gdb launches using tcsh. If I unset SHELL, gdb launches using sh. This is enough for me to make forward progress. I don't have a crisp explanation for what would be different in tcsh to manifest the issue but if others have the same behavior, it may shed more light on the issue.
I checked that in my GNU gdb version 7.11.1. It worked really fine in it.
I first compiled the same program and built it using:
g++ -g m1.cpp
Then, ran the executable in the gdb as follows:
gdb -q ./a.out
And did the same things you mentioned. It worked fine.
Update your gdb, and check that again and let know.

log4cxx: Segmentation fault in apr_pool_create_ex

I need to use log4cxx for a C++ project. However I fail to understand the basic setup of this library. Here is my minimal attempt:
$ cat logger.cpp
#include <log4cxx/logger.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/helpers/properties.h>
static log4cxx::LoggerPtr ptr;
int main()
{
log4cxx::helpers::Properties prop;
prop.setProperty("log4j.rootLogger","DEBUG, A1");
prop.setProperty("log4j.appender.A1","org.apache.log4j.ConsoleAppender");
prop.setProperty("log4j.appender.A1.layout","org.apache.log4j.PatternLayout");
prop.setProperty("log4j.appender.A1.layout.ConversionPattern","%d{ABSOLUTE} %-5p [%c] %m%n");
log4cxx::PropertyConfigurator::configure(prop);
ptr = log4cxx::Logger::getLogger("API");
LOG4CXX_INFO(ptr , "test_info");
return 0;
}
I then compile it using:
$ g++ -g -o logger logger.cpp -llog4cxx
It fails with:
$ gdb ./logger
[...]
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff5f69dc9 in apr_pool_create_ex () from /lib64/libapr-1.so.0
Missing separate debuginfos, use: debuginfo-install apr-1.5.1-3.fc21.x86_64 apr-util-1.5.4-1.fc21.x86_64 cyrus-sasl-lib-2.1.26-19.fc21.x86_64 expat-2.1.0-10.fc21.x86_64 libdb-5.3.28-9.fc21.x86_64 libgcc-4.9.2-6.fc21.x86_64 libstdc++-4.9.2-6.fc21.x86_64 libuuid-2.25.2-3.fc21.x86_64 log4cxx-0.10.0-17.fc21.x86_64 nspr-4.10.8-1.fc21.x86_64 nss-3.19.1-1.0.fc21.x86_64 nss-softokn-freebl-3.19.1-1.0.fc21.x86_64 nss-util-3.19.1-1.0.fc21.x86_64 openldap-2.4.40-3.fc21.x86_64 zlib-1.2.8-7.fc21.x86_64
(gdb) bt
#0 0x00007ffff5f69dc9 in apr_pool_create_ex () from /lib64/libapr-1.so.0
#1 0x00007ffff7b26b58 in log4cxx::helpers::Pool::Pool() () from /lib64/liblog4cxx.so.10
#2 0x00007ffff7ae06ea in log4cxx::helpers::MutexException::formatMessage(int) () from /lib64/liblog4cxx.so.10
#3 0x00007ffff7ae0786 in log4cxx::helpers::MutexException::MutexException(int) () from /lib64/liblog4cxx.so.10
#4 0x00007ffff7b4a310 in log4cxx::helpers::synchronized::synchronized(log4cxx::helpers::Mutex const&) () from /lib64/liblog4cxx.so.10
#5 0x00007ffff7b5d9c8 in log4cxx::WriterAppender::close() () from /lib64/liblog4cxx.so.10
#6 0x00007ffff7ac979c in log4cxx::ConsoleAppender::~ConsoleAppender() () from /lib64/liblog4cxx.so.10
#7 0x00007ffff7ac98b9 in log4cxx::ConsoleAppender::~ConsoleAppender() () from /lib64/liblog4cxx.so.10
#8 0x00007ffff7aba247 in log4cxx::helpers::AppenderAttachableImpl::~AppenderAttachableImpl() () from /lib64/liblog4cxx.so.10
#9 0x00007ffff7b0494c in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#10 0x00007ffff7b388b4 in log4cxx::spi::RootLogger::~RootLogger() () from /lib64/liblog4cxx.so.10
#11 0x00007ffff7b0429a in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#12 0x00007ffff7b04429 in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#13 0x0000000000401d6e in log4cxx::helpers::ObjectPtrT<log4cxx::Logger>::~ObjectPtrT (this=0x6031a0 <ptr>, __in_chrg=<optimized out>) at /usr/include/log4cxx/helpers/objectptr.h:100
#14 0x00007ffff6e38392 in __run_exit_handlers () from /lib64/libc.so.6
#15 0x00007ffff6e383e5 in exit () from /lib64/libc.so.6
#16 0x00007ffff6e1efe7 in __libc_start_main () from /lib64/libc.so.6
#17 0x0000000000401599 in _start ()
What is so fundamentally wrong to using a global variable for the logger ? It does make sense to me to have a singleton pattern here.
System is: Fedora 21 with:
$ rpm -qv log4cxx
log4cxx-0.10.0-17.fc21.x86_64
and:
$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.9.2/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.9.2 20150212 (Red Hat 4.9.2-6) (GCC)
An even more minimal attempt will crash in exactly the same way:
#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
static log4cxx::LoggerPtr ptr;
int main()
{
log4cxx::BasicConfigurator::configure();
ptr = log4cxx::Logger::getLogger("API");
return 0;
}
The ultimate reasons for this silly debacle are very deep-seated, having to do with fundamental design flaws in log4cxx (starting with the misguided attempt - and philosophy - to make C++ code "look and feel and work like Java").
The proximate reason is that the destructor of the Logger held by the global LoggerPtr ptr is invoked too late. By that time, the APR library (on which the log4cxx codebase depends) will have been dismissed, and consequently the synchronized::synchronized constructor call (#4 in the stack trace) will necessarily fail - after which some kind of crash becomes inevitable. Why the code has to jump through such hoops to release resources is a saga unto itself, and not worth going into here.
The problem, as such, has to do with the order of static de-initializations: The APR library is dismissed "too early" because it's actually initialized, through a static singleton, too late (in the example code, when the LoggerPtr is actually given a Logger to hold).
The example code can be "fixed" by adding this statement before exiting main() (e.g. before the return 0;):
ptr = 0;
This will have the effect of invoking the complex resource release sequence while the APR library is still "alive".
A more general "solution" would involve controlling the lifetime of the APR library appropriately. The log4cxx codebase has static Meyers singletons scattered all over, including one to wrap calls to apr_initialize() and apr_terminate() for the APR library, but the well known "static initialization order fiasco" makes it difficult to arrange that particular singleton to be the "oldest". (It would have to be invoked from the constructors of every other singleton.) The practical "answer", therefore, is to keep the APR library alive forever, by an extra call to apr_initialize() - say, early in main() - not balanced with a matching apr_terminate(), and leaking that particular resource at program termination.
Note that the bug can be triggered in other ways. And, in fact, this bug became a show-stopper for subsequent releases, and that may be why the entire log4cxx project basically died a decade ago.

GCC __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535)

Pursuant to the post, Standalone functions/data in C++, I proceeded to put my "common data" in an anonymous namespace as below and everything worked great on Windows (Vista 64 bit) on VS 2005/2008/2010
namespace {
...
static std::string mystrings[] = {
str1,
str2,
...,
strN
};
...
}
namespace mynamesp {
...
use mystrings[] here..
...
}
But on Linux (so far tested RHEL5 built with GCC-4.1.2) I promptly got a segmentation fault.
$>myprog
Segmentation fault
$>gdb myprog
GNU gdb Fedora (6.8-27.el5)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu"...
(gdb) r
Starting program: <path/to>/myprog
[Thread debugging using libthread_db enabled]
[New Thread 0x2b8901a9da60 (LWP 32710)]
Program received signal SIGSEGV, Segmentation fault.
0x0000003e4ce9c928 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string ()
from /usr/lib64/libstdc++.so.6
(gdb) bt
#0 0x0000003e4ce9c928 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string ()
from /usr/lib64/libstdc++.so.6
#1 0x00002b88ffde482b in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535)
at <path/to>/mysource.cpp:140
#2 0x00002b88ffde4d65 in global constructors keyed to _ZN91_GLOBAL__N__underscore_separated_path_to_mysource.cpp_00000000_6994A7DA2_1E () at <path/to>/mysource.cpp:12139
#3 0x00002b890011a296 in __do_global_ctors_aux ()
from <path/to/libs>/debug/libmylibd.so
#4 0x00002b88ffcd7f33 in _init () from <path/to/libs>/debug/libmylibd.so
#5 0x00002b8901672e40 in ?? ()
#6 0x000000326940d22b in call_init () from /lib64/ld-linux-x86-64.so.2
#7 0x000000326940d335 in _dl_init_internal () from /lib64/ld-linux-x86-64.so.2
#8 0x0000003269400aaa in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#9 0x0000000000000001 in ?? ()
#10 0x0000000000000000 in ?? ()
(gdb)
Line 140 in the backtrace call stack item #1 basically points to the end of my array of strings definition. I've seen some others get this error; but no obvious fixes. Appreciate any thoughts/ideas/corrections as always. Thanks!
Your problem could be releated to a static initialization order fiasco.
This happens when you initialize a static variable using another static variable. When the latter one has not been initialized yet, then the first one is using a non-initialized variable for its initialization.
The root cause is that the order, in which static variables are initialized, is undefined.
Further reading:
https://isocpp.org/wiki/faq/ctors#static-init-order
A typical workaround would be to wrap the static variables inside a function. Example:
T& GetStaticA() {
T static_var_A; // <--initialization here
return A;
}
T static_var_B = GetStaticA(); // <-- static_var_A is guaranteed to be initialized
I had this problem and it turned out that in my compiling line I had missed the final output file in the linking.
g++ main.o logger.o timer.o keyboard.o -o main -lSDL -lSDL_image -lSDL_ttf -Wall
should have been
g++ main.o logger.o timer.o keyboard.o drawer.o -o main -lSDL -lSDL_image -lSDL_ttf -Wall
(Notice the now inclusion of drawer.o?)
It was easy to miss because my actual bash compilation script had many more lines to it.