Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
82fadf6a
Unverified
Commit
82fadf6a
authored
Apr 23, 2023
by
protolambda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
contracts,mipsevm: state format update progress, bugfixes, EVM MIPS-tests passing
parent
788522a9
Changes
10
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
323 additions
and
155 deletions
+323
-155
MIPS.sol
contracts/src/MIPS.sol
+100
-37
evm.go
mipsevm/evm.go
+2
-2
evm_test.go
mipsevm/evm_test.go
+60
-69
patch.go
mipsevm/patch.go
+1
-0
solutil.go
mipsevm/solutil.go
+14
-4
state.go
mipsevm/state.go
+45
-22
state_test.go
mipsevm/state_test.go
+2
-2
tracer.go
mipsevm/tracer.go
+19
-14
unicorn.go
mipsevm/unicorn.go
+79
-5
unicorn_test.go
mipsevm/unicorn_test.go
+1
-0
No files found.
contracts/src/MIPS.sol
View file @
82fadf6a
This diff is collapsed.
Click to expand it.
mipsevm/evm.go
View file @
82fadf6a
...
@@ -80,7 +80,7 @@ type Addresses struct {
...
@@ -80,7 +80,7 @@ type Addresses struct {
Challenge
common
.
Address
Challenge
common
.
Address
}
}
func
NewEVMEnv
(
contracts
*
Contracts
,
addrs
*
Addresses
)
*
vm
.
EVM
{
func
NewEVMEnv
(
contracts
*
Contracts
,
addrs
*
Addresses
)
(
*
vm
.
EVM
,
*
state
.
StateDB
)
{
chainCfg
:=
params
.
MainnetChainConfig
chainCfg
:=
params
.
MainnetChainConfig
bc
:=
&
testChain
{}
bc
:=
&
testChain
{}
header
:=
bc
.
GetHeader
(
common
.
Hash
{},
100
)
header
:=
bc
.
GetHeader
(
common
.
Hash
{},
100
)
...
@@ -100,7 +100,7 @@ func NewEVMEnv(contracts *Contracts, addrs *Addresses) *vm.EVM {
...
@@ -100,7 +100,7 @@ func NewEVMEnv(contracts *Contracts, addrs *Addresses) *vm.EVM {
env
.
StateDB
.
SetCode
(
addrs
.
MIPSMemory
,
contracts
.
MIPSMemory
.
DeployedBytecode
.
Object
)
env
.
StateDB
.
SetCode
(
addrs
.
MIPSMemory
,
contracts
.
MIPSMemory
.
DeployedBytecode
.
Object
)
env
.
StateDB
.
SetCode
(
addrs
.
Challenge
,
contracts
.
Challenge
.
DeployedBytecode
.
Object
)
env
.
StateDB
.
SetCode
(
addrs
.
Challenge
,
contracts
.
Challenge
.
DeployedBytecode
.
Object
)
// TODO: any state to set, or immutables to replace, to link the contracts together?
// TODO: any state to set, or immutables to replace, to link the contracts together?
return
env
return
env
,
state
}
}
type
testChain
struct
{
type
testChain
struct
{
...
...
mipsevm/evm_test.go
View file @
82fadf6a
...
@@ -3,12 +3,14 @@ package main
...
@@ -3,12 +3,14 @@ package main
import
(
import
(
"bytes"
"bytes"
"encoding/binary"
"encoding/binary"
"fmt"
"math/big"
"math/big"
"os"
"os"
"path"
"path"
"testing"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
...
@@ -17,7 +19,6 @@ import (
...
@@ -17,7 +19,6 @@ import (
)
)
func
TestEVM
(
t
*
testing
.
T
)
{
func
TestEVM
(
t
*
testing
.
T
)
{
t
.
Skip
(
"work in progress!"
)
testFiles
,
err
:=
os
.
ReadDir
(
"test/bin"
)
testFiles
,
err
:=
os
.
ReadDir
(
"test/bin"
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
...
@@ -26,7 +27,7 @@ func TestEVM(t *testing.T) {
...
@@ -26,7 +27,7 @@ func TestEVM(t *testing.T) {
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// the first unlisted source seems to be the ABIDecoderV2 code that the compiler inserts
// the first unlisted source seems to be the ABIDecoderV2 code that the compiler inserts
mipsSrcMap
,
err
:=
contracts
.
MIPS
.
SourceMap
([]
string
{
"
~ABIDecoderV2?
"
,
"~compiler?"
,
"../contracts/src/MIPS.sol"
})
mipsSrcMap
,
err
:=
contracts
.
MIPS
.
SourceMap
([]
string
{
"
../contracts/src/MIPS.sol
"
,
"~compiler?"
,
"../contracts/src/MIPS.sol"
})
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
addrs
:=
&
Addresses
{
addrs
:=
&
Addresses
{
...
@@ -42,14 +43,14 @@ func TestEVM(t *testing.T) {
...
@@ -42,14 +43,14 @@ func TestEVM(t *testing.T) {
t
.
Skip
(
"oracle test needs to be updated to use syscall pre-image oracle"
)
t
.
Skip
(
"oracle test needs to be updated to use syscall pre-image oracle"
)
}
}
env
:=
NewEVMEnv
(
contracts
,
addrs
)
env
,
evmState
:=
NewEVMEnv
(
contracts
,
addrs
)
env
.
Config
.
Debug
=
tru
e
env
.
Config
.
Debug
=
fals
e
//env.Config.Tracer = logger.NewMarkdownLogger(&logger.Config{}, os.Stdout)
//env.Config.Tracer = logger.NewMarkdownLogger(&logger.Config{}, os.Stdout)
env
.
Config
.
Tracer
=
mipsSrcMap
.
Tracer
(
os
.
Stdout
)
env
.
Config
.
Tracer
=
mipsSrcMap
.
Tracer
(
os
.
Stdout
)
fn
:=
path
.
Join
(
"test/bin"
,
f
.
Name
())
fn
:=
path
.
Join
(
"test/bin"
,
f
.
Name
())
programMem
,
err
:=
os
.
ReadFile
(
fn
)
programMem
,
err
:=
os
.
ReadFile
(
fn
)
state
:=
&
State
{
PC
:
0
,
Memory
:
make
(
map
[
uint32
]
*
Page
)}
state
:=
&
State
{
PC
:
0
,
NextPC
:
4
,
Memory
:
make
(
map
[
uint32
]
*
Page
)}
err
=
state
.
SetMemoryRange
(
0
,
bytes
.
NewReader
(
programMem
))
err
=
state
.
SetMemoryRange
(
0
,
bytes
.
NewReader
(
programMem
))
require
.
NoError
(
t
,
err
,
"load program into state"
)
require
.
NoError
(
t
,
err
,
"load program into state"
)
...
@@ -70,52 +71,37 @@ func TestEVM(t *testing.T) {
...
@@ -70,52 +71,37 @@ func TestEVM(t *testing.T) {
err
=
HookUnicorn
(
state
,
mu
,
os
.
Stdout
,
os
.
Stderr
,
al
)
err
=
HookUnicorn
(
state
,
mu
,
os
.
Stdout
,
os
.
Stderr
,
al
)
require
.
NoError
(
t
,
err
,
"hook unicorn to state"
)
require
.
NoError
(
t
,
err
,
"hook unicorn to state"
)
// Add hook to stop unicorn once we reached the end of the test (i.e. "ate food")
_
,
err
=
mu
.
HookAdd
(
uc
.
HOOK_CODE
,
func
(
mu
uc
.
Unicorn
,
addr
uint64
,
size
uint32
)
{
if
state
.
PC
==
endAddr
{
require
.
NoError
(
t
,
mu
.
Stop
(),
"stop test when returned"
)
}
},
0
,
^
uint64
(
0
))
require
.
NoError
(
t
,
err
,
""
)
so
:=
NewStateCache
()
so
:=
NewStateCache
()
for
i
:=
0
;
i
<
1000
;
i
++
{
var
stateData
[]
byte
insn
:=
state
.
GetMemory
(
state
.
PC
)
var
insn
uint32
var
pc
uint32
al
.
Reset
()
// reset
var
post
[]
byte
require
.
NoError
(
t
,
RunUnicorn
(
mu
,
state
.
PC
,
1
))
preCode
:=
func
()
{
require
.
LessOrEqual
(
t
,
len
(
al
.
memReads
)
+
len
(
al
.
memWrites
),
1
,
"expecting at most a single mem read or write"
)
insn
=
state
.
GetMemory
(
state
.
PC
)
pc
=
state
.
PC
proofData
:=
make
([]
byte
,
0
,
32
*
2
)
fmt
.
Printf
(
"PRE - pc: %08x insn: %08x
\n
"
,
pc
,
insn
)
proofData
=
append
(
proofData
,
uint32ToBytes32
(
32
)
...
)
// length in bytes
// remember the pre-state, to repeat it in the EVM during the post processing step
var
tmp
[
32
]
byte
stateData
=
state
.
EncodeWitness
(
so
)
binary
.
BigEndian
.
PutUint32
(
tmp
[
0
:
4
],
insn
)
// instruction
if
post
!=
nil
{
if
len
(
al
.
memReads
)
>
0
{
require
.
Equal
(
t
,
hexutil
.
Bytes
(
stateData
)
.
String
(),
hexutil
.
Bytes
(
post
)
.
String
(),
binary
.
BigEndian
.
PutUint32
(
tmp
[
4
:
8
],
state
.
GetMemory
(
al
.
memReads
[
0
])
)
"unicorn produced different state than EVM"
)
}
}
if
len
(
al
.
memWrites
)
>
0
{
binary
.
BigEndian
.
PutUint32
(
tmp
[
4
:
8
],
state
.
GetMemory
(
al
.
memWrites
[
0
]))
}
proofData
=
append
(
proofData
,
tmp
[
:
]
...
)
memRoot
:=
state
.
MerkleizeMemory
(
so
)
al
.
Reset
()
// reset access list
}
postCode
:=
func
()
{
fmt
.
Printf
(
"POST - pc: %08x insn: %08x
\n
"
,
pc
,
insn
)
stateData
:=
make
([]
byte
,
0
,
44
*
32
)
var
proofData
[]
byte
stateData
=
append
(
stateData
,
memRoot
[
:
]
...
)
proofData
=
binary
.
BigEndian
.
AppendUint32
(
proofData
,
insn
)
stateData
=
append
(
stateData
,
make
([]
byte
,
32
)
...
)
// TODO preimageKey
if
len
(
al
.
memReads
)
>
0
{
stateData
=
append
(
stateData
,
make
([]
byte
,
32
)
...
)
// TODO preimageOffset
proofData
=
binary
.
BigEndian
.
AppendUint32
(
proofData
,
al
.
memReads
[
0
]
.
PreValue
)
for
i
:=
0
;
i
<
32
;
i
++
{
}
else
if
len
(
al
.
memWrites
)
>
0
{
stateData
=
append
(
stateData
,
uint32ToBytes32
(
state
.
Registers
[
i
])
...
)
proofData
=
binary
.
BigEndian
.
AppendUint32
(
proofData
,
al
.
memWrites
[
0
]
.
PreValue
)
}
else
{
proofData
=
append
(
proofData
,
make
([]
byte
,
4
)
...
)
}
}
stateData
=
append
(
stateData
,
uint32ToBytes32
(
state
.
PC
)
...
)
proofData
=
append
(
proofData
,
make
([]
byte
,
32
-
4
-
4
)
...
)
stateData
=
append
(
stateData
,
uint32ToBytes32
(
state
.
NextPC
)
...
)
stateData
=
append
(
stateData
,
uint32ToBytes32
(
state
.
LR
)
...
)
stateData
=
append
(
stateData
,
uint32ToBytes32
(
state
.
LO
)
...
)
stateData
=
append
(
stateData
,
uint32ToBytes32
(
state
.
HI
)
...
)
stateData
=
append
(
stateData
,
uint32ToBytes32
(
state
.
Heap
)
...
)
stateData
=
append
(
stateData
,
uint8ToBytes32
(
state
.
ExitCode
)
...
)
stateData
=
append
(
stateData
,
boolToBytes32
(
state
.
Exited
)
...
)
stateData
=
append
(
stateData
,
uint64ToBytes32
(
state
.
Step
)
...
)
stateHash
:=
crypto
.
Keccak256Hash
(
stateData
)
stateHash
:=
crypto
.
Keccak256Hash
(
stateData
)
var
input
[]
byte
var
input
[]
byte
...
@@ -129,14 +115,39 @@ func TestEVM(t *testing.T) {
...
@@ -129,14 +115,39 @@ func TestEVM(t *testing.T) {
input
=
append
(
input
,
uint32ToBytes32
(
uint32
(
len
(
proofData
)))
...
)
// proof data length in bytes
input
=
append
(
input
,
uint32ToBytes32
(
uint32
(
len
(
proofData
)))
...
)
// proof data length in bytes
input
=
append
(
input
,
proofData
[
:
]
...
)
input
=
append
(
input
,
proofData
[
:
]
...
)
startingGas
:=
uint64
(
30
_000_000
)
startingGas
:=
uint64
(
30
_000_000
)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap
:=
env
.
StateDB
.
Snapshot
()
ret
,
leftOverGas
,
err
:=
env
.
Call
(
vm
.
AccountRef
(
sender
),
addrs
.
MIPS
,
input
,
startingGas
,
big
.
NewInt
(
0
))
ret
,
leftOverGas
,
err
:=
env
.
Call
(
vm
.
AccountRef
(
sender
),
addrs
.
MIPS
,
input
,
startingGas
,
big
.
NewInt
(
0
))
require
.
NoError
(
t
,
err
,
"evm should not fail"
)
require
.
NoError
(
t
,
err
,
"evm should not fail"
)
t
.
Logf
(
"step took %d gas"
,
startingGas
-
leftOverGas
)
require
.
Len
(
t
,
ret
,
32
,
"expecting 32-byte state hash"
)
t
.
Logf
(
"output (state hash): %x"
,
ret
)
// remember state hash, to check it against state
// TODO compare output against unicorn (need to reconstruct state and memory hash)
postHash
:=
common
.
Hash
(
*
(
*
[
32
]
byte
)(
ret
))
logs
:=
evmState
.
Logs
()
require
.
Equal
(
t
,
1
,
len
(
logs
),
"expecting a log with post-state"
)
post
=
logs
[
0
]
.
Data
require
.
Equal
(
t
,
crypto
.
Keccak256Hash
(
post
),
postHash
,
"logged state must be accurate"
)
env
.
StateDB
.
RevertToSnapshot
(
snap
)
t
.
Logf
(
"EVM step took %d gas, and returned stateHash %s"
,
startingGas
-
leftOverGas
,
postHash
)
}
}
firstStep
:=
true
_
,
err
=
mu
.
HookAdd
(
uc
.
HOOK_CODE
,
func
(
mu
uc
.
Unicorn
,
addr
uint64
,
size
uint32
)
{
if
state
.
PC
==
endAddr
{
require
.
NoError
(
t
,
mu
.
Stop
(),
"stop test when returned"
)
}
if
!
firstStep
{
postCode
()
}
preCode
()
firstStep
=
false
},
0
,
^
uint64
(
0
))
require
.
NoError
(
t
,
err
,
"hook code"
)
err
=
RunUnicorn
(
mu
,
state
.
PC
,
1000
)
require
.
NoError
(
t
,
err
,
"must run steps without error"
)
require
.
NoError
(
t
,
err
,
"must run steps without error"
)
// inspect test result
// inspect test result
done
,
result
:=
state
.
GetMemory
(
baseAddrEnd
+
4
),
state
.
GetMemory
(
baseAddrEnd
+
8
)
done
,
result
:=
state
.
GetMemory
(
baseAddrEnd
+
4
),
state
.
GetMemory
(
baseAddrEnd
+
8
)
require
.
Equal
(
t
,
done
,
uint32
(
1
),
"must be done"
)
require
.
Equal
(
t
,
done
,
uint32
(
1
),
"must be done"
)
...
@@ -145,28 +156,8 @@ func TestEVM(t *testing.T) {
...
@@ -145,28 +156,8 @@ func TestEVM(t *testing.T) {
}
}
}
}
func
uint64ToBytes32
(
v
uint64
)
[]
byte
{
var
out
[
32
]
byte
binary
.
BigEndian
.
PutUint64
(
out
[
32
-
8
:
],
v
)
return
out
[
:
]
}
func
uint32ToBytes32
(
v
uint32
)
[]
byte
{
func
uint32ToBytes32
(
v
uint32
)
[]
byte
{
var
out
[
32
]
byte
var
out
[
32
]
byte
binary
.
BigEndian
.
PutUint32
(
out
[
32
-
4
:
],
v
)
binary
.
BigEndian
.
PutUint32
(
out
[
32
-
4
:
],
v
)
return
out
[
:
]
return
out
[
:
]
}
}
func
uint8ToBytes32
(
v
uint8
)
[]
byte
{
var
out
[
32
]
byte
out
[
31
]
=
v
return
out
[
:
]
}
func
boolToBytes32
(
v
bool
)
[]
byte
{
var
out
[
32
]
byte
if
v
{
out
[
31
]
=
1
}
return
out
[
:
]
}
mipsevm/patch.go
View file @
82fadf6a
...
@@ -11,6 +11,7 @@ import (
...
@@ -11,6 +11,7 @@ import (
func
LoadELF
(
f
*
elf
.
File
)
(
*
State
,
error
)
{
func
LoadELF
(
f
*
elf
.
File
)
(
*
State
,
error
)
{
s
:=
&
State
{
s
:=
&
State
{
PC
:
uint32
(
f
.
Entry
),
PC
:
uint32
(
f
.
Entry
),
NextPC
:
uint32
(
f
.
Entry
+
4
),
HI
:
0
,
HI
:
0
,
LO
:
0
,
LO
:
0
,
Heap
:
0x20000000
,
Heap
:
0x20000000
,
...
...
mipsevm/solutil.go
View file @
82fadf6a
...
@@ -82,7 +82,7 @@ type SourceMap struct {
...
@@ -82,7 +82,7 @@ type SourceMap struct {
func
(
s
*
SourceMap
)
Info
(
pc
uint64
)
(
source
string
,
line
uint32
,
col
uint32
)
{
func
(
s
*
SourceMap
)
Info
(
pc
uint64
)
(
source
string
,
line
uint32
,
col
uint32
)
{
instr
:=
s
.
Instr
[
pc
]
instr
:=
s
.
Instr
[
pc
]
if
instr
.
F
<
0
{
if
instr
.
F
<
0
{
return
return
"generated"
,
0
,
0
}
}
if
instr
.
F
>=
int32
(
len
(
s
.
Sources
))
{
if
instr
.
F
>=
int32
(
len
(
s
.
Sources
))
{
source
=
"unknown"
source
=
"unknown"
...
@@ -103,7 +103,7 @@ func (s *SourceMap) Info(pc uint64) (source string, line uint32, col uint32) {
...
@@ -103,7 +103,7 @@ func (s *SourceMap) Info(pc uint64) (source string, line uint32, col uint32) {
func
(
s
*
SourceMap
)
FormattedInfo
(
pc
uint64
)
string
{
func
(
s
*
SourceMap
)
FormattedInfo
(
pc
uint64
)
string
{
f
,
l
,
c
:=
s
.
Info
(
pc
)
f
,
l
,
c
:=
s
.
Info
(
pc
)
return
fmt
.
Sprintf
(
"%s:%d:%d
%v"
,
f
,
l
,
c
,
s
.
Instr
[
pc
]
)
return
fmt
.
Sprintf
(
"%s:%d:%d
"
,
f
,
l
,
c
)
}
}
// ParseSourceMap parses a solidity sourcemap: mapping bytecode indices to source references.
// ParseSourceMap parses a solidity sourcemap: mapping bytecode indices to source references.
...
@@ -200,11 +200,21 @@ func (s *SourceMapTracer) CaptureEnter(typ vm.OpCode, from common.Address, to co
...
@@ -200,11 +200,21 @@ func (s *SourceMapTracer) CaptureEnter(typ vm.OpCode, from common.Address, to co
func
(
s
*
SourceMapTracer
)
CaptureExit
(
output
[]
byte
,
gasUsed
uint64
,
err
error
)
{}
func
(
s
*
SourceMapTracer
)
CaptureExit
(
output
[]
byte
,
gasUsed
uint64
,
err
error
)
{}
func
(
s
*
SourceMapTracer
)
CaptureState
(
pc
uint64
,
op
vm
.
OpCode
,
gas
,
cost
uint64
,
scope
*
vm
.
ScopeContext
,
rData
[]
byte
,
depth
int
,
err
error
)
{
func
(
s
*
SourceMapTracer
)
CaptureState
(
pc
uint64
,
op
vm
.
OpCode
,
gas
,
cost
uint64
,
scope
*
vm
.
ScopeContext
,
rData
[]
byte
,
depth
int
,
err
error
)
{
fmt
.
Fprintf
(
s
.
out
,
"%
s: pc %x opcode %s map %v
\n
"
,
s
.
srcMap
.
FormattedInfo
(
pc
),
pc
,
op
.
String
(),
s
.
srcMap
.
Instr
[
pc
]
)
fmt
.
Fprintf
(
s
.
out
,
"%
-40s : pc %x opcode %s
\n
"
,
s
.
srcMap
.
FormattedInfo
(
pc
),
pc
,
op
.
String
()
)
}
}
func
(
s
*
SourceMapTracer
)
CaptureFault
(
pc
uint64
,
op
vm
.
OpCode
,
gas
,
cost
uint64
,
scope
*
vm
.
ScopeContext
,
depth
int
,
err
error
)
{
func
(
s
*
SourceMapTracer
)
CaptureFault
(
pc
uint64
,
op
vm
.
OpCode
,
gas
,
cost
uint64
,
scope
*
vm
.
ScopeContext
,
depth
int
,
err
error
)
{
fmt
.
Fprintf
(
s
.
out
,
"%s: FAULT %v
\n
"
,
s
.
srcMap
.
FormattedInfo
(
pc
),
err
)
fmt
.
Fprintf
(
s
.
out
,
"%-40s: pc %x opcode %s FAULT %v
\n
"
,
s
.
srcMap
.
FormattedInfo
(
pc
),
pc
,
op
.
String
(),
err
)
fmt
.
Println
(
"----"
)
fmt
.
Fprintf
(
s
.
out
,
"calldata: %x
\n
"
,
scope
.
Contract
.
Input
)
fmt
.
Println
(
"----"
)
fmt
.
Fprintf
(
s
.
out
,
"memory: %x
\n
"
,
scope
.
Memory
.
Data
())
fmt
.
Println
(
"----"
)
fmt
.
Fprintf
(
s
.
out
,
"stack:
\n
"
)
stack
:=
scope
.
Stack
.
Data
()
for
i
:=
range
stack
{
fmt
.
Fprintf
(
s
.
out
,
"%3d: %x
\n
"
,
-
i
,
stack
[
len
(
stack
)
-
1
-
i
]
.
Bytes32
())
}
}
}
var
_
vm
.
EVMLogger
=
(
*
SourceMapTracer
)(
nil
)
var
_
vm
.
EVMLogger
=
(
*
SourceMapTracer
)(
nil
)
mipsevm/state.go
View file @
82fadf6a
...
@@ -5,6 +5,8 @@ import (
...
@@ -5,6 +5,8 @@ import (
"encoding/hex"
"encoding/hex"
"fmt"
"fmt"
"io"
"io"
"github.com/ethereum/go-ethereum/common"
)
)
const
(
const
(
...
@@ -36,27 +38,47 @@ func (p *Page) UnmarshalText(dat []byte) error {
...
@@ -36,27 +38,47 @@ func (p *Page) UnmarshalText(dat []byte) error {
type
State
struct
{
type
State
struct
{
Memory
map
[
uint32
]
*
Page
`json:"memory"`
Memory
map
[
uint32
]
*
Page
`json:"memory"`
Registers
[
32
]
uint32
`json:"registers"`
PreimageKey
common
.
Hash
`json:"preimageKey"`
PreimageOffset
uint32
`json:"preimageOffset"`
PC
uint32
`json:"pc"`
PC
uint32
`json:"pc"`
NextPC
uint32
`json:"nextPC"`
NextPC
uint32
`json:"nextPC"`
LR
uint32
`json:"lr"`
HI
uint32
`json:"hi"`
LO
uint32
`json:"lo"`
LO
uint32
`json:"lo"`
HI
uint32
`json:"hi"`
Heap
uint32
`json:"heap"`
// to handle mmap growth
Heap
uint32
`json:"heap"`
// to handle mmap growth
ExitCode
uint8
`json:"exit"`
ExitCode
uint8
`json:"exit"`
Exited
bool
`json:"exited"`
Exited
bool
`json:"exited"`
Step
uint64
`json:"step"`
Step
uint64
`json:"step"`
Registers
[
32
]
uint32
`json:"registers"`
}
}
// TODO: VM state pre-image:
func
(
s
*
State
)
EncodeWitness
(
so
StateOracle
)
[]
byte
{
// PC, HI, LO, Heap = 4 * 32/8 = 16 bytes
out
:=
make
([]
byte
,
0
)
// Registers = 32 * 32/8 = 256 bytes
memRoot
:=
s
.
MerkleizeMemory
(
so
)
// Memory tree root = 32 bytes
memRoot
=
common
.
Hash
{
31
:
42
}
// TODO need contract to actually write memory
// Misc exit/step data = TBD
out
=
append
(
out
,
memRoot
[
:
]
...
)
// + proof(s) for memory leaf nodes
out
=
append
(
out
,
s
.
PreimageKey
[
:
]
...
)
out
=
binary
.
BigEndian
.
AppendUint32
(
out
,
s
.
PreimageOffset
)
out
=
binary
.
BigEndian
.
AppendUint32
(
out
,
s
.
PC
)
out
=
binary
.
BigEndian
.
AppendUint32
(
out
,
s
.
NextPC
)
out
=
binary
.
BigEndian
.
AppendUint32
(
out
,
s
.
LO
)
out
=
binary
.
BigEndian
.
AppendUint32
(
out
,
s
.
HI
)
out
=
binary
.
BigEndian
.
AppendUint32
(
out
,
s
.
Heap
)
out
=
append
(
out
,
s
.
ExitCode
)
if
s
.
Exited
{
out
=
append
(
out
,
1
)
}
else
{
out
=
append
(
out
,
0
)
}
out
=
binary
.
BigEndian
.
AppendUint64
(
out
,
s
.
Step
)
for
_
,
r
:=
range
s
.
Registers
{
out
=
binary
.
BigEndian
.
AppendUint32
(
out
,
r
)
}
return
out
}
func
(
s
*
State
)
MerkleizeMemory
(
so
StateOracle
)
[
32
]
byte
{
func
(
s
*
State
)
MerkleizeMemory
(
so
StateOracle
)
[
32
]
byte
{
// empty parts of the tree are all zero. Precompute the hash of each full-zero range sub-tree level.
// empty parts of the tree are all zero. Precompute the hash of each full-zero range sub-tree level.
...
@@ -120,20 +142,21 @@ func (s *State) MerkleizeMemory(so StateOracle) [32]byte {
...
@@ -120,20 +142,21 @@ func (s *State) MerkleizeMemory(so StateOracle) [32]byte {
return
merkleizeMemory
(
1
,
0
)
return
merkleizeMemory
(
1
,
0
)
}
}
func
(
s
*
State
)
SetMemory
(
addr
uint32
,
size
uint32
,
v
uint32
)
{
func
(
s
*
State
)
SetMemory
(
addr
uint32
,
v
uint32
)
{
for
i
:=
size
;
i
>
0
;
i
--
{
// addr must be aligned to 4 bytes
pageIndex
:=
addr
>>
pageAddrSize
if
addr
&
0x3
!=
0
{
pa
geAddr
:=
addr
&
pageAddrMask
pa
nic
(
fmt
.
Errorf
(
"unaligned memory access: %x"
,
addr
))
p
,
ok
:=
s
.
Memory
[
pageIndex
]
}
if
!
ok
{
pageIndex
:=
addr
>>
pageAddrSize
// allocate the page if we have not already.
pageAddr
:=
addr
&
pageAddrMask
// Go may mmap relatively large ranges, but we only allocate the pages just in time.
p
,
ok
:=
s
.
Memory
[
pageIndex
]
p
=
&
Page
{}
if
!
ok
{
s
.
Memory
[
pageIndex
]
=
p
// allocate the page if we have not already.
}
// Go may mmap relatively large ranges, but we only allocate the pages just in time.
p
[
pageAddr
]
=
uint8
(
v
>>
(
i
-
1
))
p
=
&
Page
{}
addr
+=
1
s
.
Memory
[
pageIndex
]
=
p
}
}
binary
.
BigEndian
.
PutUint32
(
p
[
pageAddr
:
pageAddr
+
4
],
v
)
}
}
func
(
s
*
State
)
GetMemory
(
addr
uint32
)
uint32
{
func
(
s
*
State
)
GetMemory
(
addr
uint32
)
uint32
{
...
...
mipsevm/state_test.go
View file @
82fadf6a
...
@@ -37,7 +37,7 @@ func TestState(t *testing.T) {
...
@@ -37,7 +37,7 @@ func TestState(t *testing.T) {
//state, err := LoadELF(elfProgram)
//state, err := LoadELF(elfProgram)
//require.NoError(t, err, "must load ELF into state")
//require.NoError(t, err, "must load ELF into state")
programMem
,
err
:=
os
.
ReadFile
(
fn
)
programMem
,
err
:=
os
.
ReadFile
(
fn
)
state
:=
&
State
{
PC
:
0
,
Memory
:
make
(
map
[
uint32
]
*
Page
)}
state
:=
&
State
{
PC
:
0
,
NextPC
:
4
,
Memory
:
make
(
map
[
uint32
]
*
Page
)}
err
=
state
.
SetMemoryRange
(
0
,
bytes
.
NewReader
(
programMem
))
err
=
state
.
SetMemoryRange
(
0
,
bytes
.
NewReader
(
programMem
))
require
.
NoError
(
t
,
err
,
"load program into state"
)
require
.
NoError
(
t
,
err
,
"load program into state"
)
...
@@ -69,7 +69,7 @@ func TestState(t *testing.T) {
...
@@ -69,7 +69,7 @@ func TestState(t *testing.T) {
require
.
NoError
(
t
,
mu
.
Stop
(),
"stop test when returned"
)
require
.
NoError
(
t
,
mu
.
Stop
(),
"stop test when returned"
)
}
}
},
0
,
^
uint64
(
0
))
},
0
,
^
uint64
(
0
))
require
.
NoError
(
t
,
err
,
""
)
require
.
NoError
(
t
,
err
,
"
hook code
"
)
err
=
RunUnicorn
(
mu
,
state
.
PC
,
1000
)
err
=
RunUnicorn
(
mu
,
state
.
PC
,
1000
)
require
.
NoError
(
t
,
err
,
"must run steps without error"
)
require
.
NoError
(
t
,
err
,
"must run steps without error"
)
...
...
mipsevm/tracer.go
View file @
82fadf6a
package
main
package
main
type
MemEntry
struct
{
EffAddr
uint32
PreValue
uint32
}
type
AccessList
struct
{
type
AccessList
struct
{
memReads
[]
uint32
memReads
[]
MemEntry
memWrites
[]
uint32
memWrites
[]
MemEntry
}
}
func
(
al
*
AccessList
)
Reset
()
{
func
(
al
*
AccessList
)
Reset
()
{
...
@@ -10,39 +15,39 @@ func (al *AccessList) Reset() {
...
@@ -10,39 +15,39 @@ func (al *AccessList) Reset() {
al
.
memWrites
=
al
.
memWrites
[
:
0
]
al
.
memWrites
=
al
.
memWrites
[
:
0
]
}
}
func
(
al
*
AccessList
)
OnRead
(
addr
uint32
)
{
func
(
al
*
AccessList
)
OnRead
(
effAddr
uint32
,
preValue
uint32
)
{
// if it matches the last, it's a duplicate; this happens because of multiple callbacks for the same effective addr.
// if it matches the last, it's a duplicate; this happens because of multiple callbacks for the same effective addr.
if
len
(
al
.
memReads
)
>
0
&&
al
.
memReads
[
len
(
al
.
memReads
)
-
1
]
==
a
ddr
{
if
len
(
al
.
memReads
)
>
0
&&
al
.
memReads
[
len
(
al
.
memReads
)
-
1
]
.
EffAddr
==
effA
ddr
{
return
return
}
}
al
.
memReads
=
append
(
al
.
memReads
,
addr
)
al
.
memReads
=
append
(
al
.
memReads
,
MemEntry
{
EffAddr
:
effAddr
,
PreValue
:
preValue
}
)
}
}
func
(
al
*
AccessList
)
OnWrite
(
addr
uint32
)
{
func
(
al
*
AccessList
)
OnWrite
(
effAddr
uint32
,
preValue
uint32
)
{
// if it matches the last, it's a duplicate; this happens because of multiple callbacks for the same effective addr.
// if it matches the last, it's a duplicate; this happens because of multiple callbacks for the same effective addr.
if
len
(
al
.
memWrites
)
>
0
&&
al
.
memWrites
[
len
(
al
.
memWrites
)
-
1
]
==
a
ddr
{
if
len
(
al
.
memWrites
)
>
0
&&
al
.
memWrites
[
len
(
al
.
memWrites
)
-
1
]
.
EffAddr
==
effA
ddr
{
return
return
}
}
al
.
memWrites
=
append
(
al
.
memWrites
,
addr
)
al
.
memWrites
=
append
(
al
.
memWrites
,
MemEntry
{
EffAddr
:
effAddr
,
PreValue
:
preValue
}
)
}
}
var
_
Tracer
=
(
*
AccessList
)(
nil
)
var
_
Tracer
=
(
*
AccessList
)(
nil
)
type
Tracer
interface
{
type
Tracer
interface
{
// OnRead remembers reads from the given
a
ddr.
// OnRead remembers reads from the given
effA
ddr.
// Warning: the addr is an effective-addr, i.e. always aligned.
// Warning: the addr is an effective-addr, i.e. always aligned.
// But unicorn will fire it multiple times, for each byte that was changed within the effective addr boundaries.
// But unicorn will fire it multiple times, for each byte that was changed within the effective addr boundaries.
OnRead
(
addr
uint32
)
OnRead
(
effAddr
uint32
,
value
uint32
)
// OnWrite remembers writes to the given
a
ddr.
// OnWrite remembers writes to the given
effA
ddr.
// Warning: the addr is an effective-addr, i.e. always aligned.
// Warning: the addr is an effective-addr, i.e. always aligned.
// But unicorn will fire it multiple times, for each byte that was changed within the effective addr boundaries.
// But unicorn will fire it multiple times, for each byte that was changed within the effective addr boundaries.
OnWrite
(
addr
uint32
)
OnWrite
(
effAddr
uint32
,
value
uint32
)
}
}
type
NoOpTracer
struct
{}
type
NoOpTracer
struct
{}
func
(
n
NoOpTracer
)
OnRead
(
addr
uint32
)
{}
func
(
n
NoOpTracer
)
OnRead
(
effAddr
uint32
,
value
uint32
)
{}
func
(
n
NoOpTracer
)
OnWrite
(
addr
uint32
)
{}
func
(
n
NoOpTracer
)
OnWrite
(
effAddr
uint32
,
value
uint32
)
{}
var
_
Tracer
=
NoOpTracer
{}
var
_
Tracer
=
NoOpTracer
{}
mipsevm/unicorn.go
View file @
82fadf6a
...
@@ -109,8 +109,8 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
...
@@ -109,8 +109,8 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
}
}
_
,
err
=
mu
.
HookAdd
(
uc
.
HOOK_MEM_READ
,
func
(
mu
uc
.
Unicorn
,
access
int
,
addr64
uint64
,
size
int
,
value
int64
)
{
_
,
err
=
mu
.
HookAdd
(
uc
.
HOOK_MEM_READ
,
func
(
mu
uc
.
Unicorn
,
access
int
,
addr64
uint64
,
size
int
,
value
int64
)
{
a
ddr
:=
uint32
(
addr64
&
0xFFFFFFFC
)
// pass effective addr to tracer
effA
ddr
:=
uint32
(
addr64
&
0xFFFFFFFC
)
// pass effective addr to tracer
tr
.
OnRead
(
addr
)
tr
.
OnRead
(
effAddr
,
st
.
GetMemory
(
effAddr
)
)
},
0
,
^
uint64
(
0
))
},
0
,
^
uint64
(
0
))
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to set up mem-write hook: %w"
,
err
)
return
fmt
.
Errorf
(
"failed to set up mem-write hook: %w"
,
err
)
...
@@ -123,9 +123,26 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
...
@@ -123,9 +123,26 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
if
size
<
0
||
size
>
4
{
if
size
<
0
||
size
>
4
{
panic
(
"invalid mem size"
)
panic
(
"invalid mem size"
)
}
}
st
.
SetMemory
(
uint32
(
addr64
),
uint32
(
size
),
uint32
(
value
))
effAddr
:=
uint32
(
addr64
&
0xFFFFFFFC
)
addr
:=
uint32
(
addr64
&
0xFFFFFFFC
)
// pass effective addr to tracer
tr
.
OnWrite
(
effAddr
,
st
.
GetMemory
(
effAddr
))
tr
.
OnWrite
(
addr
)
rt
:=
value
rs
:=
addr64
&
3
if
size
==
1
{
mem
:=
st
.
GetMemory
(
effAddr
)
val
:=
uint32
((
rt
&
0xFF
)
<<
(
24
-
(
rs
&
3
)
*
8
))
mask
:=
0xFFFFFFFF
^
uint32
(
0xFF
<<
(
24
-
(
rs
&
3
)
*
8
))
st
.
SetMemory
(
effAddr
,
(
mem
&
mask
)
|
val
)
}
else
if
size
==
2
{
mem
:=
st
.
GetMemory
(
effAddr
)
val
:=
uint32
((
rt
&
0xFFFF
)
<<
(
16
-
(
rs
&
2
)
*
8
))
mask
:=
0xFFFFFFFF
^
uint32
(
0xFFFF
<<
(
16
-
(
rs
&
2
)
*
8
))
st
.
SetMemory
(
effAddr
,
(
mem
&
mask
)
|
val
)
}
else
if
size
==
4
{
st
.
SetMemory
(
effAddr
,
uint32
(
rt
))
}
else
{
log
.
Fatal
(
"bad size write to ram"
)
}
},
0
,
^
uint64
(
0
))
},
0
,
^
uint64
(
0
))
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to set up mem-write hook: %w"
,
err
)
return
fmt
.
Errorf
(
"failed to set up mem-write hook: %w"
,
err
)
...
@@ -141,9 +158,57 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
...
@@ -141,9 +158,57 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
for
i
:=
0
;
i
<
32
;
i
++
{
for
i
:=
0
;
i
<
32
;
i
++
{
st
.
Registers
[
i
]
=
uint32
(
batch
[
i
])
st
.
Registers
[
i
]
=
uint32
(
batch
[
i
])
}
}
prevPC
:=
st
.
PC
st
.
PC
=
uint32
(
batch
[
32
])
st
.
PC
=
uint32
(
batch
[
32
])
// We detect if we are potentially in a delay-slot.
// If we may be (i.e. last PC is 1 instruction before current),
// then parse the last instruction to determine what the next PC would be.
// This reflects the handleBranch / handleJump behavior that schedules next-PC.
if
st
.
PC
==
prevPC
+
4
{
st
.
NextPC
=
prevPC
+
8
prevInsn
:=
st
.
GetMemory
(
prevPC
)
opcode
:=
prevInsn
>>
26
switch
opcode
{
case
2
,
3
:
// J/JAL
st
.
NextPC
=
signExtend
(
prevInsn
&
0x03FFFFFF
,
25
)
<<
2
case
1
,
4
,
5
,
6
,
7
:
// branching
rs
:=
st
.
Registers
[(
prevInsn
>>
21
)
&
0x1F
]
shouldBranch
:=
false
switch
opcode
{
case
4
,
5
:
rt
:=
st
.
Registers
[(
prevInsn
>>
16
)
&
0x1F
]
shouldBranch
=
(
rs
==
rt
&&
opcode
==
4
)
||
(
rs
!=
rt
&&
opcode
==
5
)
case
6
:
shouldBranch
=
int32
(
rs
)
<=
0
// blez
case
7
:
shouldBranch
=
int32
(
rs
)
>
0
// bgtz
case
1
:
rtv
:=
(
prevInsn
>>
16
)
&
0x1F
if
rtv
==
0
{
shouldBranch
=
int32
(
rs
)
<
0
}
// bltz
if
rtv
==
1
{
shouldBranch
=
int32
(
rs
)
>=
0
}
// bgez
}
if
shouldBranch
{
st
.
NextPC
=
prevPC
+
4
+
(
signExtend
(
prevInsn
&
0xFFFF
,
15
)
<<
2
)
}
case
0
:
if
funcv
:=
prevInsn
&
0x3f
;
funcv
==
8
||
funcv
==
9
{
// JR/JALR
rs
:=
st
.
Registers
[(
prevInsn
>>
21
)
&
0x1F
]
st
.
NextPC
=
rs
}
}
}
else
{
st
.
NextPC
=
st
.
PC
+
4
}
st
.
LO
=
uint32
(
batch
[
33
])
st
.
LO
=
uint32
(
batch
[
33
])
st
.
HI
=
uint32
(
batch
[
34
])
st
.
HI
=
uint32
(
batch
[
34
])
fmt
.
Printf
(
"pc: 0x%08x
\n
"
,
st
.
PC
)
},
0
,
^
uint64
(
0
))
},
0
,
^
uint64
(
0
))
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to set up instruction hook: %w"
,
err
)
return
fmt
.
Errorf
(
"failed to set up instruction hook: %w"
,
err
)
...
@@ -152,6 +217,15 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
...
@@ -152,6 +217,15 @@ func HookUnicorn(st *State, mu uc.Unicorn, stdOut, stdErr io.Writer, tr Tracer)
return
nil
return
nil
}
}
func
signExtend
(
v
uint32
,
i
uint32
)
uint32
{
mask
:=
^
((
uint32
(
1
)
<<
i
)
-
1
)
if
v
&
(
1
<<
i
)
!=
0
{
return
v
|
mask
}
else
{
return
v
&^
mask
}
}
func
RunUnicorn
(
mu
uc
.
Unicorn
,
entrypoint
uint32
,
steps
uint64
)
error
{
func
RunUnicorn
(
mu
uc
.
Unicorn
,
entrypoint
uint32
,
steps
uint64
)
error
{
return
mu
.
StartWithOptions
(
uint64
(
entrypoint
),
^
uint64
(
0
),
&
uc
.
UcOptions
{
return
mu
.
StartWithOptions
(
uint64
(
entrypoint
),
^
uint64
(
0
),
&
uc
.
UcOptions
{
Timeout
:
0
,
// 0 to disable, value is in ms.
Timeout
:
0
,
// 0 to disable, value is in ms.
...
...
mipsevm/unicorn_test.go
View file @
82fadf6a
...
@@ -4,6 +4,7 @@ import (
...
@@ -4,6 +4,7 @@ import (
"testing"
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
uc
"github.com/unicorn-engine/unicorn/bindings/go/unicorn"
uc
"github.com/unicorn-engine/unicorn/bindings/go/unicorn"
)
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment