From 0dcccf6aa828810b179e06dfff2e56df142ed7ed Mon Sep 17 00:00:00 2001 From: Inphi Date: Wed, 28 Aug 2024 22:26:43 -0400 Subject: [PATCH] cannon: Fix stack patching (#11632) * cannon: Fix stack patching And add `memprofilerate=0` to envp * Update cannon/mipsevm/program/patch.go Co-authored-by: protolambda * cleanup argv/envp string ptrs * nit * fix envar name * Update cannon/mipsevm/program/patch.go Co-authored-by: mbaxter * align op-program arg0 --------- Co-authored-by: protolambda Co-authored-by: mbaxter --- cannon/mipsevm/program/patch.go | 34 +++++++++++-------- cannon/mipsevm/tests/evm_common_test.go | 44 +++++++++++++++++++++++++ cannon/testdata/example/entry/go.mod | 3 ++ cannon/testdata/example/entry/main.go | 30 +++++++++++++++++ 4 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 cannon/testdata/example/entry/go.mod create mode 100644 cannon/testdata/example/entry/main.go diff --git a/cannon/mipsevm/program/patch.go b/cannon/mipsevm/program/patch.go index 52a262fee585..46b75a69ff1c 100644 --- a/cannon/mipsevm/program/patch.go +++ b/cannon/mipsevm/program/patch.go @@ -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) @@ -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 } diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index 8294cb12c3df..445ed5031d2d 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -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") + }) + } +} diff --git a/cannon/testdata/example/entry/go.mod b/cannon/testdata/example/entry/go.mod new file mode 100644 index 000000000000..2e4d29124f54 --- /dev/null +++ b/cannon/testdata/example/entry/go.mod @@ -0,0 +1,3 @@ +module entry + +go 1.21 diff --git a/cannon/testdata/example/entry/main.go b/cannon/testdata/example/entry/main.go new file mode 100644 index 000000000000..78866f88abeb --- /dev/null +++ b/cannon/testdata/example/entry/main.go @@ -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") + } +}