Pages

Friday, July 15, 2005

Solaris: Initialization & Termination routines in a dynamic object

Some times it is desirable to have some work to be done during the loading and unloading of a dynamic library. For example it is convenient to initialize some global variables, or to open some files, or to allocate a chunk of memory etc., when the library loads up, instead of waiting for the caller to make reference to any of the interface symbols of the library. As a result of separating initialization, finalization code, we may end up with some what clean code. Run-time linker processes the initialization sections (if any) found in the application, and the dependencies, before transferring the control to the application.

On Solaris, these entry (initialization), exit (finilization) points can be encapsulated in either an array of function pointers or a single code block. With the help of link-editor (ld), we can write code to fire up during pre-loading, loading, and unloading of a shared object. Link-editor creates sections .preinit_array, .init_array, .init, .initfirst, .fini_array and .fini in the ELF object being built, based on the linker options and the compiler pragmas. Let's have a brief look at those sections in the following paragraphs:

  1. .preinit_array section

    A dynamic executable may provide pre-initialization functions, to get executed once the run-time linker has built the process image and performed the necessary relocations. As the name suggests, pre-initialization functions get executed before any other initialization functions. Dynamic libraries are not permitted to have pre-initialization functions.

    The -z preinitarray=function,[function,..] flag of link-editor lets us specify the names of the pre-initialization function(s). During linking, it will be encoded into .preinit_array section of the dynamic object

    The run-time linker (ld.so.1) constructs a dependency ordered list of initialization routines from the dependencies that have been loaded, and executes them in the reverse topological order of the dependencies.

  2. .init_array section

    The -z initarray=function,[function,..] flag of link-editor lets us specify the names of the initialization function(s). During linking, it will be encoded into .init_array section of the dynamic object. Dynamic executables and libraries are permittied to have initialization functions.

  3. .initfirst section

    The link-editor has another flag -z initfirst for fine granularity of control over the initialization routines. When the object is built with this flag, the run-time initialization occurs before the run-time initialization of any other objects brought into the process at the same time. In addition, the object run-time finalization will occur after the run-time finalization of any other objects removed from the process at the same time. Dynamic executables are not permitted to build with this linker flag.

  4. .init and .fini sections

    The .init and .fini provide a run-time initialization and termination code block, respectively. However, the compiler drivers typically supply .init and .fini sections with files they add to the beginning and end of your input file list. These files have the effect of encapsulating the .init and .fini code into individual functions. These functions are identified by the reserved symbol names _init and _fini respectively.

    Sun Studio C/C++ compilers provide the pragmas init and fini , for the developers to specify the initialization and termination code blocks in terms of functions. These pragmas must appear somewhere before the definition of the init/fini functions.

    Syntax:
    #pragma init (function[,function..])
    #pragma fini (function[,function..])
  5. .fini_array section

    As with initialization routines, we can also specify the finalization code to be executed, with the help of link-editor's -z initarray=function[,function..]

    Finalization code will be called when a program terminates under program control or when the containing shared object is removed from memory. As with initialization functions, finalization functions are executed in the order processed by the link editor. In other words, the dynamic executable's .fini section is called first, before its dependencies termination sections are executed
Note that all the above mentioned initialization, termination functions must not accept any arguments, and must not return any values.

Let's put together the things we learned so far in an example.

Example 1. Creating init, fini sections with Sun Studio pragmas
% cat fileops.h
FILE *fd;

void fileopen();
void fileclose();
void filewrite(FILE *, char *);

% cat fileops.c
#include <stdio.h>
#include "fileops.h"

#pragma init (fileopen)
#pragma fini (fileclose)


void fileopen() {
printf("\nInside fileopen() ..");
fd = fopen("/tmp/dummy", "a");
}

void fileclose() {
printf("\nInside fileclose() ..");
fclose(fd);
}

void filewrite(FILE *fd, char *string) {
fprintf(fd, string);
}

% cc -G -o libfileops.so fileops.c

% cat filedriver.c
#include <stdio.h>
#include "fileops.h"

int main() {
filewrite(fd, (char *)"Hey!");
return (0);
}

% cc -o driver -lfileops filedriver.c

%ls -l /tmp/dummy
/tmp/dummy: No such file or directory

% ./driver
Inside fileopen() ..
Inside fileclose() ..

% ls -l /tmp/dummy
-rw-rw-r-- 1 build engr 4 Jul 15 18:17 /tmp/dummy

% cat /tmp/dummy
Hey!

% elfdump libfileops.so
... some parts of the dump were elided ...

Relocation Section: .rela.init
type offset addend section with respect to
R_SPARC_WDISP30 0x44c 0 .rela.init fileopen

Relocation Section: .rela.fini
type offset addend section with respect to
R_SPARC_WDISP30 0x460 0 .rela.fini fileclose

... some parts of the dump were elided ...
2. Creating initialization, termination sections with linker options

Let's remove the pragma statements from fileops.c and rebuild the libfileops library, with -zinitarray=fileopen -zfiniarray=fileclose linker flags

% cat fileops.c
#include <stdio.h>
#include "fileops.h"

void fileopen() {
printf("\nInside fileopen() ..");
fd = fopen("/tmp/dummy", "a");
}

void fileclose() {
printf("\nInside fileclose() ..");
fclose(fd);
}

void filewrite(FILE *fd, char *string) {
fprintf(fd, string);
}

% cc -G -o libfileops.so -zinitarray=fileopen -zfiniarray=fileclose fileops.c

% elfdump libfileops.so
... some parts of the dump were elided ...

Relocation Section: .rela.initarray
type offset addend section with respect to
R_SPARC_32 0x10544 0 .rela.initarra fileopen

Relocation Section: .rela.finiarray
type offset addend section with respect to
R_SPARC_32 0x10548 0 .rela.finiarra fileclose

... some parts of the dump were elided ...

% ./driver
Inside fileopen() ..
Inside fileclose() ..
Finally, we can observe the sequence of initialization, termination calls, by setting the LD_DEBUG environment variable of run-time linker to basic.

% setenv LD_DEBUG basic
% ./driver
29503:
29503: configuration file=/var/ld/ld.config: unable to process file
29503:
29503: calling .init (from sorted order): /usr/lib/libc.so.1
29503:
29503: calling .init (done): /usr/lib/libc.so.1
29503:
29503: calling .init (from sorted order): ./libfileops.so
29503:
Inside fileopen() ..29503:
29503: calling .init (done): ./libfileops.so
29503:
29503: transferring control: ./driver
29503:
29503: calling .fini: ./libfileops.so
29503:
Inside fileclose() ..29503:
29503: calling .fini: /usr/lib/libc.so.1
29503:
Reference:
Solaris Linker and Libraries Guide

[09/15/2005: Adam Levanthal posted some interesting content about _init, in his blog with title The mysteries of _init]
_____________________
Technorati tags: | | |

No comments:

Post a Comment