Products > Programming

C - How to doing things before main() and after exit()

<< < (4/4)

westfw:
Avr-gcc has .initN and .finiN sections. (N goes up to about 9?)
This seems to be entirely implemented within the crt files and linker scripts, so it would probably be implementable for any gcc target.


https://www.nongnu.org/avr-libc/user-manual/mem_sections.html

Nominal Animal:

--- Quote from: westfw on May 18, 2021, 09:27:36 am ---Avr-gcc has .initN and .finiN sections. (N goes up to about 9?)
This seems to be entirely implemented within the crt files and linker scripts, so it would probably be implementable for any gcc target.
--- End quote ---
They are.  You can even do this in your own programs.

For example, foo.h, to show how to export string pointers to particular sections:

--- Code: ---#ifndef   FOO_H
#define   FOO_H

#define  _MERGE2(a, b)  a ## b
#define  MERGE2(a, b)  _MERGE2(a, b)
#define  FOO1_STRING(string)  static const char *const MERGE2(magic_, __LINE__) __attribute__((used, section ("FOO1"))) = string
#define  FOO2_STRING(string)  static const char *const MERGE2(magic_, __LINE__) __attribute__((used, section ("FOO2"))) = string

#endif /* FOO_H */

--- End code ---
Instead of strings, those could be structures instead.  (Note that when defined this way, the string contents are in the standard .rodata section; only the static const char *const magic_linenum pointers are put into the special section.)

An example program, example.c:

--- Code: ---#include <stdlib.h>
#include <stdio.h>
#include "foo.h"

extern const char *const __start_FOO1;
extern const char *const __stop_FOO1;
extern const char *const __start_FOO2;
extern const char *const __stop_FOO2;

int main(void)
{
    printf("We have the following %zu foo1 strings:\n", (size_t)(&__stop_FOO1 - &__start_FOO1));
    for (const char *const *p = &__start_FOO1; p < &__stop_FOO1; p++) {
        printf("\t\"%s\"\n", *p);
    }

    printf("We have the following %zu foo2 strings:\n", (size_t)(&__stop_FOO2 - &__start_FOO2));
    for (const char *const *p = &__start_FOO2; p < &__stop_FOO2; p++) {
        printf("\t\"%s\"\n", *p);
    }

    return EXIT_SUCCESS;
}

/* We do need at least one entry per section, to have the __start_/__stop_ symbols. */
FOO1_STRING("example.c: foo1 string");
FOO2_STRING("example.c: foo2_string");

--- End code ---
Compile using
    gcc -Wall -Wextra -O2 -c example.c
    gcc -Wall -Wextra -O2 example.o -o test0
and run
    ./test0
which will output
    We have the following 1 foo1 strings:
       "example.c: foo1 string"
    We have the following 1 foo2 strings:
       "example.c: foo2_string"

Now, let's add a couple of additional object files. foo1.c:

--- Code: ---#include "foo.h"

FOO1_STRING("foo1.c: Hello, world!");

--- End code ---
and foo2.c:

--- Code: ---#include "foo.h"

FOO2_STRING("foo2.c has two strings");
FOO2_STRING("That and this one");

--- End code ---
Let's compile them into object files,
    gcc -Wall -Wextra -O2 -c foo1.c
    gcc -Wall -Wextra -O2 -c foo2.c
and compile three additional versions of the binary:
    gcc -Wall -Wextra -O2 example.o foo1.o -o test1
    gcc -Wall -Wextra -O2 example.o foo2.o -o test2
    gcc -Wall -Wextra -O2 example.o foo1.o foo2.o -o test3
Running these new binaries (./test1, ./test2, and ./test3) shows that the strings have magically appeared to the pointer array!

Yes, this is exactly the section stuff I described earlier.  If we examine the binaries using say objdump -h test3, we can see the FOO1 and FOO2 sections among the section headers.  GCC places the data we wanted into custom sections (of type CONTENTS, ALLOC, LOAD, DATA; i.e. available just like initialized variables aka .data section – this can be overridden using the section attribute, but you usually do not want to do that), and the linker provides us with the __start_ and __stop_ symbols for these custom sections.

The order of the entries in the section array is determined by the link object order.  Above, test3 has example.o foo1.o foo2.o so the objects they declare appear in the array in that order.

This works even with freestanding C (bare metal, without the standard C library at all), as it relies on the ELF file format, and the linker.

An example of where this could be very useful, is a modular menu for an AVR (or ARM) gadget, compiled using the GNU toolchain.  If you have a structure that describes a main menu entry or page, with buttons that can switch between previous and next ones (and optionally other buttons that switch between sub-entries or sub-pages, or fields in the entry) – in any case, with the main menu entries as an array; for example, as a pointer to a more complex menu structure –, you can use this to automagically enable menus for features based on whether they're compiled in or not, without any preprocessor ifdeffery or compile-time configuration per se.

For larger applications, using an array (of pointers) to describe supported command-line options is an useful use case.  Typically, my structures contain the short and the long options, usage description, parameter type/count, and a callback function parsing the option if used. The -h or --help option then scans through the section array, displaying the usage for each option this binary supports.  That way, a new feature can be cleanly implemented in a separate file – like foo1.c and foo2.c above –, without modifications to the existing code.

Doctorandus_P:
Static variables are initialized before main() runs, and if you use C++ then the constructors of static classes also run before main().

Apart from that, if you really want to know what is going on, and you should if you want to go into details as this, you get into linker scripts and startup code and those are compiler dependent.

Some use assembly code you can modify. Using some special compiler constructs is also common.

Jan Audio:
I have seen a topic on microchip forum there is actually something before main in the XC compiler, for initializing.
If you wanto be the best and know everything i understand you want it, else i see no reason.

Nominal Animal:

--- Quote from: Jan Audio on June 29, 2021, 03:58:22 pm ---I have seen a topic on microchip forum there is actually something before main in the XC compiler, for initializing.

--- End quote ---
It is nothing special, really; it just feels odd the first time you encounter it.  I can explain it all in a single post, if you want.

And there are two completely separate things before main(), not just one.  One is that for dynamic executables, the run time linker will execute things it sees in the executable. That, of coursse, only occurs if you have a run time linker; and you only have those on full-blown OSes like Linux and Windows, and not on embedded devices.
The second is the one you alluded to; for various reasons, it is often called C Runtime, or crt; and it is part of the set of base libraries –– usually avr-libc or newlib with AVR and ARM targets, respectively; I'm not sure which XC (Microchip compiler) you refer to, but I wouldn't know for sure which base libraries it uses anyway.

If you look at say avr-libc, compiled with either gcc or clang for various AVR targets, the magic happens in avr-libc/crt1/gcrt1.S.  The compiler provides a default linker script (see say hardware/tools/avr/avr/lib/ldscripts/avr5.x in Arduino sources to see the default linker file for ATmega32u4 – the suffix describes the criteria (a set of compiler and linker options), not any format, by the way), which describes how the various ELF sections are arranged and combined to put together to get an uploadable binary.

Internally, avr-libc uses 21 ELF sections for the critical parts: .vectors for the array of entries the hardware uses when an interrupt occurs, the first one of which is __init, the one the hardware uses when it starts up; .init0 through .init8 for the machine code run before main() is called, .init9 doing that call or jump; and .fini9 through .fini1 for stuff that needs to be done after main() returns or exit() (or its variants) is called, with .fini0 being responsible for the forever loop or system restart.

The exact same "trick" as I showed in my example above, is used by the linker script to merge the ten .init sections into a single consecutive chunk of machine code that finally jumps into or calls C main() function.  Similarly, the .fini sections are merged into a single consecutive chunk of machine code that gets called if main() returns, or one of the exit() functions is called.

So, there is not much to it at all.  If you use Arduino, go to Preferences, and make sure you have Show verbose output during: compilation checked, and I suggest also the Compiler warnings: all.  Then, when you Verify/Compile a sketch, you can see the temporary directory and ELF object file names and paths it uses in the output.  If you look up the .ino.elf one, you can use the avr-objdump utility included in Arduino to examine the ELF object file.  Use the -d flag to get the AVR disassembly; other flags show other useful stuff.  In the disassembly, find symbol __vectors.  On AVRs, the interrupt vectors are actually jump instructions, and the very first one is taken at startup.  In Arduino, for AVRs, it is usually to __ctors_end, but may vary.   An example one I just checked contains 36 instructions before the call to main(): __ctors_end (writes 0 to I/O port 0x3F, 0x0A to port 0x3E, 0xFF to port 0x3D), __do_copy_data (copies initialized variable and object data from Flash to RAM), __do_clear_bss+__do_clear_bss_loop+__do_clear_bss_start (clearing RAM corresponding to uninitialized/zero initialized variables), and __do_global_ctors that calls all ELF constructor functions (via __tablejump2__ helper function, which is just six additional instructions that first loads the address to jump to from 2*Z (Z being the register pair r30:r31 on AVRs), then jumps to that address; return address is in another register pair).  Those ELF constructor functions are the ones marked with __attribute__((constructor)); and for C++ constructors, one that takes no parameters and uses the explicit object address to call the C++ function using the C++ calling convention, will be created automatically by the compiler.

I'm not the best, and although I want to know everything, it's only because I'm curious to a fault.  The reason anyone else might want to know the details, is because sometimes they may come in handy.

For example, if I found I need to get a C function executed in Arduino before its secret main() starts, in an AVR Arduino sketch, then by knowing the above, I'd *know* that
    __attribute__((constructor)) static void myfunc(void) { /* do stuff before main */ }
would do exactly that.  I happen to know that not only does it work with ALL Arduino cores, but it also works in normal hosted environments in Linux and BSDs the exact same way.

Oh, and because XC are derived from either GCC or LLVM/Clang, which both support all of the above as long as ELF object files are used, all of the above applies to XC too.

For an example XC use case, perhaps you have a largeish table in RAM that you don't want to waste Flash for.  What you do, is leave it uninitialized, but create the above constructor function to initialize it.  Done!   For all intents and purposes, by the time main() starts, it will be initialized correctly.  If you do need it initialized before other constructor functions get executed (perhaps a C++ object constructor uses that table?), it gets a bit more complicated (you need to check if setting a priority suffices, or whether you need to add a linker script detail, to ensure the order) – say, maybe five minutes of tinkering, and half an hour of testing the results are correct.

Granted, this information is a bit esoteric and not needed by everyone, but there definitely are use cases and reasons why one might care to know this.

Navigation

[0] Message Index

[*] Previous page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod