# How Magic‑Trace Handles ELF Files and Symbol Resolution

> Learn how Magic-Trace processes ELF files and resolves symbols. Discover its advanced techniques for address translation and source symbol annotation for precise profiling.

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

---

**Magic‑Trace uses the `Elf` module in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml) to parse ELF binaries via the Owee_elf library, extract symbol tables, detect PIE vs. non‑PIE layouts, and translate between runtime addresses and source symbols for accurate snapshot placement and annotation.**

Magic‑Trace, the low‑overhead sampling profiler from Jane Street, relies on accurate ELF handling to map machine addresses back to human‑readable function names and source lines. When tracing a program, the tool must parse the executable’s layout, detect whether it is position‑independent (PIE), and resolve snapshot stop points from symbols to concrete addresses. All of this logic is concentrated in the core ELF handling module of the **janestreet/magic‑trace** repository, making it critical for both trace setup and result annotation.

## Loading ELF Binaries with `Elf.create`

The entry point for ELF handling is `Elf.create` in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml). This function opens the binary using `Owee_buf.map_binary`, reads the ELF header, and extracts section headers for the symbol table (`.symtab`), string table (`.strtab`), and debug line information (`.debug_line`). It also records the **base offset**—the gap between file offsets and runtime addresses—which is essential for later address translation as implemented in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml) lines 88‑100.

The resulting `Elf.t` record stores these tables alongside metadata indicating whether the binary is a PIE (Position Independent Executable) or fixed‑address executable.

## Detecting PIE vs. Non‑PIE Executables

Before calculating addresses, Magic‑Trace determines how the binary will be loaded into memory. The helper `is_non_pie_executable` checks the ELF type field: `ET_EXEC` indicates a fixed load address, while `ET_DYN` signals a PIE that can be mapped at a variable base according to [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml) lines 29‑34.

This distinction affects every subsequent address calculation. For non‑PIE binaries, offsets are absolute, whereas PIE binaries require runtime base address adjustment.

## Symbol Table Operations

### Finding Symbols by Name

To locate a specific function, `Elf.find_symbol` traverses the symbol table and returns the first entry where the symbol type is a function and the name matches the requested string as shown in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml) lines 59‑68.

```ocaml
match Elf.find_symbol elf "my_function" with
| None -> printf "Symbol not found\n"
| Some sym ->
  let addr = Owee_elf.Symbol_table.Symbol.value sym in
  printf "my_function starts at 0x%Lx\n" addr

```

### Pattern Matching with Regex

For user interfaces that accept wildcards, `Elf.matching_functions` filters the symbol table against a regular expression, returning a map of symbol names to their metadata as defined in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml) lines 28‑39.

```ocaml
let regex = Re.Pcre.regexp ".*_handler$" in
let matches = Elf.matching_functions elf regex in
Map.iteri matches ~f:(fun ~key:name ~data:sym ->
  printf "%s → 0x%Lx\n" name (Owee_elf.Symbol_table.Symbol.value sym))

```

## Source Line Resolution via DWARF

Magic‑Trace supports exact breakpoint placement at source lines by reading DWARF debug information. The function `Elf.traverse_debug_line` iterates over the `.debug_line` section, invoking a callback for each line table row as implemented in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml) lines 42‑56.

Building on this, `Elf.all_file_selections` generates "file:line:col" strings for a given symbol, enabling the UI to present a list of precise code locations to the user.

## Runtime Address Translation

When the kernel returns a raw instruction pointer, the `Symbol_resolver` submodule translates it back to symbolic information. First, `resolve` adjusts the sampled address from runtime space to file offset using the recorded base offset. Then it looks up the enclosing ELF symbol to yield the function name and its address bounds according to [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml) lines 78‑100.

```ocaml
let resolver = Elf.Symbol_resolver.{ elf; file_offset = 0; loaded_offset = 0 } in
match Elf.Symbol_resolver.resolve resolver 0x7f123456L with
| None -> printf "Address not covered\n"
| Some r -> printf "Address belongs to %s (0x%x‑0x%x)\n"
            r.name r.start_addr r.end_addr

```

## Integration with the Tracing Core

The `Elf` module is orchestrated from [`src/trace.ml`](https://github.com/janestreet/magic-trace/blob/main/src/trace.ml). At trace initialization, `Trace.create_elf` invokes `Elf.create` to load the binary as seen in lines 33‑36. Later, functions such as `Elf.addr_table` and `Elf.selection_stop_info` convert user‑provided symbol names or source lines into concrete addresses for the Linux `perf_event` filters, and annotate sampled events with human‑readable symbols as implemented in lines 257‑352.

## Summary

- Magic‑Trace centralizes ELF handling in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml), using the **Owee_elf** library to parse headers and section tables.
- It detects **PIE vs. non‑PIE** executables via `is_non_pie_executable` to handle address translation correctly across different binary types.
- Symbol lookup supports both exact name matching (`find_symbol`) and regex filtering (`matching_functions`).
- **DWARF debug lines** are traversed via `traverse_debug_line` to map addresses to source file and line numbers for precise snapshot placement.
- The **Symbol_resolver** submodule converts sampled runtime addresses back to function names and boundaries during trace analysis.

## Frequently Asked Questions

### How does Magic‑Trace determine if a binary is a PIE?

Magic‑Trace checks the ELF header’s `e_type` field via `is_non_pie_executable` in [`src/elf.ml`](https://github.com/janestreet/magic-trace/blob/main/src/elf.ml). If the type is `ET_EXEC`, the binary loads at a fixed address; if `ET_DYN`, it is treated as a PIE requiring runtime base address adjustment.

### Can Magic‑Trace resolve addresses to function names without debug symbols?

Yes, as long as the binary contains a symbol table (`.symtab`). Magic‑Trace uses `Elf.find_symbol` and the `Symbol_resolver` to map addresses to function names. However, source line information requires DWARF debug data (`.debug_line`).

### What library does Magic‑Trace use for ELF parsing?

Magic‑Trace uses **Owee_elf**, an OCaml library for reading ELF files and DWARF debugging information. The `Owee_buf.map_binary` function memory‑maps the binary, and `Owee_elf` provides iterators for symbol and line tables consumed by the `Elf` module.

### How are user‑specified symbol names converted to addresses for tracing?

The `Elf.selection_stop_info` function converts symbol or line selections into `Stop_info.t` records containing concrete runtime addresses. This takes into account the base offset and whether the binary is PIE, producing the exact address used for perf snapshot filters.