Linking
Linking
1 / 44
Today’s Big Adventure
ld
a.out
c.c gcc c.s as c.o
ld loader cache
• Hello program
- Write friendly greeting to terminal
- Exit cleanly
• Every programming language addresses this problem
[demo]
6 / 44
Running example: hello program
• Hello program
- Write friendly greeting to terminal
- Exit cleanly
• Every programming language addresses this problem
6 / 44
Hello world – CS212-style
#include <sys/syscall.h>
int my_errno;
const char greeting[] = "hello world\n";
9 / 44
How is a process specified?
$ readelf -h hello1
ELF Header:
...
Entry point address: 0x8049030
Start of program headers: 52 (bytes into file)
Start of section headers: 14968 (bytes into file)
Number of program headers: 8
Number of section headers: 23
Section header string table index: 22
• Executable files are the linker/loader interface. Must tell OS:
- What is code? What is data? Where should they live?
- This is part of the purpose of the ELF standard
• Every ELF file starts with ELF an header
- Specifies entry point virtual address at which to start executing
- But how should the loader set up memory?
10 / 44
Recall what process memory looks like
kernel
stack
mmapped
dynamic regions
heap
uninitialized data (bss)
initialized data
static
read-only data
code (text)
• Address space divided into “segments”
- Text, read-only data, data, bss, heap (dynamic data), and stack
- Recall gcc told assembler in which segments to put what contents
11 / 44
Who builds what?
• Heap: allocated and laid out at runtime by malloc
- Namespace constructed dynamically, managed by programmer
(names stored in pointers, and organized using data structures)
- Compiler, linker not involved other than saying where it can start
• Stack: allocated at runtime (func. calls), layout by compiler
- Names are relative off of stack (or frame) pointer
- Managed by compiler (alloc on procedure entry, free on exit)
- Linker not involved because namespace entirely local:
Compiler has enough information to build it.
• Global data/code: allocated by compiler, layout by linker
- Compiler emits them and names with symbolic references
- Linker lays them out and translates references
• Mmapped regions: Managed by programmer or linker
- Some programs directly call mmap; dynamic linker uses it, too
12 / 44
ELF program header
$ readelf -l hello1
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x08049000 0x08049000 0x00304 0x00304 R E 0x1000
LOAD 0x002000 0x0804a000 0x0804a000 0x00158 0x00158 R 0x1000
LOAD 0x002ff8 0x0804bff8 0x0804bff8 0x0001c 0x0003c RW 0x1000
...
Section to Segment mapping:
Segment Sections...
01 ... .text ...
02 .rodata ...
03 ... .data .bss
• For executables, the ELF header points to a program header
- Says what segments of file to map where, with what permissions
• Segment 03 has shorter file size then memory size
- Only 0x1c bytes must be read into memory from file
- Remaining 0x20 bytes constitute the .bss
• Who creates the program header? The linker
13 / 44
Linkers (Linkage editors)
• Unix: ld
- Usually hidden behind compiler
- Run gcc -v hello.c to see ld or invoked (may see collect2)
• Three functions:
- Collect together all pieces of a program
- Coalesce like segments
- Fix addresses of code and data so the program can run
• Result: runnable program stored in new object file
• Why can’t compiler do this?
• Unix: ld
- Usually hidden behind compiler
- Run gcc -v hello.c to see ld or invoked (may see collect2)
• Three functions:
- Collect together all pieces of a program
- Coalesce like segments
- Fix addresses of code and data so the program can run
• Result: runnable program stored in new object file
• Why can’t compiler do this?
- Limited world view: sees one file, rather than all files
• Usually linkers don’t rearrange segments, but can
- E.g., re-order instructions for fewer cache misses;
remove routines that are never called from a.out
14 / 44
Simple linker: two passes needed
• Pass 1:
- Coalesce like segments; arrange in non-overlapping memory
- Read files’ symbol tables, construct global symbol table with entry
for every symbol used or defined
- Compute virtual address of each segment (at start+offset)
• Pass 2:
- Patch references using file and global symbol table
- Emit result
• Symbol table: information about program kept while linker
running
- Segments: name, size, old location, new location
- Symbols: name, input segment, offset within segment
15 / 44
Where to put emitted objects?
• Assember:
- Doesn’t know where data/code should be 0 main:
placed in the process’s address space .
.
.
- Assumes each segment starts at zero call my_write
- Emits symbol table that holds the name and .
.
offset of each created object .
- Routines/variables exported by file are ret
recorded as global definitions 60 my_strlen:
.
.
• Simpler perspective: .
- Code is in a big byte array ret
- Data is in another big byte array main: 0: T
- Assembler creates (object name, index) my_strlen: 60: t
tuple for each interesting thing greeting: 0: R
- Linker then merges all of these arrays
16 / 44
Object files
$ objdump -Sr hello2.o
...
48: 50 push %eax
49: 68 00 00 00 00 push $0x0
4a: R_386_32 greeting
4e: 6a 01 push $0x1
50: e8 fc ff ff ff call 51 <main+0x2a>
51: R_386_PC32 my_write
55: 83 c4 10 add $0x10,%esp
• Let’s create two-file program hello2 with my_write in separate
file
- Compiler and assembler can’t possibly know final addresses
• Notice push uses 0 as address of greeting
• And call uses -4 as address of my_write—why?
17 / 44
Object files
$ objdump -Sr hello2.o
...
48: 50 push %eax
49: 68 00 00 00 00 push $0x0
4a: R_386_32 greeting
4e: 6a 01 push $0x1
50: e8 fc ff ff ff call 51 <main+0x2a>
51: R_386_PC32 my_write
55: 83 c4 10 add $0x10,%esp
• Let’s create two-file program hello2 with my_write in separate
file
- Compiler and assembler can’t possibly know final addresses
• Notice push uses 0 as address of greeting
• And call uses -4 as address of my_write—why?
- Target (sitting at offset 51 in text) encoded relative to next
instruction (add at offset 55)
17 / 44
Where is everything?
• How to call procedures or reference variables?
- E.g., call to my_write needs a target addr
- Assembler uses 0 or PC (%eip) for address
- Emits an external reference telling the linker the instruction’s
offset and the symbol it needs to be patched with
0 main:
.
.
.
49 pushl $0x0
4e pushl $0x1
50 call -4
.
.
.
main: 0: T
my_strlen: 40: t
greeting: 4a
my_write: 51
• At link time the linker patches every reference
18 / 44
Relocations
$ readelf -r hello2.o
.
.
.
Offset Info Type Sym.Value Sym. Name
00000039 00000801 R_386_32 00000000 greeting
0000004a 00000801 R_386_32 00000000 greeting
00000051 00000a02 R_386_PC32 00000000 my_write
.
.
.
• Object file stores list of required relocations
- R_386_32 says add symbol value to value already in file (often 0)
- R_386_PC32 says add difference between symbol value and patch
location to value already in file (often -4 for call)
- Info encodes type and index of symbol value to use for patch
19 / 44
ELF sections
$ readelf -S hello2.o
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 0000a4 00 AX 0 0 1
[ 2] .rel.text REL 00000000 0005f8 000018 08 I 20 1 4
[ 3] .data PROGBITS 00000000 0000d8 000000 00 WA 0 0 1
[ 4] .bss NOBITS 00000000 0000d8 000000 00 WA 0 0 1
[ 5] .rodata PROGBITS 00000000 0000d8 00000d 00 A 0 0 4
.
.
.
[20] .symtab SYMTAB 00000000 0004f0 0000d0 10 21 9 4
[21] .strtab STRTAB 00000000 0005c0 000038 00 0 0 1
$ readelf -s hello2.o
Num: Value Size Type Bind Vis Ndx Name
.
.
.
3: 00000000 39 FUNC LOCAL DEFAULT 1 my_strlen
.
.
.
9: 00000000 13 OBJECT GLOBAL DEFAULT 5 greeting
10: 00000027 62 FUNC GLOBAL DEFAULT 1 main
11: 00000000 0 NOTYPE GLOBAL DEFAULT UND my_write
.
.
.
• Lists all global, exported symbols
- Sometimes local ones, too, for debugging (e.g., my_strlen)
• Each symbol has an offset in a particular section number
- On previous slide, 1 = .text, 5 = .rodata
- Special undefined section 0 means need symbol from other file
21 / 44
How to lay out emitted objects?
22 / 44
What is a library?
23 / 44
Examining programs with nm
int uninitialized;
VA $ nm a.out symbol type
...
int initialized = 1; 0400400 T _start
const int constant = 2; 04005bc R constant
int main () 0601008 W data_start
{ 0601020 D initialized
return 0; 04004b8 T main
} 0601028 B uninitialized
• If don’t need full readelf, can use nm (nm -D on shared objects)
- Handy -o flag prints file, useful with grep
• R means read-only data (.rodata in elf)
- Note constant VA on same page as main
- Share pages of read-only data just like text
• B means uninitialized data in “BSS”
• Lower-case letters correspond to local symbols (static in C)
24 / 44
Examining sections with objdump
Note Load mem addr. and File off have
same page alignment for easy mmapping
$ objdump -h a.out
a.out: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
...
12 .text 000001a8 00400400 00400400 00000400 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
...
14 .rodata 00000008 004005b8 004005b8 000005b8 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
...
17 .ctors 00000010 00600e18 00600e18 00000e18 2**3
CONTENTS, ALLOC, LOAD, DATA
...
23 .data 0000001c 00601008 00601008 00001008 2**3
CONTENTS, ALLOC, LOAD, DATA
...
24 .bss 0000000c 00601024 00601024 00001024 2**2
ALLOC
... No contents in file
• Another portable alternative to readelf
25 / 44
Name mangling
Mangling not
% nm overload.o compatible across
// C++ 0000000 T _Z3fooi compiler versions
int foo (int a)
{ 000000e T _Z3fooii
return 0; U __gxx_personality_v0
}
Demangle names
int foo (int a, int b) % nm overload.o | c++filt
{ 0000000 T foo(int)
return 0; 000000e T foo(int, int)
} U __gxx_personality_v0
// C++
struct foo_t { • Throwing exceptions destroys
~foo_t() {/*...*/} automatic variables
except() { throw 0; } • During exception, must find
};
void fn () - All such variables with non-trivial
destructors
{
foo_t foo; - In all procedures’ call frames until
exception caught
foo.except();
/* ... */ • Record info in special sections
}
• Executables can include debug info (compile w. -g)
- What source line does each binary instruction correspond to?
28 / 44
Dynamic (runtime) linking (hello3.c)
#include <dlfcn.h>
int main(int argc, char **argv, char **envp)
{
size_t (*my_strlen)(const char *p);
int (*my_write)(int, const void *, size_t);
void *handle = dlopen("dest/libmy.so", RTLD_LAZY);
if (!handle
|| !(my_strlen = dlsym(handle, "my_strlen"))
|| !(my_write = dlsym(handle, "my_write")))
return 1;
return my_write (1, greeting, my_strlen(greeting)) < 0;
}
• Link time isn’t special, can link at runtime too
- Get code (e.g., plugins) not available when program compiled
• Issues:
- How can behavior differ compared to static linking?
- Where to get unresolved symbols (e.g., my_write) from?
- How does my_write know its own addresses (e.g., for my_errno)?
29 / 44
Dynamic linking (continued)
$ readelf -r dest/libmy.so
30 / 44
Static shared libraries
31 / 44
Static shared libraries
• Define a “shared library segment” at same address in every
program’s address space
34 / 44
Lazy dynamic linking
35 / 44
Dynamic linking with ELF
36 / 44
Dynamic shared library example: hello4
$ objdump -Sr hello4
.
.
.
08049030 <my_write@plt>:
8049030: ff 25 0c c0 04 08 jmp *0x804c00c
8049036: 68 00 00 00 00 push $0x0
804903b: e9 e0 ff ff ff jmp 8049020 <.plt>
08049040 <my_strlen@plt>:
8049040: ff 25 10 c0 04 08 jmp *0x804c010
8049046: 68 08 00 00 00 push $0x8
804904b: e9 d0 ff ff ff jmp 8049020 <.plt>
.
.
.
804917a: 68 08 a0 04 08 push $0x804a008
804917f: e8 bc fe ff ff call 8049040 <my_strlen@plt>
• 0x804c00c and 0x804c010 initially point to next instruction
- Calls dlfixup with relocation index
- Note second jmp of each entry goes to 0th PLT entry, which jumps
to dlfixup
37 / 44
hello4 relocations
$ readelf -r hello4
Relocation section ’.rel.plt’ at offset 0x314 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
0804c00c 00000107 R_386_JUMP_SLOT 00000000 my_write
0804c010 00000507 R_386_JUMP_SLOT 00000000 my_strlen
mywrite.c
int my_errno;
int my_write(int fd, const void *buf, size_t len) {
int ret;
asm volatile (/* ... */);
if (ret < 0) {
my_errno = -ret;
return -1;
}
return ret;
}
mywrite.s mywrite-pic.s
negl %eax
negl %eax movl %eax, %edx
movl %eax, my_errno movl my_errno@GOT(%ebx), %eax
movl %edx, (%eax)
39 / 44
How does %ebx get set?
mywrite-pic.s
my_write:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $16, %esp
call __x86.get_pc_thunk.bx
addl $_GLOBAL_OFFSET_TABLE_, %ebx
.
.
.
__x86.get_pc_thunk.bx:
movl (%esp), %ebx
ret
$ readelf -r .libs/mywrite.o
Offset Info Type Sym.Value Sym. Name
00000008 00000a02 R_386_PC32 00000000 __x86.get_pc_thunk.bx
0000000e 00000b0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
00000036 0000082b R_386_GOT32X 00000000 my_errno
40 / 44
Linking and security
42 / 44
Code = data, data = code
43 / 44
How?