# How Magic-Trace Extracts OCaml Exception Information from Debug Symbols

> Learn how Magic-Trace extracts OCaml exception information from debug symbols using ELF note sections and address lists to reconstruct exception control flow for effective tracing.

- Repository: [Jane Street/magic-trace](https://github.com/janestreet/magic-trace)
- Tags: how-to-guide
- Published: 2026-05-24

---

**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`](https://github.com/janestreet/magic-trace/blob/main/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`](https://github.com/janestreet/magic-trace/blob/main/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`](https://github.com/janestreet/magic-trace/blob/main/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`](https://github.com/janestreet/magic-trace/blob/main/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`](https://github.com/janestreet/magic-trace/blob/main/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`](https://github.com/janestreet/magic-trace/blob/main/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:

```ocaml
(* 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)

```

```ocaml
(* 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`](https://github.com/janestreet/magic-trace/blob/main/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.