Can a running C program access its own symbol table?

JimKleck picture JimKleck · Jun 29, 2012 · Viewed 7.7k times · Source

I have a linux C program that handles request sent to a TCP socket (bound to a particular port). I want to be able to query the internal state of the C program via a request to that port, but I dont want to hard code what global variables can be queried. Thus I want the query to contain the string name of a global and the C code to look that string up in the symbol table to find its address and then send its value back over the TCP socket. Of course the symbol table must not have been stripped. So can the C program even locate its own symbol table, and is there a library interface for looking up symbols given their name? This is an ELF executable C program built with gcc.

Answer

Dietrich Epp picture Dietrich Epp · Jun 29, 2012

This is actually fairly easy. You use dlopen / dlsym to access symbols. In order for this to work, the symbols have to be present in the dynamic symbol table. There are multiple symbol tables!

#include <dlfcn.h>
#include <stdio.h>

__attribute__((visibility("default")))
const char A[] = "Value of A";

__attribute__((visibility("hidden")))
const char B[] = "Value of B";

const char C[] = "Value of C";

int main(int argc, char *argv[])
{
    void *hdl;
    const char *ptr;
    int i;

    hdl = dlopen(NULL, 0);
    for (i = 1; i < argc; ++i) {
        ptr = dlsym(hdl, argv[i]);
        printf("%s = %s\n", argv[i], ptr);
    }
    return 0;
}

In order to add all symbols to the dynamic symbol table, use -Wl,--export-dynamic. If you want to remove most symbols from the symbol table (recommended), set -fvisibility=hidden and then explicitly add the symbols you want with __attribute__((visibility("default"))) or one of the other methods.

~ $ gcc dlopentest.c -Wall -Wextra -ldl
~ $ ./a.out A B C
A = (null)
B = (null)
C = (null)
~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic
~ $ ./a.out A B C
A = Value of A
B = (null)
C = Value of C
~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic -fvisibility=hidden
~ $ ./a.out A B C
A = Value of A
B = (null)
C = (null)

Safety

Notice that there is a lot of room for bad behavior.

$ ./a.out printf
printf = ▯▯▯▯ (garbage)

If you want this to be safe, you should create a whitelist of permissible symbols.