Global constructor call not in .init_array section

10,820

Solution 1

I know it has been almost two years since this question was asked, but I just had to figure out the mechanics of bare-metal C++ initialization with GCC myself, so I thought I'd share the details here. There turns out to be a lot of out-of-date or confusing information on the web. For example, the oft-mentioned collect2 wrapper does not appear to be used for ARM ELF targets, since its arbitrary section support enables the approach described below.

First, when I compile the code above with the given command line using Sourcery CodeBench Lite 2012.09-63, I do see the correct .init_array section size of 4:

$ arm-none-eabi-objdump -h foo.o

foo.o:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
...
 13 .init_array   00000004  00000000  00000000  0000010c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
...

When I look at the section contents, it just contains 0:

$ arm-none-eabi-objdump -j .init_array -s foo.o
Contents of section .init_array:
 0000 00000000                             ....

However, there is also a relocation section that sets it correctly to _GLOBAL__sub_I_foo:

$ arm-none-eabi-objdump -x foo.o
...
RELOCATION RECORDS FOR [.init_array]:
OFFSET   TYPE              VALUE
00000000 R_ARM_TARGET1     _GLOBAL__sub_I_foo

In general, .init_array points to all of your _GLOBAL__sub_I_XXX initializer stubs, each of which calls its own copy of _Z41__static_initialization_and_destruction_0ii (yes, it is multiply-defined), which calls the constructor with the appropriate arguments.

Because I'm using -nostdlib in my build, I can't use CodeSourcery's __libc_init_array to execute the .init_array for me, so I need to call the static initializers myself:

extern "C"
{
    extern void (**__init_array_start)();
    extern void (**__init_array_end)();

    inline void static_init()
    {
        for (void (**p)() = __init_array_start; p < __init_array_end; ++p)
            (*p)();
    }
}

__init_array_start and __init_array_end are defined by the linker script:

. = ALIGN(4);
.init_array :
{
__init_array_start = .;
KEEP (*(.init_array*))
__init_array_end = .;
}

This approach seems to work with both the CodeSourcery cross-compiler and native ARM GCC, e.g. in Ubuntu 12.10 for ARM. Supporting both compilers is one reason for using -nostdlib and not relying on the CodeSourcery CS3 bare-metal support.

Solution 2

Timmmm,

I just had the same issue on the nRF51822 and solved it by adding KEEP() around a couple lines in the stock Nordic .ld file:

KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))

While at it, I did the same to the fini_array area too. Solved my problem and the linker can still remove other unused sections...

Solution 3

You have only produced an object file, due to the -c argument to gcc. To create the .init section, I believe that you need to link that .o into an actual executable or shared library. Try removing the -c argument and renaming the output file to "foo", and then check the resulting executable with the disassembler.

Solution 4

If you look carefully _Z41__static_initialization_and_destruction_0ii would be called inside global constructor. Which inturn would be linked in .init_array section (in arm-none-eabi- from CodeSourcery.) or some other function (__main() if you are using Linux g++). () This should be called at startup or at main(). See also this link.

Solution 5

I had a similar issue where my constructors were not being called (nRF51822 Cortex-M0 with GCC). The problem turned out to be due to this linker flag:

 -Wl,--gc-sections

Don't ask me why! I thought it only removed dead code.

Share:
10,820
Ingmar Blonk
Author by

Ingmar Blonk

Updated on June 27, 2022

Comments

  • Ingmar Blonk
    Ingmar Blonk about 2 years

    I'm trying to add global constructor support on an embedded target (ARM Cortex-M3). Lets say I've the following code:

    class foobar
    {
        int i;
    
    public:
        foobar()
        {
            i = 100;
        }
    
        void inc()
        {
            i++;
        }
    };
    
    foobar foo;
    
    int main()
    {
        foo.inc();
        for (;;);
    }
    

    I compile it like this:

    arm-none-eabi-g++ -O0 -gdwarf-2 -mcpu=cortex-m3 -mthumb -c foo.cpp -o foo.o
    

    When I look at the .init_array section with objdump it shows the .init_section has a zero size.

    I do get an symbol named _Z41__static_initialization_and_destruction_0ii. When I disassemble the object file I see that the global construction is done in the static_initialization_and_destruction symbol.

    Why isn't a pointer added to this symbol in the .init_section?

  • Ingmar Blonk
    Ingmar Blonk about 13 years
    When I do that I still have the same issue.
  • acm
    acm about 13 years
    OK, so with the caveat that I know essentially nothing about ARM, I would say that it means that you have another step to perform in implementing support for global ctor's and dtors for ARM. The fact that you are getting the Z41... symbol is encouraging, because it suggests to me that the .o file is correct. But clearly something isn't happening at link time. On x86 GCC, a link time process called collect2 handles merging all of the various static constructors into the .init section before handing off to ld (or something like that). I think linker support is your next step.
  • Timma
    Timma over 9 years
    I don't know if things have changed since Feb 2013, but I found that I had to treat __init_array_start and __init_array_end as function pointers, not pointers to function pointers: typedef void (InitFunc)(void); extern InitFunc __init_array_start; extern InitFunc __init_array_end; InitFunc pFunc = &__init_array_start; for ( ; pFunc < &__init_array_end; ++pFunc ) { (*pFunc)(); }
  • Admin
    Admin over 8 years
    Many, many naïve "dead code" elimination algorithms remove unreferenced symbols / sections that can't contribute to reachable output... even if they're required for interrupt handlers, static global data structures, etc. This might not be how gcc works, but it's a common gotcha. Try also link-time optimization -flto -O4 under gcc and clang to see what happens.
  • dascandy
    dascandy over 8 years
    It removes dead code. If there's a global object that nobody ever references, is its constructor call dead code or not? Practically nobody will ever call into that object...
  • lorcap
    lorcap over 7 years
    @Timma, there are a couple of mistakes in your code. It compiles when changed to typedef void (*InitFunc)(void); extern InitFunc __init_array_start; extern InitFunc __init_array_end; InitFunc *pFunc = &__init_array_start; for ( ; pFunc < &__init_array_end; ++pFunc ) { (*pFunc)(); } .
  • lorcap
    lorcap over 7 years
    Question understanding the __libc_init_array provides a clearer code which makes use of an array of pointers to function. And it works.
  • Timma
    Timma over 7 years
    @lorcap Looks like my asterisks got interpret as markdown. As you can see, some of my text is italicised. That just so happens to be exactly where I would have typed asterisks. Sorry about that, don't know how I didn't see it. Perhaps there was an SO update to comment formatting?