Wikipedia mentions that "the bss section typically includes all uninitialized variables declared at file scope." Given the following file:
int uninit;
int main() {
uninit = 1;
return 0;
}
When I compile this to an executable I see the bss segment filled properly:
$ gcc prog1.c -o prog1
$ size prog1
text data bss dec hex filename
1115 552 8 1675 68b prog1
However if I compile it as an object file I don't see the bss segment (I'd expect it to be 4):
$ gcc -c prog1.c
$ size prog1.o
text data bss dec hex filename
72 0 0 72 48 prog1.o
Is there something obvious I am missing?
I am using gcc version 4.8.1.
If we use readelf -s to look at the symbol table, we'll see:
$ readelf -s prog1.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 bss.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 6
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 5
8: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM uninit <<<<
9: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 main
We see that your uninit symbol ("variable") is, at this stage, a "common" symbol. It has not yet been "assigned" to the BSS.
See this question for more information on "common" symbols: What does "COM" means in the Ndx column of the .symtab section?
Once your final executable is linked together, it will be put in the BSS as you expected.
You can bypass this behavior by passing the -fno-common flag to GCC:
$ gcc -fno-common -c bss.c
$ size bss.o
text data bss dec hex filename
72 0 4 76 4c bss.o
Instead, you could mark uninit as static. This way, the compiler will know that no other .o file can refer to it, so it will not be a "common" symbol. Instead, it will be placed into the BSS immediately, as you expected:
$ cat bss.c
static int uninit;
int main() {
uninit = 1;
return 0;
}
$ gcc -c bss.c
$ size bss.o
text data bss dec hex filename
72 0 4 76 4c bss.o
Related
I'm building a simple payload to execute on an ARM64 system that will print a "Hello, world!" string over UART.
hello-world-payload.c:
#include <stdint.h>
typedef uint32_t u32;
int _start() {
const char* txt = "Hello, world!\n";
volatile u32* uart_wfifo = (volatile u32*)0xc81004c0;
volatile u32* uart_status = (volatile u32*)0xc81004cc;
u32 i = 0;
char c = txt[0];
while (c) {
// wait for UART availability
do {} while (! (*uart_status & (1 << 22)) );
// print 1 character
*uart_wfifo = (0x000000ff & c);
c = txt[++i];
}
while (1) {} // wait for watchdog
}
Makefile:
CROSS_COMPILE ?= aarch64-linux-gnu-
CC = $(CROSS_COMPILE)gcc
OBJCOPY = $(CROSS_COMPILE)objcopy
AFLAGS = -nostdlib
CFLAGS = -O0 -nostdlib
LDFLAGS = -Wl,--build-id=none
all: hello-world-payload.bin
%.elf: %.c
$(CC) $(CFLAGS) $(LDFLAGS) -o $# $^
%.bin: %.elf
$(OBJCOPY) -O binary -S -g --strip-unneeded \
-j .text \
-j .rodata \
$< $#
.PHONY: clean
clean:
rm hello-world-payload.bin
For cross compiler I use the gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf (AArch64 ELF bare-metal target) toolchain from ARM Developer.
With code above I get a 159 bytes binary that works just fine.
Once I move the txt out of the function scope this way:
typedef uint32_t u32;
const char* txt = "Hello, world!\n";
int _start() {
, the payload doesn't run anymore. After loading the payload binary into Ghidra I notice that the code tries to access txt at DAT_000100a0 while in fact it's stored at 0x90.
Since txt is const and is already initialized it should belong to the .rodata section which I confirmed by inspecting the assembly output of ${CROSS_COMPILE}gcc -O0 -nostdlib -Wl,--build-id=none -o hello-world-payload.s hello-world-payload.c -S, here's an excerpt from it:
.arch armv8-a
.file "hello-world-payload.c"
.text
.global txt
.section .rodata
.align 3
.LC0:
.string "Hello, world!\n"
.data
.align 3
.type txt, %object
.size txt, 8
I made sure I didn't forget to include .rodata in Makefile:
%.bin: %.elf
$(OBJCOPY) -O binary -S -g --strip-unneeded \
-j .text \
-j .rodata \
$< $#
The environment this binary runs in puts some constraints such as the max payload size (approx 29000 bytes in my case) and as far as I understood the binary must begin with the .text section so my goal is to keep the payload size as small as possible but I want to access various objects from different functions.
I inspected the ${CROSS_COMPILE}readelf -S output for hello-world-payload.o (${CROSS_COMPILE}gcc -O0 -nostdlib -Wl,--build-id=none -o hello-world-payload.o hello-world-payload.c):
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000400000 00010000 0000000000000090 0000000000000000 AX 0 0 4
[ 2] .rodata PROGBITS 0000000000400090 00010090 000000000000000f 0000000000000000 A 0 0 8
[ 3] .data PROGBITS 00000000004100a0 000100a0 0000000000000008 0000000000000000 WA 0 0 8
[ 4] .comment PROGBITS 0000000000000000 000100a8 000000000000005d 0000000000000001 MS 0 0 1
[ 5] .symtab SYMTAB 0000000000000000 00010108 00000000000001e0 0000000000000018 6 9 8
[ 6] .strtab STRTAB 0000000000000000 000102e8 000000000000006f 0000000000000000 0 0 1
[ 7] .shstrtab STRTAB 0000000000000000 00010357 0000000000000038 0000000000000000
I see there's a .data section so I tried to add it to the objcopy command in my Makefile:
%.bin: %.elf
$(OBJCOPY) -O binary -S -g --strip-unneeded \
-j .text \
-j .rodata \
-j .data \
$< $#
The binary size grows to whopping 65704 bytes but even with the .data section Ghidra shows the same DAT_000100a0 reference with nothing like the `"Hello, world!\n" string at that position:
The actual string is at 0x90 as it was before adding the .data section.
It is clear to me that the compiler messes up addresses of .rodata section where the string resides but I don't know how to fix it. Adding .data section didn't help.
Commonly with microcontrollers, the content of the .data section needs to be initialized by the start-up code from a section in non-volatile memory of the same size. Apparently your start-up code does not fulfill this requirement to run a C application.
In contrast to your belief, txt is an separate non-constant variable, because it is a modifiable pointer to the constant text. Your C code specifies to initialize this global variable with the address of the unnamed string. But no code does this.
You can make the global pointer variable constant, if you change your code to:
const char * const txt = "Hello, world!\n";
Now txt is located in .rodata.
You can avoid the global pointer variable at all, if you change your code to:
const char txt[] = "Hello, world!\n";
Now txt names the array of characters, which is located in .rodata.
In your first version of your program, txt was a dynamic variable on the stack. The code initialized it with the address of the unnamed string after entering the function _start().
I have a big text file that I want to include in a C program. I could just make it a string literal but it's pretty big and that would be cumbersome. So I'm currently linking like this:
$ ld -r -b binary -o /tmp/stuff.o /tmp/stuff.txt
$ clang -o myprogram main.o /tmp/stuff.o
Objdump output:
$ objdump -t /tmp/stuff.o
/tmp/stuff.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l d .data 0000000000000000 .data
0000000000000006 g *ABS* 0000000000000000 _binary__tmp_stuff_txt_size
0000000000000006 g .data 0000000000000000 _binary__tmp_stuff_txt_end
0000000000000000 g .data 0000000000000000 _binary__tmp_stuff_txt_start
In the code, I do this (gotten from this question):
extern char _binary__tmp_stuff_txt_start[];
extern char _binary__tmp_stuff_txt_size[];
int f(void) {
size_t size = (size_t)_binary__tmp_stuff_txt_size;
do_stuff(size, _binary__tmp_stuff_txt_start);
}
Everything works great, but when I compile with GCC instead of Clang, it segfaults. Looking at it in GDB, the size variable initialized like this size_t size = (size_t)_binary__tmp_stuff_txt_size; is garbage. It seems that when GCC links, it passes the -pie flag to ld but Clang doesn't. I could fix this by just passing -no-pie to GCC, but it seems kindof sad that doing something so simple would prevent using PIE. Is there something I should change to make this work?
// foo.c
int main() { return 0; }
When I compiled the code above I noticed some symbols located in *ABS*:
$ gcc foo.c
$ objdump -t a.out | grep ABS
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
0000000000000000 l df *ABS* 0000000000000000 foo.c
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
0000000000000000 l df *ABS* 0000000000000000
Looks like they're some debug symbols but isn't debug info are stored in somewhere like .debug_info section?
According to man objdump:
*ABS* if the section is absolute (ie not connected with any section)
I don't understand it since no example is given here.
Question here shows an interesting way to pass some extra symbols in *ABS* by --defsym. But I think it could be easier by passing macros.
So what is this *ABS* section and when would someone use it?
EDIT:
Absolute symbols don't get relocated, their virtual addresses (0000000000000000 in the example you gave) are fixed.
I wrote a demo but it seems that the addresses of absolute symbols can be modified.
// foo.c
#include <stdio.h>
extern char foo;
int main()
{
printf("%p\n", &foo);
return 0;
}
$ gcc foo.c -Wl,--defsym,foo=0xbeef -g
$ objdump -t a.out | grep ABS
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
0000000000000000 l df *ABS* 0000000000000000 foo.c
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
0000000000000000 l df *ABS* 0000000000000000
000000000000beef g *ABS* 0000000000000000 foo
# the addresses are not fixed
$ ./a.out
0x556e06629eef
$ ./a.out
0x564f0d7aeeef
$ ./a.out
0x55c2608dceef
# gdb shows that before entering main(), &foo == 0xbeef
$ gdb a.out
(gdb) p &foo
$1 = 0xbeef <error: Cannot access memory at address 0xbeef>
(gdb) br main
Breakpoint 1 at 0x6b4: file foo.c, line 7.
(gdb) r
Starting program: /home/user/a.out
Breakpoint 1, main () at foo.c:7
7 printf("%p", &foo);
(gdb) p &foo
$2 = 0x55555555feef <error: Cannot access memory at address 0x55555555feef>
If you look at other symbols you might find an index (or section name if the reader does the mapping for you) in place of *ABS*. This is a section index in the section headers table. It points to the section header of a section the symbol is defined in (or SHN_UNDEF (zero) if it is undefined in the object you are looking at). So the value (virtual address) of a symbol will be adjusted by the same value its containing section is adjusted during loading. (This process is called relocation.) Not so for absolute symbols (having special value SHN_ABS as their st_shndx). Absolute symbols don't get relocated, their virtual addresses (0000000000000000 in the example you gave) are fixed.
Such absolute symbols are sometimes used to store some meta information. In particular, the compiler can create symbols with symbol names equivalent to the names of translation units it compiles. Such symbols aren't needed for linking or running the program, they are just for humans and binary processing tools.
As for your question w.r.t the reason this isn't stored in .debug_info section (and why this info is emitted even though no debug switches were specified), the answer is that it is a separate thing; it is just the symbol table (.symtab). It is also needed for debugging, sure, but it's primary purpose is linking of object (.o) files. By default it is preserved in linked executables/libraries. You can get rid of it with strip.
Much of what I wrote here is in man 5 elf.
I don't think doing what you are doing (with --defsym) is supported/supposed to work with dynamic linking. Looking at the compiler output (gcc -S -masm=intel), I see this
lea rsi, foo[rip]
Or, if we look at objdump -M intel -rD a.out (linking with -q to preserve relocations), we see the same thing: rip-relative addressing is used to get the address of foo.
113d: 48 8d 35 ab ad 00 00 lea rsi,[rip+0xadab] # beef <foo>
1140: R_X86_64_PC32 foo-0x4
The compiler doesn't know that it's going to be an absolute symbol, so it produces the code it does (as for a normal symbol). rip is the instruction pointer, so it depends on the base address of the segment containing .text after the program is mapped into memory by ld.so.
I found this answer shedding light on the proper use-case for absolute symbols.
I want to remove unused functions from code while compiling. Then I write some code (main.c):
#include <stdio.h>
const char *get1();
int main()
{
puts( get1() );
}
and getall.c:
const char *get1()
{
return "s97symmqdn-1";
}
const char *get2()
{
return "s97symmqdn-2";
}
const char *get3()
{
return "s97symmqdn-3";
}
Makefile
test1 :
rm -f a.out *.o *.a
gcc -ffunction-sections -fdata-sections -c main.c getall.c
ar cr libgetall.a getall.o
gcc -Wl,--gc-sections main.o -L. -lgetall
After run make test1 && objdump --sym a.out | grep get , I only find the next 2 lines output:
0000000000000000 l df *ABS* 0000000000000000 getall.c
0000000000400535 g F .text 000000000000000b get1
I guess the get2 and get3 was removed. But when I open the a.out by vim, I found s97symmqdn-1 s97symmqdn-2 s97symmqdn-3 exists.
Is the function get2 get3 removed really ? How I can remove the symbol s97symmqdn-2 s97symmqdn-3 ? Thank you for your reply.
My system is centos7 and gcc version is 4.8.5
The compilation options -ffunction-sections -fdata-sections and linkage option --gc-sections
are working correctly in your example. Your static library is superfluous, so it can
be simplified to:
$ gcc -ffunction-sections -fdata-sections -c main.c getall.c
$ gcc -Wl,--gc-sections main.o getall.o -Wl,-Map=mapfile
in which I'm also asking for the linker's mapfile.
The unused functions get2 and get3 are absent from the executable:
$ nm a.out | grep get
0000000000000657 T get1
and the mapfile shows that the unused function-sections .text.get2 and .text.get3 in which get2 and get3 are
respectively defined were discarded in the linkage:
mapfile (1)
...
Discarded input sections
...
.text.get2 0x0000000000000000 0xd getall.o
.text.get3 0x0000000000000000 0xd getall.o
...
Nevertheless, as you found, all three of the string literals "s97symmqdn-(1|2|3)"
are in the program:
$ strings a.out | egrep 's97symmqdn-(1|2|3)'
s97symmqdn-1
s97symmqdn-2
s97symmqdn-3
That is because -fdata-sections applies just to the same data objects that
__attribute__ ((__section__("name"))) applies to1, i.e. to the definitions
of variables that have static storage duration. It is not applied to anonymous string literals like your
"s97symmqdn-(1|2|3)". They are all just placed in the .rodata section as usual,
and there we find them:
$ objdump -s -j .rodata a.out
a.out: file format elf64-x86-64
Contents of section .rodata:
06ed 73393773 796d6d71 646e2d31 00733937 s97symmqdn-1.s97
06fd 73796d6d 71646e2d 32007339 3773796d symmqdn-2.s97sym
070d 6d71646e 2d3300 mqdn-3.
--gc-sections does not allow the linker to discard .rodata from the program
because it is not an unused section: it contains "s97symmqdn-1", referenced
in the program by get1 as well as the unreferenced strings "s97symmqdn-2"
and "s97symmqdn-3"
Fix
To get these three string literals separated into distinct data sections, you
need to assign them to distinct named objects, e.g.
getcall.c (2)
const char *get1()
{
static const char s[] = "s97symmqdn-1";
return s;
}
const char *get2()
{
static const char s[] = "s97symmqdn-2";
return s;
}
const char *get3()
{
static const char s[] = "s97symmqdn-3";
return s;
}
If we recompile and relink with that change, we see:
mapfile (2)
...
Discarded input sections
...
.text.get2 0x0000000000000000 0xd getall.o
.text.get3 0x0000000000000000 0xd getall.o
.rodata.s.1797
0x0000000000000000 0xd getall.o
.rodata.s.1800
0x0000000000000000 0xd getall.o
...
Now there are two new discarded data-sections, which contain
the two string literals we don't need, as we can see in the object file:
$ objdump -s -j .rodata.s.1797 getall.o
getall.o: file format elf64-x86-64
Contents of section .rodata.s.1797:
0000 73393773 796d6d71 646e2d32 00 s97symmqdn-2.
and:
$ objdump -s -j .rodata.s.1800 getall.o
getall.o: file format elf64-x86-64
Contents of section .rodata.s.1800:
0000 73393773 796d6d71 646e2d33 00 s97symmqdn-3.
Only the referenced string "s97symmqdn-1" now appears anywhere in the program:
$ strings a.out | egrep 's97symmqdn-(1|2|3)'
s97symmqdn-1
and it is the only string in the program's .rodata:
$ objdump -s -j .rodata a.out
a.out: file format elf64-x86-64
Contents of section .rodata:
06f0 73393773 796d6d71 646e2d31 00 s97symmqdn-1.
[1] Likewise, -function-sections has the same effect as qualifying the
definition of every function foo with __attribute__ ((__section__(".text.foo")))
What exactly does -rdynamic (or --export-dynamic at the linker level) do and how does it relate to symbol visibility as defined by the -fvisibility* flags or visibility pragmas and __attribute__s?
For --export-dynamic, ld(1) mentions:
...
If you use "dlopen" to load a dynamic object which needs to refer back
to the symbols defined by the program, rather than some other dynamic
object, then you will probably need
to use this option when linking the program itself. ...
I'm not sure I completely understand this. Could you please provide an example that doesn't work without -rdynamic but does with it?
Edit:
I actually tried compiling a couple of dummy libraries (single file, multi-file, various -O levels, some inter-function calls, some hidden symbols, some visible), with and without -rdynamic, and so far I've been getting byte-identical outputs (when keeping all other flags constant of course), which is quite puzzling.
Here is a simple example project to illustrate the use of -rdynamic.
bar.c
extern void foo(void);
void bar(void)
{
foo();
}
main.c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void foo(void)
{
puts("Hello world");
}
int main(void)
{
void * dlh = dlopen("./libbar.so", RTLD_NOW);
if (!dlh) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
void (*bar)(void) = dlsym(dlh,"bar");
if (!bar) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
bar();
return 0;
}
Makefile
.PHONY: all clean test
LDEXTRAFLAGS ?=
all: prog
bar.o: bar.c
gcc -c -Wall -fpic -o $# $<
libbar.so: bar.o
gcc -shared -o $# $<
main.o: main.c
gcc -c -Wall -o $# $<
prog: main.o | libbar.so
gcc $(LDEXTRAFLAGS) -o $# $< -L. -lbar -ldl
clean:
rm -f *.o *.so prog
test: prog
./$<
Here, bar.c becomes a shared library libbar.so and main.c becomes
a program that dlopens libbar and calls bar() from that library.
bar() calls foo(), which is external in bar.c and defined in main.c.
So, without -rdynamic:
$ make test
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -o prog main.o -L. -lbar -ldl
./prog
./libbar.so: undefined symbol: foo
Makefile:23: recipe for target 'test' failed
And with -rdynamic:
$ make clean
rm -f *.o *.so prog
$ make test LDEXTRAFLAGS=-rdynamic
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -rdynamic -o prog main.o -L. -lbar -ldl
./prog
Hello world
-rdynamic exports the symbols of an executable, this mainly addresses scenarios as described in Mike Kinghan's answer, but also it helps e.g. Glibc's backtrace_symbols() symbolizing the backtrace.
Here is a small experiment (test program copied from here)
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;
size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
printf ("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
free (strings);
}
/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
print_trace ();
}
int
main (void)
{
dummy_function ();
return 0;
}
compile the program: gcc main.c and run it, the output:
Obtained 5 stack frames.
./a.out() [0x4006ca]
./a.out() [0x400761]
./a.out() [0x40076d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f026597f830]
./a.out() [0x4005f9]
Now, compile with -rdynamic, i.e. gcc -rdynamic main.c, and run again:
Obtained 5 stack frames.
./a.out(print_trace+0x28) [0x40094a]
./a.out(dummy_function+0x9) [0x4009e1]
./a.out(main+0x9) [0x4009ed]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f85b23f2830]
./a.out(_start+0x29) [0x400879]
As you can see, we get a proper stack trace now!
Now, if we investigate ELF's symbol table entry (readelf --dyn-syms a.out):
without -rdynamic
Symbol table '.dynsym' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free#GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts#GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace_symbols#GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace#GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail#GLIBC_2.4 (3)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf#GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
with -rdynamic, we have more symbols, including the executable's:
Symbol table '.dynsym' contains 25 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts#GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace_symbols#GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace#GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail#GLIBC_2.4 (3)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf#GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
11: 0000000000601060 0 NOTYPE GLOBAL DEFAULT 24 _edata
12: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 24 __data_start
13: 0000000000601068 0 NOTYPE GLOBAL DEFAULT 25 _end
14: 00000000004009d8 12 FUNC GLOBAL DEFAULT 14 dummy_function
15: 0000000000601050 0 NOTYPE WEAK DEFAULT 24 data_start
16: 0000000000400a80 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
17: 0000000000400a00 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
18: 0000000000400850 42 FUNC GLOBAL DEFAULT 14 _start
19: 0000000000601060 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
20: 00000000004009e4 16 FUNC GLOBAL DEFAULT 14 main
21: 00000000004007a0 0 FUNC GLOBAL DEFAULT 11 _init
22: 0000000000400a70 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
23: 0000000000400a74 0 FUNC GLOBAL DEFAULT 15 _fini
24: 0000000000400922 182 FUNC GLOBAL DEFAULT 14 print_trace
I hope that helps!
I use rdynamic to print out backtraces using the backtrace()/backtrace_symbols() of Glibc.
Without -rdynamic, you cannot get function names.
To know more about the backtrace() read it over here.
From The Linux Programming Interface:
42.1.6
Accessing Symbols in the Main Program
Suppose that we use dlopen() to dynamically load a shared library,
use dlsym() to obtain the address of a function x() from that
library, and then call x(). If x() in turn calls a function y(),
then y() would normally be sought in one of the shared libraries
loaded by the program.
Sometimes, it is desirable instead to have x() invoke an
implementation of y() in the main program. (This is similar to a
callback mechanism.) In order to do this, we must make the
(global-scope) symbols in the main program available to the dynamic
linker, by linking the program using the --export-dynamic linker
option:
$ gcc -Wl,--export-dynamic main.c (plus further options and
arguments)
Equivalently, we can write the following:
$ gcc -export-dynamic main.c
Using either of these options allows a dynamically loaded library to
access global symbols in the main program.
The gcc -rdynamic option and the gcc -Wl,-E option are further
synonyms for -Wl,--export-dynamic.
I guess this only works for dynamically loaded shared library, opened with dlopen(). Correct me if I am wrong.