Skip to main content
PHolt.1
Senior
March 20, 2024
Question

The mysterious __libc_init_array() in Cube IDE - is it only for C++ ??

  • March 20, 2024
  • 21 replies
  • 9215 views

Supposedly this is only used by C++ but see various threads e.g.

https://www.eevblog.com/forum/microcontrollers/a-question-on-gcc-ld-linker-script-syntax/msg4754165/#msg4754165

https://www.eevblog.com/forum/microcontrollers/how-do-i-enable-link-section-overlap/msg3715957/#msg3715957

https://www.eevblog.com/forum/microcontrollers/mcu-programming-101-writeup/msg5402906/#msg5402906

https://www.eevblog.com/forum/microcontrollers/is-st-cube-ide-a-piece-of-buggy-***/msg5403485/#msg5403485

If you google it, you find a disturbing number of suggestions (nothing concrete!) that in the STM environment, Cube IDE, this function is not only for C++ constructors etc but is also used to initialise some statics in libc.a functions. One suggestion was that malloc() does not work without it. ST does not distribute sources to libc.a although some people on eevblog located probable sources for newlib (dated 1990, IIRC). Much of libc.a is also not re-entrant and needs to be mutexed in any RTOS environment.

Does anyone know the real answer?

    This topic has been closed for replies.

    21 replies

    Pavel A.
    March 21, 2024

    This is a feature of GCC runtime library and linker.  Yes, these things can be used to implement C++ constructors and destructors of static objects and also to initialize local data in dynamic libraries. More details in the GCC documentation (for any architecture, not specific to ARM).

     Much of libc.a is also not re-entrant and needs to be mutexed in any RTOS environment.

    This was heavily discussed in this forum (before migration). The real answer is that CubeMX/IDE provide a software component for RTOS-aware locks in newlib. Whether to use that or not, and validation, is up to developer.

    Uwe Bonnes
    Chief
    March 21, 2024

    Apointer to the exact location would be helpfull. Thanks.

    PHolt.1
    PHolt.1Author
    Senior
    March 21, 2024

    "Yes, these things can be used to implement C++ constructors and destructors of static objects and also to initialize local data in dynamic libraries"

    OK, same imprecise answer as all over google :beaming_face_with_smiling_eyes:

    So is it C++ only, or not?

    Disassembling libc.a should yield the answer. Of course there are 66 (literally; just counted them) different versions of libc.a supplied with Cube IDE, according to the processor. No src for any of them. I am using a 32F417.

    I have just stepped through the code (has to be done with Cube switched to displaying assembler) and there is very little code being run. There is a loop which is taken ~3 times.

    PHolt.1
    PHolt.1Author
    Senior
    March 21, 2024

    The code from 306: to 31e: is executed twice. So it is not doing "nothing". My project has no C++ in it.

    The whole thing is weird with 1.15.0. The STLINK V3 debugger fails as posted in the EEVBLOG thread. And when that fails, ST Utility, while programming the chip OK, also fails to deliver a running target, suggesting that code generated by 1.15.0 is faulty in some way. But when I reinstall 1.14.1 everything immediately works, including the ST Utility loading working code. And obviously without a working debugger there is no way to track down the problem.

     

    waclawek.jan
    Super User
    March 21, 2024

    I believe the answer is in newlib's sources (and for motivation we should interview is authors). There's probably no ST involvement there.

    > The whole thing is weird with 1.15.0. The STLINK V3 debugger fails

    How is this __libc_init_array- related?

    JW

    PHolt.1
    PHolt.1Author
    Senior
    March 21, 2024

    Yeah but which sources :beaming_face_with_smiling_eyes:

    The debugger issue is unrelated, sorry - for another thread. But it does mean that with Cube IDE v1.15.0 I can't step through any code.

    Pavel A.
    March 21, 2024

    So is it C++ only, or not?

    Not for C++ only. It can be used in plain C, with compatible compilers that understand  __attribute__((constructor)) and __attribute__((destructor)) .  Example:

    https://www.geeksforgeeks.org/__attribute__constructor-__attribute__destructor-syntaxes-c/

     

    PHolt.1
    PHolt.1Author
    Senior
    March 21, 2024

    From above URL:

    Write two functions in C using GCC compiler, one of which executes before main function and other executes after the main function.

    Why is this needed (in C)? If you want to run some code before main() you just insert it in the startup.s code which calls main() at the end. Actually startup.s, in asm in Cube IDE/MX, could be done wholly in C too, AFAICT.

    And if you want a function to run after main() then, ahem, you just call it after main(). I must be missing something totally obvious.

    Pavel A.
    March 21, 2024

    you just insert it in the startup.s code which calls main() at the end. 

    Yes, and this is exactly what you've seen in the startup code: it calls array of __attribute__((constructor)) functions, automagically collected by the linker. Yes, in the newer ARM CMSIS releases the startup modules are in C. But ST has not updated to this version yet. Perhaps only to the better.

     

    PHolt.1
    PHolt.1Author
    Senior
    March 21, 2024

    That is a circular example, however. 

    If you want to run some code before main() you just insert it in the startup.s code which calls main() at the end.

    I don't see the point of __libc_init_array(). You can call any C function from anywhere before main(), which is just an entry point of a void function like any other C function.

     

     

    Tesla DeLorean
    Guru
    March 21, 2024

    I'm not sure why this has devolved into this binary philosophical argument.

    The mechanics here is to allow for constructor / new() and destructor / delete() functionality in LIBC more broadly. Yes, ideally everything in C won't use this, but that's a bit myopic, and code might be brought it that's not entirely pure.

    Would it be a pain to review all the objects, in all the libraries? Sure, but it could be done and automated.

    ARM wants you call SystemInit() in a pre-main() sense to get the MCU and related hardware, memories and peripherals to a point that the C/C++ run time can initialize the system properly and fully.

    Once the hardware's up the statics need to be cleared or copied into place in RAM, and then anything with constructor expectations is brought up. The linker/script facilitates the construction of a call-table listing everything that need explicit initialization to operates as expected. The kitchen sink approach allows for whatever is built to actually work, without requiring everyone to understand every nuance of the pieces being brought together.

    The GNU/GCC chains are aimed more general purpose executables in Linux, in the bare-metal / embedded sense we're using a subset of functionality with hard addresses, and systems that don't ever exit and return to a command prompt. The compiler is going to build destructors with the expectation they are going to happen but we don't handle that in startup.s, nor exit() / return conditions.

    The staging of information in ROM to moving it to RAM is also somewhat alien to GNU/GCC, and ST's implementation is particularly weak/crude when compared to Arduino's or more embedded specific compilers from Keil and IAR, etc that have had to build ROM code for many decades, across many architectures.

     

    If you don't want to use __libc_init_array(), then DON'T, but perhaps put in other cross-checks to catch situations where the linker and your entire build foot-print doesn't create a constructor call table, so it doesn't surprise you when the code doesn't initialize properly and fails. Should be a relatively trivial task to put a sanity check in main() that alerts you in some fashion that pre-deployment checks can catch. Perhaps have a post-link RULES CHECK in your build processes.

    Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
    waclawek.jan
    Super User
    March 21, 2024

    The keyword here is "magic".

    It's the widespread expectation, that things should "happen" and the unfortunate urge of many influential people to provide the "magic", instead of - arguably harder, but arguably more appropriate - proper documentation/education. It's exactly the same principle as in IDEs and Cubes.

    Here, the expectation is, that you should be able to run a function before main() without knowing what leads up to main, i.e. without knowledge of the startup code. Said atributes are the incantation to invoke the magic, __libc_init_array() is part of the set of mirrors which perform the magic.

    JW

     

    PHolt.1
    PHolt.1Author
    Senior
    March 21, 2024

    Jan, I accept your tongue in cheek post :beaming_face_with_smiling_eyes: but I must still be missing something big.

    Why would anyone want to run a function before main() ? After all, main() is the entry point of your code. Nothing should be happening before main() - other than stuff to set up the language standards e.g. zero bss, copy init data from ROM to RAM, zero the stack (or fill with "s") for good measure, and of course load SP which is necessary before calling functions (of which main() is one). No application specific code should ever run before main().

    I am talking "embedded" here, not the command line stuff whereby main() implicitly picks up argc argv arguments etc.

    I know nothing about C++ but can fully understand some needs for setting up data structures.

    If a function is needed to set up RAM static variables in C code (as has been alleged __libc_init_array() does, setting up stuff like stdlib printf malloc etc) why can't this go after main()? Only if you want to avoid documenting "why".

    I posted some disassembled code and maybe someone has an idea what it does in C.

     

    Pavel A.
    March 21, 2024

    I posted some disassembled code and maybe someone has an idea what it does in C.

    I haven't fully decode the disassembly  but it *should* iterate thru array of pointers to functions, and calls each function.

    With other questions you may have better luck on Stack Overflow. (caution: downvoters there...)