gcc, segfault, and the mystery of the changing address of a static variable (across stack frames)

373 views Asked by At

I get a segfault in my application and have been poking at it for multiple hours now. I was analysing the backtrace using gdb and noticed the following:

(gdb) frame 3
(gdb) info address C_STATIC_STRING
Symbol "C_STATIC_STRING" is static storage at address 0x66a660.
(gdb) frame 2
(gdb) info address C_STATIC_STRING
Symbol "C_STATIC_STRING" is static storage at address 0x66b800.

Above there are 2 stack frames referring to the same const string C_STATIC_STRING in the same header file, but one frame correctly addresses the variable (frame 3) and the other (frame 2) has an offset address (by 4512 bytes if I calculated correctly).

  • The 0x66a660 one addresses the correct string
  • The 0x66b800 results in error if read: Cannot access memory at address 0xffffffffffffffe8

g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39.0.3)

ADDITIONAL INFO:

I have managed to reproduce the issue using a simpler code:

  • constants.h - containing the macro and the constatnt
#ifndef CONSTANTS_H
#define CONSTANTS_H

using namespace std;

#include <iostream>
#include <string>


#ifndef C_MACRO
#define C_MACRO  "MACRO "
#endif

const std::string CONSTANT = C_MACRO "CONSTANT_STRING";


#endif
  • Test1 class - has a private string that it initializes during construction using the CONSTANT test1.h
#ifndef TEST1_H
#define TEST1_H

using namespace std;

#include <iostream>
#include <string>
#include "constants.h"

class Test1 {
 public:
 Test1();
 std::string getString() {
  return m_str;   
 }
 private:
 std::string m_str;
};

#endif

test1.cpp

using namespace std;

#include <iostream>
#include <string>
#include "test1.h"

Test1::Test1(): m_str(std::string("Extra ") + CONSTANT) 
{
     
};
  • Test class - owns an instance of Test1 test.h
#ifndef TEST_H
#define TEST_H

using namespace std;

#include <iostream>
#include <string>
#include "test1.h"
#include "constants.h"


class Test {
    public:
    Test1 getTest() {
        return m_test;   
    }

 private:
 Test1 m_test;    
};

#endif

test.cpp - pretty much empty

using namespace std;

#include <iostream>
#include <string>
#include "test.h"

  • main.cpp -- has a static instance of Test class

using namespace std;

#include <iostream>
#include <string>
#include "test.h"


namespace NOTSTD {
    
    Test variable;
}
using namespace NOTSTD;

int main()
{
  
  std::cout << variable.getTest().getString() << " printed";
}

Now the build process

  • Makefile
#Test makefile
CPP = g++ 
CPPFLAGS = -Wall -ggdb -O0

AR = ar
RANLIB = ranlib

OUTPUT = test

all:: $(OUTPUT)

for_static = test1.o
static_lib.a: $(for_static)
    $(AR) qc $@ $(for_static)
    $(RANLIB) $@
    
$(OUTPUT): static_lib.a test.o main.o
    $(CPP) ${CPPFLAGS} test.o main.o -o $(OUTPUT) static_lib.a
    
%.o : %.cpp
    $(CPP) $(CPPFLAGS) -c $< -o $@
    
clean:
    rm -f $(OUTPUT)
    rm -f *.o
    rm -f *.a

Test1 gets compiled into a static library and later used to compile the rest. In Cygwin, it works as expected On OEL 7 it gets a segmentation fault (no matter the optimization level) If I omit the statically linked library and just compile in test1, then it works on OEL too.

Disassembly seems to indicate that the issue lies with initialization order of static variables/constants.

I'm not too good at C++ and compilers. Perhaps anyone has an idea on what is exactly going on? GCC bug or is it just me?

1

There are 1 answers

0
Denis On BEST ANSWER

I would like to summarize the things I learned from the helpful comments above:

  1. It is inevitable for static variables to have different addresses in different Translation Units.
  2. Due to the phenomenon known as Static Initialization Fiasco, using the static variables in the way that I did, relies on "good luck" for the variable to be initialized before they are used. If the fortune is not on your side at compile time, you will get a segmentation fault while attempting to use the variable.

To work around the issue nr 2, I have wrapped my static variable in a method (a getter of sorts) and used the method instead of the variable. It forces the initialization of other static variables at the correct time. the method looks something like that:

Test getTest(){
    static Test test;
    return test;
}

Would like to thank David Schwartz and n.'pronouns'm for their guidance.