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

ecall.gno

4.08 Kb · 135 lines
  1package riscv
  2
  3import "gno.land/p/moul/x/vm/vmkit/v0"
  4
  5// The syscall table: the entire interface between a guest and the chain.
  6//
  7// Register-passing, in the RISC-V Linux convention a compiler already knows:
  8// a7 (x17) selects the call, a0..a2 (x10..x12) carry arguments, a0 receives the
  9// result. A guest written against these needs no gno-specific runtime, only the
 10// three lines of inline assembly every no_std program already has for ecall.
 11//
 12// Deliberately small. Every call here maps onto something [vmkit.Host] already
 13// offers, because the Host is the audited surface and a syscall that reaches
 14// past it would be a second, unreviewed one.
 15const (
 16	sysExit   = 93 // a0 = status. The Linux number, so a guest can reuse its libc shim.
 17	sysWrite  = 64 // a0 = fd (ignored), a1 = buf, a2 = len -> Host.Output
 18	sysRead   = 63 // a0 = fd (ignored), a1 = buf, a2 = len <- Host.Input
 19	sysHeight = 90 // -> block height
 20	sysNow    = 91 // -> block time, Unix seconds
 21	sysCaller = 92 // a0 = buf, a1 = len; writes the caller address, returns bytes written
 22	sysLog    = 94 // a0 = buf, a1 = len -> Host.Log
 23	sysEmit   = 95 // a0 = type buf, a1 = type len, a2 = body buf, a3 = body len
 24)
 25
 26// maxSyscallBytes bounds one call's buffer. A guest asking to write a gigabyte
 27// is a guest that found the host's allocator, not one with something to say.
 28const maxSyscallBytes = 64 * 1024
 29
 30// ecall dispatches a syscall. It returns the machine status and whether the
 31// machine should stop: only exit stops it, everything else returns to the guest
 32// with a0 set.
 33func (m *Machine) ecall(h vmkit.Host) (vmkit.Status, bool) {
 34	switch m.reg[17] { // a7
 35	case sysExit:
 36		// The guest's exit status is not the machine's: a program that returns
 37		// 1 has halted, it has not trapped. Trapping is for what the machine
 38		// could not do, and a chain should not confuse "your code said no"
 39		// with "the VM broke".
 40		return vmkit.Halted, true
 41
 42	case sysWrite:
 43		buf, ok := m.readGuest(m.reg[11], m.reg[12])
 44		if !ok {
 45			m.reg[10] = ^uint32(0)
 46			return vmkit.Running, false
 47		}
 48		h.Output(buf)
 49		m.outLen += len(buf)
 50		m.reg[10] = uint32(len(buf))
 51
 52	case sysRead:
 53		in := h.Input()
 54		n := int(m.reg[12])
 55		if n > len(in) {
 56			n = len(in)
 57		}
 58		if n > maxSyscallBytes {
 59			n = maxSyscallBytes
 60		}
 61		if !m.writeGuest(m.reg[11], in[:n]) {
 62			m.reg[10] = ^uint32(0)
 63			return vmkit.Running, false
 64		}
 65		m.reg[10] = uint32(n)
 66
 67	case sysHeight:
 68		m.reg[10] = uint32(h.Height())
 69
 70	case sysNow:
 71		m.reg[10] = uint32(h.Now())
 72
 73	case sysCaller:
 74		addr := []byte(h.Caller().String())
 75		n := int(m.reg[11])
 76		if n > len(addr) {
 77			n = len(addr)
 78		}
 79		if !m.writeGuest(m.reg[10], addr[:n]) {
 80			m.reg[10] = ^uint32(0)
 81			return vmkit.Running, false
 82		}
 83		m.reg[10] = uint32(n)
 84
 85	case sysLog:
 86		buf, ok := m.readGuest(m.reg[10], m.reg[11])
 87		if !ok {
 88			m.reg[10] = ^uint32(0)
 89			return vmkit.Running, false
 90		}
 91		h.Log(string(buf))
 92		m.reg[10] = uint32(len(buf))
 93
 94	case sysEmit:
 95		typ, ok1 := m.readGuest(m.reg[10], m.reg[11])
 96		body, ok2 := m.readGuest(m.reg[12], m.reg[13])
 97		if !ok1 || !ok2 {
 98			m.reg[10] = ^uint32(0)
 99			return vmkit.Running, false
100		}
101		h.Emit(string(typ), "body", string(body))
102		m.reg[10] = 0
103
104	default:
105		// An unknown syscall is a trap, not a silent zero. A guest compiled
106		// against a newer table must fail loudly on an older host rather than
107		// read a success it did not get.
108		m.trap = "unknown syscall"
109		return vmkit.Trapped, true
110	}
111	return vmkit.Running, false
112}
113
114// readGuest copies a buffer out of guest memory, refusing anything out of range
115// or larger than maxSyscallBytes.
116func (m *Machine) readGuest(addr, length uint32) ([]byte, bool) {
117	if length > maxSyscallBytes || !m.mem.inRange(addr, int(length)) {
118		return nil, false
119	}
120	out := make([]byte, length)
121	copy(out, m.mem.b[addr:addr+length])
122	return out, true
123}
124
125// writeGuest copies into guest memory, marking the pages it touched.
126func (m *Machine) writeGuest(addr uint32, b []byte) bool {
127	if len(b) > maxSyscallBytes || !m.mem.inRange(addr, len(b)) {
128		return false
129	}
130	copy(m.mem.b[addr:], b)
131	for i := 0; i < len(b); i++ {
132		m.mem.dirty[(uint64(addr)+uint64(i))/PageSize] = true
133	}
134	return true
135}