Guarantees on the Object lifetime of inline static Meyers singleton, with explicit placement in a section?

Viewed 49

I have inherited some code that compiles fine under g++ 9 and 10, but gives a runtime error for both compilers when optimization is turned on (that is, compiling -O0 works, but compiling -Og gives a runtime error from the MMU.)

The problem is that there is a Meyers singleton defined in an inline static method of a class, and that object seems to be optimized away. There is a complication that the static object in the method is declared with a section attribute (this is the g++ language extension for placing options in specific sections in the object file.)

Here is a summary of the situation.

File c.hpp


namespace my_prod {

   class C {
   // C has a default c'tor
   public:
   static C& instance() {
      static C c __attribute__((section("MY_C_SECTION")));
      return c;
   }

   void f();
   };
}

File c.cpp

#include "c.hpp"

using namespace my_prod;

void C::f() {
   // implementation of f, doesn't use instance()
}

File p.hpp

namespace my_prod {

   class P {
   public:
   static void g();
   };
}

Then file p.cpp

#include "p.hpp"
#include "c.hpp"

using namespace my_prod;

void P::g() {
    C::instance().f();
}

The linker script includes:

MEMORY
{
   BIG_CHUNK (rw) : ORIGIN = <address>, LENGTH = <enormous>
}

SECTIONS
{
   .my_space (NOLOAD) :
   {
      . = ALIGN(32);
      *(MY_C_SECTION)
   } > BIG_CHUNK
}

For both optimization levels, objdump -C -r -t p.o gives

00000000  w    O MY_C_SECTION    00002220 my_prod::C::instance::c

(ie, so not local, not global, but it is weak.)

But objdump on the elf file shows that symbol in BIG_CHUNK when optimization is -O0, but missing when it is -Og.

It may be relevant that the project defines the following switches::

-ffunction-sections
-fdata-sections
-Wl,--gc-sections

although these switches are applied consistently for all builds.

The solution was to move the definition of the my_prod::C::instance() method into c.cpp. The symbol is then locally defined and not weak anymore, and appears in the final elf irrespective of the optimization level.

My question is, what are the rules of C++ that explain this behavior?

1 Answers

GCC uses COMDAT section groups when available to implement vague linkage. Despite being explicitly named as MY_C_SECTION, the compiler still emits a COMDAT group with _ZZN7my_prod1C8instanceEvE1c as the key symbol:

    .section    MY_C_SECTION,"awG",@progbits,_ZZN7my_prod1C8instanceEvE1c,comdat

I expect that your other uses of MY_C_SECTION are not part of a COMDAT group with the same signature symbol. This creates an ambiguous situation for section garbage collection by the link editor.

Without optimization, the compiler emits a reference to the _ZZN7my_prod1C8instanceEvE1c signature symbol, and it happens that in this particular implementation of linker garbage collection, this is enough to retain the MY_C_SECTION section in the object file and the _ZZN7my_prod1C8instanceEvE1c symbol definition. With optimization, that reference is gone, and due to the ambiguity of the situation, the link editor discards the definition.

Without an inline definition of my_prod::C::instance(), there is no vague linkage and COMDAT group involved, so the ambiguity does not arise, and the linker script works as expected.

To address this while preserving the inline definition, it may be sufficient to use a unique section name instead of MY_C_SECTION and reference that in the linker script.

Related