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.

ecall.gno

4.09 Kb · 135 lines
  1package riscv
  2
  3import "gno.land/p/nym-moultest123/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}