c code reports undef sprintf in dumpbin Compiles no error - c

I have c code that compiles and links fine on another PC. I am tring now to get everything to work on my Windows 7 64 bit OS. The Code compiles into a object with no errors however when I run dumpbin on the object with /SYMBOLS I get several UNDEF messages.
0F9 00000000 UNDEF notype External | __imp__sprintf
0FA 00000000 UNDEF notype External | __imp__fclose
0FB 00000000 UNDEF notype External | __imp__fopen
0FC 00000008 SECT3 notype Static | _i
0FD 00000000 UNDEF notype () External | _strcmp
0FE 00000000 UNDEF notype External | __imp__setlocale
0FF 00000000 UNDEF notype () External | _strcpy
100 00000290 SECT5 notype () Static | _f_read_common_fields
101 00000000 UNDEF notype () External | _memcpy
102 00000000 UNDEF notype External | __imp__fread
103 00000000 UNDEF notype () External | _memset
104 00000000 UNDEF notype External | ___security_cookie
105 00000000 UNDEF notype () External | #__security_check_cookie#4
Seems like I am just message a path statement or something but can not find it. This is all external code (Not in a VS Project) using VS2005 to compile from the command line.

It looks like a linker problem. I see your compile line, but what about the link line. Off the top of my head I can't remember the VC library for standard c stuff.
Is it msvcrt.lib? Are you including that on the link line?

Related

How to sort the symbol table (generated by "objdump -t") sequentially by address?

I would like to see at one glance which variables are placed next to each other in memory.
If I generate a symbol table by using objdump -t I get the symbol table, however sorted seemingly randomly.
SYMBOL TABLE:
00100584 l F .text 00000000 deregister_tm_clones
001005a8 l F .text 00000000 register_tm_clones
001005d4 l F .text 00000000 __do_global_dtors_aux
0019c020 l .bss 00000001 completed.10177
0019c00c l O .fini_array 00000000 __do_global_dtors_aux_fini_array_entry
00100604 l F .text 00000000 frame_dummy
0019c024 l .bss 00000018 object.10182
0019c008 l O .init_array 00000000 __frame_dummy_init_array_entry
00000000 l df *ABS* 00000000 tcp_server_test.c
0019c03c l .bss 00000004 xServerWorkTaskHandle
001006a4 l F .text 00000098 prvServerWorkTask
0019c040 l .bss 00000008 xMyTelnet
...
Is there a way to get it sorted by address (first column) in upcounting manner?
It would be great if there was a switch that could be added to objdump -t to get the desired output. I looked through the documentation and did not find anything like that, but maybe I overlooked something?
Another approach could be to write a python script that resorts it, but I would like to avoid that if possible.
Thanks!
You can use the universal tool "sort" to sort the output of "objdump".
Please read its documentation for options. Without any option, it simply sorts on the first column.
For the no-yet-enlightened command line user...
You need to "pipe" the output of "objdump" as input into "sort" like this:
objdump -t my_module.o | sort

Compatability of cross-built COFF files and MSVS

I have cross-built a win32 static library using i686-w64-mingw32-gccon a linux box.
I have inspected the resulting .lib file with nm on the linux side and see:
bsd_offset.o:
U __assert
00000000 T _bsd_offset
00000000 b .bss
00000000 d .data
00000000 i .drectve
U _flp3
00000004 C _randNo
00000000 r .rdata
00000000 r .rdata$zzz
00000000 t .text
bsdi.o:
00000000 T _bsdi
00000000 b .bss
U _clp3
00000000 d .data
00000000 i .drectve
00000004 C _randNo
00000000 r .rdata$zzz
00000000 t .text
...
I have also inspected the .lib file with dumpbin /symbols on the windows side and see:
Microsoft (R) COFF/PE Dumper Version 9.00.21022.08
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file libLib_rel.lib
File Type: LIBRARY
COFF SYMBOL TABLE
000 00000000 DEBUG notype Filename | .file
bsd_offset.c
002 00000000 SECT1 notype () External | _bsd_offset
tag index 00000000 size 00000000 lines 00000000 next function 00000000
004 00000000 SECT1 notype Static | .text
Section length BF, #relocs 4, #linenums 0, checksum 0
006 00000000 SECT2 notype Static | .data
Section length 0, #relocs 0, #linenums 0, checksum 0
008 00000000 SECT3 notype Static | .bss
Section length 0, #relocs 0, #linenums 0, checksum 0
00A 00000000 SECT4 notype Static | .rdata
Section length 1A, #relocs 0, #linenums 0, checksum 0
00C 00000000 SECT6 notype Static | .drectve
Section length 2C, #relocs 0, #linenums 0, checksum 0
00E 00000000 SECT7 notype Static | .rdata$zzz
Section length 1A, #relocs 0, #linenums 0, checksum 0
010 00000004 UNDEF notype External | _randNo
011 00000000 UNDEF notype () External | _flp3
012 00000000 UNDEF notype () External | __assert
String Table Size = 0x35 bytes
COFF SYMBOL TABLE
000 00000000 DEBUG notype Filename | .file
bsdi.c
002 00000000 SECT1 notype () External | _bsdi
tag index 00000000 size 00000000 lines 00000000 next function 00000000
004 00000000 SECT1 notype Static | .text
Section length 70, #relocs 1, #linenums 0, checksum 0
006 00000000 SECT2 notype Static | .data
Section length 0, #relocs 0, #linenums 0, checksum 0
008 00000000 SECT3 notype Static | .bss
Section length 0, #relocs 0, #linenums 0, checksum 0
00A 00000000 SECT5 notype Static | .drectve
Section length 26, #relocs 0, #linenums 0, checksum 0
00C 00000000 SECT6 notype Static | .rdata$zzz
Section length 1A, #relocs 0, #linenums 0, checksum 0
00E 00000004 UNDEF notype External | _randNo
00F 00000000 UNDEF notype () External | _clp3
String Table Size = 0x29 bytes
...
A colleague is attempting to build an exe using the lib using MSVS2008 and is seeing errors like:
1>------ Build started: Project: StaticLinkExample, Configuration: Release Win32 ------
1>Linking...
1>testLib_rel.lib(initialise.o) : warning LNK4229: invalid directive '/aligncomm:_randNo,2' encountered; ignored
1>testLib_rel.lib(lib.o) : warning LNK4229: invalid directive '/aligncomm:_randNo,2' encountered; ignored
...
1> Creating library D:\Software\ThirdParty\Lib_v230\Trial\StaticLinkExample\Release\StaticLinkExample.lib and object D:\Software\ThirdPart\Lib_v230\Trial\StaticLinkExample\Release\StaticLinkExample.exp
1>testLib_rel.lib(shared.o) : error LNK2001: unresolved external symbol ___ms_vsnprintf
1>testLib_rel.lib(iniparser.o) : error LNK2001: unresolved external symbol ___ms_vsnprintf
1>testLib_rel.lib(utility.o) : error LNK2001: unresolved external symbol ___ms_vsnprintf
1>testLib_rel.lib(load_glf.o) : error LNK2001: unresolved external symbol ___chkstk_ms
1>testLib_rel.lib(utility.o) : error LNK2001: unresolved external symbol ___chkstk_ms
1>testLib_rel.lib(lib.o) : error LNK2001: unresolved external symbol _fmax
1>testLib_rel.lib(lib.o) : error LNK2001: unresolved external symbol _fmin
1>StaticLinkExample.exp : error LNK2001: unresolved external symbol bsd_offset
1>StaticLinkExample.exp : error LNK2001: unresolved external symbol bsdi
Do the nm and dumpbin outputs look sensible. If the symbols are there in the static library why would the link errors be occuring. Could it be that somehow the cross-built lib is not binary compatible with MSVS 2008?

Interpreting ELF symbol table

I wrote a simple program called a.c to understand the symbol table on x86 machine:
extern int foo;
int function(void){
return foo;
}
I compile it and readelf
$gcc -c -m32 a.c
$readelf -a a.o > a.log
The symbol table in a.log is shown below:
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS a.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 6
6: 00000000 0 SECTION LOCAL DEFAULT 7
7: 00000000 0 SECTION LOCAL DEFAULT 5
8: 00000000 10 FUNC GLOBAL DEFAULT 1 function
9: 00000000 0 NOTYPE GLOBAL DEFAULT UND foo
What are the purpose of symbol table entry 0 and entries from 2 to 7?

LLVM (arm-none-eabi target) is producing an ARM.exidx section for C based code(?)

Compiling a simple HelloWorld.c using Clang/LLVM (arm-none-eabi target) produces a relocation section '.rel.ARM.exidx' but using arm-gcc does not. These LLVM produced unwind table entries are correctly tagged as canunwind. But why are they even produced at all as they are not needed and just cause bloat as you get an entry for every C function in your AXF?
readelf edxidx from HelloWorld.o
Relocation section '.rel.ARM.exidx' at offset 0x580 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000000 00000b2a R_ARM_PREL31 00000000 .text
00000008 00000b2a R_ARM_PREL31 00000000 .text
Unwind table index '.ARM.exidx' at offset 0xcc contains 2 entries:
0x0 <print_uart0>: 0x1 [cantunwind]
0x54 <c_entry>: 0x1 [cantunwind]
In testing Clang defaults: If I pass "-funwind-tables" to Clang to force unwinding for even C functions, I get what I would expect had I been writing .cpp functions and "-fno-unwind-tables" results in the same as above.
Relocation section '.rel.ARM.exidx' at offset 0x5a4 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
00000000 00000b2a R_ARM_PREL31 00000000 .text
00000000 00001600 R_ARM_NONE 00000000 __aeabi_unwind_cpp_pr0
00000008 00000b2a R_ARM_PREL31 00000000 .text
00000008 00001600 R_ARM_NONE 00000000 __aeabi_unwind_cpp_pr0
Unwind table index '.ARM.exidx' at offset 0xcc contains 2 entries:
0x0 <print_uart0>: 0x8001b0b0
Compact model index: 0
0x01 vsp = vsp + 8
0xb0 finish
0xb0 finish
0x54 <c_entry>: 0x809b8480
Compact model index: 0
0x9b vsp = r11
0x84 0x80 pop {r11, r14}
1) Is there anyway to turn off the .ARM.exidx section when only using C functions as they will always be flagged as "cantunwind".
2) Anyway to strip this section during linking? (gc-section will not workof course since these table entries reference in-use functions)
3) Why does arm-gcc not create this section (well, it does if you are using new lib, nano, etc... but I use and link no std libs)
I'll answer (2), since that's what I did. Add to your linker script:
/DISCARD/ :
{
*(.ARM.exidx)
}

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.

Resources