1
\$\begingroup\$

I would like to add a const string identifier at the start locations of few functions. The reason is that I need to maintain a table of function pointers, the indexes of which are to be in a specific order. I need to verify that only the intended function pointers are placed at each index of the table. My idea is that if I place a known string identifier to the start of each function, it can be identified if the function pointers are misplaced in the table, by verifying what is present at the starting addresss of a function.

Eg.

void Foo()
{
   const char Foo_StringId[3] = "FOO";
   //Function code
}
void Bar()
{
   const char Bar_StringId[3] = "BAR";
   //Function code
}

FnPtr_t FnTable[2] = {&Foo,&Bar}; //Foo must be at index 0  and Bar at 1

void Check_FnTable()
{
    char* pFooId = (char*)FnTable[0];
    char* pBarId = (char*)FnTable[1];
    //if string pFooId is "FOO" and pBarId is "BAR", all good.
    //otherwise function pointers are misplaced 
}

The problem is that I am not able to place a string identifier at the start of functions, it's simply being optimized by the compiler. I tried using volatile in vain and __attribute__((__used__)) doesn't seem to be supported by the compiler(DIAB 5.9.6 for PowerPC). How can this be achieved?

As side questions,

  1. Would such an approach really place the string at the starting address of the function, so that the string can be accessed by casting the function pointer as char* ?
  2. Is there a better way to solve this problem of misplaced function pointers in a table?

Update: I need to perform the check because the function table is provided by a different binary executable(mostly from a different developer). I want to make sure that the table in the other binary is not messed up, probably because of a mistake from the developer, before using the table.

\$\endgroup\$
  • 2
    \$\begingroup\$ This seems like something a unit test should catch, rather than trying to code around potential developer errors. \$\endgroup\$ – Ron Beyer Oct 6 '19 at 1:01
  • \$\begingroup\$ other than undefined behavior what could cause the table it to get messed up? \$\endgroup\$ – Jasen Oct 6 '19 at 5:11
  • \$\begingroup\$ So is FnTable actually an 'extern' to your code? \$\endgroup\$ – jonk Oct 6 '19 at 5:28
  • \$\begingroup\$ There's no code that can reliably detect or recover from undefined behaviour. \$\endgroup\$ – Jasen Oct 6 '19 at 10:12
  • 1
    \$\begingroup\$ @RonBeyer The OP is only attempting to do it in runtime because they don't know how to do it any other way. See my answer below for a compile-time check. My point is that rather than running off to create tests, question if the code makes sense to begin with. \$\endgroup\$ – Lundin Oct 8 '19 at 12:02
2
\$\begingroup\$

It's not 100% clear in my mind what's going on to make you want to be paranoid in your situation. You say that you are maintaining a table of function pointers and need to verify them. But this actually suggests to me that other developer(s) are maintaining that table, not you, and that you don't trust that table and want a way to verify, independently, that certain function addresses in the table point to routines that you expect to be located there. (If you were maintaining the table yourself, then it seems to me that all you'd need to do is make sure that you put the right function pointers in the right places so there'd be no need for such paranoia.)

A table also implies to me that all of the functions have the same signature (declaration.)

To repeat what's already been written in various ways here, you don't really get to place data (string values) into the code space. Sure, back in the day when most computers were von Neumann, you could technically achieve it if the compiler provided non-standard methods for you. But in these days, with many Harvard and von Neumann machines around, C is pretty careful about remaining relevant to both architecture families. Suppose there existed a custom compiler keyword which would place the name of a function (or any given string you wanted to use) at the head of a function or at a reliable location nearby. This string would be in the code space. But on some processors (x86, for example) this space may be marked "execute-only" and therefore cannot even be read. Or in a Harvard machine, different instructions (or perhaps no instructions, at all) would have to be used to access the data, as data, rather than as code. All of this would make any such "additions" to a C compiler rather complex and of little general value so it would be very unlikely for a C compiler vendor to bother -- even if it were possible. But it's not even possible across the board, because as I just mentioned the x86 can have its code segments marked as "execute only" and this pretty much means the whole idea isn't going to fly.

Okay. So that's at least one argument about why you should abandon the idea of co-locating literals with function body code in C. And while I think there are statements in other answers that might be used to infer similarly, I've mentioned a very specific case where the hardware itself may prevent the idea and then there would be nothing that a compiler/linker/toolchain could do about it. Not even an __asm{} body can help you here. So abandon the idea.

So what should you do if you don't trust function pointers in a table? (I frankly haven't encountered a situation where I would want to distrust tables -- but my experience is admittedly limited to 40 years of C development by one person and that obviously doesn't cover every case.) You will need to pick your poison.

Let's assume that while your code uses a function pointer table for making the calls, it's still possible that other code (perhaps written by those other programmer(s)) may also directly call these functions, as well. Put another way, these functions you want to verify aren't ONLY called via the table but may also be called in any of the usual ways available to C code that has visibility to them.

One suggestion I can offer that doesn't necessarily have to break such existing code is to ask your counter-part(s) programmers writing/maintaining these routines to add one additional parameter to them. This added parameter can be positioned anywhere, I suppose, though I might suggest placing it at the end. This parameter would be a pointer to a special datatype pointer. If the pointer is null, then the code does the usual thing. But if the pointer isn't null, then the pointer is used to update the referenced location with a "special value," that may be a magic integer or a string literal.

To avoid breaking older code that might directly reference those functions, you can use an old trick. For modules that need to reference the functions, directly, they usually use an include file of some kind that declares the functions. In that file, use:

#define foo(p1, p2, ..., pn) ((foo)(p1, p2, ..., pn, 0))

Of course, the actual function will look something like:

extern <type> (foo)(<type> p1, <type> p2, ..., <type> pn, magiccode_t* pz) {
    if ( pz != 0 ) {
        // write a special, unique code into *pz and return
    }
    // Do normal things
    // .
    // .
    // . }

It's not "pretty" but it can get the job done.

By wrapping the function name with (), you keep the pre-processor from attempting to replace the macro using the definition that is found in the include file for it.

For your code that must test the function pointer in the table, you'd define a variable of type "magiccode_t" call it something like this:

    magiccode_t code;
    FnTable[0](0, 0, ..., 0, &code);
    if ( code != EXPECTEDMAGICCODE0 ) error;
    FnTable[1](0, 0, ..., 0, &code);
    if ( code != EXPECTEDMAGICCODE1 ) error;
    FnTable[2](0, 0, ..., 0, &code);
    if ( code != EXPECTEDMAGICCODE2 ) error;
    .    
    .    
    .    

An unfortunate detail is that your code, when calling the function through the table for normal usage, will need to include a 0 at the end of the parameter list which wasn't needed before. You could adjust things a little by using FnTable as a #define in your own code, rather than as an array element:

#define FnTable(idx, p1, p2, ..., pn) ((FnTable)[idx](p1, p2, ..., pn, 0))
#define FnTableTest(idx, pz)          ((FnTable)[idx](0, 0, ..., 0, pz))

Then your testing code would use FnTableTest() in order to get the magic code and test it. But the rest of your code would just use FnTable(i, p1, p2, ..., pn). This doesn't look exactly like an array, as the index itself appears to be a parameter instead. But it may be "livable" for you. In that case, your testing code might look more like:

    magiccode_t code;
    FnTableTest(0, &code);
    if ( code != EXPECTEDMAGICCODE0 ) error;
    FnTableTest(1, &code);
    if ( code != EXPECTEDMAGICCODE1 ) error;
    FnTableTest(2, &code);
    if ( code != EXPECTEDMAGICCODE2 ) error;
    .    
    .    
    .    

Just some thoughts.

Added: Above, I elected to show examples where the special parameter is added at the end of the parameter list for each function. But an advantage can be had if you place it into the first position, with C. In that case, you only need to pass that first parameter when requesting the magic code. You may need to cast the function pointer type before calling it that way to avoid error/warning messages, but that's easily done and the routines themselves won't attempt to look at other parameters not passed if the first parameter is not null. Just another variation of the above approach.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your assumption regarding why this test is needed is right. It is not a hard requirement, but since the executable containing the function table is coming from a totally different source, i just thought of validating the table before using it. Your suggestions look interesting. One point is that the routines provided by the table are ONLY accessed via the table, and are not directly accessed. It's not affecting the idea you proposed anyway, I think? \$\endgroup\$ – Soju T Varghese Oct 9 '19 at 9:42
  • \$\begingroup\$ @Soju I just wanted to cover all bases, when discussing the idea. It still works fine if you only use the table to access the routines. You can then avoid bothering with the individual #define macros I'd mentioned at the outset, so even less work. But it still requires cooperation, since those functions do have to have an added parameter and associated code in each routine that supports the idea. \$\endgroup\$ – jonk Oct 9 '19 at 14:55
  • \$\begingroup\$ Harvard vs von Neumann doesn't really matter because C compilers won't store string literals or local constants together with the machine code for the function no matter the CPU. \$\endgroup\$ – Lundin Oct 10 '19 at 8:30
  • \$\begingroup\$ @London I've written a C compiler and linker tools. And I worked on the Unix v6 kernel in 1978. I've a small idea what C compilers have done and can do. I've also a small idea about operating system program and process models over time. If you get specific, I'll deal with your challenges on that basis. If not, let's leave this topic for another day. However, if you are merely pointing out that sting literals defined or used for initialization aren't placed in the code segment(s), prior to linkage steps, then I agree, broadly speaking. \$\endgroup\$ – jonk Oct 10 '19 at 14:27
4
\$\begingroup\$

Sorry, but your idea won't work at all, for numerous reasons (see the bottom of this answer). You can't use strings for this. You should enumerate the functions with an unique index instead. The standard way is to use an enum.

Preferably, you should ensure data integrity of the table at compile-time. I wouldn't recommend having read/write function-pointer tables unless you have a very specialized case.

With a standard C compiler, you can do this:

typedef void func_t (void); // the function type

typedef enum
{
  FUNC_FOO,
  FUNC_BAR,
  FUNC_N  // represents the number of functions supported
} func_id_t;

func_t* const fntable [] =    // a read-only table of function pointers
{
  [FUNC_FOO] = foo,
  [FUNC_BAR] = bar,
};

_Static_assert(sizeof fntable / sizeof *fntable == FUNC_N, "fntable corrupt");

...

func_t* pFoo = fntable [FUNC_FOO];

Misc notes:

  • char* pFooId = (char*)FnTable[0]; wouldn't work reliably even if the string was stored inside the function. This is a highly fishy cast - we cannot go from function pointers to object pointers or the other way around. C doesn't specify what will happen - it is undefined behavior.

    If you know the physical address layout, you could convert a function pointer to uintptr_t, which is a portable integer type. Then do all arithmetic on that integer type, and from there you can go to object pointers if necessary.

  • What you attempted wouldn't work regardless, because if you had managed to allocate a const string, it would go into the .rodata section of the memory and not likely into the part of .text where the function itself is allocated. Strings in particular tend to be stored together by compilers, in order to utilize optimization techniques like "string pooling". Please study What resides in the different memory types of a microcontroller?. And the first binary you'd encounter inside the function would be related to ABI calling convention stacking of parameters etc anyhow.

  • Please note that you can use the standard feature __func__ inside a function, to get the equivalent of a static char[] null-terminated string containing the function's name "Foo" etc. So you could have done something like static const char* ptr = __func__; inside each function.

  • Never declare functions with empty parenthesis void foo() in C. That is obsolete style (since 20 years back!) and means that the function will accept any parameter. Instead, use void foo (void). C and C++ are different here.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I do like the use of compile-time checks here (+1) but the OP may not have control over the declaration of the function table (see the note at the bottom of the question). Also I'd be careful using C++ and C11+ features, some compilers for microcontrollers are stuck in C99. A cheap hack is #define _static_assert(cond) switch(0){case 0:case cond:;} for pre-C11. \$\endgroup\$ – Ron Beyer Oct 8 '19 at 12:29
  • \$\begingroup\$ @RonBeyer Well that remark should be solved with code review. C11 compilers have been available forever for all modern MCUs, there's no excuses to lag behind any longer. Static assert alone is worth the update. As for cheap pre-C11 hacks, #define static_assert(cond) typedef int arr[!!cond]; is more common and flexible (but still ugly as sin). Please note that a case must use integer constants, not integer constant expressions. \$\endgroup\$ – Lundin Oct 8 '19 at 14:16
  • \$\begingroup\$ @Lundin But is suppose the assertion technique mentioned in the answer only takes care of the number of entries in the table and not its contents? Just some trivia, "What resides in the different memory types of a microcontroller?" was asked by myself before three years :D . My bad, i completely overlooked the fact that string literals would just go to .rodata . \$\endgroup\$ – Soju T Varghese Oct 9 '19 at 9:52
  • 1
    \$\begingroup\$ @SojuTVarghese The designated initializers [FUNC_FOO] = ... guarantees the integrity of the contents. Should you forget to initialize one member, it will be set to zero. If you for some reason fear that some items may not be initialized, then you could create an invalid dummy entry at the beginning of the enum, FUNC_INVALID or some such, then check against that before calling the function pointer. \$\endgroup\$ – Lundin Oct 9 '19 at 10:27
2
\$\begingroup\$

The code at the beginning of a function needs to be actual executable function code. The data declarations that you are attempting to place at the top of the functions will be placed in the section of the program space allocated for data, or in the case of variables like you have declared as "const" will be collected together by the linker and will be placed at some part of the of the program code load area. They will not generally be encapsulated inside the function routines themselves.

It does not make sense to try casting the function pointer to a data type because the function pointer has to point to executable code so that the function can start up properly.

Lastly I fail to understand why you have to be checking the function pointers in the function pointer array. The compiler will put the function addresses into the table in the order that you place the function names in the initializer list. If you cannot trust the compiler and linker to build the code as you write it then it is time to move on to a different tool set.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Sorry, I forgot to mention that the project setup is such that the function table is provided by a different binary executable(mostly from a different developer). I want to make sure that the table in the other binary is not messed up, probably because of a mistake from the developer and not the compiler. \$\endgroup\$ – Soju T Varghese Oct 6 '19 at 0:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.