Skip to main content

Instructions

Each instruction record in a program corresponds to one EVM opcode in the bytecode. Instructions carry the context information that debuggers need.

Structure

An instruction requires only offset; operation and context are optional:

{
"offset": 42,
"operation": {
"mnemonic": "SSTORE"
},
"context": { }
}
  • offset: byte position in the bytecode (the program counter value when this instruction executes)
  • operation: machine operation info — includes mnemonic (required) and optional arguments (immediate values)
  • context: high-level information valid after this instruction (defaults to {} when omitted)

Operation

The optional operation field describes the machine-level operation:

{
"offset": 0,
"operation": {
"mnemonic": "PUSH1",
"arguments": ["0x60"]
}
}
  • mnemonic (required): the opcode name (e.g., "PUSH1", "SSTORE", "ADD")
  • arguments (optional): immediate values passed to the opcode (relevant for PUSH instructions)

Context types

The context field can take several forms:

Code context

Maps the instruction to source code:

{
"offset": 42,
"context": {
"code": {
"source": { "id": 0 },
"range": {
"offset": 150,
"length": 16
}
}
}
}

The source field references a source file by ID and specifies the exact character range.

Variables context

Declares variables that are in scope:

{
"offset": 100,
"context": {
"variables": [
{
"identifier": "amount",
"type": { "kind": "uint", "bits": 256 },
"pointer": {
"location": "stack",
"slot": 0
}
}
]
}
}

Each variable includes:

  • identifier: the variable name from source code
  • type: an ethdebug/format type reference
  • pointer: where to find the variable's value

Frame context

Indicates which compilation frame the context applies to. This is useful for compilers with distinct frontend/backend stages (e.g., source language vs. intermediate representation):

{
"offset": 200,
"context": {
"frame": "ir"
}
}

The frame value is a string naming the compilation frame, e.g.:

  • "source": the original source language
  • "ir": an intermediate representation

Composing contexts

Gather

Combine multiple contexts (like nested scopes):

{
"offset": 150,
"context": {
"gather": [
{
"code": {
"source": { "id": 0 },
"range": { "offset": 200, "length": 12 }
}
},
{
"variables": [
{
"identifier": "x",
"type": { "kind": "uint", "bits": 256 },
"pointer": { "location": "stack", "slot": 0 }
}
]
}
]
}
}

Pick

Indicate that one of several possible contexts is true, possibly requiring additional information to disambiguate:

{
"offset": 175,
"context": {
"pick": [
{
"code": {
"source": { "id": 5 },
"range": { "offset": 68, "length": 16 }
}
},
{
"code": {
"source": { "id": 5 },
"range": { "offset": 132, "length": 16 }
}
}
]
}
}

Each item in the pick array is a full context object. The debugger may need runtime information to determine which context applies.

Remark

Add metadata without affecting scope:

{
"offset": 180,
"context": {
"remark": "loop iteration boundary"
}
}

Instruction ordering

Instructions must be listed in bytecode order, matching the sequence of opcodes. The list is indexed by offset, so debuggers can quickly find the instruction for any program counter value.

Not every byte offset needs an instruction—only positions where opcodes begin. Push data, for instance, doesn't get its own instruction entry.

Learn more

For complete schemas, see: