Home > Mobile >  Function address in a Position Independent Executable
Function address in a Position Independent Executable

Time:08-28

I created a small unit test library in C.

Its main feature is the fact that you don't need to register your test functions, they are identified as test functions because they have a predefined prefix (test_).

For example, if you want to create a test function, you can write something like this:

int test_abc(void *t)
{
    ...
}

Yes, just like in Go.

To find the test functions, the runner:

  1. takes the name of the executable from argv[0];
  2. parses the ELF sections to find the symbol table;
  3. from the symbol table, takes all the functions named test_*;
  4. treats the addresses from the symbol table as function pointers;
  5. invoke the test functions.

For PIE binaries, there is one additional step. To find the load address for the test functions, I assume there is a common offset that applies to all functions. To figure out the offset, I subtract the address of main (runtime, function pointer) from the address of main read from the symbol table.

All the things described above are working fine: https://github.com/rodrigo-dc/testprefix

However, as far as I understood, function pointer arithmetic is not allowed by the C99 standard.

Given that I have the address from the symbol table - Is there a reliable way to get the runtime address of functions (in case of PIE binaries)?

I was hoping for some linker variable, some base address, or anything like that.

CodePudding user response:

Because you have an ELF executable, this probably precludes "funny" architectures (e.g. Intel 8051, PIC, etc.) that might have segmented or non-linear, non-contiguous address spaces.

So, you [probably] can use the method you've described with main to get the actual address. You just need to convert to/from either char * or uintptr_t types so you are using byte offsets/differences.


But, you can also create a unified table of pointers to the various functions using by creating descriptor structs that are placed in a special linker section of your choosing using (e.g.) __attribute__((section("mysection"))

Here is some code that shows what I mean:

#include <stdio.h>

typedef struct {
    int (*test_func)(void *);           // pointer to test function
    const char *test_name;              // name of the test
    int test_retval;                    // test return value

    // more data ...
    int test_xtra;
} testctl_t;

// define a struct instance for a given test
#define ATTACH_TEST(_func) \
    testctl_t _func##_ctl __attribute__((section("testctl"))) = { \
        .test_func = _func, \
        .test_name = #_func \
    }

// advance to next struct (must be 16 byte aligned)
#define TESTNEXT(_test) \
    (testctl_t *) (((char *) _test)   asiz)

int
test_abc(void *t)
{
    printf("test_abc: hello\n");
    return 1;
}
ATTACH_TEST(test_abc);

int
test_def(void *t)
{
    printf("test_def: hello\n");
    return 2;
}
ATTACH_TEST(test_def);

int
main(void)
{
    // these are special symbols defined by the linker for our special linker
    // section that denote the start/end of the section (similar to
    // _etext/_edata)
    extern testctl_t __start_testctl;
    extern testctl_t __stop_testctl;

    size_t rsiz = sizeof(testctl_t);
    size_t asiz;
    testctl_t *test;

    // align the size to a 16 byte boundary
    asiz = rsiz;
    asiz  = 15;
    asiz /= 16;
    asiz *= 16;

    // show the struct sizes
    printf("main: sizeof(testctl_t)=%zx/%zx\n",rsiz,asiz);

    // section start and stop symbol addresses
    printf("main: start=%p stop=%p\n",&__start_testctl,&__stop_testctl);

    // cross check of expected pointer values
    printf("main: test_abc=%p test_abc_ctl=%p\n",test_abc,&test_abc_ctl);
    printf("main: test_def=%p test_def_ctl=%p\n",test_def,&test_def_ctl);

    for (test = &__start_testctl;  test < &__stop_testctl;
        test = TESTNEXT(test)) {
        printf("\n");

        // show the address of our test descriptor struct and the pointer to
        // the function
        printf("main: test=%p test_func=%p\n",test,test->test_func);

        printf("main: calling %s ...\n",test->test_name);
        test->test_retval = test->test_func(test);

        printf("main: return is %d\n",test->test_retval);
    }

    return 0;
}

Here is the program output:

main: sizeof(testctl_t)=18/20
main: start=0x404040 stop=0x404078
main: test_abc=0x401146 test_abc_ctl=0x404040
main: test_def=0x401163 test_def_ctl=0x404060

main: test=0x404040 test_func=0x401146
main: calling test_abc ...
test_abc: hello
main: return is 1

main: test=0x404060 test_func=0x401163
main: calling test_def ...
test_def: hello
main: return is 2

CodePudding user response:

Is there a reliable way to get the runtime address of functions (in case of PIE binaries)?

Yes: see this answer, and also the comment about using dladdr().

P.S. Note that taking address of main in C is not allowed.

  • Related