2014-01-19

How to run custom code before and after main in GCC?

This blog post explains how to register C or C++ code to be run at process startup (i.e. just before *main*) and process exit (e.g. when *main* returns or when *exit*(...) is called). Code to be run at loading and unloading of shared libraries and Linux kernel modules are not covered in this post.

The behavior described in this post has been tested with gcc-4.1 ... gcc-4.8 and clang-3.0 ... clang-3.4. Older versions of GCC and Clang may behave differently.

The behavior described in this post has been tested with (e)glibc-2.7 ... (e)glibc-2.15 and uClibc-0.9.30.1 ... uClibc-0.9.33). Earlier versions and other libc implementations may behave differently. For example, dietlibc-0.33 doesn't execute any of the registered code (so the example below prints just MAIN; MYATEX2; MYATEX1).

The new way (available since gcc-2.7) is declaring a function with attribute((constructor)) (this will make it run at process startup) and declaring a function with attribute((destructor)) (this will make it run at process exit). The double underscores around __attribute__ are there to prevent GCC warnings when compiling standard C (or C++) code. Example:

#include <unistd.h>

__attribute__((constructor)) static void myctor(void) {
  (void)!write(2, "HI\n", 3);
}

__attribute__((destructor)) static void mydtor(void) {
  (void)!write(2, "BYE\n", 4);
}

Upon specifying one of these attributes, GCC (or Clang) appends the function pointer to the sections .init_array (or sometimes .ctors) or .fini_array (or sometimes .dtors), respectively. (You can take a look at objdump -x prog to see if these sections are present.) The libc initialization and exit code will run all functions in these sections. There is a well-defined order (see below) in which these registered functions get run, but the order is within the same translation unit (C or C++ source file) only. It's undefined in which order the translation units are processed.

Please note that the process exit functions are not always called: for example, if the process receives a signal which terminates it (e.g. either from another process or from itself, or from itself, by calling abort()), or if the process calls _exit(...) (with an underscore), then none of the process exit functions are called.

Please note that it's possible to register more process exit functions at runtime, by calling atexit(3) or on_exit(3).

C++ static initialization is equivalent to attribute((constructor)):

#include <unistd.h>
#include <string>

static int get_answer() {
  (void)!write(1, "GA\n", 3);
  return 42;
}
 
/* The call to get_answer works in C++, but it doesn't work in C, because
 * the value of myanswer1 is not a compile-time constant.
 */
int myanswer = get_answer();
std::string hello("World");  /* Registers both a constructor and destructor. */

There is an older alternative for registering process startup and exit functions: by adding code to the body of the _init function in the .init section and to the body of _fini function in the .fini section. The headers of these functions are defined in crti.o and the footers are defined in crtn.o (both of which are part of the libc, use e.g. objdump -d .../crti.o to disassemble them). GCC itself uses this registration mechanism in crtbegin.o to register __do_global_dtors_aux and in crtend.o to register __do_global_ctors_aux.

It is possible to use this older registration alternative in your C or C++ code, but it's a bit inconvenient. Here are some helper macros which make it easy:

/* Usage: DEFINE_INIT(my_init1) { ... }
 * Defines function my_init1 which will be called at startup, before main().
 * As a side effect, defines `static void name() { ... }'.
 */
#define DEFINE_INIT(name) \
    static void name(void); \
    /* If we declared this static, it wouldn't get called. */ \
    __attribute__((section(".trash"))) void __INIT_HELPER__##name(void) { \
      static void (* volatile f)(void) = name; \
      __asm__ __volatile__ (".section .init"); \
      f(); \
      __asm__ __volatile__ (".section .trash"); \
    } \
    static void name(void)

/* Usage: DEFINE_FINI(my_fini1) { ... }
 * Defines function my_fini1 which will be called at process exit.
 * As a side effect, defines `static void name() { ... }'.
 */
#define DEFINE_FINI(name) \
    static void name(void); \
    /* If we declared this static, it wouldn't get called. */ \
    __attribute__((section(".trash"))) void __FINI_HELPER__##name(void) { \
      static void (* volatile f)(void) = name; \
      __asm__ __volatile__ (".section .fini"); \
      f(); \
      __asm__ __volatile__ (".section .trash"); \
    } \
    static void name(void)

For your reference, here are the corresponding much simpler macros for attribute((constructor)) and attribute((destructor)):

/* Usage: DEFINE_CONSTRUCTOR(my_init1) { ... }
 * Defines function my_init1 which will be called at startup, before main().
 * As a side effect, defines `static void name() { ... }'.
 */
#define DEFINE_CONSTRUCTOR(name) \
    __attribute__((constructor)) static void name(void)

/* Usage: DEFINE_DESTRUCTOR(my_init1) { ... }
 * Defines function my_fini1 which will be called at process exit.
 * As a side effect, defines `static void name() { ... }'.
 */
#define DEFINE_DESTRUCTOR(name) \
    __attribute__((destructor)) static void name(void)

It is possible to use the old and the new registration mechanisms at the same time. Here is a sample code which uses both, and C++ static initialization and atexit and on_exit as well.

#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#ifdef __cplusplus
class C {
 public:
  C(const char *msg): msg_(msg) {
    (void)!write(1, "+", 1);  (void)!write(1, msg_, strlen(msg_));
  }
  ~C() {
    (void)!write(1, "-", 1);  (void)!write(1, msg_, strlen(msg_));
  }
 private:
  const char *msg_;
};
#endif

DEFINE_INIT(myinit1) { (void)!write(1, "MYINIT1\n", 8); }
DEFINE_CONSTRUCTOR(myctor1) { (void)!write(1, "MYCTOR1\n", 8); }

#ifdef __cplusplus
static int get_answer(const char *msg) {
  (void)!write(1, msg, strlen(msg));
  return 42;
}
C myobj1("MYOBJ1\n");
int myanswer1 = get_answer("ANSWER1\n");
C myobj2("MYOBJ2\n");
int myanswer2 = get_answer("ANSWER2\n");
#endif

DEFINE_INIT(myinit2) { (void)!write(1, "MYINIT2\n", 8); }
DEFINE_CONSTRUCTOR(myctor2) { (void)!write(1, "MYCTOR2\n", 8); }
DEFINE_FINI(myfini1) { (void)!write(1, "MYFINI1\n", 8); }
DEFINE_DESTRUCTOR(mydtor1) { (void)!write(1, "MYDTOR1\n", 8); }
DEFINE_FINI(myfini2) { (void)!write(1, "MYFINI2\n", 8); }
DEFINE_DESTRUCTOR(mydtor2) { (void)!write(1, "MYDTOR2\n", 8); }
static void myatex1() { (void)!write(1, "MYATEX1\n", 8); }
static void myatex2() { (void)!write(1, "MYATEX2\n", 8); }
static void myonexit(int exitcode, void *arg) {
  const char *msg = (const char*)arg;
  (void)exitcode;
  (void)!write(1, msg, strlen(msg));
}

int main(int argc, char **argv) {
  (void)argc; (void)argv;
  atexit(myatex1);
  on_exit(myonexit, (void*)"MYONEX1\n");
  (void)!write(1, "MAIN\n", 5);
  atexit(myatex2);
  on_exit(myonexit, (void*)"MYONEX2\n");
  return 0;
}

It is not intuitive in which order these are run. Here is the output:

MYINIT1
MYINIT2
+MYOBJ1
ANSWER1
+MYOBJ2
ANSWER2
MYCTOR2
MYCTOR1
MAIN
MYONEX2
MYATEX2
MYONEX1
MYATEX1
-MYOBJ2
-MYOBJ1
MYDTOR1
MYDTOR2
MYFINI1
MYFINI2

Please note that gcc-4.3 and below run MYDTOR1 and MYDTOR2 in the opposite order. All other compilers tested (see above which) use exactly this order. The order is libc-independent, because newer compiler versions with the same libc resulted in different order, while other libc versions with the same compiler version kept the order intact. Please note again that the order of .ctors, .dtors and others is undefined across translation units (C or C++ source files).

3 comments:

Tamás said...

Is the order of call for .ctors specified?

pts said...

@Tamás: .ctors within the same translation unit happen in the order you see in the post. The order of .ctors across translation unit is undefined.

taken said...

Hi,

What are the differences between calls made by adding a function using atexit() and those in .dtors? Why does C++ provide two ways to call a function after exit?

thanks