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:
__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.
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:
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:
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)
Your question doesn't really make sense to me.
What is to stop you doing the following?
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?
Your question doesn't really make sense to me.
What is to stop you doing the following?
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.
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:
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.
__attribute__((constructor))
These function attributes (https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html) 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 (https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html) 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 (https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-ifunc-function-attribute) 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.
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:
#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:
#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:
#include "foo.h"
FOO1_STRING("foo1.c: Hello, world!");
and foo2.c:
#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.