gcc
converts a C source file into an ELF object
file (*.o)
gcc -c source.c -o source.o
Also known as relocatable files.
ar
combines several object files into a static
library (*.a)
ar rcs libexample.a a.o b.o c.o
It is essentially an archive file with a symbol table.
Extract an object with ar x
:
ar x libexample.a b.o
ld
combines several object files and/or static
libraries into an executable or a shared library
(*.so).
ld -o a.out \
--dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib/x86_64-linux-gnu/crt1.o \
/usr/lib/x86_64-linux-gnu/crti.o \
source.o \
/usr/lib/x86_64-linux-gnu/crtn.o
Also known as static linker or link editor
Linking object files with gcc
(instead of
ld
) is easier.
gcc -o a.out source.o
gcc
adds default linker flags
and default crt*.o files.
gcc
passes the options starts with -Wl
to the linker.
gcc -Wl,-soname,libfoo.so ...
ld -soname libfoo.so ...
(comma are replaced by spaces)
A loader loads an executable into memory and relocates the fixups.
A loader can be bootloader (e.g. GRUB) or OS kernel (e.g. Linux and FreeBSD).
ELF shared objects will need a dynamic linker.
ELF file format supports two scenarios:
(1) object files for static linkers
(2) binaries for loaders
An ELF file may have several sections and segments.
Compilers generate sections.
Linkers combine sections into segments.
Loaders load segments to memory.
Sections are recorded in section headers.
Segments are recorded in program headers.
struct Elf64_Ehdr {
unsigned char e_ident[EI_NIDENT]; // magic, class, endianness
Elf64_Half e_type; // relocatable | executable | shared objects
Elf64_Half e_machine; // machine architecture
Elf64_Word e_version;
Elf64_Addr e_entry; // start address
Elf64_Off e_phoff; // offset to program headers
Elf64_Off e_shoff; // offset to section headers
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
};
Dump ELF header with readelf -h
struct Elf64_Shdr {
Elf64_Word sh_name; // Section name
Elf64_Word sh_type; // Section type
Elf64_Xword sh_flags; // WRITE | ALLOC | EXECINSTR
Elf64_Addr sh_addr; // Section virtual address
Elf64_Off sh_offset; // Section file offset
Elf64_Xword sh_size // Section size in bytes
Elf64_Word sh_link
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
};
Dump section headers with readelf -S
or
objdump -h
.
struct Elf64_Phdr {
Elf64_Word p_type; // PT_LOAD | PT_DYNAMIC | PT_INTERP | PT_PHDR
Elf64_Word p_flags; // PF_R | PF_W | PF_X
Elf64_Off p_offset // Segment file offset
Elf64_Addr p_vaddr; // Segment virtual address
Elf64_Addr p_paddr; // Segment physical address
Elf64_Xword p_filesz; // Segment size in file
Elf64_Xword p_memsz; // Segment size in memory
Elf64_Xword p_align;
};
Dump program headers with readelf -l
.
Relocatable files do not have program headers.
static int bss_variable;
static int local_variable = 42;
static int local_function() {
local_variable++;
bss_variable++;
return bss_variable + local_variable;
}
extern int printf(const char *fmt, ...);
int main() {
printf("%d\n", local_function());
}
gcc -c header.c && readelf -S header.o
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000005a 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002e8
00000000000000c0 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 0000009c
0000000000000004 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 A 0 0 1
[10] .symtab SYMTAB 0000000000000000 00000128
0000000000000168 0000000000000018 11 12 8
[11] .strtab STRTAB 0000000000000000 00000290
0000000000000057 0000000000000000 0 0 1
gcc -o header header.o && readelf -l header
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R E 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000008a8 0x00000000000008a8 R E 0x200000
LOAD 0x0000000000000db8 0x0000000000200db8 0x0000000000200db8
0x000000000000025c 0x0000000000000268 RW 0x200000
DYNAMIC 0x0000000000000dc8 0x0000000000200dc8 0x0000000000200dc8
0x00000000000001f0 0x00000000000001f0 RW 0x8
gcc -o header header.o && readelf -l header
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr
.gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got
.text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .dynamic .got .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .dynamic .got
Some sections are not mapped to fragments (e.g.
.symtab
and .strtab
)
Symbols are the names of functions, variables, or sections.
Defined symbols are defined in the ELF file and have section index.
Undefined symbols are not in the ELF file and must be resolved by linkers. Usually undefined symbols are referred by relocation entries.
struct Elf64_Sym {
Elf64_Word st_name; // Symbol name
unsigned char st_info; // Symbol type and binding
unsigned char st_other; // Symbol visibility
Elf64_Half st_shndx; // Section index where the symbol resides
Elf64_Addr st_value; // Symbol value (e.g. offset)
Elf64_Xword st_size;
};
Dump .symtab
with readelf -s
,
objdump -t
, and nm
Dump .dynsym
with readelf -s
,
objdump -T
, and nm -D
.
Local symbols are only visible to itself
(static
functions or variables)
Global symbols are visible to other object files.
Weak symbols are similar to global symbols
except that multiple definitions are accepted and undefined references
are resolved to NULL
.
static int local_variable = 42;
int global_variable = 43;
extern int undef_global_variable;
extern void undef_global_function(int x);
static void local_function() {
undef_global_function(local_variable);
undef_global_function(global_variable);
undef_global_function(undef_global_variable);
}
void global_function() {
local_function();
}
void weak_func() __attribute__((weak)) { }
void __attribute__((weak)) undef_weak_func();
inline void inline_func() { }
void use_weak_functions() {
weak_func();
if (undef_weak_func) {
undef_weak_func();
}
inline_func();
}
template <typename T>
void cpp_template_function() { }
void use_template() {
cpp_template_function<int>();
cpp_template_function<char>();
}
struct Elf64_Rel {
Elf64_Addr r_offset; /* Location to apply relocation */
Elf64_Xword r_info; /* Symbol index and relocation type */
}
struct Elf64_Rela {
Elf64_Addr r_offset; /* Location to apply relocation */
Elf64_Xword r_info; /* Symbol index and relocation type */
Elf64_Sxword r_addend; /* Addend */
};
Dump .rel
and .rela with
readelf -r
or objdump -r
Dump .rel.dyn
, .rela.dyn
,
with readelf -r
or objdump -R
static int local_variable = 42;
int global_variable = 43;
extern int undef_global_variable;
extern void undef_global_function(int x);
static void local_function() {
undef_global_function(local_variable);
undef_global_function(global_variable);
undef_global_function(undef_global_variable);
}
void global_function() {
local_function();
}
gcc -c symbol_binding.c && readelf -r symbol_binding.o
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000006 R_X86_64_PC32 .data-0x0000000000000004
000000000000000d R_X86_64_PLT32 undef_global_function-0x0000000000000004
0000000000000013 R_X86_64_PC32 global_variable-0x0000000000000004
000000000000001a R_X86_64_PLT32 undef_global_function-0x0000000000000004
0000000000000020 R_X86_64_PC32 undef_global_variable-0x0000000000000004
0000000000000027 R_X86_64_PLT32 undef_global_function-0x0000000000000004
0000000000000000 <local_function>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax
a: 89 c7 mov %eax,%edi
c: e8 00 00 00 00 callq 11 <local_function+0x11>
11: 8b 05 00 00 00 00 mov 0x0(%rip),%eax
17: 89 c7 mov %eax,%edi
19: e8 00 00 00 00 callq 1e <local_function+0x1e>
1e: 8b 05 00 00 00 00 mov 0x0(%rip),%eax
24: 89 c7 mov %eax,%edi
26: e8 00 00 00 00 callq 2b <local_function+0x2b>
2b: 90 nop
2c: 5d pop %rbp
2d: c3 retq
gcc -L. main.o -lfoo -lbar
-L
options are library search paths for static
linker.
-l
specifies the static libraris or
shared libraries to be linked.
gcc -L. main.o -lfoo -lbar
The order of static libraries is important. Linkers will search for undefined symbols from subsequent libraries.
Link errors will be raised if libbar.a
depends on symbols defined in libfoo.a
in this
example.
Linkers will only extract the object files which define the referenced symbols from archive files.
gcc *.o -L. \
-Wl,--whole-archive
-lfoo -lbar
-Wl,--no-whole-archive
If a static library is specified between
-Wl,--whole-archive
and
-Wl,--no-whole-archive
, all object files
will be extracted and included in the linked binary.
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu")
SECTIONS
{
. = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
.interp : { *(.interp) }
.rela.dyn : { *(.rela.text .rela.text.*) }
.text : { *(.text .text.*) }
. = DATA_SEGMENT_ALIGN (CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE));
.dynamic : { *(.dynamic) }
.data : { *(.data .data.*) }
.bss : { *(.bss .bss.*) }
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
}
Dump the linker script with gcc -fuse-ld=bfd -Wl,-t -Wl,--verbose
.
Use your own linker script with -Wl,-T,[script-path]
.
Add -ffunction-sections
and -fdata-sections
to CFLAGS so
that C/C++ compiler generates one section for each symbol.
And then, add -Wl,--gc-sections
to
LDFLAGS so that static linker will discard all unreferenced
sections.
C++ requires the global variables must be constructed before running
main()
and after running main
the global
variables must be destructed.
class Example {
public:
Example() { fprintf(stderr, "construct %p\n", this); }
~Example() { fprintf(stderr, "destruct %p\n", this); }
};
Example a;
Example b;
GCC adds __attribute__((constructor))
and
__attribute__((destructor))
for initializers and
finalizers in C.
void __attribute__((constructor)) my_init() {
puts("my_init");
}
void __attribute__((destructor)) my_fini() {
puts("my_fini");
}
GCC generates .init_array
and
.fini_array
sections which include function pointers to
constructors and destructors.
Linker merges .init_array
and
.fini_array
from object files.
__libc_csu_init
calls the
functions in .init_array
.
__libc_csu_fini
calls the
functions in .fini_array
.
These sections will be mapped to
DT_INIT_ARRAY
and DT_FINI_ARRAY
in shared
objects.
Compilers generates relocatable files for static linker
Static linker may generate static executable files, which are self-contained at run-time.
Static linker may generate shared object files, which must be linked by dynamic linker at run-time.
Shared object files includes both shared libraries and dynamically-linked executables, which depend on shared libraries.
ELF type can be found in the header:
struct Elf64_Ehdr {
unsigned char e_ident[EI_NIDENT]; // magic, class, endianness
Elf64_Half e_type; // relocatable | executable | shared objects
And can be checked with readelf -h
:
$ readelf -h /bin/bash | grep Type
Type: EXEC (Executable file)
$ readelf -h /usr/bin/python | grep Type
Type: DYN (Shared object file)
$ readelf -h /lib/x86_64-linux-gnu/libc.so.6 | grep Type
Type: DYN (Shared object file)
(non-PIC code)
#include <stdio.h>
int global_int = 0;
void put_int(int x) {
printf("%d\n", x);
}
int main() {
put_int(global_int);
}
$ gcc test.c -fno-PIC -static -m32
$ objdump -d a.out | sed '/\<main\>/,$!d' | head -n 18
080488a1 <main>:
80488a1: 8d 4c 24 04 lea 0x4(%esp),%ecx
80488a5: 83 e4 f0 and $0xfffffff0,%esp
80488a8: ff 71 fc pushl -0x4(%ecx)
80488ab: 55 push %ebp
80488ac: 89 e5 mov %esp,%ebp
80488ae: 51 push %ecx
80488af: 83 ec 04 sub $0x4,%esp
80488b2: a1 9c 6f 0d 08 mov 0x80d6f9c,%eax
80488b7: 83 ec 0c sub $0xc,%esp
80488ba: 50 push %eax
80488bb: e8 c5 ff ff ff call 8048885 <put_int>
80488c0: 83 c4 10 add $0x10,%esp
80488c3: b8 00 00 00 00 mov $0x0,%eax
80488c8: 8b 4d fc mov -0x4(%ebp),%ecx
80488cb: c9 leave
80488cc: 8d 61 fc lea -0x4(%ecx),%esp
80488cf: c3 ret
$ objdump -t a.out | grep global_int
080d6f9c g O .bss 00000004 global_int
Why do we need position-independent code?
Shared libraries and address space
$ gcc test.c -fPIC -m32 -shared
$ objdump -d a.out | sed '/\<main\>/,$!d' | head -n 18
0000054b <main>:
54b: 8d 4c 24 04 lea 0x4(%esp),%ecx
54f: 83 e4 f0 and $0xfffffff0,%esp
552: ff 71 fc pushl -0x4(%ecx)
555: 55 push %ebp
556: 89 e5 mov %esp,%ebp
558: 53 push %ebx
559: 51 push %ecx
55a: e8 2a 00 00 00 call 589 <__x86.get_pc_thunk.ax>
55f: 05 a1 1a 00 00 add $0x1aa1,%eax // COMPUTE GLOBAL OFFSET TABLE ADDRESS
564: 8b 90 f0 ff ff ff mov -0x10(%eax),%edx // LOAD global_int ADDRESS FROM GOT
56a: 8b 12 mov (%edx),%edx // LOAD global_int
56c: 83 ec 0c sub $0xc,%esp
56f: 52 push %edx
570: 89 c3 mov %eax,%ebx
572: e8 79 fe ff ff call 3f0 <put_int@plt> // JUMP TO PLT
577: 83 c4 10 add $0x10,%esp
57a: b8 00 00 00 00 mov $0x0,%eax
57f: 8d 65 f8 lea -0x8(%ebp),%esp
582: 59 pop %ecx
583: 5b pop %ebx
584: 5d pop %ebp
585: 8d 61 fc lea -0x4(%ecx),%esp
588: c3 ret
$ objdump -R pic.out
DYNAMIC RELOCATION RECORDS
00001ff0 R_386_GLOB_DAT global_int@@Base
$ objdump -t a.out | grep global_int
0000200c g O .bss 00000004 global_int
-fPIC vs. -fPIE
$ gcc test.c -fPIE -m32 -shared
$ objdump -d a.out | sed '/\<main\>/,$!d' | head -n 18
0000052b <main>:
52b: 8d 4c 24 04 lea 0x4(%esp),%ecx
52f: 83 e4 f0 and $0xfffffff0,%esp
532: ff 71 fc pushl -0x4(%ecx)
535: 55 push %ebp
536: 89 e5 mov %esp,%ebp
538: 51 push %ecx
539: 83 ec 04 sub $0x4,%esp
53c: e8 24 00 00 00 call 565 <__x86.get_pc_thunk.ax>
541: 05 bf 1a 00 00 add $0x1abf,%eax // LOAD DATA SEGMENT ADDRESS
546: 8b 80 18 00 00 00 mov 0x18(%eax),%eax // LOAD global_int
54c: 83 ec 0c sub $0xc,%esp
54f: 50 push %eax
550: e8 fc ff ff ff call 551 <main+0x26> // RELATIVE BRANCH
555: 83 c4 10 add $0x10,%esp
558: b8 00 00 00 00 mov $0x0,%eax
55d: 8b 4d fc mov -0x4(%ebp),%ecx
560: c9 leave
561: 8d 61 fc lea -0x4(%ecx),%esp
564: c3 ret
execve()
triggers these tasks:
Kernel loads the main executable, reads its program header, and maps its segments.
Kernel loads and maps the ELF
interpreter (RTLD) specified in PT_INTERP
.
Kernel fills the end of data segments (bss sections) with zeros.
Kernel initializes the stack pointer and builds the auxilary vector.
Kernel sets the program counter to the start address of RTLD.
Return to user mode (context switch)
Auxilary vector holds some kernel information that are essential to the user program.
AT_BASE
is the base address where RTLD
is loaded.
AT_PHDR
is the address of the program
headers of the main executable.
AT_ENTRY
is the start address of the
main executable.
Debug tricks: LD_SHOW_AUXV=1 command
RTLD bootstraps itself.
RTLD loads the shared libraries specified in
DT_NEEDED
entries recursively.
RTLD maps the segments of the shared libraries
with mmap()
.
RTLD relocates the entries in
DT_REL
or DT_RELA
.
RTLD runs the DT_INIT
and
DT_INIT_ARRAY
from every loaded shared
objects.
RTLD branches to
AT_ENTRY
(the start address of the main executable).
struct Elf64_Phdr {
Elf64_Word p_type; /* PT_LOAD | PT_DYNAMIC | PT_INTERP | PT_PHDR */
Elf64_Word p_flags; /* PF_R | PF_W | PF_X */
Elf64_Off p_offset /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align;
};
PT_LOAD
maps a segment from the
ELF file into the memory.
p_flags
is the protection mode
PF_R
, PF_W
, PF_X
.
If a segment does not have PF_W
,
then the segment will be mapped with MAP_SHARED
and
PROT_READ
.
If p_memsz
is greater than
p_filesz
, initialize them to zero. This is the case for
the .bss
section in the data
segment.
PT_INTERP
keeps the path to the
ELF interpreter.
ELF interpreter is the dynamic linker that loads and links shared libraries.
(also known as rtld or ldso)
Similar to the shebang
#!/bin/bash
in shell scripts.
Set PT_INTERP
with
-Wl,-dynamic-linker,[name]
-Wl,-dynamic-linker,/lib/ld-linux.so.2
ANALOGY: ld-linux.so
is the
dynamic linker and x86_64-linux-gnu-ld.gold
is the static
linker.
PT_DYNAMIC
refers the
.dynamic
section.
It contains several information for the dynamic linker.
ANALOGY: Dynamic linker's PT_DYNAMIC
is
static linker's LDFLAGS
.
struct Elf64_Dyn {
Elf64_Sxword d_tag; /* Tag */
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
};
Dumped these entries with readelf -d
.
readelf -d /usr/bin/python
Dynamic section at offset 0x2fc370 contains 32 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]
0x000000000000000c (INIT) 0x4cd70
0x0000000000000019 (INIT_ARRAY) 0x4fb2b0
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x0000000000000005 (STRTAB) 0xc948
0x0000000000000006 (SYMTAB) 0x2b80
0x0000000000000007 (RELA) 0x14568
0x0000000000000008 (RELASZ) 224424 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x0000000000000000 (NULL) 0x0
DT_SONAME
defines the library
name that should be recorded by the user.
Link a shared library with
-Wl,-soname,[name]
and a DT_SONAME
entry will
be created in the .dynamic
section
$ readelf -d /lib/x86_64-linux-gnu/libz.so.1 | grep SONAME
0x000000000000000e (SONAME) Library soname: [libz.so.1]
DT_NEEDED
specifies the shared libraries that the
output depends on.
When a shared library is specified by
-l
, the static linker will read soname from
DT_SONAME
and emit a DT_NEEDED
entry with the
soname.
$ readelf -d /usr/bin/python | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]
0x0000000000000001 (NEEDED) Shared library: [libutil.so.1]
0x0000000000000001 (NEEDED) Shared library: [libz.so.1]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
ANALOGY: Dynamic linker's DT_NEEDED
is
static linker's -l
.
Provide file-level shared library versioning.
$ ls -l /usr/lib/x86_64-linux-gnu/libxml2*
/usr/lib/x86_64-linux-gnu/libxml2.so -> libxml2.so.2.9.4
/usr/lib/x86_64-linux-gnu/libxml2.so.2 -> libxml2.so.2.9.4
/usr/lib/x86_64-linux-gnu/libxml2.so.2.9.4
DT_SONAME
is
libxml2.so.2
(without patch level).
If there are incompatible changes,
change soname to libxml2.so.3
and re-link
libxml2.so
to libxml2.so.3.0
.
If a shared library -l[name]
is specified
between -Wl,--as-needed
and
-Wl,--no-as-needed
, its soname won't be added to
DT_NEEDED
entries unless its symbols are referenced.
Linker discards unused shared libraries.
Unreferenced shared libraries with
.init_array
or .fini_array
will be discarded
as well.
DT_RPATH
and
DT_RUNPATH
are library search paths for dynamic
linker.
ANALOGY: Dynamic linker's
DT_RUNPATH
is static linker's -L
.
Specify -Wl,-rpath,[path]
in
LDFLAGS
.
A popular value is
-Wl,-rpath,\${ORIGIN}/../lib
.
DT_RUNPATH
has a better run-time
behavior.
Add -Wl,-enable-new-dtags
to
LDFLAGS to generate DT_RUNPATH
.
Dynamic linker configuration files
/etc/ld.so.conf*
specify several default shared libraries
search paths, e.g. /usr/lib/x86_64-linux-gnu.
Environment variable LD_LIBRARY_PATH
may specify extra search paths (separated by :
).
DT_SYMTAB
points to the
.dynsym
section, which includes the undefined/defined
symbols in an ELF shared object.
DT_RELA
and DT_REL
points to the .rela.dyn
and .rel.dyn
sections, which should be relocated by dynamic linker.
ANALOGY: Dynamic linker's
DT_SYMTAB
and DT_REL
is static linker's
.symtab
and .rel*
.
DT_GNU_HASH
(.gnu_hash
section) is a hash table which maps symbol names to ELF symbol
entries.
Multiple DT_GNU_HASH
tables are linked
together and become a link map.
Shared libraries loaded during C start up constitutes the global link map (sorted).
DT_SYMBOLIC
(-Wl,-Bsymbolic
), find the symbol defined in this
shared object.DT_NEEDED
-Wl,--no-undefined
emits errors
when there are references to undefined symbols that can't be
resolved.
-Wl,--no-allow-shlib-undefined
emit errors when there are references (in the shared libraries
specified as -lname
) to undefined symbols.
How to control the exported symbols of a shared library?
LOCAL
symbols are only visible
within a file but GLOBAL
symbols are exported by
default.
HIDDEN
symbols are
GLOBAL
symbols that can be referenced by other relocatable
objects and the static linker will convert them to
LOCAL
symbol.
Add
__attribute__((visibility("hidden")))
to the functions or
variables that you would like to hide.
Compiler will set the visibility bits to
HIDDEN in st_other
field in Elf64_Sym
.
static int local_func() {
return 1;
}
int __attribute__((visibility("hidden"))) hidden_func() {
return 2;
}
int global_func() {
return local_func() + hidden_func();
}
gcc -c visibility.c && objdump -t visibility.o
visibility.o: file format elf64-x86-64
0000000000000000 l F .text 000000000000000b local_func
000000000000000b g F .text 000000000000000b .hidden hidden_func
0000000000000016 g F .text 0000000000000020 global_func
gcc -shared visibility.o && objdump -t a.out
a.out: file format elf64-x86-64
00000000000005ca l F .text 000000000000000b local_func
00000000000005d5 l F .text 000000000000000b hidden_func
00000000000005e0 g F .text 0000000000000020 global_func
To hide most symbols by default, you may
specify -fvisibility=hidden
.
You have to revert the effect with
__attribute__((visibility("default")))
.
Alternatively, you can hide inline function
symbols with -fvisibility-inlines-hidden
Version script provides another way to control the exported symbols:
EXAMPLE_1_0 {
global:
foo;
bar;
local:
*;
};
EXAMPLE_2_0 {
global:
example;
} EXAMPLE_1_0;
gcc -shared -Wl,--version-script,libexample.map example.c
Q & A