Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

predecode_test.gno

6.42 Kb · 175 lines
  1package riscv
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/vm/vmkit/v0"
  7	"gno.land/p/nt/uassert/v0"
  8)
  9
 10// classify collapses the spec's opcode / funct3 / funct7 tree onto one byte,
 11// and every arm of the dispatch loop trusts it. These are the pairs that share
 12// a funct3 and differ only in funct7, which is where a collapse goes wrong.
 13func TestClassifySeparatesTheAmbiguousPairs(t *testing.T) {
 14	cases := []struct {
 15		inst uint32
 16		want uint8
 17	}{
 18		{add(1, 2, 3), iADD},
 19		{sub(1, 2, 3), iSUB},
 20		{mul(1, 2, 3), iMUL},
 21		{divi(1, 2, 3), iDIV},
 22		{remi(1, 2, 3), iREM},
 23		{addi(1, 2, 5), iADDI},
 24		{lw(1, 2, 0), iLW},
 25		{lbu(1, 2, 0), iLBU},
 26		{sw(1, 2, 0), iSW},
 27		{bne(1, 2, 4), iBNE},
 28		{lui(1, 0x1000), iLUI},
 29		{jal(1, 4), iJAL},
 30		{ecall(), iECALL},
 31		{0, iILLEGAL},
 32	}
 33	for _, c := range cases {
 34		op, _ := decodeWord(c.inst)
 35		uassert.Equal(t, uint64(c.want), uint64(op))
 36	}
 37}
 38
 39// A restored hart has to predecode again, because the arrays are not in the
 40// snapshot: only the memory pages are. Restoring into a machine that has never
 41// seen the image is the path a realm actually takes between transactions, and
 42// it is the one the existing slice test did not cover, because it always
 43// restored into a machine NewMachine had already loaded.
 44func TestRestoreIntoABareMachine(t *testing.T) {
 45	prog := append([]uint32{
 46		addi(5, 0, 0),
 47		addi(6, 0, 10),
 48		add(5, 5, 6),
 49		addi(6, 6, 0xFFF), // -1
 50		bne(6, 0, 0x1FF8), // -8
 51	}, exitWith()...)
 52	image := asm(prog)
 53
 54	one, err := NewMachine(image, entry)
 55	uassert.NoError(t, err)
 56	one.Step(vmkit.NewTestHost(), vmkit.Unmetered)
 57
 58	part, err := NewMachine(image, entry)
 59	uassert.NoError(t, err)
 60	_, status := part.Step(vmkit.NewTestHost(), 7)
 61	uassert.Equal(t, "running", status.String())
 62
 63	var bare Machine
 64	uassert.NoError(t, bare.Restore(part.Snapshot()))
 65	_, status = bare.Step(vmkit.NewTestHost(), vmkit.Unmetered)
 66	uassert.Equal(t, "halted", status.String())
 67	uassert.Equal(t, uint64(one.Registers()[5]), uint64(bare.Registers()[5]))
 68	uassert.Equal(t, uint64(55), uint64(bare.Registers()[5]))
 69}
 70
 71// Predecoding is only sound if the text cannot change under it, so a store
 72// into the text segment is a trap rather than a silent disagreement between
 73// the arrays and the memory. That is W xor X, which is what an ELF text
 74// segment is anyway, and it removes self-modifying code from the VM entirely.
 75func TestStoreIntoTextTraps(t *testing.T) {
 76	prog := append(li(5, entry), addi(6, 0, 1), sw(6, 5, 0))
 77	prog = append(prog, exitWith()...)
 78	m, err := NewMachine(asm(prog), entry)
 79	uassert.NoError(t, err)
 80	_, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
 81	uassert.Equal(t, "trapped", status.String())
 82	uassert.Equal(t, "store into text segment", m.Trap())
 83}
 84
 85// A store just below the text segment is legal, or the trap above would be
 86// catching the wrong thing.
 87func TestStoreBelowTextIsFine(t *testing.T) {
 88	prog := append(li(5, entry-4), addi(6, 0, 7), sw(6, 5, 0))
 89	prog = append(prog, exitWith()...)
 90	m, err := NewMachine(asm(prog), entry)
 91	uassert.NoError(t, err)
 92	_, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
 93	uassert.Equal(t, "halted", status.String())
 94	v, ok := m.Memory().Load32(entry - 4)
 95	uassert.True(t, ok)
 96	uassert.Equal(t, uint64(7), uint64(v))
 97}
 98
 99// A halted hart points past the ecall it halted on, not at it. The deferred
100// write-back is the only thing that sets m.pc, so an arm that assigned m.pc
101// directly had its assignment silently undone on the way out.
102func TestHaltedPCIsPastTheEcall(t *testing.T) {
103	m, err := NewMachine(asm(exitWith()), entry)
104	uassert.NoError(t, err)
105	_, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
106	uassert.Equal(t, "halted", status.String())
107	// entry: addi a7, x0, 93 -- entry+4: ecall -- so the next pc is entry+8.
108	uassert.Equal(t, uint64(entry+8), uint64(m.PC()))
109}
110
111// An entry point or an image that is not a whole number of instructions has no
112// predecode, so it is refused at load rather than half-executed.
113func TestNewMachineRefusesMisalignedImages(t *testing.T) {
114	_, err := NewMachine(asm(exitWith()), entry+1)
115	uassert.Error(t, err)
116	_, err = NewMachine([]byte{1, 2, 3}, entry)
117	uassert.Error(t, err)
118}
119
120// The index into the predecoded arrays is a shift, so a pc that is not
121// 4-aligned would silently execute the word containing it. Only a snapshot can
122// deliver one, because JALR checks its own target, so the guard is at entry to
123// the loop rather than inside it.
124func TestMisalignedPCTraps(t *testing.T) {
125	m, err := NewMachine(asm(exitWith()), entry)
126	uassert.NoError(t, err)
127	m.pc = entry + 2
128	_, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
129	uassert.Equal(t, "trapped", status.String())
130	uassert.Equal(t, "misaligned program counter", m.Trap())
131}
132
133// JALR is the only instruction that can compute an odd target, and it clears
134// only bit 0, so a target of 2 mod 4 has to be refused where it is made.
135func TestJalrRefusesAMisalignedTarget(t *testing.T) {
136	prog := append(li(5, entry+6), []uint32{jalr(1, 5, 0)}...)
137	prog = append(prog, exitWith()...)
138	m, err := NewMachine(asm(prog), entry)
139	uassert.NoError(t, err)
140	_, status := m.Step(vmkit.NewTestHost(), vmkit.Unmetered)
141	uassert.Equal(t, "trapped", status.String())
142	uassert.Equal(t, "misaligned jump target", m.Trap())
143}
144
145// A .data section has to land somewhere writable, and refusing an overlap with
146// the text is the check that keeps a bad linker script from producing a program
147// that traps on its first store instead of at load.
148func TestLoadDataRefusesTheTextSegment(t *testing.T) {
149	m, err := NewMachine(asm(exitWith()), entry)
150	uassert.NoError(t, err)
151
152	uassert.Error(t, m.LoadData(entry, []byte{1, 2, 3, 4}))
153	uassert.Error(t, m.LoadData(entry-2, []byte{1, 2, 3, 4}))
154	uassert.NoError(t, m.LoadData(entry-4, []byte{1, 2, 3, 4}))
155	uassert.NoError(t, m.LoadData(0x40000, []byte{9, 8, 7, 6}))
156	uassert.Error(t, m.LoadData(MemSize-2, []byte{1, 2, 3, 4}))
157
158	v, ok := m.Memory().Load32(0x40000)
159	uassert.True(t, ok)
160	uassert.Equal(t, uint64(0x06070809), uint64(v))
161}
162
163// Data is memory, so it rides in the snapshot like everything else the guest
164// touched. A resumed program must not find its constants gone.
165func TestLoadedDataSurvivesASnapshot(t *testing.T) {
166	m, err := NewMachine(asm(exitWith()), entry)
167	uassert.NoError(t, err)
168	uassert.NoError(t, m.LoadData(0x40000, []byte{0xDE, 0xAD, 0xBE, 0xEF}))
169
170	var restored Machine
171	uassert.NoError(t, restored.Restore(m.Snapshot()))
172	v, ok := restored.Memory().Load32(0x40000)
173	uassert.True(t, ok)
174	uassert.Equal(t, uint64(0xEFBEADDE), uint64(v))
175}