Commit 31d7c1ff authored by protolambda's avatar protolambda

mipsevm,contracts: unify mmap behavior, implement Go program evm test

parent 1771860d
...@@ -34,7 +34,6 @@ contract MIPS { ...@@ -34,7 +34,6 @@ contract MIPS {
// total State size: 32+32+6*4+1+1+8+32*4 = 226 bytes // total State size: 32+32+6*4+1+1+8+32*4 = 226 bytes
uint32 constant public HEAP_START = 0x20000000;
uint32 constant public BRK_START = 0x40000000; uint32 constant public BRK_START = 0x40000000;
// event DidStep(bytes32 stateHash); // event DidStep(bytes32 stateHash);
...@@ -91,11 +90,13 @@ contract MIPS { ...@@ -91,11 +90,13 @@ contract MIPS {
if (syscall_no == 4090) { if (syscall_no == 4090) {
// mmap // mmap
uint32 a0 = state.registers[4]; uint32 a0 = state.registers[4];
uint32 sz = state.registers[5];
if (sz&4095 != 0) { // adjust size to align with page size
sz += 4096 - (sz&4095);
}
if (a0 == 0) { if (a0 == 0) {
uint32 sz = state.registers[5]; v0 = state.heap;
uint32 hr = state.heap; state.heap += sz;
v0 = HEAP_START + hr;
state.heap = hr+sz;
} else { } else {
v0 = a0; v0 = a0;
} }
......
...@@ -2,7 +2,9 @@ package mipsevm ...@@ -2,7 +2,9 @@ package mipsevm
import ( import (
"bytes" "bytes"
"debug/elf"
"encoding/binary" "encoding/binary"
"io"
"math/big" "math/big"
"os" "os"
"path" "path"
...@@ -72,18 +74,7 @@ func TestEVM(t *testing.T) { ...@@ -72,18 +74,7 @@ func TestEVM(t *testing.T) {
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
stateData, proofData := us.Step(true) stateData, proofData := us.Step(true)
input := FormatStepInput(stateData, proofData)
stateHash := crypto.Keccak256Hash(stateData)
var input []byte
input = append(input, StepBytes4...)
input = append(input, stateHash[:]...)
input = append(input, uint32ToBytes32(32*3)...) // state data offset in bytes
input = append(input, uint32ToBytes32(32*3+32+uint32(len(stateData)))...) // proof data offset in bytes
input = append(input, uint32ToBytes32(uint32(len(stateData)))...) // state data length in bytes
input = append(input, stateData[:]...)
input = append(input, uint32ToBytes32(uint32(len(proofData)))...) // proof data length in bytes
input = append(input, proofData[:]...)
startingGas := uint64(30_000_000) startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run. // we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
...@@ -116,6 +107,99 @@ func TestEVM(t *testing.T) { ...@@ -116,6 +107,99 @@ func TestEVM(t *testing.T) {
} }
} }
func FormatStepInput(stateData, proofData []byte) []byte {
stateHash := crypto.Keccak256Hash(stateData)
var input []byte
input = append(input, StepBytes4...)
input = append(input, stateHash[:]...)
input = append(input, uint32ToBytes32(32*3)...) // state data offset in bytes
input = append(input, uint32ToBytes32(32*3+32+uint32(len(stateData)))...) // proof data offset in bytes
input = append(input, uint32ToBytes32(uint32(len(stateData)))...) // state data length in bytes
input = append(input, stateData[:]...)
input = append(input, uint32ToBytes32(uint32(len(proofData)))...) // proof data length in bytes
input = append(input, proofData[:]...)
return input
}
func TestMinimalEVM(t *testing.T) {
contracts, err := LoadContracts()
require.NoError(t, err)
// the first unlisted source seems to be the ABIDecoderV2 code that the compiler inserts
mipsSrcMap, err := contracts.MIPS.SourceMap([]string{"../contracts/src/MIPS.sol", "~compiler?", "../contracts/src/MIPS.sol"})
require.NoError(t, err)
addrs := &Addresses{
MIPS: common.Address{0: 0xff, 19: 1},
}
sender := common.Address{0x13, 0x37}
elfProgram, err := elf.Open("../example/bin/minimal.elf")
require.NoError(t, err, "open ELF file")
state, err := LoadELF(elfProgram)
require.NoError(t, err, "load ELF into state")
err = patchVM(elfProgram, state)
require.NoError(t, err, "apply Go runtime patches")
mu, err := NewUnicorn()
require.NoError(t, err, "load unicorn")
defer mu.Close()
err = LoadUnicorn(state, mu)
require.NoError(t, err, "load state into unicorn")
var stdOutBuf, stdErrBuf bytes.Buffer
us, err := NewUnicornState(mu, state, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
require.NoError(t, err, "hook unicorn to state")
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Debug = false
//env.Config.Tracer = logger.NewMarkdownLogger(&logger.Config{}, os.Stdout)
env.Config.Tracer = mipsSrcMap.Tracer(os.Stdout)
for i := 0; i < 400_000; i++ {
if us.state.Exited {
break
}
insn := state.Memory.GetMemory(state.PC)
if i%1000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
}
stateData, proofData := us.Step(true)
input := FormatStepInput(stateData, proofData)
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := env.StateDB.Snapshot()
ret, leftOverGas, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
require.Len(t, ret, 32, "expecting 32-byte state hash")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
env.StateDB.RevertToSnapshot(snap)
//t.Logf("EVM step took %d gas, and returned stateHash %s", startingGas-leftOverGas, postHash)
// verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(),
"unicorn produced different state than EVM")
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Equal(t, "hello world!", stdOutBuf.String(), "stdout says hello")
require.Equal(t, "", stdErrBuf.String(), "stderr silent")
}
func uint32ToBytes32(v uint32) []byte { func uint32ToBytes32(v uint32) []byte {
var out [32]byte var out [32]byte
binary.BigEndian.PutUint32(out[32-4:], v) binary.BigEndian.PutUint32(out[32-4:], v)
......
...@@ -87,8 +87,8 @@ func NewUnicornState(mu uc.Unicorn, state *State, stdOut, stdErr io.Writer) (*Un ...@@ -87,8 +87,8 @@ func NewUnicornState(mu uc.Unicorn, state *State, stdOut, stdErr io.Writer) (*Un
v0 = 0x40000000 v0 = 0x40000000
case 4246: // exit_group case 4246: // exit_group
st.Exited = true st.Exited = true
v0, _ := mu.RegRead(uc.MIPS_REG_4)
st.ExitCode = uint8(v0) st.ExitCode = uint8(v0)
mu.Stop()
return return
} }
mu.RegWrite(uc.MIPS_REG_V0, v0) mu.RegWrite(uc.MIPS_REG_V0, v0)
...@@ -242,16 +242,18 @@ func (m *UnicornState) Step(proof bool) (stateWitness []byte, memProof []byte) { ...@@ -242,16 +242,18 @@ func (m *UnicornState) Step(proof bool) (stateWitness []byte, memProof []byte) {
m.state.LO = uint32(batch[33]) m.state.LO = uint32(batch[33])
m.state.HI = uint32(batch[34]) m.state.HI = uint32(batch[34])
// 2) adopt the old nextPC as new PC. // 2) adopt the old nextPC as new PC. Unless we just exited.
// This effectively implements delay-slots, even though unicorn immediately loses // This effectively implements delay-slots, even though unicorn immediately loses
// delay-slot information when only executing a single instruction. // delay-slot information when only executing a single instruction.
m.state.PC = oldNextPC if !m.state.Exited {
err = m.mu.RegWrite(uc.MIPS_REG_PC, uint64(oldNextPC)) m.state.PC = oldNextPC
if err != nil { err = m.mu.RegWrite(uc.MIPS_REG_PC, uint64(oldNextPC))
panic("failed to write PC register") if err != nil {
} panic("failed to write PC register")
}
m.state.NextPC = newNextPC m.state.NextPC = newNextPC
}
return return
} }
......
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