ELF Files and Linking in C Programming
ELF Files and Linking in C Programming
Chris Kauffman
Last Updated:
Mon May 1 11:26:10 AM CDT 2023
1
Logistics
Reading Bryant/O’Hallaron
Date Event
▶ Ch 9: Virtual Mem Mon 24-Apr Virtmem Wrap
▶ Ch 7: ELF / Linking Obj Code/Linking
Tue 25-Apr Lab/HW 13 Due
3
Final Exam Logistics
4
Overview
5
The Immense Journey (apologies to Loren Eisley)
From C source file to running process involves a variety of tools,
formats, software and hardware, summarized for Linux below
1. Compilation: gcc preprocesses prog.c file, converts to internal
representation, optimizes, produces assembly code (stop at this
stage with -S)
2. Assembly: gas invoked by gcc to turn a prog.s file to a prog.o
ELF file, may be other .o files involved for multiple .c files
3. Linking: ld invoked by gcc to link multiple .o files to single
executable or library, copy in any statically linked library code,
indicates if executable has dynamic library dependencies
4. Stored Program: Now have an executable program in ELF format
stored on disk waiting to be run; call it [Link]
5. Loading: [Link] invoked by shell to load [Link] into
memory, sets up virtual memory map for .data / .text / heap /
stack, initializes .bss sections to 0, resolves any dynamic library
links required at load time, sets %rip to first program instruction
6. Running: OS handles remaining behavior of executing program
(process), running, sleeping, exiting, killing on segfaults
6
Exercise: Separate Compilation
# COMPILATION 1
> gcc -c func_01.c
> gcc -c main_func.c
> gcc -o main_func main_func.o func_01.o
# COMPILATION 2
> gcc -o main_func main_func.c func_01.c
▶ Describe differences between compilations above
▶ What is the result in each case?
▶ How are they different: any artifacts created in one but not
the other?
▶ Any advantages/disadvantages to them?
7
Answers: Separate Compilation
# COMPILATION 1
> gcc -c func_01.c
> gcc -c main_func.c
> gcc -o main_func main_func.o func_01.o
# COMPILATION 2
> gcc -o main_func main_func.c func_01.c
8
Object Files and ELF
Linux for Embedded Systems (Lecture Slides), Ahmed El Arabawy, Cairo Univ. 11
Linking: Merging Binary Files to One
Linking: merge multiple .o into one .o OR executable file
▶ Merge .text section with instructions
▶ Merge .data section with global variables
▶ Merge .symtab modifying positions of where things exist, etc.
Symbol Resolution
▶ Multiple object files define a symbol, must resolve which
definition to use
▶ Some tricky bugs can arise in resolution
Relocation
▶ Adjust offsets of things in symbol table
▶ Change any instructions which use locations that have
changed
Linkers must deal with a lot of details; we will only touch on a few
important principles and how they relate C/Assembly programs
12
Linker: Multiple .o to Single/Executable
▶ A linker converts multiple # Demo merging two .o files with ld
14
Exercise: Linking Trouble
Consider these two C files
// FILE: x_int.c // FILE: x_long.c
int x=0; // global vars long x; // global var
int y=0; // strongly defined // weakly defined
void x_to_neg8(){
void x_to_neg8(); // in different .o x = -8; // set global var
}
#include <stdio.h>
int main(){
x_to_neg8(); // set x only
printf("x: %d\n",x);
printf("y: %d\n",y);
return 0;
}
Compile + Run
> gcc -fcommon x_int.c x_long.c
/usr/bin/ld: Warning: ...
> ./[Link]
x: -8
y: -1 # WTF^M??
Why is this output unexpected?
What might be the cause? 15
Answers: Linking Trouble
▶ Two files define the sizes of global variable x differently
// FILE: x_long.c
long x; // uninitialized, weak symbol
// FILE: x_int.c
int x = 0; // initialized, strong symbol, prevails
int y = 0;
▶ Linker warns of this during compilation (see below)
> gcc -fcommon x_int.c x_long.c
/usr/bin/ld: Warning: alignment 4 of symbol 'x'
in /tmp/ccs1zLtj.o is smaller than 8 in /tmp/ccc7ZX9Q.o
▶ Variable y in x_int.c, adjacent to 4-byte x in memory
▶ Function void x_to_neg8() is in x_long.c
▶ Writes 8 bytes to location x clobbering y
INITIAL MEMORY
| GLOBALS | #2044 | y | 0 | 0x00000000 |
| | #2040 | x | 0 | 0x00000000 |
Memory
Map
19
Linker and Loader
Traditional: Static Linking Modern: Dynamic Linking
▶ Linker merges .o files to ▶ Linker merges .o files to
create executable create executable
▶ All global symbols must be ▶ Global symbols from
resolved: copy text for Dynamic Libraries are left
functions into the executable Undefined (U)
from libraries ▶ Loader copies executable
▶ Loader copies executable into memory, sets %rip but..
into memory, sets %rip to ▶ Creates a virtual memory
first instruction address, map to definitions for library
notifies OS to schedule it for functions dynamically
execution linking to definitions
▶ All code/data for running ▶ Code for running program is
program is in its own spread across its memory
memory image image and shared libraries
20
gcc: Statically vs Dynamically Linked Executables
▶ By default gcc produces ’mixed’ executables
▶ Use as many dynamic libraries (.so) as possible
▶ Use a static version (.a) of library ONLY if no dynamic version
is available
▶ With the -static option, use all static libraries
▶ Note the differences reported by the file command below
> cat hello.c
#include <stdio.h>
int main(int argc, char *argv[]){
printf("Hello world! I'm a program\n");
return 0;
}
22
Answers: Static/Dynamic Program Sizes
23
Libraries Required at Load/Runtime
24
Linking Against Standard Libraries
▶ At link time, linker must know about library dependencies
▶ gcc option -l will link against a library
> gcc do_math.c -lm # link to math library
> gcc do_pthreads.c -lpthread # link to threads library
▶ Default Convention: -lmystuff tries linking files
▶ [Link] (dynamic lib) THEN
▶ libmystuff.a (static lib)
▶ Force use of ONLY static libraries with -static option
▶ GCC always links libc (unless using -nostdlib)
▶ Compiler/Linker searches known directories for headers and
libraries
> gcc -v do_math.c -lm # -v: verbose output
...
#include <...> search starts here:
/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.1/include
/usr/local/include
/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.1/include-fixed
/usr/include
...
LIBRARY_PATH=/lib/:/usr/lib/:...
25
Creating/Linking Statically Linked Libraries
▶ Statically Linked Libraries
are archives with .a > gcc -g -Wall -c tree.c
> gcc -g -Wall -c array.c
extension > gcc -g -Wall -c list.c
▶ Traditional form of program > gcc -g -Wall -c util.c
libraries, comprised of a # create archive with ar
bunch of .o files > ar rcs libds_search.a \
tree.o array.o list.o util.o
▶ Utility ar allows creation,
modification, inspection of > file libds_search.a
.a files libds_search.a: current ar archive
# PROBLEM 1
> gcc do_search.c -lds_search
do_search.c:8:10: fatal error:
ds_search.h: No such file or directory # can't find header
#include "ds_search.h"
^~~~~~~~~~~~~
compilation terminated.
# PROBLEM 2
> gcc do_search.c -lds_search ...
/usr/bin/ld: cannot find -lds_search # can't find library
collect2: error: ld returned 1 exit status
▶ Compilers have options to resolve these two problems
27
Directing Compiler to non-standard Locations
> ls ds_search_static/
libds_search.a
ds_search.h
# PROBLEM 1
# Use -I to give "includes" directory with header
> gcc do_search.c -lds_search \
-I ds_search_static/ # header directory for ds_search.h
/usr/bin/ld: cannot find -lds_search
collect2: error: ld returned 1 exit status
# PROBLEM 2
# Use -L to add a directory to search for libraries
> gcc do_search.c -lds_search \
-I ds_search_static/ # header directory for ds_search.h
-L ds_search_static/ # library directory with libds_search.a
> file [Link]
28
— END SPRING 2023 CONTENT —
29
Creating Dynamic Libaries
> ./[Link]
[Link]: error while loading shared libraries:
libds_search.so: cannot open shared object file:
No such file or directory
> ./[Link]
Searching 2048 elem array, 10 repeats: 1.6470e-01 seconds
If distributing a .so, either
▶ Install it in a standard location like /usr/lib/ (admin access)
▶ Notify users of library to adjust LD_LIBRARY_PATH
33
Exercise: Dynamic Loading Tricks
> ./[Link]
Hello World!
... but most of all, Samy is my hero.
My favorite int is 32 and float is 1.234000
... but most of all, Samy is my hero.
Why would compiling another piece of code change the behavior of
an already compiled program?
34
Answers: Dynamic Loading Tricks
▶ One can interpose library calls: ask dynamic loader to link a
function to a different definition
▶ Only possible with dynamic linking but a powerful technique
▶ In this case, re-define printf(), similar tricks by valgrind
for malloc() / free()
> gcc hello.c
> [Link]
> ldd [Link]
[Link].1
[Link].6 => /usr/lib/[Link].6
/lib64/[Link].2 => /usr/lib64/[Link].2
35
Valgrind and Your own Malloc
36
Recall: Globals in Assembly
37
Relocation and PC-Relative Address
▶ Linker merges global symbols from multiple .o files into single
output sections
▶ Functions into single .text
▶ Global vars into .data / .bss sections
▶ Historically, linker would just assign a virtual memory address
to each symbol / section (simple, easy to implement)
▶ Problem: forces program to be loaded at a fixed virtual
memory address, decreases options available to
loader/dynamic linker
▶ gcc now generates relocatable code by default: all
instructions must be independent of exact memory position
where program is loaded (trickier but flexible/safer)
▶ Loader guarantees: distance between sections is constant
▶ .text might be loaded at 0x9000 or at 0x9100 by OS
▶ .text and .data always 0x1000 bytes apart
▶ .text loaded contiguously at some start address
▶ Addressing relative to PC allows flexibility in code placement,
requires extra linker work
38
Relocation Entries
▶ ELF files contain relocation entries, spots with unknown
address that must be “filled in” at link time
▶ Relocation entries are created for function calls and global
variable use in ELF sections
▶ .[Link]: Relocation info for .text section
▶ .[Link]: Relocation info for .data section
▶ Compiler notes byte locations that require insertion of info at
link time
▶ Position where the fix is needed (“fill this in”)
▶ What symbol is needed
▶ Extra arithmetic stuff
▶ Interested in two types of relocation entries
▶ R_X86_64_PC32: insert address of something relative to rip;
used for global vars, functions in same C file
▶ R_X86_64_PLT32: insert address of a procedure linkage
table entry; used for functions not in same C file
▶ Linker inserts addresses at positions indicated by relocation
entries
39
Example of Relocation Entries
ORIGINAL SOURCE CODE RELOCATION ENTRIES
// file: glob.c > readelf -r glob.o
int glob_arr[128]; Off Type Sym + Addend
void glob_func1(int scale){ ... } 66 R_X86_64_PC32 glob_func1 - 4
83 R_X86_64_PC32 glob_arr - 4
void glob_func2(int scale, inty[]) e0 R_X86_64_PLT32 printf - 4
{
glob_func1(scale); // 66 Above byte positions must have
for(int i=0; i<128; i++){ addresses inserted by the linker
glob_arr[i] += y[i]; // 83 at link time. Currently those
printf("%d\n",glob_arr[i]); // e0 position have 00's as placeholders
} until the linker fills them in.
}
43
Answers: Separate Compilation Time
44
Answers: Separate Compilation Time
Exploit Separate Compilation
▶ Assume already compiled all files, have func_01.o,
func_02.o
▶ Edit func_08.c to add a new feature
▶ Don’t recompile C files that haven’t changed
▶ Compile like this
> gcc -c func_08.c
> gcc -o main_func *.o
45
Build Systems Exploit Separate Compilation
▶ Build Systems like make / Makefile exploit separate
compilation
▶ Build system establishes a dependency structure
▶ Targets are usually files to create
▶ Dependencies are other files/targets that must be up to date
to create a given target
▶ Only rebuild a target if a dependency changes
# Typical Makefile gives targets, dependencies,
# commands to create target using dependencies
# TARGET : DEPENDENCIES
# COMMANDS / ACTIONS
main_func.o : main_func.c
gcc -c main_funcs.c
func_01.o : func_01.c
gcc -c funcs_01.c
46
Example Builds from big-compile/
> make clean
rm -f *.o main_func
# edit func_08.c
48
Answers: Initialized vs Uninitialized Data Matters
▶ ELF .data section tracks global variables that is initialized
with non-zero values
▶ Must record every value in global variable so it can be
properly set when loaded to run
▶ big_data.o will have a large .data section as the line
long arr[20000] = {1,2,3};
initializes the first few array values, rest will be 0
> readelf -S big_data.o
There are 12 section headers, starting at offset 0x27368:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
...
[ 3] .data PROGBITS 0000000000000000 00000080 <--
----> 0000000000027100 0000000000000000 WA 0 0 32
[ 4] .bss NOBITS 0000000000000000 00027180 <--
0000000000000000 0000000000000000 WA 0 0 1
...
▶ 0x27100 = 160000 bytes: entire arr array stored in file
49
Answers: Initialized vs Uninitialized Data Matters
▶ ELF .bss section tracks global variables that are not
initialized or initialized to all 0’s
▶ No specific values need be recorded, just instructions on how
much space to allocate on starting the program
▶ big_bss.o will have a miniscule .data section as the line
long arr[20000] = {};
initializes to all 0’s so .bss section
> readelf -S big_bss.o
There are 12 section headers, starting at offset 0x268:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
...
[ 3] .data PROGBITS 0000000000000000 0000007f
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000080 <--
----> 0000000000027100 0000000000000000 WA 0 0 32
[ 5] .comment PROGBITS 0000000000000000 00000080 <--
0000000000000012 0000000000000001 MS 0 0 1
...
▶ arr array NOT stored in file, significantly smaller .o file
50