dissect #
Attribute a compiled binary's size to the OCaml modules and libraries that fill it.
Installation #
Install with opam:
$ opam install dissect
If opam cannot find the package, it may not yet be released in the public
opam-repository. Add the overlay repository, then install it:
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install dissect
Overview #
An OCaml executable of 16 MiB gives no hint of which libraries made it that
size, and the usual answer, nm piped through a script, needs binutils and
knowledge of how OCaml names its symbols. dissect reads a compiled OCaml binary (Mach-O or ELF, detected from its
leading bytes), attributes every byte to the OCaml module and library that
produced it, and prints a sorted weight-map of proportional bars in the
terminal. The binary formats are parsed natively by mach-o and
elf (both built on the wire codec library); the rendering
uses console.
Usage #
dissect path/to/binary # group by library, top 20 (the default)
dissect --by-module ./a.out # group by individual module
dissect -n 40 ./a.out # show more rows (0 shows all)
! 5.4 MiB (34%) is not code: symbol & string tables, constants, unwind data
and headers. Most is removed by `strip`.
╭────────────────────┬───────────┬─────┬───...╮
│ Module │ Size │ % │ │
├────────────────────┼───────────┼─────┼───...┤
│ Stdlib │ 713.7 KiB │ 6% │ ███ │
│ Parser │ 700.7 KiB │ 6% │ ███ │
│ Typecore │ 455.0 KiB │ 4% │ ██ │
│ (... 190 more) │ 4.9 MiB │ 46% │ ████ │
╰────────────────────┴───────────┴─────┴───...╯
10.5 MiB of code across 210 module(s), 65% of the 15.9 MiB file
The bytes no symbol covers are summarised in the warning rather than given
rows in the table: the symbol and string tables (Mach-O __LINKEDIT, which
strip removes), the constant and exception sections, and the headers. The
percentages in the table are therefore shares of the attributed code rather
than of the whole file.
How symbols are bucketed #
Each linker symbol is classified by name. Dune wraps a library by default:
each module M of library Lib is compiled as Lib__M, and one generated
module Lib re-exports them, so two libraries can each have a module called
Parser without their symbols colliding. A wrapped library's symbol therefore
has the form caml<Lib>__<Module>$<function>, and it is grouped under <Lib>. A
symbol with no __ is bucketed under its own module name, since the symbol
does not encode the library. Anything that is not a module symbol starting
with caml goes to the C runtime bucket:
# Dissect.Demangle.classify "_camlStdlib__Arg$go_42"
- : Dissect.Demangle.t =
Dissect.Demangle.Ocaml {Dissect.Demangle.library = "Stdlib"; modul = "Arg"}
# Dissect.Demangle.classify "_caml_alloc"
- : Dissect.Demangle.t = Dissect.Demangle.C_runtime
Mach-O symbol records carry no size, so a symbol's size is taken as the distance to the next symbol in its section, and the last symbol of a section runs to the section's end (its address plus its size). The symbols of a section therefore sum to the bytes from its first symbol to its end. ELF records carry an explicit size, and dissect uses it.
Licence #
ISC. See LICENSE.md.