Author: Mugabi Siro

Category: ELF Support

Summary:

This entry presents an example of placing data in an arbitrary section. It is based on Trevor Woerner's Understanding The Linux Kernel Initcall Mechanism (available online). The development platform used was Ubuntu 12.04.

Tags: gnu/linux toolchain gnu linux s/w development elf support

Case Study

Placing functions or data in arbitrary sections can be achieved by way of the __attribute__((section(SECTION))) extension, where SECTION is a string e.g. "arbitrarysection". The following code is taken from Trevor Woerner's Understanding The Linux Kernel Initcall Mechanism guide (available online). Essentially, a new section is generated at compile-time that contains a table of initialized function pointers. This approach allows for a scalable and generic function call mechanism via loop constructs that simply iterate over the table entries by way of pointer referencing.

/*
 *     AUTHOR: Trevor Woerner
 * START DATE: 14 August 2003 - 09:58:33 AM
 *   MODIFIED: 23 September 2003 - 01:23:56 AM
 *   FILENAME: mycalls.c
 *    PURPOSE: Compile-time table of function pointers
 *
 * Copyright (C) 2003  Trevor Woerner
 */

#include <stdio.h>

typedef void (*funcptr_t)(void);
extern funcptr_t __start_newsect, __stop_newsect;

#define data_attr         __attribute__ ((section ("newsect")))
#define create_entry(fn)  funcptr_t _##fn data_attr = fn

void my_init1 (void) { printf ("my_init1() #1\n"); }
void my_init2 (void) { printf ("my_init2() #2\n"); }

create_entry (my_init1);
create_entry (my_init2);

int
main (void)
{
    funcptr_t *call_p;

    call_p = &__start_newsect;
    do {
        printf ("call_p: %p\n", call_p);
        (*call_p)();
        ++call_p;
    } while (call_p < &__stop_newsect);

    return 0;
}

The -E preprocessor switch may be used to view the create_entry macro expansion:

$ gcc -E mycalls.c 
[...]
void my_init1 (void) { printf ("my_init1() #1\n"); }
void my_init2 (void) { printf ("my_init2() #2\n"); }

funcptr_t _my_init1 __attribute__ ((section ("newsect"))) = my_init1;
funcptr_t _my_init2 __attribute__ ((section ("newsect"))) = my_init2;

int
main (void)
{
[...]

Now build and execute:

$ gcc -Wall -O2 mycalls.c -o mycalls

$ ./mycalls 
call_p: 0x601028
my_init2() #2
call_p: 0x601030
my_init1() #1

The following is a section dump of the final executable object file:

$ objdump -t mycalls | sort | less
00000000004005a0 g     F .text     000000000000000a              my_init1
00000000004005b0 g     F .text     000000000000000a              my_init2
[...]
0000000000601028 g       *ABS*     0000000000000000              __start_newsect
0000000000601028 g     O newsect   0000000000000008              _my_init2
0000000000601028 l    d  newsect   0000000000000000              newsect
0000000000601030 g     O newsect   0000000000000008              _my_init1
0000000000601038 g       *ABS*     0000000000000000              __bss_start
0000000000601038 g       *ABS*     0000000000000000              _edata
0000000000601038 g       *ABS*     0000000000000000              __stop_newsect
[...]

where:

  • The F, g, l, O and d flags indicate the type of symbol i.e. function, global, local, object, and debugging, respectively.

  • .text and newsect indicate the text and newsect sections, respectively. Symbols listed against *ABS* reside in a section that is absolute i.e. not connected with any section.

NOTES:

  • The lines:

    #define data_attr         __attribute__ ((section ("newsect")))
    #define create_entry(fn)  funcptr_t _##fn data_attr = fn
    [...]
    create_entry (my_init1);
    create_entry (my_init2);
    

    Result in the generation and placement of the _my_init1 and _my_init2 function pointer symbols in the newsect section. However, note that function symbols my_init1 and my_init2 themselves reside in the ordinary .text section.

  • In general, specifying __attribute__((section("X"))) causes ld to add __start_X and __stop_X global symbols to delimit the newly defined section X. This is the reason why using the declarations:

    extern funcptr_t __start_newsect, __stop_newsect;
    

    did not result in undefined reference to __start_newsect and such like symbol resolution errors during the linking stage.

  • __start_newsect has the same address as the _my_init2 function pointer symbol and __stop_newsect has an address that is one plus the address of _my_init1. This arragement is what enables the following C source code snippet to work:

    funcptr_t *call_p;
    
    call_p = &__start_newsect;
    do {
        printf ("call_p: %p\n", call_p);
        (*call_p)();
        ++call_p;
    } while (call_p < &__stop_newsect);
    
  • Finally, since _my_init2 gets placed at the start of the function pointer table, my_init2 gets executed before my_init1. This has something to do with the way the symbols are encountered by as in the .s file generated by cc1. One way of enforcing deterministic order of function calling is to, first, implement the functions in separate source files to generate isolated relocatables e.g:

    $ cat mycalls.h
    #ifndef __MYCALLS_H__
    #define __MYCALLS_H__
    
    typedef void (*funcptr_t)(void);
    extern funcptr_t __start_newsect, __stop_newsect;
    
    #define data_attr         __attribute__ ((section ("newsect")))
    #define create_entry(fn)  funcptr_t _##fn data_attr = fn
    #endif
    
    $ cat mycalls_main.c
    #include <stdio.h>
    #include "mycalls.h"
    
    int main (void)
    {
        funcptr_t *call_p;
    
        call_p = &__start_newsect;
        do {
            printf ("call_p: %p\n", call_p);
            (*call_p)();
            ++call_p;
        } while (call_p < &__stop_newsect);
    
        return 0;
    }
    
    $ cat myinit1.c
    #include <stdio.h>
    #include "mycalls.h"
    
    void my_init1 (void) { printf ("my_init1() #1\n"); }
    create_entry (my_init1);
    
    $ cat myinit2.c
    #include <stdio.h>
    #include "mycalls.h"
    
    void my_init2 (void) { printf ("my_init2() #2\n"); }
    create_entry (my_init2);
    
    $ gcc -Wall -O2 -c mycalls_main.c myinit1.c myinit2.c
    

    When generating the final executable, ld will merge sections of the same type from all its input relocatable object files. ld scans the relocatables from left to right in the same sequential order that they appear on the compiler driver's command line. The symbols in a merged section will be arranged in the same order as the input relocatables that defined them were encountered. Therefore:

    $ gcc mycalls_main.o myinit1.o myinit2.o -o mycalls2
    
    $ ./mycalls2
    call_p: 0x601028
    my_init1() #1
    call_p: 0x601030
    my_init2() #2
    

    and,

    $ gcc mycalls_main.o myinit2.o myinit1.o -o mycalls2
    
    $ ./mycalls2
    call_p: 0x601028
    my_init2() #2
    call_p: 0x601030
    my_init1() #1
    

Resources and Further Reading

  • Understanding the Linux Kernel Initcall Mechanism, Trevor Woerner (Available Online).

  • ELF: From The Programmer's Perspective, Hongjiu Lu (Available Online)