From ae12e05ef61397f8832d7bd2194feaf853dd17bd Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 1 Oct 2024 10:57:43 +0200 Subject: [PATCH] core/asm: separate legacy and eof instruction iterators --- core/asm/asm.go | 40 ++++++++++++++----- core/asm/asm_test.go | 84 +++++++++++++++++++++++++++------------ core/vm/analysis_eof.go | 2 +- core/vm/eof_immediates.go | 7 ++++ 4 files changed, 97 insertions(+), 36 deletions(-) diff --git a/core/asm/asm.go b/core/asm/asm.go index 5af6e56fff..f4b59f4471 100644 --- a/core/asm/asm.go +++ b/core/asm/asm.go @@ -26,12 +26,13 @@ import ( // Iterator for disassembled EVM instructions type instructionIterator struct { - code []byte - pc uint64 - arg []byte - op vm.OpCode - error error - started bool + code []byte + pc uint64 + arg []byte + op vm.OpCode + error error + started bool + eofEnabled bool } // NewInstructionIterator creates a new instruction iterator. @@ -41,6 +42,13 @@ func NewInstructionIterator(code []byte) *instructionIterator { return it } +// NewEOFInstructionIterator creates a new instruction iterator for EOF-code. +func NewEOFInstructionIterator(code []byte) *instructionIterator { + it := NewInstructionIterator(code) + it.eofEnabled = true + return it +} + // Next returns true if there is a next instruction and moves on. func (it *instructionIterator) Next() bool { if it.error != nil || uint64(len(it.code)) <= it.pc { @@ -63,10 +71,23 @@ func (it *instructionIterator) Next() bool { // We reached the end. return false } - it.op = vm.OpCode(it.code[it.pc]) - - if a := vm.Immediates(it.op); a > 0 { + var a int + if !it.eofEnabled { // Legacy code + if it.op.IsPush() { + a = int(it.op) - int(vm.PUSH0) + } + } else { // EOF code + if it.op == vm.RJUMPV { + // RJUMPV is unique as it has a variable sized operand. The total size is + // determined by the count byte which immediately follows RJUMPV. + maxIndex := int(it.code[it.pc+1]) + a = (maxIndex+1)*2 + 1 + } else { + a = vm.Immediates(it.op) + } + } + if a > 0 { u := it.pc + 1 + uint64(a) if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u { it.error = fmt.Errorf("incomplete instruction at %v", it.pc) @@ -105,7 +126,6 @@ func PrintDisassembled(code string) error { if err != nil { return err } - it := NewInstructionIterator(script) for it.Next() { if it.Arg() != nil && 0 < len(it.Arg()) { diff --git a/core/asm/asm_test.go b/core/asm/asm_test.go index ea6717576d..50fe9e1225 100644 --- a/core/asm/asm_test.go +++ b/core/asm/asm_test.go @@ -17,44 +17,78 @@ package asm import ( - "testing" - "encoding/hex" + "fmt" + "strings" + "testing" ) // Tests disassembling instructions func TestInstructionIterator(t *testing.T) { for i, tc := range []struct { - want int - code string - wantErr string + code string + legacyWant string + eofWant string }{ - {2, "61000000", ""}, // valid code - {0, "6100", "incomplete instruction at 0"}, // invalid code - {2, "5900", ""}, // push0 - {0, "", ""}, // empty - {2, "d1aabb00", ""}, // DATALOADN(aabb),STOP - {0, "d1aa", "incomplete instruction at 0"}, // DATALOADN(aa) invalid + {"", "", ""}, // empty + {"6100", `err: incomplete instruction at 0`, `err: incomplete instruction at 0`}, + {"61000000", ` +00000: PUSH2 0x0000 +00003: STOP`, ` +00000: PUSH2 0x0000 +00003: STOP`}, + {"5F00", ` +00000: PUSH0 +00001: STOP`, ` +00000: PUSH0 +00001: STOP`}, + {"d1aabb00", `00000: DATALOADN +00001: opcode 0xaa not defined +00002: opcode 0xbb not defined +00003: STOP`, ` +00000: DATALOADN 0xaabb +00003: STOP`}, // DATALOADN(aabb),STOP + {"d1aa", ` +00000: DATALOADN +00001: opcode 0xaa not defined`, "err: incomplete instruction at 0\n"}, // DATALOADN(aa) invalid + {"e20211223344556600", ` +00000: RJUMPV +00001: MUL +00002: GT +00003: opcode 0x22 not defined +00004: CALLER +00005: DIFFICULTY +00006: SSTORE +err: incomplete instruction at 7`, ` +00000: RJUMPV 0x02112233445566 +00008: STOP`}, // RJUMPV( 6 bytes), STOP } { var ( - have int code, _ = hex.DecodeString(tc.code) - it = NewInstructionIterator(code) + legacy = strings.TrimSpace(disassembly(NewInstructionIterator(code))) + eof = strings.TrimSpace(disassembly(NewEOFInstructionIterator(code))) ) - for it.Next() { - have++ + if want := strings.TrimSpace(tc.legacyWant); legacy != want { + t.Errorf("test %d: wrong (legacy) output. have:\n%q\nwant:\n%q\n", i, legacy, want) } - var haveErr = "" - if it.Error() != nil { - haveErr = it.Error().Error() - } - if haveErr != tc.wantErr { - t.Errorf("test %d: encountered error: %q want %q", i, haveErr, tc.wantErr) - continue - } - if have != tc.want { - t.Errorf("wrong instruction count, have %d want %d", have, tc.want) + if want := strings.TrimSpace(tc.eofWant); eof != want { + t.Errorf("test %d: wrong (eof) output. have:\n%q\nwant:\n%q\n", i, eof, want) } } } + +func disassembly(it *instructionIterator) string { + var out = new(strings.Builder) + for it.Next() { + if it.Arg() != nil && 0 < len(it.Arg()) { + fmt.Fprintf(out, "%05x: %v %#x\n", it.PC(), it.Op(), it.Arg()) + } else { + fmt.Fprintf(out, "%05x: %v\n", it.PC(), it.Op()) + } + } + if err := it.Error(); err != nil { + fmt.Fprintf(out, "err: %v\n", err) + } + return out.String() +} diff --git a/core/vm/analysis_eof.go b/core/vm/analysis_eof.go index ed35bfcccc..eb78904cfd 100644 --- a/core/vm/analysis_eof.go +++ b/core/vm/analysis_eof.go @@ -38,7 +38,7 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec { if op == RJUMPV { // RJUMPV is unique as it has a variable sized operand. // The total size is determined by the count byte which - // immediate proceeds RJUMPV. Truncation will be caught + // immediate follows RJUMPV. Truncation will be caught // in other validation steps -- for now, just return a // valid bitmap for as much of the code as is // available. diff --git a/core/vm/eof_immediates.go b/core/vm/eof_immediates.go index ceea5f5a1b..9cb7d999a2 100644 --- a/core/vm/eof_immediates.go +++ b/core/vm/eof_immediates.go @@ -58,6 +58,13 @@ func init() { // Immediates returns the number bytes of immediates (argument not from // stack but from code) a given opcode has. +// OBS: +// - This function assumes EOF instruction-set. It cannot be upon in +// a. pre-EOF code +// b. post-EOF but legacy code +// - RJUMPV is unique as it has a variable sized operand. The total size is +// determined by the count byte which immediately follows RJUMPV. This method +// will return '3' for RJUMPV, which is the minimum. func Immediates(op OpCode) int { return int(immediates[op]) }