Understanding how file offsets become virtual addresses is essential for debugging. When you see a crash at address 0x401234, you need to know which part of the file that corresponds to — and that requires understanding memory mapping.
The loader uses mmap() to map file segments into memory. For each PT_LOAD segment, it maps the file region [p_offset, p_offset + p_filesz] to virtual address [p_vaddr, p_vaddr + p_memsz]. The file offset and virtual address are related by:
virtual_address = file_offset - p_offset + p_vaddr
The alignment (p_align) affects how mapping works. For p_align = 0x1000 (4KB, typical page size):
For example, .init, .plt, and .text might all be in the same PT_LOAD segment with R-X permissions.
File: Memory: Offset Section VirtAddr Section 0x0000 ELF header 0x1000 (within first page) 0x0040 .init, .plt 0x1040 .init 0x1000 .text 0x2000 .text 0x3000 .rodata 0x4000 .rodata 0x3dc0 .data, .bss 0x4dc0 .data
The .text section at file offset 0x1000 maps to virtual address 0x2000. The formula: 0x2000 = 0x1000 - 0x1000 + 0x2000.
A segment has p_offset=0x1000, p_vaddr=0x2000. File offset 0x1200 maps to what virtual address?
0x2200 = 0x1200 - 0x1000 + 0x2000
Two PT_LOAD segments overlap in virtual address space. What happens?
Program headers describe segments for the loader. But what about the section header table — how do sections relate to segments? That's next.