Library / SDK
getsentry/symbolic avatar
getsentry/symbolic

symbolic: stack trace symbolication in Rust

Stack trace symbolication library written in Rust

555 stars93 forksRustMIT

At a glance

What is it?
Symbolic is the Rust library Sentry uses to turn machine addresses back into function names, and it handles an unusually wide range of debug formats, from DWARF and PDBs through Breakpad files to Unity IL2CPP metadata and JavaScript sourcemaps. It ships as Rust crates, as a C ABI and as a Python package, and its workspace is a good map of the problem space.
Who is it for?
symbolic fits a team that has to turn raw crash addresses into readable frames across a mixed estate, whether that estate is Linux ELF binaries, Windows PDBs, Unity games or minified web bundles, and who wants one implementation rather than four.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Turning machine addresses back into function names

Symbolic is a library written in Rust which is used at Sentry to implement symbolication of native stack traces, sourcemap handling for minified JavaScript and more.

That first clause explains the project. A crash report from a native binary arrives as a list of instruction addresses and a build identifier. Symbolic is what turns that back into a file name, a line number and a function name, using the debug information produced at build time.

What makes it more than a DWARF reader is the breadth of the format list. The README's function list starts with symbolication based on custom cache files, called symcache, and then lists the symbol cache file generators, which cover Mach, ELF and PE symbol tables, Mach, ELF and PE embedded DWARF data, PDB CodeView debug information,.NET Portable PDB, Breakpad symbol files and Unity IL2CPP.

Those are the six ways a native binary can carry its debug information, and a crash reporting service has to handle all of them because its users build on every platform. Then there is demangling support for C++ from GCC, clang and MSVC, for Objective C and Objective C++, for Rust and for Swift, and the JavaScript sourcemap handling, which covers basic token mapping, heuristics to find original function names based on minified sources, and indexed sourcemap to sourcemap merging.

There is also Proguard function mappings, generation of Breakpad symbol files from Mach, ELF and PDBs, and processing of Unreal Engine 4 native crash reports, which extracts and processes minidumps and exposes logs and UE4 context information.

The licence is MIT, with the note that it uses some Apache2 licensed code from Apple for the Swift demangling. The primary language is Rust and the default branch is master, with the last push on 2026-10-22.

The workspace is a map of the problem space

The repository root shows how the project is decomposed, and the directory names are a compact summary of what symbolic does.

There is symbolic/ for the main crate, and then symbolic-common/, symbolic-debuginfo/ and symbolic-demangle/ for the shared types, the debug information readers and the name demanglers. The output formats have their own crates: symbolic-symcache/ for the symcache format, symbolic-cfi/ for call frame information, symbolic-ppdb/ for .NET portable PDBs and symbolic-sourcemapcache/ for the JavaScript cache format.

The platform and product specific pieces have their own crates too: symbolic-il2cpp/ for Unity IL2CPP metadata, symbolic-unreal/ for Unreal Engine 4 crash archives and symbolic-wasm/ for WebAssembly. Then symbolic-cabi/ for the C bindings, symbolic-testutils/ for the shared test helpers and examples/ for the sample programs, which the Cargo workspace includes as members alongside every crate matching the symbolic* glob.

That decomposition is the practical argument for the library. If you only want the DWARF path for Linux binaries, you take the debuginfo crate; if you are a JavaScript shop you take the sourcemap and sourcemapcache crates; if you are a game studio you take il2cpp. A monolithic library would make you compile all of it.

The Cargo workspace metadata matches the same care. The workspace uses the 2024 edition with rust-version 1.85, and the dependency list is pinned to specific versions with explicit feature sets, including gimli with default features off and only read, std and fallible-iterator enabled, and goblin likewise without its default features.

One library, three language bindings

Symbolic consists of multiple largely independent crates which are bundled together into a C and Python library so it can be used independently of Rust.

For Rust users the crates are published to Crates.io and the documentation is on docs.rs. For Python users, the package is hosted on PyPI and comes as a library with prebuilt wheels for linux and macOS. On other operating systems, or when using it as a Rust library, you need to build symbolic manually. The README also notes that it should be compatible with both Python 2 and Python 3.

The Python library ships all of the above features in a flat module, so the whole surface is one import namespace rather than a package tree. The example in the README shows the shape of it:

python
from symbolic import Archive

fat = Archive.open('/path/to/object')
obj = fat.get_object(arch = 'x86_64')
print 'object debug id: {}' % obj.debug_id

The pattern is worth noting even though the print statement is Python 2 syntax. You open an Archive on a path, which may be a fat binary containing several architectures, then get the object for the architecture you care about, and read its debug identifier. That debug identifier is the key that ties a crash report to the right symbol file, and the fact that it is a first class accessor tells you the library is built around that lookup.

For everyone else there are C bindings, which the README says allow for FFI into arbitrary languages, with the details in the Symbolic C-ABI readme. The Makefile adds a fourth target, an npm package, with the comment that it builds the @sentry/symbolic WASM npm package from a shell script in symbolic-wasm, so the same engine is reachable from JavaScript as well.

Building it means passing every feature

The Building and Development section is specific about the requirements. To build the Rust crate, the project requires the latest stable Rust as well as a C++11 compiler. The crate is split into a workspace with multiple features, so when building or running tests you must always pass the `--all` and `--all-features` flags.

That last instruction is the one to internalise. A partial build of this workspace is likely to compile and then fail at runtime on a format you thought was covered, because the feature that parses it was never enabled. The two commands are:

bash
# Check whether the crate compiles
cargo check --all --all-features

# Run Rust tests
cargo test --all --all-features

Formatting and linting are pinned to the latest stable channel, and the README points at two make targets to set them up correctly with the right configuration:

bash
# Format the entire codebase
make format

# Run clippy on the entire codebase
make lint

The Makefile shows what those targets do in practice. The style target runs rustfmt across the workspace and black for the Python side, with the black version pinned to 22.3.0. The lint target runs clippy with `-D warnings` over the workspace including tests and examples, and for Python it installs flake8 at 6.0.0 and mypy at 1.4.0. Pinning the formatter and linter versions is what keeps a codebase with this many crates consistent.

The workspace also builds a WASM artefact and a set of Python wheels, including manylinux builds run inside Docker for x86_64 and for aarch64, which is the mechanism by which the PyPI wheels for Linux are produced.

The Python side is a C ABI plus a thin package

The Python development workflow is described in a way that says a lot about the architecture.

Most likely, new functionality also needs to be added to the Python package. This first requires exposing new functions in the C ABI, and the README refers to the Symbolic C-ABI readme for that. Then it recommends developing and testing the Python package in a virtual environment. Once the ABI has been updated and tested, you ensure the virtualenv is active and install the package, which builds the native library, and there are two ways to do that:

bash
# Install the release build, recommended:
pip install --editable ./py

# Install the debug build, faster installation but much slower runtime:
SYMBOLIC_DEBUG=1 pip install --editable ./py

So Python is not a separate implementation. It is the C ABI with a generated or hand-written layer on top, and every new feature has to be pushed through that boundary first. The environment variable switches the build mode of the native library, trading runtime speed for build speed, which is the right trade while you are iterating and the wrong one for anything you deploy.

Testing uses pytest, again with the virtualenv active and the native library installed:

bash
# Run tests manually
pytest ./py/tests

# Creates a new virtualenv, installs the release build and runs tests:
make pytest

The distinction between the two is the kind of detail that saves time. Running pytest yourself assumes the environment is already in the right state, while make pytest creates a fresh virtualenv, installs the release build and runs the tests, which is the reproducible path.

The examples directory also exists to serve as documentation. The README lists what each one does, from dump_cfi, which writes call frame information from an object file to standard out in Breakpad's STACK records format, through minidump_stackwalk, which extracts stack traces from a minidump and symbolicates them, to unreal_engine_crash, which lists the files in an Unreal Engine 4 crash archive. To run these examples, use the run script, for example:

sh
./run minidump_stackwalk mini.dmp /path/to/files

One of them, dump_sources, is now integrated into sentry-cli difutil bundle-sources, which is a good sign that these examples are used in production workflows rather than only as documentation.

A 1.85 floor, no promises, and an alpha line

The Supported Rust Versions section is short and unusually honest. Symbolic tries to not break MSRV compatibility but makes no guarantees about the Rust version, and although you can expect Rust version compatibility of at least 6 months, the current MSRV is 1.85.

The combination is worth reading carefully. The floor is stated precisely, so a project on 1.85 can build, but the compatibility promise is bounded at six months and explicitly disclaimed. In practice that means the crate will keep working with a recent stable Rust for a while and will be updated when the project needs it to be.

The release history explains why the current line is where it is. The recent releases are 14.0.0-alpha.4 from 2026-10-15, 14.0.0-alpha.3 from 2026-07-23 and 14.0.0-alpha.2 from 2026-07-14.

A major version at 14 in alpha says two things. There has been a great deal of change over the life of the project, and the current work is a breaking release that has not been stabilised. Alpha.2 and alpha.3 landed in July a little over a week apart, and alpha.4 followed in October, which is the shape of a release approaching its stable form.

For a library that Sentry itself uses in production, the stable line is what most integrators will actually pin, and the alpha line is where the churn is. The topics on the repository are symbolication and tag-production, and tag production is the other half of the story, because the debug files that symbolic consumes have to be uploaded and indexed somewhere first, and this project is where the format for that lives.

The repository also carries the usual quality tooling for a project of this size, with a CI workflow badge, a codecov badge with a token, a.codecov.yml, a.semgrepignore, a warden.toml for dependency policy and a.craft.yml for release packaging.

Editorial conclusion

symbolic fits a team that has to turn raw crash addresses into readable frames across a mixed estate, whether that estate is Linux ELF binaries, Windows PDBs, Unity games or minified web bundles, and who wants one implementation rather than four. It is the wrong choice if you only need to symbolicate one platform's native crashes, since the workspace carries a lot of crates you would not use, and the wrong choice if you want a stable 1.x release, because the current line is a 14.0.0 alpha. Verify first which debug formats your own binaries ship with, that your toolchain meets the stated MSRV of 1.85, and that the Python wheel for your platform exists, since the README says wheels cover Linux and macOS and other systems build from source.

Frequently asked questions

What is the symbolic library?

It is a Rust library used at Sentry to implement symbolication of native stack traces and sourcemap handling for minified JavaScript. It consists of multiple largely independent crates that are bundled into a C and Python library so it can be used without writing Rust.

Which debug file formats does symbolic support?

It generates symbol caches from Mach, ELF and PE symbol tables, Mach, ELF and PE embedded DWARF data, PDB CodeView debug information, .NET Portable PDB, Breakpad symbol files and Unity IL2CPP, and it can symbolicate against its own symcache format. It also handles Proguard function mappings and Unreal Engine 4 native crash reports.

How do I build symbolic from source?

You need the latest stable Rust and a C++11 compiler, and you must pass both the --all and --all-features flags because the crate is a workspace with multiple features. The README gives cargo check --all --all-features and cargo test --all --all-features, and points at make format and make lint for rustfmt and clippy.

How is the Python package installed and tested?

New features have to be exposed through the C ABI first, then the Python package is developed in a virtual environment and installed with pip install --editable ./py for the release build, or SYMBOLIC_DEBUG=1 pip install --editable ./py for a debug build that installs faster but runs slower. Tests run with pytest ./py/tests, and make pytest creates a fresh virtualenv and installs the release build first.

What Rust version does symbolic require?

The current MSRV is 1.85. The README says symbolic tries not to break MSRV compatibility but makes no guarantees about the Rust version, and that you can expect Rust version compatibility of at least 6 months.

Official sources

  1. getsentry/symbolic on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/getsentry-symbolic.svg)](https://hysenlabs.com/projects/getsentry-symbolic)

Was this useful?