reading binaries by hand PART 1 PART 2
RSS github
github RSS
ELFSectionsSymbolsZig 0.16.0

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 :)

Biber — ELF Section Header Table
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.

example
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 ↗

FlagMeaning
AAlloc — mapped into memory
MMerge — readonly/duplicate data can be merged by linker
SStrings — section contains string data
.rodata
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.

Program Header connection
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.

.eh_frame sections
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
.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.

Biber header
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 :)

Program Header connection
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:

bss examples
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.

.tbss and .bss
5    .tbss                NOBITS       0x0000000001087040  0x086040  0x04001C  WA       0      0      8
9    .bss                 NOBITS       0x000000000108E000  0x08ADD8  0x00A100  WA       0      0      4096

.got and RELRO

.got
.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
.relro_padding       NOBITS       0x0000000001088048  0x086048  0x000FB8  WA       0      0      1
RELRO layout
.got             0x1088040
.relro_padding   0x1088048
.data            0x1089048

0x1088048 + 0xFB8 = 0x1089000

.data and Debug Sections

.data

.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.

data example
var counter: u32 = 5;

.debug_loc

.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
.debug_abbrev        PROGBITS     0x0000000000000000  0x209666  0x000B6D

Abbreviation table. The formats of records in .debug_info are described here.

.debug_info

.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
.debug_str           PROGBITS     0x0000000000000000  0x31AD78  0x037B76  MS

Long strings inside DWARF live here. Flags MS mean Merge and Strings.

.debug_pubnames / .debug_pubtypes

debug indexes
.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
.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
.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              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:

Biber — .symtab
.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
.shstrtab            STRTAB       0x0000000000000000  0x3E36B8  0x0000D9

Section names live here.

section names example
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              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

General