For compiler authors
You're building a compiler or toolchain that produces EVM bytecode. Here's how to emit ethdebug/format data so debuggers can provide rich debugging experiences for your users.
What you need to emit
ethdebug/format defines several kinds of debug information:
- Type information — Describe the structure of data in your language (integers, structs, arrays, mappings)
- Pointer information — Describe where variables are stored at runtime, including dynamic locations computed from other values
- Program information — Describe what's in scope at each bytecode instruction, mapping bytecode back to source code
- Info schema — Bundle everything together with source files and compilation metadata
Quick example: describing a type
Here's how to describe a simple uint256:
A uint256 typeSchema:ethdebug/format/type/elementary/uint
{
"kind": "uint",
"bits": 256
}
A storage mapping from addresses to balances:
An address → uint256 mappingSchema:ethdebug/format/type/complex/mapping
{
"kind": "mapping",
"contains": {
"key": { "type": { "kind": "address" } },
"value": { "type": { "kind": "uint", "bits": 256 } }
}
}
Quick example: describing a storage variable
A pointer tells debuggers where to find a variable's value. For a uint256
at storage slot 0:
A simple storage pointerSchema:ethdebug/format/pointer/region/storage
{
"location": "storage",
"slot": "0x0",
"length": 32
}
For a dynamic array where the length is at slot 2 and elements start at
keccak256(2):
A dynamic array pointerSchema:ethdebug/format/pointer/collection
{
"group": [
{ "name": "array-length", "location": "storage", "slot": "0x2" },
{
"list": {
"count": { "~read": "array-length" },
"each": "i",
"is": {
"location": "storage",
"slot": {
"~sum": [{ "~keccak256": [{ "~wordsized": "0x2" }] }, "i"]
}
}
}
}
]
}
Operators and constants such as ~read, ~sum and ~wordsize start with
~. Names that you choose, such as array-length and i here, never start
with ~.
Integration approach
Most compilers can add ethdebug/format support incrementally:
- Start with types — Emit type definitions for your language's data structures. This can be a straightforward starting point.
- Add storage pointers — Describe where storage variables live. Many variables have static locations.
- Add memory/stack pointers — Describe temporary values. These often require tracking allocation during code generation.
- Add program information — Emit source mappings and scope information. This typically requires the most compiler changes.