Sunday, February 19, 2012

How to get function's name from function's pointer in C? (Windows)

Suppose that I am debugging a code written on C which deals with a pointer to function. This pointer is initialized somewhere else and may actually point to function1, function2 etc. I need to find out
the real name of the function and print this information to the log, ideally together with source file name and line number (all this information is available if debugging information is turned on).

Yes, I know that I can set a breakpoint and see the actual value of pointer in debug windows. But sometimes I find a log a bit more useful.

Of course there is no reliable and cross-platform solution. The C language itself does not provide the possibilities like this. However, we may find API for certain platforms and/or certain debugging information.

For instance, in case of Windows the solution can be found with the help of DbgHelp functions: SymGetSymFromAddr64 and SymGetLineFromAddr64.

To be able to call both of them, we need to enable debug information. To be able to use SymGetLineFromAddr64, we need to turn on profile information as well (although it is not mentioned in the documentation, but it seems that SymGetLineFromAddr64 is not working without profile information).

The program needs to be linked with Dbghelp.lib.

Before their use we must call SymInitialize.

Now let's put it all together:

#if defined _DEBUG

#include <dbghelp.h>

BOOL InitDebug()
{
    BOOL initRes;

  SymSetOptions(SYMOPT_LOAD_LINES);

    initRes = SymInitialize(GetCurrentProcess(), NULL, TRUE);

    if (!initRes)
    {
        printf(_T("SymInitialize failed with error %d"), GetLastError());
    }

    return initRes;
}

BOOL TracePointerInfo(DWORD64 addr)
{
    char symbolName[MAX_SYM_NAME + 1];
    char buffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_SYM_NAME*sizeof(TCHAR)] = {0};
    IMAGEHLP_LINE64 line;
    DWORD64 dis64 = 0;
    DWORD dis = 0;
    IMAGEHLP_SYMBOL64 *pSym = NULL;
    BOOL res;

    pSym = (IMAGEHLP_SYMBOL64 *) buffer;
    pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
    pSym->MaxNameLength = MAX_PATH;

    res = SymGetSymFromAddr64(GetCurrentProcess(), addr, &dis64, pSym);
    if (!res)
    {
        /* TODO: call your trace function instead of printf */
        printf("SymGetSymFromAddr64 fails, error=%ld\n", GetLastError());
        return FALSE;
    }
    else
    {
        strcpy(symbolName, pSym->Name);
    }

    memset(&line, 0, sizeof(line));
    line.SizeOfStruct = sizeof(line);
    res = SymGetLineFromAddr64(GetCurrentProcess(), addr, &dis, &line);

    if (!res)
    {
        printf("SymGetLineFromAddr64 fails, error=%ld\n", GetLastError());
        return FALSE;
    }
    else
    {
        printf("function=%s (%s, %d)\n", symbolName, line.FileName, line.LineNumber);
    }

    return TRUE;
}

#define TRACE_POINTER_INIT InitDebug()
#define TRACE_POINTER(f) TracePointerInfo((DWORD64)(f))
#else

#define TRACE_POINTER_INIT
#define TRACE_POINTER(f)

#endif

To use it, call TRACE_POINTER_INIT somewhere in the beginning of your program.
To trace information about function pointer myfunction, use
TRACE_POINTER(myfunction);

Possible problems and how to fix them:
1. SymGetSymFromAddr64 fails, last error is 487 (Attempt to access invalid address. ). Answer: most likely debug information is not found. Rebuild the project for Debug.

2. SymGetSymFromAddr64 fails, last error is 6 (The handle is invalid). Answer: most likely SymInitialize was not called.

3. SymGetSymFromAddr64 succeed but SymGetLineFromAddr64 fails, last error is 487. That was the most tricky for me: documentation says nothing about it. It seems that you have to enable profile information (Project->Properties, Configuration Properties, Linker, Advanced, make sure that Profile is "Enable ..."). However I found it by playing with options. I didn't find any explanation in the documentation.

No comments:

Post a Comment