Skip to content

Commit

Permalink
cannon: Fix stack patching (#11632)
Browse files Browse the repository at this point in the history
* cannon: Fix stack patching

And add `memprofilerate=0` to envp

* Update cannon/mipsevm/program/patch.go

Co-authored-by: protolambda <[email protected]>

* cleanup argv/envp string ptrs

* nit

* fix envar name

* Update cannon/mipsevm/program/patch.go

Co-authored-by: mbaxter <[email protected]>

* align op-program arg0

---------

Co-authored-by: protolambda <[email protected]>
Co-authored-by: mbaxter <[email protected]>
  • Loading branch information
3 people authored Aug 29, 2024
1 parent 36f093a commit 0dcccf6
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 14 deletions.
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()
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"

// 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")
}
}

0 comments on commit 0dcccf6

Please sign in to comment.