Section Headers, Symbols and Debug Info
Welcome back to Part 3. This post waited a little because I almost finished Biber, and I hope the normal release is coming very soon :)
PE parsing issue was really different probably this part is last for ELF but PE parts will be a little longer.
In this post I continue with the ELF Section Header Table.
Program headers describe the kernel / loader side: how the executable is mapped into memory. Section headers describe the linker / compiler / debugger side: which logical part of the binary lives where.
Section Header Table
Function names, debug information, readonly strings, symbol tables... most of them live around here. Program headers describe memory mappings; section headers describe logical parts of the binary.
Again I can show this with our famous Limon. This output is already from Biber :)
SECTION HEADER Nr Name Type Address Offset Size Flg Lnk Info Align ---------------------------------------------------------------------------------------------------------- 0 NULL 0x0000000000000000 0x000000 0x000000 0 0 0 1 .rodata PROGBITS 0x0000000001000240 0x000240 0x018280 AMS 0 0 16 2 .eh_frame_hdr PROGBITS 0x00000000010184C0 0x0184C0 0x000A8C A 0 0 4 3 .eh_frame PROGBITS 0x0000000001018F50 0x018F50 0x003CAC A 0 0 8 4 .text PROGBITS 0x000000000101DC00 0x01CC00 0x069439 AX 0 0 16 5 .tbss NOBITS 0x0000000001087040 0x086040 0x04001C WA 0 0 8 6 .got PROGBITS 0x0000000001088040 0x086040 0x000008 WA 0 0 8 7 .relro_padding NOBITS 0x0000000001088048 0x086048 0x000FB8 WA 0 0 1 8 .data PROGBITS 0x0000000001089048 0x086048 0x004D90 WA 0 0 8 9 .bss NOBITS 0x000000000108E000 0x08ADD8 0x00A100 WA 0 0 4096 10 .debug_loc PROGBITS 0x0000000000000000 0x08ADD8 0x17E88E 0 0 1 11 .debug_abbrev PROGBITS 0x0000000000000000 0x209666 0x000B6D 0 0 1 12 .debug_info PROGBITS 0x0000000000000000 0x20A1D3 0x0D1245 0 0 1 13 .debug_ranges PROGBITS 0x0000000000000000 0x2DB418 0x03F960 0 0 1 14 .debug_str PROGBITS 0x0000000000000000 0x31AD78 0x037B76 MS 0 0 1 15 .debug_pubnames PROGBITS 0x0000000000000000 0x3528EE 0x00FD8C 0 0 1 16 .debug_pubtypes PROGBITS 0x0000000000000000 0x36267A 0x01C242 0 0 1 17 .debug_line PROGBITS 0x0000000000000000 0x37E8BC 0x0602B5 0 0 1 18 .comment PROGBITS 0x0000000000000000 0x3DEB71 0x000013 MS 0 0 1 19 .symtab SYMTAB 0x0000000000000000 0x3DEB88 0x004B30 21 800 8 20 .shstrtab STRTAB 0x0000000000000000 0x3E36B8 0x0000D9 0 0 1 21 .strtab STRTAB 0x0000000000000000 0x3E3791 0x0042C3 0 0 1
What do we see here? We have Nr, Name, Type, Address, Offset, Size, Flag, Align and some other fields.
.rodata
Normally I expected to see .text first, to be honest. But here .rodata came first. This can happen because of linker layout, readonly grouping, alignment and other decisions.
Section order is not execution order. Seeing .rodata first does not mean execution starts there.
.rodata means readonly data. Generally string literals, const data, format strings and readonly arrays live here.
const msg[:0]const u8 = "Biber";
Quick note: [:0] means null-terminated. This is useful when talking with C style strings. Also be careful: this is const.
The type is PROGBITS. This means real bytes exist in the file, so file size gets bigger and we can see this data with Biber ↗
| Flag | Meaning |
|---|---|
A | Alloc — mapped into memory |
M | Merge — readonly/duplicate data can be merged by linker |
S | Strings — section contains string data |
1 .rodata PROGBITS 0x0000000001000240 0x000240 0x018280 AMS 0 0 16
Here we take data from file offset 0x240 and map it around memory address 0x1000240.
LOAD R-- VAddr : 0x0000000001000000 FileSize: 0x1CBFC .rodata Address : 0x1000240 Offset : 0x240
What is it? .rodata is inside the first readonly LOAD segment. Yeeeee :))
Quick Note: Before also partly partly ı wrote but again ı want to say
Section headers are not the actual loading plan for the kernel/loader.
The real loading layout comes f rom Program Headers (PT_LOAD segments).
For example .text section may show:
.text -> Address: 0x101DC00
and the corresponding LOAD R-X segment also maps memory around that address.
If binary is loaded at a different base address, relocation entries are used by the runtime loader to patch/fix required addresses.
.eh_frame_hdr and .eh_frame
.eh_frame_hdr means Exception Handling Frame Header. It is connected with .eh_frame, which means Exception Handling Frame.
.eh_frame has the real unwind data. .eh_frame_hdr is the fast lookup / index part.
When a program crashes, the runtime or debugger can use this information to walk backwards through stack frames and generate stack traces.
2 .eh_frame_hdr PROGBITS 0x00000000010184C0 0x0184C0 0x000A8C A 0 0 4 3 .eh_frame PROGBITS 0x0000000001018F50 0x018F50 0x003CAC A 0 0 8
.eh_frame is not executable code. It is metadata describing how the runtime/debugger can walk backwards through stack frames.
.text
.text PROGBITS 0x000000000101DC00 0x01CC00 0x069439 AX 0 0 16
This is important: real code starts here. This section holds actual machine code: compiled instructions, functions, calls, jumps, everything about the working application.
PS C:\Users\Hrasi\Desktop\Biber> .\zig-out\bin\Biber .\limon CLASS : ELF64 DATA : LittleEndian TYPE : ET_EXEC (executable) MACHINE : .x86_64 Entry : 0x0101DC00 P_OFFSET : 0x40 (9 entries x 56 bytes) S_OFFSET : 0x3E7A58 (22 entries x 64 bytes) FLAGS : 0x0
Look here: Entry is 0x0101DC00 and .text address is 0x000000000101DC00. That means application execution starts inside .text.
And again this was an important part for me. Actually when I noticed it, I was so happy and wanted to add it here :)
LOAD R-X 0x000000000101DC00 0x000000000101DC00 0x0000069439 0x0000069439
Something is connected here. This is the same virtual address as .text. Program header flags are R-X, so it can be read and executed as expected.
Normally, for security reasons, there should not be a write flag here. Writable + executable memory is dangerous. Section flags also say AX: Alloc and Execute, not Write.
.tbss and .bss
I will explain these two sections together.
.bss is zero-initialized global/static memory. For example:
var counter: u32 = 0;
var arr: [256]u8 = .{0} ** 256;
At runtime the program needs these bytes, but the executable does not need to store thousands of zero bytes in the file.
The type is NOBITS. This means bytes are not written into the program file, but memory will still be reserved at runtime.
.tbss is similar, but for thread-local data. Every thread can have its own local values there.
5 .tbss NOBITS 0x0000000001087040 0x086040 0x04001C WA 0 0 8 9 .bss NOBITS 0x000000000108E000 0x08ADD8 0x00A100 WA 0 0 4096
.got and RELRO
.got PROGBITS 0x0000000001088040 0x086040 0x000008 WA 0 0 8
.got means Global Offset Table. This part is related to dynamic linking. In this Limon binary, the size is only 0x8, so there is not much dynamic symbol data here.
Flags are WA: Alloc and Writable. This table can hold addresses that are resolved by the runtime loader.
For example, when a program uses something like printf from another library, the real address is not always known directly in the binary. The loader can resolve it and write the address into a table like this.
RELRO
RELRO means Relocation Read-Only. It is a security feature. Some areas need to be writable while relocations are applied, but after that they should become readonly.
.relro_padding NOBITS 0x0000000001088048 0x086048 0x000FB8 WA 0 0 1
.got 0x1088040 .relro_padding 0x1088048 .data 0x1089048 0x1088048 + 0xFB8 = 0x1089000
.data and Debug Sections
.data
.data PROGBITS 0x0000000001089048 0x086048 0x004D90 WA 0 0 8
Initialized writable global/static data lives here. Type is PROGBITS, so real bytes exist in the file. Flags are WA: Allocate and Writable.
var counter: u32 = 5;
.debug_loc
.debug_loc PROGBITS 0x0000000000000000 0x08ADD8 0x17E88E
This is a DWARF debug section. A variable can live in a register, on the stack or somewhere else depending on the instruction range. Debuggers follow that information here.
.debug_abbrev
.debug_abbrev PROGBITS 0x0000000000000000 0x209666 0x000B6D
Abbreviation table. The formats of records in .debug_info are described here.
.debug_info
.debug_info PROGBITS 0x0000000000000000 0x20A1D3 0x0D1245
This is the main debug information area. Types, functions, variables and compile-unit data can be found here.
.debug_str
.debug_str PROGBITS 0x0000000000000000 0x31AD78 0x037B76 MS
Long strings inside DWARF live here. Flags MS mean Merge and Strings.
.debug_pubnames / .debug_pubtypes
.debug_pubnames PROGBITS 0x0000000000000000 0x3528EE 0x00FD8C .debug_pubtypes PROGBITS 0x0000000000000000 0x36267A 0x01C242
These are helper index sections for public names and public types. Debuggers can use them for faster lookup.
.debug_line
.debug_line PROGBITS 0x0000000000000000 0x37E8BC 0x0602B5
This maps machine addresses back to source line locations. Stack traces and source-level debugging need this kind of information.
.comment
.comment PROGBITS 0x0000000000000000 0x3DEB71 0x000013 MS
Compiler / linker comment strings can live here.
Many debug sections have Address = 0x0. That means they are not runtime loaded memory sections. They are mostly for tools like debuggers, symbol readers and binary inspectors.
.symtab, .shstrtab and .strtab
.symtab
.symtab SYMTAB 0x0000000000000000 0x3DEB88 0x004B30 21 800 8
.symtab is the symbol table. This is very important because function/object records live here.
From Limon, some output:
.symtab (802 symbol) Nr Value Size Type Bind Ndx Name -------------------------------------------------------------------------------- 0 0x0000000000000000 0 NOTYPE LOCAL 0 1 0x0000000000000000 0 FILE LOCAL 65521 limon 2 0x000000000101DC00 14 FUNC LOCAL 4 start._start 3 0x0000000001024E20 15834 FUNC LOCAL 4 start.posixCallMainAndExit 4 0x000000000101DC10 2199 FUNC LOCAL 4 helper.helpers.drawBar
Here is what I noticed :) When we looked at .text, it was the executable code section. start._start also exists here as type FUNC, and its Ndx is 4.
In the section table, number 4 is .text. So this function lives inside .text. Same idea for start.posixCallMainAndExit.
Another important part: Lnk=21 means this symbol table uses strings from section 21, which is .strtab.
.shstrtab
.shstrtab STRTAB 0x0000000000000000 0x3E36B8 0x0000D9
Section names live here.
Nr Name Type Address Offset Size Flg Lnk Info Align ---------------------------------------------------------------------------------------------------------- 0 NULL 0x0000000000000000 0x000000 0x000000 0 0 0 1 .rodata PROGBITS 0x0000000001000240 0x000240 0x018280 AMS 0 0 16 2 .eh_frame_hdr PROGBITS 0x00000000010184C0 0x0184C0 0x000A8C A 0 0 4 3 .eh_frame PROGBITS 0x0000000001018F50 0x018F50 0x003CAC A 0 0 8 4 .text PROGBITS 0x000000000101DC00 0x01CC00 0x069439 AX 0 0 16 5 .tbss NOBITS 0x0000000001087040 0x086040 0x04001C WA 0 0 8 6 .got PROGBITS 0x0000000001088040 0x086040 0x000008 WA 0 0 8
For these names we need to open .shstrtab and walk through it. I tried this part also too much and coming data is real or garbage not understable was many times;
sometimes I got garbage data - After so many read already what is garbage or coming data can be how starting to understand -, but it is working now :)
.strtab
.strtab STRTAB 0x0000000000000000 0x3E3791 0x0042C3
This is the symbol string table. Symbol names come from here.
Quick reminder:.shstrtab → section names.strtab → symbol names.symtab → symbol records
Short Summary
Program headers showed how the executable is loaded.
Section headers show how tools understand the executable.
Symbols and debug sections show how names, source lines and stack traces come back from a binary.
Already sections explanation was some short. So many times ı am saying but again for detailed go to docs of ELF file bottom of this file you can find links.. :)
Next, I will try to finish Biber and continue with PE files :)
Biber almost finished and about this ı am really happy and again ı want to say pe32 parsing issue... It was really amazing fun for me ı thought it will be almost same but parsing issue was really different ...
References
Everything above can be verified against these primary sources.
When in doubt, go to the spec — not a blog post.
It is only what I understand; it can be wrong!
ELF
- System V ABI — official ELF specification
- linux/elf.h — struct definitions in the Linux kernel
- x86-64 System V ABI
- OSDev Wiki — ELF
- DWARF Debugging Standard