load_elf.go 4.02 KB
Newer Older
1 2
package cmd

protolambda's avatar
protolambda committed
3 4 5 6
import (
	"debug/elf"
	"fmt"

7 8
	"github.com/urfave/cli/v2"

9 10
	"github.com/ethereum-optimism/optimism/cannon/mipsevm"
	"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
11 12
	"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
	"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
13
	"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
14
	"github.com/ethereum-optimism/optimism/cannon/serialize"
15
	openum "github.com/ethereum-optimism/optimism/op-service/enum"
16
	"github.com/ethereum-optimism/optimism/op-service/ioutil"
17
	"github.com/ethereum-optimism/optimism/op-service/jsonutil"
protolambda's avatar
protolambda committed
18 19 20
)

var (
21 22
	LoadELFVMTypeFlag = &cli.StringFlag{
		Name:     "type",
23 24
		Usage:    "VM type to create state for. Valid options: " + openum.EnumString(stateVersions()),
		Required: true,
25
	}
protolambda's avatar
protolambda committed
26 27 28 29 30 31 32 33
	LoadELFPathFlag = &cli.PathFlag{
		Name:      "path",
		Usage:     "Path to 32-bit big-endian MIPS ELF file",
		TakesFile: true,
		Required:  true,
	}
	LoadELFOutFlag = &cli.PathFlag{
		Name:     "out",
34
		Usage:    "Output path to write state to. State is dumped to stdout if set to '-'. Not written if empty. Use file extension '.bin', '.bin.gz', or '.json' for binary, compressed binary, or JSON formats.",
protolambda's avatar
protolambda committed
35 36 37
		Value:    "state.json",
		Required: false,
	}
38 39 40 41 42 43
	LoadELFMetaFlag = &cli.PathFlag{
		Name:     "meta",
		Usage:    "Write metadata file, for symbol lookup during program execution. None if empty.",
		Value:    "meta.json",
		Required: false,
	}
protolambda's avatar
protolambda committed
44
)
45

46 47 48 49
func stateVersions() []string {
	vers := make([]string, len(versions.StateVersionTypes))
	for i, v := range versions.StateVersionTypes {
		vers[i] = v.String()
50
	}
51
	return vers
52 53
}

54
func LoadELF(ctx *cli.Context) error {
55 56 57 58 59 60 61 62 63
	elfPath := ctx.Path(LoadELFPathFlag.Name)
	elfProgram, err := elf.Open(elfPath)
	if err != nil {
		return fmt.Errorf("failed to open ELF file %q: %w", elfPath, err)
	}
	if elfProgram.Machine != elf.EM_MIPS {
		return fmt.Errorf("ELF is not big-endian MIPS R3000, but got %q", elfProgram.Machine.String())
	}

64 65
	var createInitialState func(f *elf.File) (mipsevm.FPVMState, error)

66
	var patcher = program.PatchStack
67 68
	ver, err := versions.ParseStateVersion(ctx.String(LoadELFVMTypeFlag.Name))
	if err != nil {
69
		return err
70 71 72
	}
	switch ver {
	case versions.VersionSingleThreaded:
73 74 75
		createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
			return program.LoadELF(f, singlethreaded.CreateInitialState)
		}
76 77 78 79 80 81 82
		patcher = func(state mipsevm.FPVMState) error {
			err := program.PatchGoGC(elfProgram, state)
			if err != nil {
				return err
			}
			return program.PatchStack(state)
		}
83
	case versions.VersionMultiThreaded:
84 85 86
		createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
			return program.LoadELF(f, multithreaded.CreateInitialState)
		}
87 88
	default:
		return fmt.Errorf("unsupported state version: %d (%s)", ver, ver.String())
89
	}
90

91
	state, err := createInitialState(elfProgram)
protolambda's avatar
protolambda committed
92 93 94
	if err != nil {
		return fmt.Errorf("failed to load ELF data into VM state: %w", err)
	}
95 96 97
	err = patcher(state)
	if err != nil {
		return fmt.Errorf("failed to patch state: %w", err)
protolambda's avatar
protolambda committed
98
	}
99
	meta, err := program.MakeMetadata(elfProgram)
100 101 102
	if err != nil {
		return fmt.Errorf("failed to compute program metadata: %w", err)
	}
103
	if err := jsonutil.WriteJSON[*program.Metadata](meta, ioutil.ToStdOutOrFileOrNoop(ctx.Path(LoadELFMetaFlag.Name), OutFilePerm)); err != nil {
104 105
		return fmt.Errorf("failed to output metadata: %w", err)
	}
106 107 108 109 110 111 112

	// Ensure the state is written with appropriate version information
	versionedState, err := versions.NewFromState(state)
	if err != nil {
		return fmt.Errorf("failed to create versioned state: %w", err)
	}
	return serialize.Write(ctx.Path(LoadELFOutFlag.Name), versionedState, OutFilePerm)
113 114
}

115 116 117 118 119 120 121 122 123 124 125 126 127
func CreateLoadELFCommand(action cli.ActionFunc) *cli.Command {
	return &cli.Command{
		Name:        "load-elf",
		Usage:       "Load ELF file into Cannon state",
		Description: "Load ELF file into Cannon state",
		Action:      action,
		Flags: []cli.Flag{
			LoadELFVMTypeFlag,
			LoadELFPathFlag,
			LoadELFOutFlag,
			LoadELFMetaFlag,
		},
	}
128
}
129 130

var LoadELFCommand = CreateLoadELFCommand(LoadELF)