How Magic-Trace Extracts OCaml Exception Information from Debug Symbols

Magic-Trace extracts OCaml exception metadata by reading the .note.ocaml_eh ELF note section, parsing three address lists (enter-traps, push-traps, and pop-traps), and constructing a searchable runtime data structure that reconstructs exception control flow during tracing.

When analyzing performance traces of OCaml applications, understanding exception unwinding is essential for accurate stack reconstruction. The janestreet/magic-trace tool extracts this information directly from ELF debug symbols embedded in compiled binaries, enabling precise tracking of exception raises without runtime instrumentation.

Locating the .note.ocaml_eh Section in ELF Binaries

Magic-Trace begins by locating a special note section named .note.ocaml_eh that the OCaml compiler emits into the ELF file. In src/elf.ml, the function find_ocaml_exception_info calls Owee_elf_notes.find_notes_section to locate this section within the binary.

The parser also reads the STAPSDT base address from the .stapsdt.base section. This base address is critical for adjusting raw offsets to actual load-time virtual addresses, ensuring correct symbol resolution even when Address Space Layout Randomization (ASLR) is active. This discovery logic spans lines 36-84 of src/elf.ml.

Parsing the Note Payload for Trap Addresses

Once located, the .note.ocaml_eh section contains a payload with three distinct address lists essential for exception unwinding:

  • Enter-traps – locations where execution enters exception handlers
  • Push-traps – addresses where exception frames are pushed onto the stack
  • Pop-traps – addresses where frames are removed during unwinding

The helper function read_note (lines 38-56 in src/elf.ml) parses this payload by first reading a 64-bit recorded base address, then iterating over subsequent 64-bit values until encountering a zero sentinel. Each address is adjusted using Owee_elf_notes.Stapsdt.adjust, which computes the actual virtual address via the formula: actual_base - recorded_base + addr.

Building the Ocaml_exception_info.t Runtime Structure

After parsing the debug symbols, magic-trace constructs a runtime data structure optimized for efficient stack unwinding. In src/ocaml_exception_info.ml (lines 12-28), the create function assembles the three address lists into an Ocaml_exception_info.t record containing:

  • A sorted array of (address, kind) tuples for push-traps and pop-traps
  • An Int64.Set of enter-trap addresses for constant-time lookup

This structure attaches to the Elf.t record via the ocaml_exception_info field, making the exception metadata available throughout the tracing session without repeated disk access.

Reconstructing Exception Control Flow During Tracing

During trace recording, magic-Trace uses the extracted metadata to update virtual call stacks when exception-related trampolines execute. In src/trace_writer.ml (lines 714-818), the trace writer inspects each thread's ocaml_exception_state when encountering exception events like caml_raise_exn or caml_raise_exception.

The writer invokes Ocaml_exception_info.iter_pushtraps_and_poptraps_in_range (defined in src/ocaml_exception_info.ml, lines 30-61) to iterate over trap addresses within an instruction pointer range, calling user-provided callbacks to push or pop stack frames as the exception unwinds:

(* Load an ELF file and extract OCaml exception metadata *)
let elf_opt = Elf.create "my_program.bc" in
match elf_opt with
| None -> printf "Failed to parse ELF\n"
| Some elf ->
  match Elf.ocaml_exception_info elf with
  | None -> printf "No OCaml exception metadata present\n"
  | Some exc_info ->
      printf "Found %d push-traps and %d pop-traps\n"
        (Array.length exc_info.pushtrap_and_poptrap_addresses)
        (Int64.Set.cardinal exc_info.entertrap_addresses)
(* Update call stack when processing exception transitions *)
let process_thread thread_info prev_ip curr_ip =
  match thread_info.ocaml_exception_state with
  | With_exception_info { ocaml_exception_info; _ } ->
      Ocaml_exception_info.iter_pushtraps_and_poptraps_in_range
        ~from:prev_ip ~to_:curr_ip
        ~f:(fun (addr, kind) ->
            match kind with
            | Pushtrap -> push_exception_frame addr
            | Poptrap  -> pop_exception_frame addr)
        ocaml_exception_info
  | Without_exception_info _ -> ()

Summary

  • ELF Section Location: find_ocaml_exception_info in src/elf.ml locates the .note.ocaml_eh section and computes address adjustments using the STAPSDT base.
  • Address Parsing: The read_note helper extracts three address lists (enter-traps, push-traps, pop-traps) and adjusts them for the actual load address.
  • Data Structure: Ocaml_exception_info.create builds a searchable record with sorted arrays and sets for O(1) enter-trap lookups.
  • Runtime Integration: iter_pushtraps_and_poptraps_in_range enables the trace writer to reconstruct precise exception unwinding by matching instruction ranges against the debug symbol data.

Frequently Asked Questions

What is the .note.ocaml_eh section?

The .note.ocaml_eh section is a special ELF note section that contains OCaml exception handling metadata. The compiler populates this section with three address lists corresponding to exception trap points in the compiled code, allowing debuggers and profilers to reconstruct exception control flow without requiring DWARF debug information.

How does magic-trace handle address relocation for ASLR?

Magic-Trace accounts for Address Space Layout Randomization by reading the .stapsdt.base section to obtain the actual load-time base address. It then applies Owee_elf_notes.Stapsdt.adjust to convert stored offsets into virtual addresses using the calculation actual_base - recorded_base + addr, ensuring correct symbol resolution regardless of where the binary loads in memory.

What are the differences between enter-traps, push-traps, and pop-traps?

Enter-traps mark instruction addresses where execution enters an exception handler body. Push-traps identify where the OCaml runtime pushes exception frames onto the stack via caml_push_trap, while pop-traps indicate where frames are popped via caml_pop_trap. Magic-Trace uses push and pop trap pairs to reconstruct the precise call stack modifications during exception propagation, and enter-traps to identify handler entry points.

Can magic-trace extract exception information from stripped binaries?

No, magic-Trace requires the .note.ocaml_eh and .stapsdt.base sections to be present in the ELF file. Stripping a binary with strip or compiling without the appropriate flags removes these sections, making it impossible to extract the exception metadata necessary for reconstructing OCaml exception control flow.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →