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

predecode_test.gno

6.38 Kb · 175 lines
  1package riscv
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/moul/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}