Commit 52d0e60c authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

cannon: Support F_GETFD cmd to fcntl (#12050)

* cannon: Support F_GETFD cmd to fcntl.

* cannon: Update fuzz test expectations.

* cannon: Update MIPS.t.sol

* cannon: Introduce a new state version for supporting get_fd.

Switches singlethreaded prestate to use .bin.gz instead of json since it now needs to detect the new state version.

* cannon: Don't override the cannon version.

* Update semver-lock.

* cannon: Update tests to detect old versions but only check writing and parsing for the currently supported versions.

* cannon: Load old version from cannon docker image

* cannon: Improve logging.

* cannon: Restore cannon version arg.

* Fix contrac semvers.

* cannon: Rename singlethreaded-getfd to just singlethreaded-2.

We could just go to using the state version number directly, but particularly the difference between singlethreaded and multithreaded feels useful to keep.

* cannon: Fix comment.

* Update semver again.
parent 3aabfe67
......@@ -1058,7 +1058,7 @@ jobs:
key: cannon-prestate-{{ checksum "./cannon/bin/cannon" }}-{{ checksum "op-program/bin/op-program-client.elf" }}
name: Save Cannon prestate to cache
paths:
- "op-program/bin/prestate.json"
- "op-program/bin/prestate.bin.gz"
- "op-program/bin/meta.json"
- "op-program/bin/prestate-proof.json"
- run:
......@@ -1079,7 +1079,7 @@ jobs:
- persist_to_workspace:
root: .
paths:
- "op-program/bin/prestate.json"
- "op-program/bin/prestate.bin.gz"
- "op-program/bin/meta.json"
- "op-program/bin/prestate-proof.json"
......
......@@ -134,7 +134,7 @@ reproducible-prestate: ## Builds reproducible-prestate binary
.PHONY: reproducible-prestate
# Include any files required for the devnet to build and run.
DEVNET_CANNON_PRESTATE_FILES := op-program/bin/prestate-proof.json op-program/bin/prestate.json op-program/bin/prestate-proof-mt.json op-program/bin/prestate-mt.bin.gz
DEVNET_CANNON_PRESTATE_FILES := op-program/bin/prestate-proof.json op-program/bin/prestate.bin.gz op-program/bin/prestate-proof-mt.json op-program/bin/prestate-mt.bin.gz
$(DEVNET_CANNON_PRESTATE_FILES):
......@@ -142,8 +142,8 @@ $(DEVNET_CANNON_PRESTATE_FILES):
make cannon-prestate-mt
cannon-prestate: op-program cannon ## Generates prestate using cannon and op-program
./cannon/bin/cannon load-elf --type singlethreaded --path op-program/bin/op-program-client.elf --out op-program/bin/prestate.json --meta op-program/bin/meta.json
./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate.json --meta op-program/bin/meta.json --proof-fmt 'op-program/bin/%d.json' --output ""
./cannon/bin/cannon load-elf --type singlethreaded-2 --path op-program/bin/op-program-client.elf --out op-program/bin/prestate.bin.gz --meta op-program/bin/meta.json
./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate.bin.gz --meta op-program/bin/meta.json --proof-fmt 'op-program/bin/%d.json' --output ""
mv op-program/bin/0.json op-program/bin/prestate-proof.json
.PHONY: cannon-prestate
......
......@@ -7,9 +7,6 @@ venv
*.log
testdata/example/bin
contracts/out
state.json
*.json
*.json.gz
*.pprof
*.out
bin
......
......@@ -19,7 +19,7 @@ cannon-impl:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon-impl .
cannon-embeds: cannon-impl
@cp bin/cannon-impl ./multicannon/embeds/cannon-0
@cp bin/cannon-impl ./multicannon/embeds/cannon-2
@cp bin/cannon-impl ./multicannon/embeds/cannon-1
cannon: cannon-embeds
......
......@@ -69,7 +69,7 @@ func LoadELF(ctx *cli.Context) error {
return err
}
switch ver {
case versions.VersionSingleThreaded:
case versions.VersionSingleThreaded2:
createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, singlethreaded.CreateInitialState)
}
......
......@@ -373,6 +373,7 @@ func Run(ctx *cli.Context) error {
if err != nil {
return fmt.Errorf("failed to load state: %w", err)
}
l.Info("Loaded input state", "version", state.Version)
vm := state.CreateVM(l, po, outLog, errLog, meta)
debugProgram := ctx.Bool(RunDebugFlag.Name)
if debugProgram {
......
......@@ -286,7 +286,15 @@ func HandleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
// args: a0 = fd, a1 = cmd
v1 = uint32(0)
if a1 == 3 { // F_GETFL: get file descriptor flags
if a1 == 1 { // F_GETFD: get file descriptor flags
switch a0 {
case FdStdin, FdStdout, FdStderr, FdPreimageRead, FdHintRead, FdPreimageWrite, FdHintWrite:
v0 = 0 // No flags set
default:
v0 = 0xFFffFFff
v1 = MipsEBADF
}
} else if a1 == 3 { // F_GETFL: get file status flags
switch a0 {
case FdStdin, FdPreimageRead, FdHintRead:
v0 = 0 // O_RDONLY
......
......@@ -97,7 +97,6 @@ func CreateInitialState(pc, heapStart uint32) *State {
}
func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM {
logger.Info("Using cannon multithreaded VM")
return NewInstrumentedState(s, po, stdOut, stdErr, logger, meta)
}
......
......@@ -69,7 +69,6 @@ func CreateInitialState(pc, heapStart uint32) *State {
}
func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM {
logger.Info("Using cannon VM")
return NewInstrumentedState(s, po, stdOut, stdErr, meta)
}
......
......@@ -150,7 +150,17 @@ func FuzzStateSyscallFcntl(f *testing.F) {
expected.Step += 1
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
if cmd == 3 {
if cmd == 1 {
switch fd {
case exec.FdStdin, exec.FdStdout, exec.FdStderr,
exec.FdPreimageRead, exec.FdHintRead, exec.FdPreimageWrite, exec.FdHintWrite:
expected.Registers[2] = 0
expected.Registers[7] = 0
default:
expected.Registers[2] = 0xFF_FF_FF_FF
expected.Registers[7] = exec.MipsEBADF
}
} else if cmd == 3 {
switch fd {
case exec.FdStdin, exec.FdPreimageRead, exec.FdHintRead:
expected.Registers[2] = 0
......
......@@ -27,7 +27,7 @@ func DetectVersion(path string) (StateVersion, error) {
}
switch ver {
case VersionSingleThreaded, VersionMultiThreaded:
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2:
return ver, nil
default:
return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver)
......
package versions
import (
"embed"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
......@@ -11,23 +13,46 @@ import (
"github.com/stretchr/testify/require"
)
const statesPath = "testdata/states"
//go:embed testdata/states
var historicStates embed.FS
func TestDetectVersion(t *testing.T) {
t.Run("SingleThreadedJSON", func(t *testing.T) {
state, err := NewFromState(singlethreaded.CreateEmptyState())
testDetection := func(t *testing.T, version StateVersion, ext string) {
filename := strconv.Itoa(int(version)) + ext
dir := t.TempDir()
path := filepath.Join(dir, filename)
in, err := historicStates.ReadFile(filepath.Join(statesPath, filename))
require.NoError(t, err)
path := writeToFile(t, "state.json", state)
version, err := DetectVersion(path)
require.NoError(t, os.WriteFile(path, in, 0o644))
detectedVersion, err := DetectVersion(path)
require.NoError(t, err)
require.Equal(t, VersionSingleThreaded, version)
require.Equal(t, version, detectedVersion)
}
// Iterate all known versions to ensure we have a test case to detect every state version
for _, version := range StateVersionTypes {
version := version
t.Run(version.String(), func(t *testing.T) {
testDetection(t, version, ".bin.gz")
})
if version == VersionSingleThreaded {
t.Run(version.String()+".json", func(t *testing.T) {
testDetection(t, version, ".json")
})
}
}
// Additionally, check that the latest supported versions write new states in a way that is detected correctly
t.Run("SingleThreadedBinary", func(t *testing.T) {
state, err := NewFromState(singlethreaded.CreateEmptyState())
require.NoError(t, err)
path := writeToFile(t, "state.bin.gz", state)
version, err := DetectVersion(path)
require.NoError(t, err)
require.Equal(t, VersionSingleThreaded, version)
require.Equal(t, VersionSingleThreaded2, version)
})
t.Run("MultiThreadedBinary", func(t *testing.T) {
......
......@@ -19,6 +19,8 @@ const (
// VersionSingleThreaded is the version of the Cannon STF found in op-contracts/v1.6.0 - https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.6.0/packages/contracts-bedrock/src/cannon/MIPS.sol
VersionSingleThreaded StateVersion = iota
VersionMultiThreaded
// VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall
VersionSingleThreaded2
)
var (
......@@ -26,7 +28,7 @@ var (
ErrJsonNotSupported = errors.New("json not supported")
)
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded}
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2}
func LoadStateFromFile(path string) (*VersionedState, error) {
if !serialize.IsBinaryFile(path) {
......@@ -44,7 +46,7 @@ func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) {
switch state := state.(type) {
case *singlethreaded.State:
return &VersionedState{
Version: VersionSingleThreaded,
Version: VersionSingleThreaded2,
FPVMState: state,
}, nil
case *multithreaded.State:
......@@ -79,7 +81,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
}
switch s.Version {
case VersionSingleThreaded:
case VersionSingleThreaded2:
state := &singlethreaded.State{}
if err := state.Deserialize(in); err != nil {
return err
......@@ -113,6 +115,8 @@ func (s StateVersion) String() string {
return "singlethreaded"
case VersionMultiThreaded:
return "multithreaded"
case VersionSingleThreaded2:
return "singlethreaded-2"
default:
return "unknown"
}
......@@ -124,6 +128,8 @@ func ParseStateVersion(ver string) (StateVersion, error) {
return VersionSingleThreaded, nil
case "multithreaded":
return VersionMultiThreaded, nil
case "singlethreaded-2":
return VersionSingleThreaded2, nil
default:
return StateVersion(0), errors.New("unknown state version")
}
......
......@@ -4,6 +4,7 @@ import (
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/serialize"
......@@ -11,11 +12,11 @@ import (
)
func TestNewFromState(t *testing.T) {
t.Run("singlethreaded", func(t *testing.T) {
t.Run("singlethreaded-2", func(t *testing.T) {
actual, err := NewFromState(singlethreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &singlethreaded.State{}, actual.FPVMState)
require.Equal(t, VersionSingleThreaded, actual.Version)
require.Equal(t, VersionSingleThreaded2, actual.Version)
})
t.Run("multithreaded", func(t *testing.T) {
......@@ -27,16 +28,6 @@ func TestNewFromState(t *testing.T) {
}
func TestLoadStateFromFile(t *testing.T) {
t.Run("SinglethreadedFromJSON", func(t *testing.T) {
expected, err := NewFromState(singlethreaded.CreateEmptyState())
require.NoError(t, err)
path := writeToFile(t, "state.json", expected)
actual, err := LoadStateFromFile(path)
require.NoError(t, err)
require.Equal(t, expected, actual)
})
t.Run("SinglethreadedFromBinary", func(t *testing.T) {
expected, err := NewFromState(singlethreaded.CreateEmptyState())
require.NoError(t, err)
......@@ -58,14 +49,26 @@ func TestLoadStateFromFile(t *testing.T) {
})
}
func TestMultithreadedDoesNotSupportJSON(t *testing.T) {
state, err := NewFromState(multithreaded.CreateEmptyState())
func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
tests := []struct {
version StateVersion
createState func() mipsevm.FPVMState
}{
{VersionSingleThreaded2, func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }},
{VersionMultiThreaded, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
}
for _, test := range tests {
test := test
t.Run(test.version.String(), func(t *testing.T) {
state, err := NewFromState(test.createState())
require.NoError(t, err)
dir := t.TempDir()
path := filepath.Join(dir, "test.json")
err = serialize.Write(path, state, 0o644)
require.ErrorIs(t, err, ErrJsonNotSupported)
})
}
}
func writeToFile(t *testing.T, filename string, data serialize.Serializable) string {
......
{
"memory": [],
"preimageKey": "0x0000000000000000000000000000000000000000000000000000000000000000",
"preimageOffset": 0,
"pc": 0,
"nextPC": 4,
"lo": 0,
"hi": 0,
"heap": 0,
"exit": 0,
"exited": false,
"step": 0,
"registers": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
}
......@@ -21,7 +21,7 @@ const baseDir = "embeds"
func ExecuteCannon(ctx context.Context, args []string, ver versions.StateVersion) error {
switch ver {
case versions.VersionSingleThreaded, versions.VersionMultiThreaded:
case versions.VersionSingleThreaded, versions.VersionSingleThreaded2, versions.VersionMultiThreaded:
default:
return errors.New("unsupported version")
}
......
......@@ -43,7 +43,7 @@ DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json)
--cannon-l2-genesis .devnet/genesis-l2.json \
--cannon-bin ./cannon/bin/cannon \
--cannon-server ./op-program/bin/op-program \
--cannon-prestate ./op-program/bin/prestate.json \
--cannon-prestate ./op-program/bin/prestate.bin.gz \
--l2-eth-rpc http://localhost:9545 \
--mnemonic "test test test test test test test test test test test junk" \
--hd-path "m/44'/60'/0'/0/8" \
......
......@@ -124,7 +124,7 @@ func applyCannonConfig(c *config.Config, t *testing.T, rollupCfg *rollup.Config,
t.Log("Using MT-Cannon absolute prestate")
c.CannonAbsolutePreState = root + "op-program/bin/prestate-mt.bin.gz"
} else {
c.CannonAbsolutePreState = root + "op-program/bin/prestate.json"
c.CannonAbsolutePreState = root + "op-program/bin/prestate.bin.gz"
}
c.Cannon.SnapshotFreq = 10_000_000
......
......@@ -35,11 +35,11 @@ RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-pro
GOOS=linux GOARCH=mips GOMIPS=softfloat GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROGRAM_VERSION"
# Run the op-program-client.elf binary directly through cannon's load-elf subcommand.
RUN /app/cannon/bin/cannon load-elf --type singlethreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.json --meta ""
RUN /app/cannon/bin/cannon load-elf --type singlethreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.bin.gz --meta ""
RUN /app/cannon/bin/cannon load-elf --type multithreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta ""
# Generate the prestate proof containing the absolute pre-state hash.
RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate.json --meta "" --proof-fmt '/app/op-program/bin/%d.json' --output ""
RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d.json' --output ""
RUN mv /app/op-program/bin/0.json /app/op-program/bin/prestate-proof.json
RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate-mt.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d-mt.json' --output ""
......@@ -51,7 +51,7 @@ RUN mv /app/op-program/bin/0-mt.json /app/op-program/bin/prestate-proof-mt.json
FROM scratch AS export-stage
COPY --from=builder /app/op-program/bin/op-program .
COPY --from=builder /app/op-program/bin/op-program-client.elf .
COPY --from=builder /app/op-program/bin/prestate.json .
COPY --from=builder /app/op-program/bin/prestate.bin.gz .
COPY --from=builder /app/op-program/bin/prestate-proof.json .
COPY --from=builder /app/op-program/bin/prestate-mt.bin.gz .
COPY --from=builder /app/op-program/bin/prestate-proof-mt.json .
......@@ -45,7 +45,7 @@ After running `make reproducible-prestate`, the following files can be found in
[./bin/](./bin/):
- [`op-program`](./bin/op-program)
- [`op-program-client.elf`](./bin/op-program-client.elf)
- [`prestate.json`](./bin/prestate.json)
- [`prestate.bin.gz`](./bin/prestate.bin.gz)
- [`prestate-proof.json`](./bin/prestate-proof.json)
The `prestate-proof.json` file is what contains the absolute pre-state hash under
......
......@@ -30,9 +30,15 @@ do
LOG_FILE="${LOGS_DIR}/build-$(echo "${VERSION}" | cut -c 12-).txt"
echo "Building Version: ${VERSION} Logs: ${LOG_FILE}"
git checkout "${VERSION}" > "${LOG_FILE}" 2>&1
rm -rf "${BIN_DIR}"
make reproducible-prestate >> "${LOG_FILE}" 2>&1
HASH=$(cat "${BIN_DIR}/prestate-proof.json" | jq -r .pre)
if [ -f "${BIN_DIR}/prestate.bin.gz" ]
then
cp "${BIN_DIR}/prestate.bin.gz" "${STATES_DIR}/${HASH}.bin.gz"
else
cp "${BIN_DIR}/prestate.json" "${STATES_DIR}/${HASH}.json"
fi
echo "Built ${VERSION}: ${HASH}"
done
......
......@@ -233,7 +233,7 @@ services:
OP_CHALLENGER_CANNON_L2_GENESIS: ./.devnet/genesis-l2.json
OP_CHALLENGER_CANNON_BIN: ./cannon/bin/cannon
OP_CHALLENGER_CANNON_SERVER: /op-program/op-program
OP_CHALLENGER_CANNON_PRESTATE: /op-program/prestate.json
OP_CHALLENGER_CANNON_PRESTATE: /op-program/prestate.bin.gz
OP_CHALLENGER_L2_ETH_RPC: http://l2:8545
OP_CHALLENGER_MNEMONIC: test test test test test test test test test test test junk
OP_CHALLENGER_HD_PATH: "m/44'/60'/0'/0/4"
......
......@@ -47,14 +47,11 @@ ARG TARGETARCH
# The "id" defaults to the value of "target", the cache will thus be reused during this build.
# "sharing" defaults to "shared", the cache will thus be available to other concurrent docker builds.
# For now fetch the v1 cannon binary from the op-challenger image
#FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.1.0 AS cannon-builder-0
FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.0.0-alpha.3 AS cannon-builder-0
FROM --platform=$BUILDPLATFORM builder AS cannon-builder
ARG CANNON_VERSION=v0.0.0
# uncomment these lines once there's a new Cannon version available
#COPY --from=cannon-builder-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-0
#COPY --from=cannon-builder-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-1
COPY --from=cannon-builder-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-0
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \
GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION"
......
......@@ -140,12 +140,12 @@
"sourceCodeHash": "0x2ab6be69795109a1ee04c5693a34d6ce0ff90b62e404cdeb18178bab18d06784"
},
"src/cannon/MIPS.sol": {
"initCodeHash": "0x4043f262804931bbbbecff64f87f2d0bdc4554b4d0a8b22df8fff940e8d239bf",
"sourceCodeHash": "0xba4674e1846afbbc708877332a38dfabd4b8d1e48ce07d8ebf0a45c9f27f16b0"
"initCodeHash": "0x3992081512da36af76b707aee7d8ef9e084c54fb1dc9f8ce9989ed16d1216f01",
"sourceCodeHash": "0x7630362c20fbca071452031b88c9384d3215c4f2cbee24c7989901de63b0c178"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0xbb8c2370460e66274210d16ae527a29cb432bb646ebdccc0db0b21e53a4e428c",
"sourceCodeHash": "0x50ed780b621521047ed36ffb260032f2e5ec287f3e1ab3d742c7de45febb280d"
"initCodeHash": "0x590be819d8f02a7f9eb04ddc447f93ccbfd8bc9339f7c2e65336f9805b6c9a66",
"sourceCodeHash": "0x5bc0ab24cf926953b2ea9eb40b929821e280a7181c6cb18e7954bc3f7dc59be1"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xa0b19e18561da9990c95ebea9750dd901f73147b32b8b234eca0f35073c5a970",
......
......@@ -45,8 +45,8 @@ contract MIPS is ISemver {
}
/// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.1.1-beta.4
string public constant version = "1.1.1-beta.4";
/// @custom:semver 1.2.1-beta.1
string public constant version = "1.2.1-beta.1";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......
......@@ -57,8 +57,8 @@ contract MIPS2 is ISemver {
}
/// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.12
string public constant version = "1.0.0-beta.12";
/// @custom:semver 1.0.0-beta.13
string public constant version = "1.0.0-beta.13";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......
......@@ -347,7 +347,7 @@ library MIPSSyscalls {
/// retrieve the file-descriptor R/W flags.
/// @param _a0 The file descriptor.
/// @param _a1 The control command.
/// @param v0_ The file status flag (only supported command is F_GETFL), or -1 on error.
/// @param v0_ The file status flag (only supported commands are F_GETFD and F_GETFL), or -1 on error.
/// @param v1_ An error number, or 0 if there is no error.
function handleSysFcntl(uint32 _a0, uint32 _a1) internal pure returns (uint32 v0_, uint32 v1_) {
unchecked {
......@@ -355,8 +355,19 @@ library MIPSSyscalls {
v1_ = uint32(0);
// args: _a0 = fd, _a1 = cmd
if (_a1 == 3) {
// F_GETFL: get file descriptor flags
if (_a1 == 1) {
// F_GETFD: get file descriptor flags
if (
_a0 == FD_STDIN || _a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_READ
|| _a0 == FD_HINT_READ || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE
) {
v0_ = 0; // No flags set
} else {
v0_ = 0xFFffFFff;
v1_ = EBADF;
}
} else if (_a1 == 3) {
// F_GETFL: get file status flags
if (_a0 == FD_STDIN || _a0 == FD_PREIMAGE_READ || _a0 == FD_HINT_READ) {
v0_ = 0; // O_RDONLY
} else if (_a0 == FD_STDOUT || _a0 == FD_STDERR || _a0 == FD_PREIMAGE_WRITE || _a0 == FD_HINT_WRITE) {
......
......@@ -1613,7 +1613,7 @@ contract MIPS_Test is CommonTest {
assertEq(postState, outputState(expect), "unexpected post state");
}
function test_fcntl_succeeds() external {
function test_fcntl_getfl_succeeds() external {
uint32 insn = 0x0000000c; // syscall
(MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0);
state.registers[2] = 4055; // fcntl syscall
......@@ -1639,6 +1639,25 @@ contract MIPS_Test is CommonTest {
assertEq(postState, outputState(expect), "unexpected post state");
}
function test_fcntl_getfd_succeeds() external {
uint32 insn = 0x0000000c; // syscall
(MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0);
state.registers[2] = 4055; // fcntl syscall
state.registers[4] = 0x0; // a0
state.registers[5] = 0x1; // a1
MIPS.State memory expect;
expect.memRoot = state.memRoot;
expect.pc = state.nextPC;
expect.nextPC = state.nextPC + 4;
expect.step = state.step + 1;
expect.registers[2] = 0;
expect.registers[5] = state.registers[5];
bytes32 postState = mips.step(encodeState(state), proof, 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
function test_prestate_exited_succeeds() external {
uint32 insn = 0x0000000c; // syscall
(MIPS.State memory state, bytes memory proof) = constructMIPSState(0, insn, 0x4, 0);
......
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