COURSE 6 — Process / ELF / Syscall Internals
This course connects everything from compiled program → process → kernel → hardware.
1. Big Picture
When you run a Linux program:
1./hello
Linux performs these steps:
1Source Code 2 ↓ 3Compiler (gcc) 4 ↓ 5ELF executable 6 ↓ 7Kernel loader 8 ↓ 9Dynamic linker 10 ↓ 11Shared libraries 12 ↓ 13Process created 14 ↓ 15Virtual memory mapped 16 ↓ 17CPU executes instructions 18 ↓ 19System calls enter kernel 20 ↓ 21Hardware performs work
2. Create a Sample Program
1// hello.c 2#include <stdio.h> 3 4int global_var = 42; 5const char *msg = "Hello ELF"; 6 7int main() { 8 printf("%s\n", msg); 9 return 0; 10}
Compile:
1gcc hello.c -o hello
Check file type:
1file hello
Example output:
1hello: ELF 64-bit LSB pie executable, x86-64
This tells us the program is an ELF executable.
3. What is ELF?
ELF = Executable and Linkable Format
Think of ELF as a container with metadata and machine code.
1+----------------------+ 2| ELF Header | 3+----------------------+ 4| Program Headers | 5+----------------------+ 6| Section Headers | 7+----------------------+ 8| .text | 9| .rodata | 10| .data | 11| .bss | 12| .symtab | 13| .strtab | 14| ... | 15+----------------------+
4. ELF Header
Display it:
1readelf -h hello
Important fields:
| Field | Meaning |
|---|---|
| Magic | Identifies ELF |
| Class | 32-bit or 64-bit |
| Data | Endianness |
| Type | Executable, shared object, etc. |
| Machine | CPU architecture |
| Entry point | First instruction address |
Example:
1Entry point address: 0x1050
The CPU begins execution at this address.
5. Program Headers (Segments)
View them:
1readelf -l hello
Example:
1LOAD 0x000000 0x0000000000000000 R 2LOAD 0x001000 0x0000000000001000 R E 3LOAD 0x002000 0x0000000000002000 R 4LOAD 0x002dd0 0x0000000000003dd0 RW
Meaning
| Segment | Permissions | Purpose |
|---|---|---|
| R | Read | Read-only data |
| R E | Read + Execute | Machine code |
| RW | Read + Write | Global variables |
The kernel loads segments, not sections.
6. Sections
Display sections:
1readelf -S hello
Important sections:
| Section | Purpose |
|---|---|
| .text | Executable instructions |
| .rodata | Constant strings |
| .data | Initialized globals |
| .bss | Zero-initialized globals |
| .symtab | Symbol table |
| .strtab | Symbol names |
Map our program
1int global_var = 42; // .data 2const char *msg = "Hello"; // pointer in .data, string in .rodata
7. Inspect Machine Code
1objdump -d hello | less
Example:
10000000000001139 <main>: 21139: 55 push %rbp 3113a: 48 89 e5 mov %rsp,%rbp 4...
This is the actual x86-64 code executed by the CPU.
8. Symbol Table
List symbols:
1nm hello
Example:
10000000000001139 T main 20000000000004018 D global_var
Symbol types:
| Letter | Meaning |
|---|---|
| T | Text/code |
| D | Initialized data |
| B | BSS |
| U | Undefined (from shared library) |
9. Dynamic Linking
Check required libraries:
1ldd hello
Example:
1libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
The program does not contain printf. It asks the dynamic linker to provide it from libc.
10. Dynamic Linker
Find interpreter:
1readelf -l hello | grep interpreter
Example:
1Requesting program interpreter: 2/lib64/ld-linux-x86-64.so.2
Execution order:
1kernel 2 ↓ 3ld-linux 4 ↓ 5libc.so 6 ↓ 7main()
11. PLT and GOT
Why?
External functions are resolved lazily.
PLT
1objdump -d hello | grep plt -A5
Example:
1<printf@plt>: 2jmp *0x2fca(%rip)
GOT
Stores real addresses after resolution.
First call:
1main → printf@plt → dynamic linker → libc printf
Later calls:
1main → printf@plt → libc printf
This is lazy binding.
12. Relocations
View relocation entries:
1readelf -r hello
They tell the linker which addresses must be fixed at load time.
13. Shared Libraries
Create one.
Library
1// mathlib.c 2int add(int a, int b) { 3 return a + b; 4}
Compile:
1gcc -fPIC -shared mathlib.c -o libmathlib.so
Program
1// use.c 2#include <stdio.h> 3 4int add(int,int); 5 6int main() { 7 printf("%d\n", add(2,3)); 8}
Build:
1gcc use.c -L. -lmathlib -o use
Run:
1LD_LIBRARY_PATH=. ./use
Output:
15
14. Process Creation
Use fork().
1#include <stdio.h> 2#include <unistd.h> 3 4int main() { 5 pid_t pid = fork(); 6 7 if (pid == 0) 8 printf("Child\n"); 9 else 10 printf("Parent\n"); 11 12 return 0; 13}
Compile and run:
1gcc fork_demo.c -o fork_demo 2./fork_demo
Two processes are created.
15. Virtual Address Space
Inspect current process:
1cat /proc/$$/maps
Typical layout:
100400000-00401000 r-xp /bin/bash 2... 37f8c... libc.so.6 47ffd... [stack]
Memory map
1Low Address 2+-------------+ 3| Code | 4+-------------+ 5| Data | 6+-------------+ 7| Heap ↑ | 8| | 9| | 10| Stack ↓ | 11+-------------+ 12High Address
16. ASLR
Address Space Layout Randomization changes addresses each run.
Test:
1cat /proc/sys/kernel/randomize_va_space
Run repeatedly:
1./hello 2cat /proc/$(pgrep hello)/maps
Addresses differ.
17. System Calls
User programs cannot directly access hardware. They request kernel services.
Example:
1#include <unistd.h> 2 3int main() { 4 write(1, "Hello\n", 6); 5 return 0; 6}
Compile:
1gcc write_demo.c -o write_demo
Trace:
1strace ./write_demo
Output:
1write(1, "Hello\n", 6) = 6
18. What Happens During a Syscall?
1User mode 2 write() 3 ↓ 4syscall instruction 5 ↓ 6Kernel mode 7 ↓ 8sys_write 9 ↓ 10terminal driver 11 ↓ 12hardware
CPU switches privilege level from ring 3 to ring 0.
19. Common Syscalls
| Syscall | Purpose |
|---|---|
| read | Read data |
| write | Write data |
| open | Open file |
| close | Close file |
| fork | Create process |
| execve | Run program |
| mmap | Map memory |
| brk | Grow heap |
| exit | Terminate |
20. Trace Library Calls with ltrace
1ltrace ./hello
Shows:
1printf("Hello ELF\n") = 10
Difference:
| Tool | Shows |
|---|---|
| strace | Kernel syscalls |
| ltrace | Library function calls |
21. Observe Program Startup
1strace ./hello
You will see:
1execve(...) 2mmap(...) 3openat(...) 4read(...) 5write(...) 6exit_group(...)
This reveals how the loader maps libraries before main() runs.
22. Mini Project — ELF Analyzer
Goal:
1ELF Header 2Sections 3Segments 4Symbols 5Dynamic Libraries 6Entry Point
Source Code
1// elf_analyzer.c 2#include <stdio.h> 3#include <stdlib.h> 4#include <elf.h> 5 6int main(int argc, char *argv[]) { 7 if (argc != 2) { 8 printf("Usage: %s <elf-file>\n", argv[0]); 9 return 1; 10 } 11 12 FILE *f = fopen(argv[1], "rb"); 13 if (!f) { 14 perror("fopen"); 15 return 1; 16 } 17 18 Elf64_Ehdr eh; 19 20 fread(&eh, 1, sizeof(eh), f); 21 22 if (!(eh.e_ident[0] == 0x7f && 23 eh.e_ident[1] == 'E' && 24 eh.e_ident[2] == 'L' && 25 eh.e_ident[3] == 'F')) { 26 printf("Not an ELF file\n"); 27 fclose(f); 28 return 1; 29 } 30 31 printf("ELF file detected\n"); 32 printf("Entry point : 0x%lx\n", eh.e_entry); 33 printf("Machine : %u\n", eh.e_machine); 34 printf("Sections : %u\n", eh.e_shnum); 35 printf("Segments : %u\n", eh.e_phnum); 36 37 fclose(f); 38 return 0; 39}
Compile:
1gcc elf_analyzer.c -o elf_analyzer
Run:
1./elf_analyzer hello
Example output:
1ELF file detected 2Entry point : 0x1050 3Machine : 62 4Sections : 31 5Segments : 13
23. Extend the Project
Add features:
- Print section names.
- Print segment permissions.
- Print imported libraries.
- Print exported symbols.
- Show memory layout diagram.
- Support ELF32 and ELF64.
Useful structures:
1Elf64_Shdr 2Elf64_Phdr 3Elf64_Sym
24. Debugging Tools Cheat Sheet
| Purpose | Command |
|---|---|
| File type | file hello |
| ELF header | readelf -h hello |
| Sections | readelf -S hello |
| Segments | readelf -l hello |
| Symbols | nm hello |
| Disassembly | objdump -d hello |
| Dynamic libs | ldd hello |
| Relocations | readelf -r hello |
| Syscalls | strace ./hello |
| Library calls | ltrace ./hello |
| Memory map | cat /proc/<pid>/maps |
25. Interview Questions
Q1. Difference between section and segment?
- Section: linker view.
- Segment: loader/kernel view.
Q2. What is PLT?
Stub code used to call external functions.
Q3. What is GOT?
Table storing resolved addresses.
Q4. What is ASLR?
Randomizes memory addresses for security.
Q5. Difference between strace and ltrace?
strace→ syscalls.ltrace→ library calls.
Q6. What syscall runs a new program?
execve.
26. End-to-End Execution Walkthrough
1gcc hello.c -o hello 2 ↓ 3ELF file created 4 ↓ 5./hello 6 ↓ 7kernel execve() 8 ↓ 9maps executable segments 10 ↓ 11starts ld-linux 12 ↓ 13loads libc.so 14 ↓ 15resolves printf 16 ↓ 17calls main() 18 ↓ 19printf() 20 ↓ 21write() syscall 22 ↓ 23kernel terminal driver 24 ↓ 25screen displays text
This is the complete journey from source code to hardware output.
27. Final Learning Outcome
After this course you should be able to:
- Read and analyze ELF binaries.
- Understand sections and segments.
- Explain dynamic linking, PLT, GOT, and relocations.
- Trace program startup.
- Inspect process memory with
/proc. - Explain ASLR.
- Trace syscalls with
strace. - Build a basic ELF analyzer in C.
- Describe how Linux executes a program internally.
You now understand the internal path:
1ELF → Loader → Process → Virtual Memory → Syscalls → Kernel → Hardware
This is the foundation required for systems programming, reverse engineering, security research, exploit development, performance engineering, and operating-system internals.