How Magic‑Trace Handles ELF Files and Symbol Resolution

Magic‑Trace uses the Elf module in 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. 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 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 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 lines 59‑68.

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 lines 28‑39.

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 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 lines 78‑100.

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

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 →