10

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?

Trevor Robinson
  • 15,694
  • 5
  • 73
  • 72
Ingmar Blonk
  • 246
  • 1
  • 2
  • 5

5 Answers5

29

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.

Trevor Robinson
  • 15,694
  • 5
  • 73
  • 72
  • 2
    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)(); } – Timma Mar 11 '15 at 02:43
  • @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 Dec 01 '16 at 14:23
  • Question [understanding the __libc_init_array](http://stackoverflow.com/questions/15265295/understanding-the-libc-init-array) provides a clearer code which makes use of an array of pointers to function. And it works. – lorcap Dec 01 '16 at 16:35
  • @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? – Timma Dec 01 '16 at 22:32
4

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...

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.

acm
  • 12,183
  • 5
  • 39
  • 68
  • When I do that I still have the same issue. – Ingmar Blonk Jun 14 '11 at 13:15
  • 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. – acm Jun 14 '11 at 13:32
2

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.

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • 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. –  Nov 24 '15 at 01:53
  • 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... – dascandy Dec 07 '15 at 09:12
  • 1
    @dascandy Did its constructor touch state or pass data to non-dead-code? If so, then no it isn't dead. Dead code is code *if eliminated* causes no changes in the behavior of the program. – Yakk - Adam Nevraumont Jun 05 '23 at 15:14
2

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.

Nightfirecat
  • 11,432
  • 6
  • 35
  • 51
itdl
  • 221
  • 2
  • 4