Author Topic: C - How to doing things before main() and after exit()  (Read 1612 times)

0 Members and 1 Guest are viewing this topic.

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2482
  • Country: nz
C - How to doing things before main() and after exit()
« on: May 14, 2021, 01:04:02 am »
Does anybody know of a handy guide detailing when an how you can get things to run before and after main() and maybe after exit()?

Things like:

Code: [Select]
__attribute__((constructor)) static void before_main(void) {
  ...
}


Specifically I want to find out what resources are available at what stage... more specifically, when it is OK to allocate stuff on the heap, and when are the standard file handles valid.
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 7766
  • Country: fr
Re: C - How to doing things before main() and after exit()
« Reply #1 on: May 14, 2021, 01:44:01 am »
I'm not sure what you want to do exactly.

By definition, on a "hosted" environment, main() is the programmer's entry point. The environment is responsible for doing anything it wants, call main(), and then do anything else it wants.

You can also use C in a "freestanding" way. No supposed environment, no standard library. In this case, you're in complete control.

But in the hosted case, there is no standard way of dealing with anything "before" or "after" main(). I'm not sure what you mean by before main and after main anyway? How before is before? The hosted environment will call main(), what it does before that is beyond your control, and what point would be your "before" point anyway?

Since you're talking about allocation on the heap and standard files, I'm assuming you're in the "hosted environment" case.
Allocation on the heap is guaranteed to work as soon as you've entered the main() function (and if there's enough memory available, of course.) Likewise, standard files handles are usable as soon as you've entered main(). I'm still not sure what you want to do.
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2482
  • Country: nz
Re: C - How to doing things before main() and after exit()
« Reply #2 on: May 14, 2021, 04:06:21 am »
I found using constructors useful in a project, and am wondering what else I can do.

So my project is an SDR thing I'm playing with, and here is the main function that sets up the processing pipeline and then processes the samples:

Code: [Select]
int main(int argc, char *argv[]) {
  unsigned n = 0;
  struct pipeline *p = pipelineNew();
  if(p == NULL) {
     fprintf(stderr,"Unable to create pipeline\n");
     return 0;
  }
  pipelineAdd(p, "read_wave",      1, readArgs);
  pipelineAdd(p, "filter",         0, NULL);
  pipelineAdd(p, "demodulate_fsk", 0, NULL);
  pipelineAdd(p, "atan2",          0, NULL);
  pipelineAdd(p, "interpolate",    2, interpArgs);
  pipelineAdd(p, "quantize",       0, NULL);
  pipelineAdd(p, "ccsds",          0, NULL);

  while(1) {
     uint8_t buf[255];
     size_t bits = pipelinePull(p, &buf, sizeof(buf));
     if(bits == 0)
       break;
     n+= bits;
  }
  pipelineDelete(p);
  fprintf(stderr, "%u symbols generated\n",n);
  return 0;
}

The eventual aim is to define the processing pipeline in a text file, rather than in code.

I've got 10 or so processing modules that perform the different steps of processing. Each module is completely standalone, and register themselves before main() is run:

Code: [Select]
static struct module atan2_module = {
  .setup      = atan2_setup,
  .inputType  = atan2_input_type,
  .outputType = atan2_output_type,
  .pull       = atan2_pull,
  .teardown   = atan2_teardown,
  .name       = atan2_name
};

__attribute__((constructor)) static void before_main(void) {
  pipelineRegister(&atan2_module);
}

Each processing module self-registers inside the "before_main()" constructor function, adds this module to the global list of available modules. This makes adding new processing modules is particularly easy - just include the .o file when you link.

I fully appreciate that this is very compiler specific, but it is a handy technique. I was wondering:
- what other things are out there that I am ignorant of.
- is it safe to call malloc() in the constructor function (which I currently avoid)
- are file handles ready to go when the constructor functions are run (it seems they are)
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Online rstofer

  • Super Contributor
  • ***
  • Posts: 8672
  • Country: us
Re: C - How to doing things before main() and after exit()
« Reply #3 on: May 14, 2021, 04:28:05 am »
For the older ARM7TDMI chips (and maybe the new stuff as well), there was a file crt.S that held the startup code.  It did things like clear the .bss segment, initialize the .data segment by copying the values from flash and call main().  All of the interrupt vectors were defined in the file.  Following the call to main was an infinite loop because main() wasn't suppose to return.  It would be easy to add code before and after the call to main().

I never paid much attention to where the constructors were called.

The code to do startup stuff has to exist somewhere.  Memory doesn't get cleared by magic and copying initialized values from flash to RAM takes code.

I don't know where the code would be on the more modern ARM-M chips but the functionality still has to exist somewhere.  I think it can now be written in C.
« Last Edit: May 14, 2021, 04:29:47 am by rstofer »
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 10125
  • Country: us
Re: C - How to doing things before main() and after exit()
« Reply #4 on: May 14, 2021, 04:36:07 am »
Your question doesn't really make sense to me.

What is to stop you doing the following?

Code: [Select]
int main()
{
    // call setup code
    my_setup_routine();

    // call main processing code
    my_main_code();

    // call cleanup code
    my_cleanup_routine();
}

You can obviously elaborate this with error checking, exception handling and so on to catch problems. How would you propose to handle errors with your "before main" design?
I'm a ChemE--I know all about the flow of fluids.
 

Online ledtester

  • Super Contributor
  • ***
  • Posts: 1797
  • Country: us
Re: C - How to doing things before main() and after exit()
« Reply #5 on: May 14, 2021, 04:43:41 am »
From all of the examples of __attribute__((constructor)) I've seen it seems that you can pretty much do anything in them that you do in main, e.g.:

https://try2explore.com/questions/10021250
https://www.geeksforgeeks.org/__attribute__constructor-__attribute__destructor-syntaxes-c/

Also, that lack of caveats in the documentation seems to suggest that there aren't really any restrictions on standard library calls like malloc or stdio calls

(link to gcc-11.1.0 docs)

Another option is to use dlopen() to explicitly load in shared libraries. Then you can specify the libraries to be loaded as command line arguments/environment variables obviating the need to even recompile.

If you need cross-platform shared library loading support have a look at libltdl.

https://www.gnu.org/software/libtool/manual/html_node/Using-libltdl.html
« Last Edit: May 14, 2021, 04:50:46 am by ledtester »
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2482
  • Country: nz
Re: C - How to doing things before main() and after exit()
« Reply #6 on: May 14, 2021, 05:32:08 am »
Your question doesn't really make sense to me.

What is to stop you doing the following?

Code: [Select]
int main()
{
    // call setup code
    my_setup_routine();

    // call main processing code
    my_main_code();

    // call cleanup code
    my_cleanup_routine();
}

my_setup_routine() will need to be revisited as additional processing modules are added. Using the current method this function isn't needed as adding new modules is automagically handled by the linker & C environment.


Quote
You can obviously elaborate this with error checking, exception handling and so on to catch problems. How would you propose to handle errors with your "before main" design?

1. At present it can't error if used correctly. The only thing that happens when a module is registered is that a linked list of modules is constructed:

Code: [Select]
static struct module *first_module;
void pipelineRegister(struct module *m) {
   m->next_module = first_module;
   first_module = m;   
}

However, should something critically fatal occur (e.g. a NULL pointer is passed in), you can still write stuff to stdout and exit the program.

2. If it does error, and you decide that you don't need abort the entire program, then the failed pipeline module won't be able to be found when pipelineAdd() is called, and the issue can be trapped and then.
« Last Edit: May 14, 2021, 05:37:29 am by hamster_nz »
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Online ledtester

  • Super Contributor
  • ***
  • Posts: 1797
  • Country: us
Re: C - How to doing things before main() and after exit()
« Reply #7 on: May 14, 2021, 05:46:35 am »
Quote
However, should something critically fatal occur (e.g. a NULL pointer is passed in), you can still write stuff to stdout and exit the program.

Good question whether or not you can/should call exit() within a constructor function.

Alternatively you could set a global variable to indicate an error so that main() can perform the exit.
 

Offline AntiProtonBoy

  • Frequent Contributor
  • **
  • Posts: 946
  • Country: au
  • I think I passed the Voight-Kampff test.
Re: C - How to doing things before main() and after exit()
« Reply #8 on: May 14, 2021, 05:55:05 am »
Specifically I want to find out what resources are available at what stage... more specifically, when it is OK to allocate stuff on the heap, and when are the standard file handles valid.

Soon as execution enters into main(), everything is initialised for you, static variables, constants, etc. And you can certainly use malloc and file IO at that point.

Think of main() as a "sandbox" for user implemented code. The sandbox and all its plumbing must exist before the user code can run.
 

Online Berni

  • Super Contributor
  • ***
  • Posts: 3835
  • Country: si
Re: C - How to doing things before main() and after exit()
« Reply #9 on: May 14, 2021, 06:17:06 am »
What you are trying to do is a pretty common pattern in programming.

It is used in modular systems that support any sort of plugins, be it drivers, modules in a OS, modding APIs in games...etc. The module defines various "stages" that the master calls upon at the appropriate time. So you might have:
- Init
- Pre-Start
- Start
- Tick
- Pre-Stop
- Stop

These stages are simply functions that are made visible to the master. This can be a C++ class with appropriately names functions, it could be a C struct that holds function pointers to the functions that handle each stage, it could be some fancy object in C# or Java or something that registers itself in a array on creation etc..

Then once its time to actually start the master will look at the table of modules and call first PreStart for each module, then call Start for each module, then as its running it might call Tick every 10ms, then once its time to shut down it might call PreStop in each module and finally Stop for each module.

Having multiple stages for start and stop might sound redundant, but it allows one module to set up some data or service in the PreStart that other modules might use as part of the Start function. This prevents the need to call certain modules in a certain order. There could be even more, like PreStart1 Prestart2 PreStart3 PreStartCleanup... etc if the modules are particularly complex and need a lot of interaction before starting.

For your particular implementation in C. I would suggest first manually calling the ModuleName_Init() function for each module at the start of main() this init function would add its service calls to arrays of pointers, each for the appropriate stage such as "ServiceTick[n] = ModuleName_Tick;" Then you just use a for loop to call every function pointer inside ServiceStart[] then ServiceTick[] a bunch of time then ServiceStop[] at the end.

 

Offline Keith956

  • Regular Contributor
  • *
  • Posts: 79
  • Country: gb
Re: C - How to doing things before main() and after exit()
« Reply #10 on: May 14, 2021, 06:25:16 am »
Sounds to me like you want an app that is extendable using plugins. Have a look at:

http://www.cplusplus.com/articles/48TbqMoL/

The basic idea is you create DLL's (or shared libraries in the *nix world) that have well-defined interfaces. Then your app looks for them in some predetermined location, loads them and makes them available as extensions to your app.


 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2482
  • Country: nz
Re: C - How to doing things before main() and after exit()
« Reply #11 on: May 14, 2021, 06:26:37 am »
I might not have made it clear that this already works, and works really well.

I am just exploring what other similar things can be done, and where such things are documented.
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 10125
  • Country: us
Re: C - How to doing things before main() and after exit()
« Reply #12 on: May 14, 2021, 06:29:12 am »
I might not have made it clear that this already works, and works really well.

I am just exploring what other similar things can be done, and where such things are documented.

These things are not generally part of the language standard, but rather are specific to particular vendors and implementations. So they will be documented by the vendor of the tools that you are using.

You should understand that what you are doing is absolutely not "C", and therefore is not portable. If you move to a different platform everything you are doing will likely stop working.

Any time you see something with two underscores in it, such as "__attribute__", that is an indication that you are dealing with a local extension, and have taken a detour off the main road.
« Last Edit: May 14, 2021, 06:33:14 am by IanB »
I'm a ChemE--I know all about the flow of fluids.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 3079
  • Country: fi
    • My home page and email address
Re: C - How to doing things before main() and after exit()
« Reply #13 on: May 15, 2021, 05:02:04 am »
Code: [Select]
__attribute__((constructor))
These function attributes are GNU extensions provided by GCC, and implemented by the linker in Linux and some BSD variants (but not Mac OS); they are not part of C, nor are they supported in all operating systems.

In Linux, functions marked constructor have their addresses listed in .init_array section, and functions marked destructor in .fini_array section.  The startup/linker code executes the constructors just before calling main() and destructors when returning from main() and when calling exit() (but not when calling _exit().  This means that such constructor/destructor functions can be static, without an exported symbol at all.

(The section variable attribute can be used to collect elements (of the same type, properly aligned and sized) from different compilation units into a single array.  This is used in e.g. the Linux kernel to collect module license information, and can be used to make it trivial for a system utility to collect its command-line options and other structures automagically based on what parts were enabled at compile time.)

When an executable is loaded in Linux, the linker can even call a function to resolve a symbol.  This is done via the ifunc function attribute, and is based on the ELF file standard optional features.  (This means it is not technically a Linux extension, but an ELF extension.  The constructor/destructor attributes are similarly based on optional features in the ELF standard).  This is extremely useful if you want your binaries to include optimized functions for different hardware architectures, as this incurs no extra overhead at all compared to a normal, non-inline function.

When an executable or library is loaded in Linux, these get called recursively, but I'm not exactly sure of the ordering; normally (without priority overrides, i.e. a priority argument to the constructor function attribute), I do believe "everything just works" because the depended-on library constructors get called before the dependee's.  Thus, for the executable itself, everything that is available at the beginning of main() should be available in the constructors (assuming no priority has been set for the constructor); for dynamic libraries, everything the dynamic library is linked against, should be available when its default-priority constructors are called.

This also means that if your application does not need to unload plugins at run time, it is basically trivial to implement plugins in Linux due to the support of these ELF features.  Basically, your application provides a registration function that plugins can use to register the new features the plugin implements, and the plugin just needs to have a (static) constructor function that registers the features when the library is loaded.  Trick is, if you write the code properly (so that it is safe to call these registration functions before main(), as long as the standard C library has been initialized), you don't even need to add dlopen() code to the application (although I definitely would; I'd probably scan an user-specific plugin directory for symlinks to the currently active plugins), as the user can preload the desired plugins by setting the LD_PRELOAD environment variable to point to the plugin file paths (separated either by spaces or by colons : ).

Although there are more "tricks" described in the above pages related to GCC and ELF binaries and libraries, these are the ones I routinely use.
« Last Edit: May 15, 2021, 05:03:59 am by Nominal Animal »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 3079
  • Country: fi
    • My home page and email address
Re: C - How to doing things before main() and after exit()
« Reply #14 on: May 15, 2021, 05:08:46 am »
(Before anyone asks, similar features are available in the Linux kernel because its binary is also in ELF format.  Obviously, it uses its own internal dynamic linker, but that one implements these features as well.  That linker is actually pretty crafty, as it examines various headers in kernel module ELF headers, and marks the kernel tainted etc. depending on those (including the license).)
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: us
Re: C - How to doing things before main() and after exit()
« Reply #15 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.


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

 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 3079
  • Country: fi
    • My home page and email address
Re: C - How to doing things before main() and after exit()
« Reply #16 on: May 18, 2021, 12:20:52 pm »
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.
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: [Select]
#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 */
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: [Select]
#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");
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: [Select]
#include "foo.h"

FOO1_STRING("foo1.c: Hello, world!");
and foo2.c:
Code: [Select]
#include "foo.h"

FOO2_STRING("foo2.c has two strings");
FOO2_STRING("That and this one");
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.
« Last Edit: May 18, 2021, 12:22:36 pm by Nominal Animal »
 
The following users thanked this post: actuallyjaseg

Offline Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 1847
  • Country: nl
Re: C - How to doing things before main() and after exit()
« Reply #17 on: May 23, 2021, 03:51:38 am »
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.
 

Offline Jan Audio

  • Frequent Contributor
  • **
  • Posts: 803
  • Country: nl
Re: C - How to doing things before main() and after exit()
« Reply #18 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.
If you wanto be the best and know everything i understand you want it, else i see no reason.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 3079
  • Country: fi
    • My home page and email address
Re: C - How to doing things before main() and after exit()
« Reply #19 on: June 30, 2021, 12:42:59 am »
I have seen a topic on microchip forum there is actually something before main in the XC compiler, for initializing.
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.
« Last Edit: June 30, 2021, 12:48:16 am by Nominal Animal »
 
The following users thanked this post: hamster_nz


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf