Search code examples
cgccportable-executable

How does GCC implement __attribute__((constructor)) on MinGW?


I know that on ELF platforms, __attribute__((constructor)) uses the .ctors ELF section. Now I realized that the function attribute works with GCC on MinGW as well and I'm wondering how it is implemented.


Solution

  • For MinGW targets (and other COFF targets, like Cygwin) compiler just emits each constructor function address in .ctors COFF section:

    $ cat c1.c
    void c1() {
    }
    $ x86_64-w64-mingw32-gcc -c c1.c
    $ objdump -x c1.o | grep ctors
    # nothing
    $ cat c1.c
    __attribute__((constructor)) void c1() {
    }
    $ x86_64-w64-mingw32-gcc -c c1.c
    $  objdump -x c1.o | grep ctors
      5 .ctors        00000008  0000000000000000  0000000000000000  00000150  2**3
    

    GNU ld linker (for MinGW targets) is then configured (via its default linker script) to combine these sections into regular .text section with __CTOR_LIST__ symbol pointing to the first item, and having the last item terminated with zero. (Probably .rdata section would be clearer since these are just addresses of functions, not CPU instructions, but for some reason .text is used. In fact LLVM LLD linker targeting MinGW places them in .rdata.)

    LD linker:

    $ x86_64-w64-mingw32-ld --verbose
    ...
    .text ... {
      ...
      __CTOR_LIST__ = .;   
      LONG (-1); LONG (-1);
      KEEP (*(.ctors));
      KEEP (*(.ctor));
      KEEP (*(SORT_BY_NAME(.ctors.*)));
      LONG (0); LONG (0);
      ...
    ...
    }
    

    Then it is up to C runtime library to run these constructors during initialization, by using this __CTOR_LIST__ symbol.

    From mingw-w64 C runtime:

    extern func_ptr __CTOR_LIST__[];
    
    void __do_global_ctors (void)
    {
      // finds the last (zero terminated) item
      ...    
      // then runs from last to first:
      for (i = nptrs; i >= 1; i--)
      {
        __CTOR_LIST__[i] ();
      }
      ...
    }
    

    (also, it is very similar in Cygwin runtime)

    This can be also seen in the debugger:

    $ echo $MSYSTEM
    MINGW64
    
    $ cat c11.c
    #include <stdio.h>
    
    __attribute__((constructor))
    void i1() {
      puts("i 1");
    }
    
    int main() {
      puts("main");
      return 0;
    }
        
    $ gcc c11.c -o c11
        
    $ gdb ./c11.exe    
    (gdb) b i1    
    (gdb) r    
    (gdb) bt
    #0  0x00007ff603591548 in i1 ()
    #1  0x00007ff6035915f2 in __do_global_ctors () at C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gccmain.c:44
    #2  0x00007ff60359164f in __main () at C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gccmain.c:58
    #3  0x00007ff60359139b in __tmainCRTStartup () at C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:313
    #4  0x00007ff6035914f6 in mainCRTStartup () at C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:202
    (gdb)
    

    Note that in some environments (not MinGW and not Linux) it is instead the responsibility of GCC (its compiler runtime libgcc, more specifically its static part called crtbegin.o and crtend.o) and not C runtime to run these constructors.

    Also, for comparison, on ELF targets (like Linux) GCC compiler used similar mechanism like the one described above for MinGW (it used ELF .ctors sections, although the rest was a bit different), but since GCC 4.7 (released in 2012) it uses slightly different mechanism (ELF .init_array section).