Deep EVM #18: EVM-Bytecode debuggen — Traces, Stack-Dumps und cast run
Engineering Team
Debugging auf Bytecode-Ebene
Wenn eine Solidity-Transaktion revertiert, erhalten Sie typischerweise eine beschreibende Fehlermeldung. Wenn eine Huff- oder Yul-Transaktion revertiert, erhalten Sie ein leeres Revert-Payload ohne Kontext. Debugging auf Bytecode-Ebene erfordert andere Werkzeuge und mentale Modelle.
cast run: Transaktionen nachspielen
cast run ist das maechtigste Debugging-Werkzeug fuer On-Chain-Transaktionen:
# Transaktion nachspielen mit vollstaendigem Trace
cast run 0x1234...abcd --rpc-url mainnet -vvvv
# Nur den Trace einer bestimmten Adresse anzeigen
cast run 0x1234...abcd --rpc-url mainnet -vvvv \
--label 0xContract:MyContract
Die Ausgabe zeigt jeden Opcode, den Stack-Zustand und Gasverbrauch:
[0] PUSH1 0x80
Stack: [0x80]
Gas: 499979
[2] PUSH1 0x40
Stack: [0x80, 0x40]
Gas: 499976
[4] MSTORE
Stack: []
Gas: 499964
forge debug: Interaktiver Debugger
# Test im Debug-Modus starten
forge debug test/MyTest.t.sol --sig "testSwap()"
# Bestimmte Transaktion debuggen
forge debug --fork-url mainnet 0x1234...abcd
Der interaktive Debugger ermoeglicht:
- Schritt-fuer-Schritt durch Opcodes navigieren
- Stack und Memory zu jedem Zeitpunkt inspizieren
- Breakpoints an bestimmten Programmzaehlern setzen
- Storage-Aenderungen verfolgen
Haeufige Debugging-Szenarien
1. Leerer Revert
Problem: Transaktion revertiert ohne Fehlermeldung.
Loesung:
# Trace analysieren — suche den letzten REVERT-Opcode
cast run 0x... -vvvv | grep -A5 "REVERT"
Haeufige Ursachen:
- Stack-Unterlauf (Opcode versucht, mehr zu entnehmen als vorhanden)
- JUMPDEST-Fehler (Sprung zu einer Position ohne JUMPDEST)
- Gaserschoepfung in einem Unteraufruf
2. Falsches Ergebnis
Problem: Contract gibt falschen Wert zurueck.
Loesung: Stack-Zustand vor RETURN pruefen:
# RETURN-Opcode finden und Memory inspizieren
cast run 0x... -vvvv | grep -B10 "RETURN"
3. Out-of-Gas
Problem: Transaktion laeuft aus dem Gas.
Loesung:
# Gas-Verbrauch pro Opcode analysieren
cast run 0x... -vvvv | awk '/Gas:/ {print $NF}' | sort -n
Opcode-Trace lesen
Ein typischer Trace fuer einen ERC-20-Transfer:
// Funktionsselektor laden
PUSH1 0x00 CALLDATALOAD // [calldata[0:32]]
PUSH1 0xe0 SHR // [selector]
// Gegen transfer(address,uint256) pruefen
DUP1 PUSH4 0xa9059cbb EQ // [selector==transfer, selector]
PUSH2 0x00a4 JUMPI // Springe wenn match
// Bei transfer:
JUMPDEST // <- Sprungziel
PUSH1 0x04 CALLDATALOAD // [to_address]
PUSH1 0x24 CALLDATALOAD // [amount, to_address]
Debugging-Checkliste fuer Huff
- Stack-Kommentare in jedem Makro pruefen
takes()undreturns()Deklarationen verifizieren- Jeden externen Aufruf mit cast run nachverfolgen
- Gas-Budget pro Pfad mit forge test –gas-report analysieren
- Differentielles Testen gegen Solidity-Referenz durchfuehren
- Edge Cases testen: leere Calldata, maximale Werte, Reentrancy
Fazit
EVM-Bytecode-Debugging ist eine Kunst, die Uebung erfordert. cast run und forge debug sind Ihre wichtigsten Werkzeuge. Der Schluessel: Verstehen Sie den Stack-Zustand zu jedem Zeitpunkt der Ausfuehrung, und Sie werden jedes Problem loesen koennen.