Meaning of the value column in the elf symbol table - c

I have an elf binary which has the following dynsym symbol table as output by readelf:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 0000000000400440 0 FUNC GLOBAL DEFAULT UND printf#GLIBC_2.2.5 (2)
4: 0000000000400460 0 FUNC GLOBAL DEFAULT UND fgets#GLIBC_2.2.5 (2)
What does the value column mean? Since this table has 400440 for printf, does that mean that the dynamic linker has to map printf at that address? If yes, how is this value decided? Is it random?
EDIT: Also, this is linux x86-64 with gcc

It appears that the value of undefined dynamic symbols of function types is just the address of their entry in the PLT. Likewise, the values of entries for variables is probably just their entry in the GOT.

Related

Why some global variables' size is zero in symbol table?

Here is code:
wubing#ubuntu:~/Downloads$ readelf -s testVar1.o
Symbol table '.symtab' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS testVar1.cc
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
**8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 uninit1
9: 0000000000000004 4 OBJECT GLOBAL DEFAULT 3 uninit2
10: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 uninit3
11: 0000000000000004 4 OBJECT GLOBAL DEFAULT 2 unitit4**
12: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 main
wubing#ubuntu:~/Downloads$ cat testVar1.cc
int uninit1;
int uninit2;
int uninit3=3;
int unitit4=4;
int main()
{
;
}
Why some global variables' size is zero in symbol table?
Look, the marked variables don't have zero, but 4 in the Size column, just uninit1 and uninit3 have zero in the Value column, which is the segment address offset. And if you're about to ask why both have the same offset: They're in different segments (Ndx 3 and 2, probably BSS and DATA).

Hide symbols from a 3rd party .a file that is linked into a .so file

I am building a shared (.so) library that is composed of several .a files and a thin API layer that invokes them. I only want my API and external dependencies to be visible, so I build my code using the "hidden" visibility offered by GCC (-fvisibility=hidden).
However, one of the libraries is a proprietary third party .a file (which we have paid to use) and I only have access to its binary. When I link it statically into my .so file, its symbols are visible in my .so's dynamic symbol table. I'm guessing that this is because the library was not built with the hidden visibility options. I'd rather keep these functions hidden as the manage a sensitive part of our software and I don't want third parties linking to those symbols.
Is there any way that I can mark these symbols as "hidden" after the fact so that they do not appear in my .so file's symbol list? I have looked at objdump and objcopy but I'm having a hard time with the terminology.
Other things I have tried:
Extract a .o from the .a and try recompiling as described here:
how to add prebuilt object files to executable in cmake
Here is a worked example of how to solve your problem.
This is the source for the proprietary static library that you can't recompile:
$ cat tpa.c
int tpa(void)
{
return 2;
}
$ cat tpb.c
int tpb(void)
{
return 3;
}
The library, libtp.a, must have been built essentially like this1:
$ gcc -fPIC -c -O1 tpa.c tpb.c
$ ar rcs libtp.a tpa.o tpb.o
The symbol tables of tpa.o and tpb.o are:-
$ readelf -s libtp.a
File: libtp.a(tpa.o)
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS tpa.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 7
8: 0000000000000000 0 SECTION LOCAL DEFAULT 4
9: 0000000000000000 10 FUNC GLOBAL DEFAULT 1 tpa
File: libtp.a(tpb.o)
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS tpb.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 7
8: 0000000000000000 0 SECTION LOCAL DEFAULT 4
9: 0000000000000000 10 FUNC GLOBAL DEFAULT 1 tpb
where you see that both of the function symbols tpa and tpb are GLOBAL
( = available for linkage) and have DEFAULT dynamic visibility, not HIDDEN.
Now here's the source code for your own static library, libus.a
$ cat usa.c
int usa(void)
{
return 5;
}
$ cat usb.c
int usb(void)
{
return 7;
}
Which you build like this:
$ gcc -fPIC -c -O1 -fvisibility=hidden usa.c usb.c
$ ar rcs libus.a usa.o usb.o
The function symbols in libus.a are also GLOBAL but their dynamic
visibility is HIDDEN:-
$ readelf -s libus.a
File: libus.a(usa.o)
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS usa.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 7
8: 0000000000000000 0 SECTION LOCAL DEFAULT 4
9: 0000000000000000 10 FUNC GLOBAL HIDDEN 1 usa
File: libus.a(usb.o)
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS usb.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 7
8: 0000000000000000 0 SECTION LOCAL DEFAULT 4
9: 0000000000000000 10 FUNC GLOBAL HIDDEN 1 usb
Here's the source code for your shared library:
$ cat usc.c
extern int tpa(void);
extern int tpb(void);
extern int usa(void);
extern int usb(void);
int usc(void)
{
return tpa() * tpb() * usa() * usb();
}
Which you compile:-
$ gcc -fPIC -c -O1 usc.c
Now you want to link usc.o, libtp.a and libus.a in your shared library libsus.so. If you
do it the ordinary way:
$ gcc -shared -o libsus.so usc.o -L. -ltp -lus
then you find:
$ readelf --dyn-syms libsus.so
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __cxa_finalize
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000001139 38 FUNC GLOBAL DEFAULT 12 usc
6: 000000000000115f 10 FUNC GLOBAL DEFAULT 12 tpa
7: 0000000000001169 10 FUNC GLOBAL DEFAULT 12 tpb
that the HIDDEN visibility symbols from libus.a are absent from the dynamic
symbol table, but the DEFAULT visibility symbols from libtp.a are included,
which you don't want.
To exclude the latter as well, link your shared library as follows:
$ gcc -shared -o libsus.so usc.o -L. -ltp -lus -Wl,--exclude-libs=libtp.a
Then the dynamic symbol table becomes:
$ readelf --dyn-syms libsus.so
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __cxa_finalize
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 00000000000010f9 38 FUNC GLOBAL DEFAULT 10 usc
as you want.
The linker option --exclude-libs is documented:
--exclude-libs lib,lib,...
Specifies a list of archive libraries from which symbols should not be automatically exported.
The library names may be delimited by commas or colons. Specifying --exclude-libs ALL excludes symbols in all archive libraries from automatic export.
...
For ELF targeted ports, symbols affected by this option will be treated as hidden.
For reassurance that the the tp* symbol definitions have been linked, you
can still see them in the full symbol table of the shared library:
$ readelf -s libsus.so | egrep 'FUNC.*(us|tp)(a|b|c)'
5: 00000000000010f9 38 FUNC GLOBAL DEFAULT 10 usc
41: 0000000000001133 10 FUNC LOCAL DEFAULT 10 usa
44: 000000000000111f 10 FUNC LOCAL DEFAULT 10 tpa
46: 000000000000113d 10 FUNC LOCAL DEFAULT 10 usb
48: 0000000000001129 10 FUNC LOCAL DEFAULT 10 tpb
50: 00000000000010f9 38 FUNC GLOBAL DEFAULT 10 usc
Just like the explicity hidden us* symbols, they become LOCAL, not available for further linkage. (You see usc twice in the grep because it is listed as both a global and a dynamic symbol).
And as you can infer from this, we need not have troubled to compile our
own us* code with -fvisibility=hidden, as long as we were going to archive
it in libus.a for further linkage. We could have linked the shared library like:
$ gcc -shared -o libsus.so usc.o -L. -ltp -lus -Wl,--exclude-libs=libtp.a,libus.a
with the same effect.
[1] I specify -fPIC explicitly to be sure of generating position-independent
object code that I can link in a DSO, but this has been the GCC default since
GCC 6.

Incorrect output of `nm` on GCC LTO fat object files

If I have tmp.c:
char constantFOO[0x12];
char constantBAR[0x34];
I see gcc -c tmp.c -o tmp.o && nm tmp.o shows
0000000000000034 C constantBAR
0000000000000012 C constantFOO
But if I compile with -flto -ffat-lto-objects, nm outputs zeros for the symbol values:
00000000 C constantBAR
00000000 C constantFOO
I can the 34 and 12 values in a hexdump of both .o files.
My questions are
Is the behavior of nm on the LTO fat file expected? Am I just giving it input it's not expecting and it's outputting garbage?
What explains the original output (symbol value matching uninitialized array length)? This question didn't seem to help for the question of arrays, but maybe I misunderstood.
I compiled your tmp.c both with and without -flto -ffat-lto-objects, in -S mode (output assembly language), using GCC 8.3. In both cases, the same basic definitions of your constants are emitted:
.comm constantFOO,18,16
.comm constantBAR,52,32
Most of the additional data emitted by LTO goes into ELF sections named .gnu.lto_.something. LTO mode adds an additional marker object:
.comm __gnu_lto_v1,1,1
appears in the LTO-compiled object but not in the one without.
On its face, this shouldn't affect the output of nm for these symbols at all, and the lower-level tool readelf -s produces matching output for them:
$ readelf -s tmp-normal.o
Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 4
7: 0000000000000010 18 OBJECT GLOBAL DEFAULT COM constantFOO
8: 0000000000000020 52 OBJECT GLOBAL DEFAULT COM constantBAR
$ readelf -s tmp-lto.o
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 4
6: 0000000000000000 0 SECTION LOCAL DEFAULT 5
7: 0000000000000000 0 SECTION LOCAL DEFAULT 6
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 9
11: 0000000000000000 0 SECTION LOCAL DEFAULT 10
12: 0000000000000000 0 SECTION LOCAL DEFAULT 12
13: 0000000000000000 0 SECTION LOCAL DEFAULT 11
14: 0000000000000010 18 OBJECT GLOBAL DEFAULT COM constantFOO
15: 0000000000000020 52 OBJECT GLOBAL DEFAULT COM constantBAR
16: 0000000000000001 1 OBJECT GLOBAL DEFAULT COM __gnu_lto_v1
Therefore I believe that the behavior of nm is a bug, which should be reported to the maintainers of GNU binutils (see https://sourceware.org/binutils/).
As for the "original output" with symbol value matching array length, what's going on is that normally a symbol's value as shown by nm is its offset within its section of the object file. Common symbols, however, are not in any section and do not have an offset, so nm prints the size of the symbol as its value. This is, IIRC, historical behavior going all the way back to whichever iteration of System V added support for FORTRAN-like common data. Notice how readelf -s prints 18 and 52 as the sizes of the objects, and the third argument to .comm (the desired alignment of each symbol) as their values.
If you compile with -fno-common you will see different output:
$ gcc -c -fno-common tmp.c -o tmp-nc.o
$ nm tmp-nc.o
0000000000000020 B constantBAR
0000000000000000 B constantFOO
$ readelf -s tmp-nc.o | grep constant
7: 0000000000000000 18 OBJECT GLOBAL DEFAULT 3 constantFOO
8: 0000000000000020 52 OBJECT GLOBAL DEFAULT 3 constantBAR
because now your arrays are in the .bss section and have a defined offset within that section.
Note that char constantFOO[0x12]; defines a writable array of 0x12 chars. If you want it actually to be constant you need to say const char. (And then it will be put in the .rodata section of the object file and the output of nm and readelf will be different yet again.)

gcc debug symbols (-g flag) vs linker's -rdynamic option

glibc provides backtrace() and backtrace_symbols() to get the stack trace of a running program. But for this to work the program has to be built with linker's -rdynamic flag.
What is the difference between -g flag passed to gcc vs linker's -rdynamic flag ? For a sample code I did readelf to compare the outputs. -rdynamic seems to produce more info under Symbol table '.dynsym' But I am not quite sure what the additional info is.
Even if I strip a program binary built using -rdynamic, backtrace_symbols() continue to work.
When strip removes all the symbols from the binary why is it leaving behind whatever was added by the -rdynamic flag ?
Edit: Follow-up questions based on Mat's response below..
For the same sample code you took this is the difference I see with -g & -rdynamic
without any option..
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 218 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 70 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
2: 000000000040021c 0 SECTION LOCAL DEFAULT 2
with -g there are more sections, more entries in .symtab table but .dynsym remains the same..
[26] .debug_aranges PROGBITS 0000000000000000 0000095c
0000000000000030 0000000000000000 0 0 1
[27] .debug_pubnames PROGBITS 0000000000000000 0000098c
0000000000000023 0000000000000000 0 0 1
[28] .debug_info PROGBITS 0000000000000000 000009af
00000000000000a9 0000000000000000 0 0 1
[29] .debug_abbrev PROGBITS 0000000000000000 00000a58
0000000000000047 0000000000000000 0 0 1
[30] .debug_line PROGBITS 0000000000000000 00000a9f
0000000000000038 0000000000000000 0 0 1
[31] .debug_frame PROGBITS 0000000000000000 00000ad8
0000000000000058 0000000000000000 0 0 8
[32] .debug_loc PROGBITS 0000000000000000 00000b30
0000000000000098 0000000000000000 0 0 1
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 218 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 77 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
with -rdynamic no additional debug sections, .symtab entries are 70 (same as vanilla gcc invocation), but more .dynsym entries..
Symbol table '.dynsym' contains 19 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 218 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
2: 00000000005008e8 0 OBJECT GLOBAL DEFAULT ABS _DYNAMIC
3: 0000000000400750 57 FUNC GLOBAL DEFAULT 12 __libc_csu_fini
4: 00000000004005e0 0 FUNC GLOBAL DEFAULT 10 _init
5: 0000000000400620 0 FUNC GLOBAL DEFAULT 12 _start
6: 00000000004006f0 86 FUNC GLOBAL DEFAULT 12 __libc_csu_init
7: 0000000000500ab8 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
8: 00000000004006de 16 FUNC GLOBAL DEFAULT 12 main
9: 0000000000500aa0 0 NOTYPE WEAK DEFAULT 23 data_start
10: 00000000004007c8 0 FUNC GLOBAL DEFAULT 13 _fini
11: 00000000004006d8 6 FUNC GLOBAL DEFAULT 12 foo
12: 0000000000500ab8 0 NOTYPE GLOBAL DEFAULT ABS _edata
13: 0000000000500a80 0 OBJECT GLOBAL DEFAULT ABS _GLOBAL_OFFSET_TABLE_
14: 0000000000500ac0 0 NOTYPE GLOBAL DEFAULT ABS _end
15: 00000000004007d8 4 OBJECT GLOBAL DEFAULT 14 _IO_stdin_used
16: 0000000000500aa0 0 NOTYPE GLOBAL DEFAULT 23 __data_start
17: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
18: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 70 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
2: 000000000040021c 0 SECTION LOCAL DEFAULT 2
Now these are the questions I have..
In gdb you can do bt to get the bactrace. If that works with just -g why do we need -rdynamic for backtrace_symbols to work ?
Comparing the additions to .symtab with -g & additions to .dynsym with -rdynamic they are not exactly the same.. Does either one provide better debugging info compared to the other ?
FWIW, size of the output produced is like this: with -g > with -rdynamic > with neither option
What exactly is the usage of .dynsym ? Is it all the symbols exported by this binary ? In that case why is foo going into .dynsym because we are not compiling the code as a library.
If I link my code using all static libraries then -rdynamic is not needed for backtrace_symbols to work ?
According to the docs:
This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table.
Those are not debug symbols, they are dynamic linker symbols. Those are not removed by strip since it would (in most cases) break the executable - they are used by the runtime linker to do the final link stage of your executable.
Example:
$ cat t.c
void foo() {}
int main() { foo(); return 0; }
Compile and link without -rdynamic (and no optimizations, obviously)
$ gcc -O0 -o t t.c
$ readelf -s t
Symbol table '.dynsym' contains 3 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 50 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400270 0 SECTION LOCAL DEFAULT 1
....
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS t.c
28: 0000000000600e14 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
29: 0000000000600e40 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
So the executable has a .symtab with everything. But notice that .dynsym doesn't mention foo at all - it has the bare essentials in there. This is not enough information for backtrace_symbols to work. It relies on the information present in that section to match code addresses with function names.
Now compile with -rdynamic:
$ gcc -O0 -o t t.c -rdynamic
$ readelf -s t
Symbol table '.dynsym' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS _edata
5: 0000000000601008 0 NOTYPE GLOBAL DEFAULT 24 __data_start
6: 0000000000400734 6 FUNC GLOBAL DEFAULT 13 foo
7: 0000000000601028 0 NOTYPE GLOBAL DEFAULT ABS _end
8: 0000000000601008 0 NOTYPE WEAK DEFAULT 24 data_start
9: 0000000000400838 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
10: 0000000000400750 136 FUNC GLOBAL DEFAULT 13 __libc_csu_init
11: 0000000000400650 0 FUNC GLOBAL DEFAULT 13 _start
12: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
13: 000000000040073a 16 FUNC GLOBAL DEFAULT 13 main
14: 0000000000400618 0 FUNC GLOBAL DEFAULT 11 _init
15: 00000000004007e0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
16: 0000000000400828 0 FUNC GLOBAL DEFAULT 14 _fini
Symbol table '.symtab' contains 50 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400270 0 SECTION LOCAL DEFAULT 1
....
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS t.c
28: 0000000000600e14 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
29: 0000000000600e40 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
Same thing for symbols in .symtab, but now foo has a symbol in the dynamic symbol section (and a bunch of other symbols appear there now too). This makes backtrace_symbols work - it now has enough information (in most cases) to map code addresses with function names.
Strip that:
$ strip --strip-all t
$ readelf -s t
Symbol table '.dynsym' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS _edata
5: 0000000000601008 0 NOTYPE GLOBAL DEFAULT 24 __data_start
6: 0000000000400734 6 FUNC GLOBAL DEFAULT 13 foo
7: 0000000000601028 0 NOTYPE GLOBAL DEFAULT ABS _end
8: 0000000000601008 0 NOTYPE WEAK DEFAULT 24 data_start
9: 0000000000400838 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
10: 0000000000400750 136 FUNC GLOBAL DEFAULT 13 __libc_csu_init
11: 0000000000400650 0 FUNC GLOBAL DEFAULT 13 _start
12: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
13: 000000000040073a 16 FUNC GLOBAL DEFAULT 13 main
14: 0000000000400618 0 FUNC GLOBAL DEFAULT 11 _init
15: 00000000004007e0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
16: 0000000000400828 0 FUNC GLOBAL DEFAULT 14 _fini
$ ./t
$
Now .symtab is gone, but the dynamic symbol table is still there, and the executable runs. So backtrace_symbols still works too.
Strip the dynamic symbol table:
$ strip -R .dynsym t
$ ./t
./t: relocation error: ./t: symbol , version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference
... and you get a broken executable.
An interesting read for what .symtab and .dynsym are used for is here: Inside ELF Symbol Tables. One of the things to note is that .symtab is not needed at runtime, so it is discarded by the loader. That section does not remain in the process's memory. .dynsym, on the otherhand, is needed at runtime, so it is kept in the process image. So it is available for things like backtrace_symbols to gather information about the current process from within itself.
So in short:
dynamic symbols are not stripped by strip since that would render the executable non-loadable
backtrace_symbols needs dynamic symbols to figure out what code belongs which function
backtrace_symbols does not use debugging symbols
Hence the behavior you noticed.
For your specific questions:
gdb is a debugger. It uses debug information in the executable and libraries to display relevant information. It is much more complex than backtrace_symbols, and inspects the actual files on your drive in addition to the live process. backtrace_symbols does not, it is entirely in-process - so it cannot access sections that are not loaded into the executable image. Debug sections are not loaded into the runtime image, so it can't use them.
.dynsym is not a debugging section. It is a section used by the dynamic linker. .symbtab isn't a debugging section either, but it can be used by debugger that have access to the executable (and library) files. -rdynamic does not generate debug sections, only that extended dynamic symbol table. The executable growth from -rdynamic depends entirely on the number of symbols in that executable (and alignment/padding considerations). It should be considerably less than -g.
Except for statically linked binaries, executables need external dependencies resolved at load time. Like linking printf and some application startup procedures from the C library. These external symbols must be indicated somewhere in the executable: this is what .dynsym is used for, and this is why the exe has a .dynsym even if you don't specify -rdynamic. When you do specify it, the linker adds other symbols that are not necessary for the process to work, but can be used by things like backtrace_symbols.
backtrace_symbols will not resolve any function names if you statically link. Even if you specify -rdynamic, the .dynsym section will not be emitted to the executable. No symbol tables gets loaded into the executable image, so backtrace_symbols cannot map code adresses to symbols.

Is there any way to know which symbols are exported in a object file?

Hi I'm working in a Linux environment and I have to link to a object file already compiled which offers me some services (services.o) and I know some of them, but I'd like to know which are all of the exported symbols of it.
Is there any way to accomplish this not having the sources? If so, how?
Thanks you very much.
Try nm -- this tool is there for just this purpose.
Another option is objdump which also can show you a bunch of other stuff
or you can use readelf -s, this provides more detail infos.
Symbol table '.symtab' contains 19 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS a.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 11
10: 0000000000000000 0 SECTION LOCAL DEFAULT 12
11: 0000000000000000 0 SECTION LOCAL DEFAULT 14
12: 0000000000000000 0 SECTION LOCAL DEFAULT 16
13: 0000000000000000 0 SECTION LOCAL DEFAULT 17
14: 0000000000000000 0 SECTION LOCAL DEFAULT 15
15: 0000000000000000 71 FUNC GLOBAL DEFAULT 1 fa_global
16: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 a
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND fb_ex
18: 0000000000000050 17 FUNC GLOBAL DEFAULT 1 test

Resources