Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannon: Fix stack patching #11632

Merged
merged 7 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions cannon/mipsevm/program/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,11 @@ func PatchGo(f *elf.File, st mipsevm.FPVMState) error {
})); err != nil {
return fmt.Errorf("failed to patch Go runtime.gcenable: %w", err)
}
case "runtime.MemProfileRate":
if err := st.GetMemory().SetMemoryRange(uint32(s.Value), bytes.NewReader(make([]byte, 4))); err != nil { // disable mem profiling, to avoid a lot of unnecessary floating point ops
return err
}
}
}
return nil
}

// TODO(cp-903) Consider setting envar "GODEBUG=memprofilerate=0" for go programs to disable memprofiling, instead of patching it out in PatchGo()
Inphi marked this conversation as resolved.
Show resolved Hide resolved
func PatchStack(st mipsevm.FPVMState) error {
// setup stack pointer
sp := uint32(0x7f_ff_d0_00)
Expand All @@ -73,16 +68,27 @@ func PatchStack(st mipsevm.FPVMState) error {
}

// init argc, argv, aux on stack
storeMem(sp+4*1, 0x42) // argc = 0 (argument count)
storeMem(sp+4*2, 0x35) // argv[n] = 0 (terminating argv)
storeMem(sp+4*3, 0) // envp[term] = 0 (no env vars)
storeMem(sp+4*4, 6) // auxv[0] = _AT_PAGESZ = 6 (key)
storeMem(sp+4*5, 4096) // auxv[1] = page size of 4 KiB (value) - (== minPhysPageSize)
storeMem(sp+4*6, 25) // auxv[2] = AT_RANDOM
storeMem(sp+4*7, sp+4*9) // auxv[3] = address of 16 bytes containing random value
storeMem(sp+4*8, 0) // auxv[term] = 0
storeMem(sp+4*0, 1) // argc = 1 (argument count)
storeMem(sp+4*1, sp+4*21) // argv[0]
storeMem(sp+4*2, 0) // argv[1] = terminating
storeMem(sp+4*3, sp+4*14) // envp[0] = x (offset to first env var)
storeMem(sp+4*4, 0) // envp[1] = terminating
storeMem(sp+4*5, 6) // auxv[0] = _AT_PAGESZ = 6 (key)
storeMem(sp+4*6, 4096) // auxv[1] = page size of 4 KiB (value) - (== minPhysPageSize)
storeMem(sp+4*7, 25) // auxv[2] = AT_RANDOM
storeMem(sp+4*8, sp+4*10) // auxv[3] = address of 16 bytes containing random value
storeMem(sp+4*9, 0) // auxv[term] = 0

_ = st.GetMemory().SetMemoryRange(sp+4*10, bytes.NewReader([]byte("4;byfairdiceroll"))) // 16 bytes of "randomness"
Inphi marked this conversation as resolved.
Show resolved Hide resolved

// append 4 extra zero bytes to end at 4-byte alignment
envar := append([]byte("GODEBUG=memprofilerate=0"), 0x0, 0x0, 0x0, 0x0)
_ = st.GetMemory().SetMemoryRange(sp+4*14, bytes.NewReader(envar))

_ = st.GetMemory().SetMemoryRange(sp+4*9, bytes.NewReader([]byte("4;byfairdiceroll"))) // 16 bytes of "randomness"
// 24 bytes for GODEBUG=memprofilerate=0 + 4 null bytes
// Then append program name + 2 null bytes for 4-byte alignment
programName := append([]byte("op-program"), 0x0, 0x0)
_ = st.GetMemory().SetMemoryRange(sp+4*21, bytes.NewReader(programName))

return nil
}
44 changes: 44 additions & 0 deletions cannon/mipsevm/tests/evm_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,47 @@ func TestClaimEVM(t *testing.T) {
})
}
}

func TestEntryEVM(t *testing.T) {
var tracer *tracing.Hooks // no-tracer by default, but see test_util.MarkdownTracer
versions := GetMipsVersionTestCases(t)

for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
evm := testutil.NewMIPSEVM(v.Contracts)
evm.SetTracer(tracer)
testutil.LogStepFailureAtCleanup(t, evm)

var stdOutBuf, stdErrBuf bytes.Buffer
elfFile := "../../testdata/example/bin/entry.elf"
goVm := v.ElfVMFactory(t, elfFile, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger())
state := goVm.GetState()

start := time.Now()
for i := 0; i < 400_000; i++ {
curStep := goVm.GetState().GetStep()
if goVm.GetState().GetExited() {
break
}
insn := state.GetMemory().GetMemory(state.GetPC())
if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
}

stepWitness, err := goVm.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness, curStep, v.StateHashFn)
// verify the post-state matches.
goPost, _ := goVm.GetState().EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
}
end := time.Now()
delta := end.Sub(start)
t.Logf("test took %s, %d instructions, %s per instruction", delta, state.GetStep(), delta/time.Duration(state.GetStep()))

require.True(t, state.GetExited(), "must complete program")
require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0")
})
}
}
3 changes: 3 additions & 0 deletions cannon/testdata/example/entry/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module entry

go 1.21
30 changes: 30 additions & 0 deletions cannon/testdata/example/entry/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"os"
"runtime"
)

func main() {
if len(os.Args) != 1 {
panic("expected 1 arg")
}
if os.Args[0] != "op-program" {
panic("unexpected arg0")
}

var memProfileRate bool
env := os.Environ()
for _, env := range env {
if env != "GODEBUG=memprofilerate=0" {
panic("invalid envar")
}
memProfileRate = true
}
if !memProfileRate {
panic("memProfileRate env is not set")
}
if runtime.MemProfileRate != 0 {
panic("runtime.MemProfileRate is non-zero")
}
}