Back to course

Sections

15 minintermediateModule 3: Load CommandsContinues from macho-segments
15 min · Module 3: Load Commands

Why This Matters

Segments decide where memory comes from; sections decide what it means. ELF programmers reach for .text, .data, .bss; PE programmers for .text and .rdata. Mach-O's equivalents are sections — __text, __data, __cstring, __stubs — and they carry the metadata that lets every tool behave correctly: the linker merges same-named sections, nm finds functions, dyld discovers stub tables and initialiser arrays, debuggers skip sections marked S_ATTR_DEBUG.

The flags field is the contract. Get it wrong and tools will execute your data or treat code as strings. One 32-bit word separates a section of machine instructions from a section of NUL-terminated C strings.

A Simple Model

Immediately after each segment_command_64 come nsects fixed-size records — section_64, 80 bytes each:

The model's missing piece: sections are numbered. loader.h and nlist.h number them 1, 2, 3... in load-command order across all segments of the file — that number is what a symbol's n_sect field stores, so "section 4" means "the fourth section_64 record the loader walked past."

The Actual Detail

loader.h lays out section_64 as 80 bytes: sectname[16], segname[16], addr u64, size u64, offset u32, align u32, reloff u32, nreloc u32, flags u32, reserved1 u32, reserved2 u32, reserved3 u32. It then defines the flags split: SECTION_TYPE = 0x000000ff, SECTION_ATTRIBUTES = 0xffffff00. The types you will actually meet:

Type (low 8)NameContains
0x0S_REGULAROrdinary bytes
0x1S_ZEROFILLUninitialised space — no file bytes, zero-filled at load
0x2S_CSTRING_LITERALSNUL-terminated C strings
0x6S_NON_LAZY_SYMBOL_POINTERSGOT-style pointers, bound before main
0x7S_LAZY_SYMBOL_POINTERSLazy binding slots (la_symbol_ptr)
0x8S_SYMBOL_STUBSJump stubs that trigger lazy binding
0x9S_MOD_INIT_FUNC_POINTERSInitialiser function pointers (constructors)
0xbS_COALESCEDSymbols to coalesce across objects (eh_frame)

The attribute bits you will meet in real files: S_ATTR_PURE_INSTRUCTIONS = 0x80000000, S_ATTR_SOME_INSTRUCTIONS = 0x00000400, S_ATTR_DEBUG = 0x02000000, S_ATTR_EXT_RELOC = 0x00000200, S_ATTR_LOC_RELOC = 0x00000100. Two reserved words are not reserved in practice: for stub and symbol-pointer sections, reserved1 is the section's start index into the indirect symbol table and reserved2 is the stub's byte size — loader.h documents both.

Common mistake. Reading flags as one number. It is always two questions: flags AND 0xff gives the type; flags AND 0xffffff00 gives the attributes. Also: a zerofill section's offset is 0 with no backing bytes — loader.h notes zero-fill sections are always last in their segment so the segment's tail padding can serve them.

A Real Example

All four sections of prog64.o, straight from otool:

  sectname __text
    offset 544
     align 2^4 (16)
    reloff 816
    nreloc 5
     flags 0x80000400
  sectname __data
    offset 664
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x00000000
  sectname __common
    offset 0
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x00000001
  sectname __eh_frame
    offset 672
     align 2^3 (8)
    reloff 0
    nreloc 0
     flags 0x6800000b

A linked dylib shows the richer cases: __stubs has flags 0x80000408 (type 8, instructions) with reserved2 = 6 (each stub is 6 bytes); __cstring has flags 2; __la_symbol_ptr flags 7; __got flags 6 with reserved1 = 172 — the index where its 41 indirect-symbol entries begin.

Try It

$ otool -l prog64.o | grep -E "^  sectname|^    offset|^     align|^    reloff|^    nreloc|^     flags"
  sectname __text
    offset 544
     align 2^4 (16)
    reloff 816
    nreloc 5
     flags 0x80000400
  ...

What to look for: align 2^n is a power-of-two exponent, not a byte count (2^4 = 16); a non-zero reloff/nreloc pair marks an unlinked object file; and every section you see is followed by loader.h's rule that same-named sections in the same segment get merged by the link editor into one aligned, zero-padded result.

Check Your Understanding

Without looking back: prog64.o's __text section reports flags = 0x80000400. What does that say?

Apply It

libasyncProfiler.dylib's __bss section prints size 0x9739, offset 0, align 2^4, flags 0x00000001. Where do its bytes live on disk?

The pattern: offset only means something when the section type stores file bytes. Check the type first, then the offset — never the reverse.

Connect

Sections subdivide the LC_SEGMENT_64 commands you just decoded — same command stream, one level finer, with the flags word doing the heavy lifting.

Next: The Symbol Table — every symbol's n_sect field is one of these section numbers, and the names you have been reading as strings live in a separate table in __LINKEDIT.