Commit 8b910d7a authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Refactor instrumented_state.go to extract reusable helpers (#11174)

* cannon: Extract TrackingOracle to allow reuse

* cannon: Extract PreimageReader logic for easier reuse

* cannon: Merge preimage-related tracking and access logic

* cannon: Extract memTracker logic for easier reuse

* cannon: Extract stack tracking logic for easier reuse

* cannon: Break up StackTracker into noop and regular impls

* cannon: Clean up PreimageReader
parent 26eac671
package exec
import (
"fmt"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
type MemTracker interface {
TrackMemAccess(addr uint32)
}
type MemoryTrackerImpl struct {
memory *memory.Memory
lastMemAccess uint32
memProofEnabled bool
memProof [memory.MEM_PROOF_SIZE]byte
}
func NewMemoryTracker(memory *memory.Memory) *MemoryTrackerImpl {
return &MemoryTrackerImpl{memory: memory}
}
func (m *MemoryTrackerImpl) TrackMemAccess(effAddr uint32) {
if m.memProofEnabled && m.lastMemAccess != effAddr {
if m.lastMemAccess != ^uint32(0) {
panic(fmt.Errorf("unexpected different mem access at %08x, already have access at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
m.memProof = m.memory.MerkleProof(effAddr)
}
}
func (m *MemoryTrackerImpl) Reset(enableProof bool) {
m.memProofEnabled = enableProof
m.lastMemAccess = ^uint32(0)
}
func (m *MemoryTrackerImpl) MemProof() [memory.MEM_PROOF_SIZE]byte {
return m.memProof
}
......@@ -5,11 +5,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
type StackTracker interface {
PushStack(target uint32)
PopStack()
}
func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits
......@@ -18,7 +13,7 @@ func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun
return insn, opcode, fun
}
func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memory *memory.Memory, insn, opcode, fun uint32, memTracker memory.MemTracker, stackTracker StackTracker) error {
func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) error {
// j-type j/jal
if opcode == 2 || opcode == 3 {
linkReg := uint32(0)
......@@ -73,7 +68,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
// M[R[rs]+SignExtImm]
rs += SignExtend(insn&0xFFFF, 16)
addr := rs & 0xFFFFFFFC
memTracker(addr)
memTracker.TrackMemAccess(addr)
mem = memory.GetMemory(addr)
if opcode >= 0x28 && opcode != 0x30 {
// store
......@@ -117,7 +112,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
// write memory
if storeAddr != 0xFF_FF_FF_FF {
memTracker(storeAddr)
memTracker.TrackMemAccess(storeAddr)
memory.SetMemory(storeAddr, val)
}
......
......@@ -36,8 +36,6 @@ const (
MipsEINVAL = 0x16
)
type PreimageReader func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32)
func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
syscallNum = registers[2] // v0
......@@ -68,7 +66,7 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
return v0, v1, newHeap
}
func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.Memory, memTracker memory.MemTracker) (v0, v1, newPreimageOffset uint32) {
func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.Memory, memTracker MemTracker) (v0, v1, newPreimageOffset uint32) {
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code
v0 = uint32(0)
......@@ -80,9 +78,9 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
// leave v0 and v1 zero: read nothing, no error
case FdPreimageRead: // pre-image oracle
effAddr := a1 & 0xFFffFFfc
memTracker(effAddr)
memTracker.TrackMemAccess(effAddr)
mem := memory.GetMemory(effAddr)
dat, datLen := preimageReader(preimageKey, preimageOffset)
dat, datLen := preimageReader.ReadPreimage(preimageKey, preimageOffset)
//fmt.Printf("reading pre-image data: addr: %08x, offset: %d, datLen: %d, data: %x, key: %s count: %d\n", a1, m.state.PreimageOffset, datLen, dat[:datLen], m.state.PreimageKey, a2)
alignment := a1 & 3
space := 4 - alignment
......@@ -110,7 +108,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
return v0, v1, newPreimageOffset
}
func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle mipsevm.PreimageOracle, memory *memory.Memory, memTracker memory.MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) {
func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle mipsevm.PreimageOracle, memory *memory.Memory, memTracker MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) {
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code
v1 = uint32(0)
......@@ -142,7 +140,7 @@ func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
v0 = a2
case FdPreimageWrite:
effAddr := a1 & 0xFFffFFfc
memTracker(effAddr)
memTracker.TrackMemAccess(effAddr)
mem := memory.GetMemory(effAddr)
key := preimageKey
alignment := a1 & 3
......
package exec
import (
"encoding/binary"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
type PreimageReader interface {
ReadPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32)
}
// TrackingPreimageOracleReader wraps around a PreimageOracle, implements the PreimageOracle interface, and adds tracking functionality.
// It also implements the PreimageReader interface
type TrackingPreimageOracleReader struct {
po mipsevm.PreimageOracle
totalPreimageSize int
numPreimageRequests int
// cached pre-image data, including 8 byte length prefix
lastPreimage []byte
// key for above preimage
lastPreimageKey [32]byte
// offset we last read from, or max uint32 if nothing is read this step
lastPreimageOffset uint32
}
func NewTrackingPreimageOracleReader(po mipsevm.PreimageOracle) *TrackingPreimageOracleReader {
return &TrackingPreimageOracleReader{po: po}
}
func (p *TrackingPreimageOracleReader) Reset() {
p.lastPreimageOffset = ^uint32(0)
}
func (p *TrackingPreimageOracleReader) Hint(v []byte) {
p.po.Hint(v)
}
func (p *TrackingPreimageOracleReader) GetPreimage(k [32]byte) []byte {
p.numPreimageRequests++
preimage := p.po.GetPreimage(k)
p.totalPreimageSize += len(preimage)
return preimage
}
func (p *TrackingPreimageOracleReader) ReadPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := p.lastPreimage
if key != p.lastPreimageKey {
p.lastPreimageKey = key
data := p.po.GetPreimage(key)
// add the length prefix
preimage = make([]byte, 0, 8+len(data))
preimage = binary.BigEndian.AppendUint64(preimage, uint64(len(data)))
preimage = append(preimage, data...)
p.lastPreimage = preimage
}
p.lastPreimageOffset = offset
datLen = uint32(copy(dat[:], preimage[offset:]))
return
}
func (p *TrackingPreimageOracleReader) LastPreimage() ([32]byte, []byte, uint32) {
return p.lastPreimageKey, p.lastPreimage, p.lastPreimageOffset
}
func (p *TrackingPreimageOracleReader) TotalPreimageSize() int {
return p.totalPreimageSize
}
func (p *TrackingPreimageOracleReader) NumPreimageRequests() int {
return p.numPreimageRequests
}
package exec
import (
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
type StackTracker interface {
PushStack(target uint32)
PopStack()
}
type TraceableStackTracker interface {
StackTracker
Traceback()
}
type NoopStackTracker struct{}
func (n *NoopStackTracker) PushStack(target uint32) {}
func (n *NoopStackTracker) PopStack() {}
func (n *NoopStackTracker) Traceback() {}
type StackTrackerImpl struct {
state mipsevm.FPVMState
stack []uint32
caller []uint32
meta *program.Metadata
}
func NewStackTracker(state mipsevm.FPVMState, meta *program.Metadata) (*StackTrackerImpl, error) {
if meta == nil {
return nil, errors.New("metadata is nil")
}
return &StackTrackerImpl{state: state}, nil
}
func (s *StackTrackerImpl) PushStack(target uint32) {
s.stack = append(s.stack, target)
s.caller = append(s.caller, s.state.GetPC())
}
func (s *StackTrackerImpl) PopStack() {
if len(s.stack) != 0 {
fn := s.meta.LookupSymbol(s.state.GetPC())
topFn := s.meta.LookupSymbol(s.stack[len(s.stack)-1])
if fn != topFn {
// most likely the function was inlined. Snap back to the last return.
i := len(s.stack) - 1
for ; i >= 0; i-- {
if s.meta.LookupSymbol(s.stack[i]) == fn {
s.stack = s.stack[:i]
s.caller = s.caller[:i]
break
}
}
} else {
s.stack = s.stack[:len(s.stack)-1]
s.caller = s.caller[:len(s.caller)-1]
}
} else {
fmt.Printf("ERROR: stack underflow at pc=%x. step=%d\n", s.state.GetPC(), s.state.GetStep())
}
}
func (s *StackTrackerImpl) Traceback() {
fmt.Printf("traceback at pc=%x. step=%d\n", s.state.GetPC(), s.state.GetStep())
for i := len(s.stack) - 1; i >= 0; i-- {
jumpAddr := s.stack[i]
idx := len(s.stack) - i - 1
fmt.Printf("\t%d %x in %s caller=%08x\n", idx, jumpAddr, s.meta.LookupSymbol(jumpAddr), s.caller[i])
}
}
......@@ -21,6 +21,8 @@ const (
PageKeyMask = MaxPageCount - 1
)
const MEM_PROOF_SIZE = 28 * 32
func HashPair(left, right [32]byte) [32]byte {
out := crypto.Keccak256Hash(left[:], right[:])
//fmt.Printf("0x%x 0x%x -> 0x%x\n", left, right, out)
......@@ -129,7 +131,7 @@ func (m *Memory) MerkleizeSubtree(gindex uint64) [32]byte {
return r
}
func (m *Memory) MerkleProof(addr uint32) (out [28 * 32]byte) {
func (m *Memory) MerkleProof(addr uint32) (out [MEM_PROOF_SIZE]byte) {
proof := m.traverseBranch(1, addr, 0)
// encode the proof
for i := 0; i < 28; i++ {
......@@ -335,5 +337,3 @@ func (m *Memory) Usage() string {
// KiB, MiB, GiB, TiB, ...
return fmt.Sprintf("%.1f %ciB", float64(total)/float64(div), "KMGTPE"[exp])
}
type MemTracker func(addr uint32)
package singlethreaded
import (
"errors"
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
)
type Debug struct {
stack []uint32
caller []uint32
meta *program.Metadata
}
type InstrumentedState struct {
state *State
stdOut io.Writer
stdErr io.Writer
lastMemAccess uint32
memProofEnabled bool
memProof [28 * 32]byte
preimageOracle *trackingOracle
// cached pre-image data, including 8 byte length prefix
lastPreimage []byte
// key for above preimage
lastPreimageKey [32]byte
// offset we last read from, or max uint32 if nothing is read this step
lastPreimageOffset uint32
memoryTracker *exec.MemoryTrackerImpl
stackTracker exec.TraceableStackTracker
debug Debug
debugEnabled bool
preimageOracle *exec.TrackingPreimageOracleReader
}
func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer) *InstrumentedState {
......@@ -43,7 +26,9 @@ func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdEr
state: state,
stdOut: stdOut,
stdErr: stdErr,
preimageOracle: &trackingOracle{po: po},
memoryTracker: exec.NewMemoryTracker(state.Memory),
stackTracker: &exec.NoopStackTracker{},
preimageOracle: exec.NewTrackingPreimageOracleReader(po),
}
}
......@@ -52,27 +37,21 @@ func NewInstrumentedStateFromFile(stateFile string, po mipsevm.PreimageOracle, s
if err != nil {
return nil, err
}
return &InstrumentedState{
state: state,
stdOut: stdOut,
stdErr: stdErr,
preimageOracle: &trackingOracle{po: po},
}, nil
return NewInstrumentedState(state, po, stdOut, stdErr), nil
}
func (m *InstrumentedState) InitDebug(meta *program.Metadata) error {
if meta == nil {
return errors.New("metadata is nil")
stackTracker, err := exec.NewStackTracker(m.state, meta)
if err != nil {
return err
}
m.debugEnabled = true
m.debug.meta = meta
m.stackTracker = stackTracker
return nil
}
func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err error) {
m.memProofEnabled = proof
m.lastMemAccess = ^uint32(0)
m.lastPreimageOffset = ^uint32(0)
m.preimageOracle.Reset()
m.memoryTracker.Reset(proof)
if proof {
insnProof := m.state.Memory.MerkleProof(m.state.Cpu.PC)
......@@ -89,18 +68,20 @@ func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err erro
}
if proof {
wit.ProofData = append(wit.ProofData, m.memProof[:]...)
if m.lastPreimageOffset != ^uint32(0) {
wit.PreimageOffset = m.lastPreimageOffset
wit.PreimageKey = m.lastPreimageKey
wit.PreimageValue = m.lastPreimage
memProof := m.memoryTracker.MemProof()
wit.ProofData = append(wit.ProofData, memProof[:]...)
lastPreimageKey, lastPreimage, lastPreimageOffset := m.preimageOracle.LastPreimage()
if lastPreimageOffset != ^uint32(0) {
wit.PreimageOffset = lastPreimageOffset
wit.PreimageKey = lastPreimageKey
wit.PreimageValue = lastPreimage
}
}
return
}
func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) {
return m.lastPreimageKey, m.lastPreimage, m.lastPreimageOffset
return m.preimageOracle.LastPreimage()
}
func (m *InstrumentedState) GetState() mipsevm.FPVMState {
......@@ -110,24 +91,11 @@ func (m *InstrumentedState) GetState() mipsevm.FPVMState {
func (m *InstrumentedState) GetDebugInfo() *mipsevm.DebugInfo {
return &mipsevm.DebugInfo{
Pages: m.state.Memory.PageCount(),
NumPreimageRequests: m.preimageOracle.numPreimageRequests,
TotalPreimageSize: m.preimageOracle.totalPreimageSize,
NumPreimageRequests: m.preimageOracle.NumPreimageRequests(),
TotalPreimageSize: m.preimageOracle.TotalPreimageSize(),
}
}
type trackingOracle struct {
po mipsevm.PreimageOracle
totalPreimageSize int
numPreimageRequests int
}
func (d *trackingOracle) Hint(v []byte) {
d.po.Hint(v)
}
func (d *trackingOracle) GetPreimage(k [32]byte) []byte {
d.numPreimageRequests++
preimage := d.po.GetPreimage(k)
d.totalPreimageSize += len(preimage)
return preimage
func (m *InstrumentedState) Traceback() {
m.stackTracker.Traceback()
}
package singlethreaded
import (
"encoding/binary"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
)
func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage
if key != m.lastPreimageKey {
m.lastPreimageKey = key
data := m.preimageOracle.GetPreimage(key)
// add the length prefix
preimage = make([]byte, 0, 8+len(data))
preimage = binary.BigEndian.AppendUint64(preimage, uint64(len(data)))
preimage = append(preimage, data...)
m.lastPreimage = preimage
}
m.lastPreimageOffset = offset
datLen = uint32(copy(dat[:], preimage[offset:]))
return
}
func (m *InstrumentedState) trackMemAccess(effAddr uint32) {
if m.memProofEnabled && m.lastMemAccess != effAddr {
if m.lastMemAccess != ^uint32(0) {
panic(fmt.Errorf("unexpected different mem access at %08x, already have access at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
m.memProof = m.state.Memory.MerkleProof(effAddr)
}
}
func (m *InstrumentedState) handleSyscall() error {
syscallNum, a0, a1, a2 := exec.GetSyscallArgs(&m.state.Registers)
......@@ -58,13 +29,13 @@ func (m *InstrumentedState) handleSyscall() error {
return nil
case exec.SysRead:
var newPreimageOffset uint32
v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.readPreimage, m.state.Memory, m.trackMemAccess)
v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker)
m.state.PreimageOffset = newPreimageOffset
case exec.SysWrite:
var newLastHint hexutil.Bytes
var newPreimageKey common.Hash
var newPreimageOffset uint32
v0, v1, newLastHint, newPreimageKey, newPreimageOffset = exec.HandleSysWrite(a0, a1, a2, m.state.LastHint, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.trackMemAccess, m.stdOut, m.stdErr)
v0, v1, newLastHint, newPreimageKey, newPreimageOffset = exec.HandleSysWrite(a0, a1, a2, m.state.LastHint, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker, m.stdOut, m.stdErr)
m.state.LastHint = newLastHint
m.state.PreimageKey = newPreimageKey
m.state.PreimageOffset = newPreimageOffset
......@@ -76,49 +47,6 @@ func (m *InstrumentedState) handleSyscall() error {
return nil
}
func (m *InstrumentedState) PushStack(target uint32) {
if !m.debugEnabled {
return
}
m.debug.stack = append(m.debug.stack, target)
m.debug.caller = append(m.debug.caller, m.state.Cpu.PC)
}
func (m *InstrumentedState) PopStack() {
if !m.debugEnabled {
return
}
if len(m.debug.stack) != 0 {
fn := m.debug.meta.LookupSymbol(m.state.Cpu.PC)
topFn := m.debug.meta.LookupSymbol(m.debug.stack[len(m.debug.stack)-1])
if fn != topFn {
// most likely the function was inlined. Snap back to the last return.
i := len(m.debug.stack) - 1
for ; i >= 0; i-- {
if m.debug.meta.LookupSymbol(m.debug.stack[i]) == fn {
m.debug.stack = m.debug.stack[:i]
m.debug.caller = m.debug.caller[:i]
break
}
}
} else {
m.debug.stack = m.debug.stack[:len(m.debug.stack)-1]
m.debug.caller = m.debug.caller[:len(m.debug.caller)-1]
}
} else {
fmt.Printf("ERROR: stack underflow at pc=%x. step=%d\n", m.state.Cpu.PC, m.state.Step)
}
}
func (m *InstrumentedState) Traceback() {
fmt.Printf("traceback at pc=%x. step=%d\n", m.state.Cpu.PC, m.state.Step)
for i := len(m.debug.stack) - 1; i >= 0; i-- {
s := m.debug.stack[i]
idx := len(m.debug.stack) - i - 1
fmt.Printf("\t%d %x in %s caller=%08x\n", idx, s, m.debug.meta.LookupSymbol(s), m.debug.caller[i])
}
}
func (m *InstrumentedState) mipsStep() error {
if m.state.Exited {
return nil
......@@ -134,5 +62,5 @@ func (m *InstrumentedState) mipsStep() error {
}
// Exec the rest of the step logic
return exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.trackMemAccess, m)
return exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment