How can I differentiate static functions with nm or readelf output in C

5.9k views Asked by At

I am trying to process the output of a nm or readelf -s on an executable. However, I am having trouble differentiating static functions from each other in the output.

Here is what I am working with:

test.c

static int foo() {
    int x = 6;
}

main() {}

other.c

static int foo() {
    int x = 5;
}

I compile these like so:

gcc -o test test.c other.c

And then run a nm command to get all the symbols:

nm test

Among which the following two symbols (for my static functions) appear:

00000000004004ed t foo
0000000000400500 t foo

Is there a method to be able to distinguish which file the specific foo function appeared from? Or will I need to do some magic before compiling to get this to work?

I should add that for my use case, I have access to the final binary and the object files used by it, but I cannot actually build it myself to ensure that it has a symbol table.

Thanks!

4

There are 4 answers

0
Mike Kinghan On BEST ANSWER

Your question assumes that, given an executable, you can always discover the names of the static (local) functions that were compiled into it, using nm or another tool. Thus you will be able to see when two or more such names are the same and to raise the question of how to discover what source files they were compiled from.

However, that assumption is false. In the case of gcc, if files are compiled with optimization -O0 then local symbols will be emitted in the object file symbol table. -O0 is the default, so it applies in the case of your:

gcc -o test test.c other.c

But if files are compiled at any higher optimization level - as they certainly will be for a release build - then local symbols are omitted from the object files symbol table. So the linker never even sees them. So you cannot recover them from the executable with nm or anything else.

Compile your sample files with:

gcc -O1 -o test test.c other.c

then nm test again, and you will observe that the:

00000000004004ed t foo
0000000000400500 t foo

have vanished, together with all of the other static function names.

In that case, if as you say you cannot control how the executable is built, then you cannot ensure that it is even possible for your question to arise.

If you could control how the executable is built to ensure that files are compiled with -O0, then there are several ways in which you can tie the static function names to source files. Two equally simple ones would be:

readelf -s test

and

objdump -t test

each of which will list a source file name at the head of each chunk of symbols that come from it.

(And if it needs saying, the gdb approach suggested by by @Amol does not escape the restriction that the executable must have been compiled with optimization -O0)

0
thurizas On

You may need to read the ELF symbol table and extract ELF32_ST_BIND value.

According to the ELF specification (see http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf) the values for ELF32_ST_BIND can be:

       Name         Value
     STB_LOCAL      0
     STB_GLOBAL     1
     STB_WEAK       2
     STB_LOPROC    13
     STB_HIPROC    15

Where STB_LOCAL is defined to be "Local symbols are not visible outside the object file containing their definition. Local symbols of the same name may exist in multiple files without interfering with each other." which seem to match up fairly well with static C functions.

For example, taking you sample and modifying it slightly:

    test.c:

    static int foo() {
        int x = 5;
    }

    int bar()
    {
         int y = 6;
    }

    main() {}

    other.c:

    static int foo()
    {
        int x = 7;
    }

and compiling with gcc -o test test.c other.c and looking at the symbol table (lots of entries removed):

    readelf -s test
    Num:    Value          Size Type    Bind   Vis      Ndx Name
    37: 00000000004004f0    13 FUNC    LOCAL  DEFAULT   13 foo
    39: 0000000000400510    13 FUNC    LOCAL  DEFAULT   13 foo
    52: 00000000004004fd    13 FUNC    GLOBAL DEFAULT   13 bar

We can see that the two static functions show up as LOCAL and the one `normal' function shows up a GLOBAL

Note: while this method will work with non-debug files, if the final file is stripped, we can not use this method.

0
4566976 On

If it should work on any stripped executable you could implant a string into the functions and search for them in the executable.

#define STR1(x) #x
#define STR(x) STR1(x)
#define FUNCID(funcname) __asm__ __volatile__ (\
    "jmp 1f;"\
    ".string \"" __FILE__ "/" STR(funcname) "()\";"\
    "1:"\
)

static int foo() {
    FUNCID(foo);
    return rand();
}
1
Amol Saindane On

I tried out following sequence.

If you have stripped output file with no debugging symbols then using gdb you can create object file. Follow the commands as below :

$ gdb a.out

will give following as output

Reading symbols from /home/amol/amol/a.out...(no debugging symbols found)...done.

Then (gdb) will come in terminal

Give following commands in sequence ((gdb) prompt come by default as you go on typing commands)

 (gdb) maint print symbols filename
 (gdb) maint print psymbols filename
 (gdb) maint print msymbols filename

Now in your folder you can see one file with named as filename. Open this file in text editor then you can see information as below :

[ 8] t 0x80483b4 foo section .text  test.c
[ 9] T 0x80483c3 main section .text  other.c
[10] t 0x80483c8 foo section .text  other.c

Here you can clearly see that which foo() function is come from which .c file. Hope this helps for you.