Esempio n. 1
0
func (f *Frame) String() string {
	res := f.fn.Name
	if f.pc > proc.Word(f.fn.Value) {
		res += fmt.Sprintf("+%#x", f.pc-proc.Word(f.fn.Entry))
	}
	return res + fmt.Sprintf(" %s:%d", f.path, f.line)
}
Esempio n. 2
0
func (f *Frame) aOuter(a aborter) *Frame {
	// Is there a cached outer frame
	if f.outer != nil {
		return f.outer
	}

	p := f.stk.r.p

	sp := f.fp
	if f.fn == p.sys.newproc && f.fn == p.sys.deferproc {
		// TODO(rsc) The compiler inserts two push/pop's
		// around calls to go and defer.  Russ says this
		// should get fixed in the compiler, but we account
		// for it for now.
		sp += proc.Word(2 * p.PtrSize())
	}

	pc := p.peekUintptr(a, f.fp-proc.Word(p.PtrSize()))
	if pc < 0x1000 {
		return nil
	}

	// TODO(austin) Register this frame for shoot-down.

	f.outer = prepareFrame(a, pc, sp, f.stk, f)
	return f.outer
}
// NewProcess constructs a new remote process around a traced
// process, an architecture, and a symbol table.
func NewProcess(tproc proc.Process, arch Arch, syms *gosym.Table) (*Process, os.Error) {
	p := &Process{
		Arch:                arch,
		proc:                tproc,
		syms:                syms,
		types:               make(map[proc.Word]*remoteType),
		breakpointHooks:     make(map[proc.Word]*breakpointHook),
		goroutineCreateHook: new(goroutineCreateHook),
		goroutineExitHook:   new(goroutineExitHook),
		goroutines:          make(map[proc.Word]*Goroutine),
	}

	// Fill in remote runtime
	p.bootstrap()

	switch {
	case p.sys.allg.addr().base == 0:
		return nil, FormatError("failed to find runtime symbol 'allg'")
	case p.sys.g0.addr().base == 0:
		return nil, FormatError("failed to find runtime symbol 'g0'")
	case p.sys.newprocreadylocked == nil:
		return nil, FormatError("failed to find runtime symbol 'newprocreadylocked'")
	case p.sys.goexit == nil:
		return nil, FormatError("failed to find runtime symbol 'sys.goexit'")
	}

	// Get current goroutines
	p.goroutines[p.sys.g0.addr().base] = &Goroutine{p.sys.g0, nil, false}
	err := try(func(a aborter) {
		g := p.sys.allg.aGet(a)
		for g != nil {
			gs := g.(remoteStruct)
			fmt.Printf("*** Found goroutine at %#x\n", gs.addr().base)
			p.goroutines[gs.addr().base] = &Goroutine{gs, nil, false}
			g = gs.field(p.f.G.Alllink).(remotePtr).aGet(a)
		}
	})
	if err != nil {
		return nil, err
	}

	// Create internal breakpoints to catch new and exited goroutines
	p.OnBreakpoint(proc.Word(p.sys.newprocreadylocked.Entry)).(*breakpointHook).addHandler(readylockedBP, true)
	p.OnBreakpoint(proc.Word(p.sys.goexit.Entry)).(*breakpointHook).addHandler(goexitBP, true)

	// Select current frames
	for _, g := range p.goroutines {
		g.resetFrame()
	}

	p.selectSomeGoroutine()

	return p, nil
}
// bootstrap constructs the runtime structure of a remote process.
func (p *Process) bootstrap() {
	// Manually construct runtime types
	p.runtime.String = newManualType(eval.TypeOfNative(rt1String{}), p.Arch)
	p.runtime.Slice = newManualType(eval.TypeOfNative(rt1Slice{}), p.Arch)
	p.runtime.Eface = newManualType(eval.TypeOfNative(rt1Eface{}), p.Arch)

	p.runtime.Type = newManualType(eval.TypeOfNative(rt1Type{}), p.Arch)
	p.runtime.CommonType = newManualType(eval.TypeOfNative(rt1CommonType{}), p.Arch)
	p.runtime.UncommonType = newManualType(eval.TypeOfNative(rt1UncommonType{}), p.Arch)
	p.runtime.StructField = newManualType(eval.TypeOfNative(rt1StructField{}), p.Arch)
	p.runtime.StructType = newManualType(eval.TypeOfNative(rt1StructType{}), p.Arch)
	p.runtime.PtrType = newManualType(eval.TypeOfNative(rt1PtrType{}), p.Arch)
	p.runtime.ArrayType = newManualType(eval.TypeOfNative(rt1ArrayType{}), p.Arch)
	p.runtime.SliceType = newManualType(eval.TypeOfNative(rt1SliceType{}), p.Arch)

	p.runtime.Stktop = newManualType(eval.TypeOfNative(rt1Stktop{}), p.Arch)
	p.runtime.Gobuf = newManualType(eval.TypeOfNative(rt1Gobuf{}), p.Arch)
	p.runtime.G = newManualType(eval.TypeOfNative(rt1G{}), p.Arch)

	// Get addresses of type.*runtime.XType for discrimination.
	rtv := reflect.Indirect(reflect.NewValue(&p.runtime)).(*reflect.StructValue)
	rtvt := rtv.Type().(*reflect.StructType)
	for i := 0; i < rtv.NumField(); i++ {
		n := rtvt.Field(i).Name
		if n[0] != 'P' || n[1] < 'A' || n[1] > 'Z' {
			continue
		}
		sym := p.syms.LookupSym("type.*runtime." + n[1:])
		if sym == nil {
			continue
		}
		rtv.Field(i).(*reflect.UintValue).Set(sym.Value)
	}

	// Get runtime field indexes
	fillRuntimeIndexes(&p.runtime, &p.f)

	// Fill G status
	p.runtime.runtimeGStatus = rt1GStatus

	// Get globals
	p.sys.lessstack = p.syms.LookupFunc("sys.lessstack")
	p.sys.goexit = p.syms.LookupFunc("goexit")
	p.sys.newproc = p.syms.LookupFunc("sys.newproc")
	p.sys.deferproc = p.syms.LookupFunc("sys.deferproc")
	p.sys.newprocreadylocked = p.syms.LookupFunc("newprocreadylocked")
	if allg := p.syms.LookupSym("allg"); allg != nil {
		p.sys.allg = remotePtr{remote{proc.Word(allg.Value), p}, p.runtime.G}
	}
	if g0 := p.syms.LookupSym("g0"); g0 != nil {
		p.sys.g0 = p.runtime.G.mk(remote{proc.Word(g0.Value), p}).(remoteStruct)
	}
}
Esempio n. 5
0
func aNewFrame(a aborter, g remoteStruct) *Frame {
	p := g.r.p
	var pc, sp proc.Word

	// Is this G alive?
	switch g.field(p.f.G.Status).(remoteInt).aGet(a) {
	case p.runtime.Gidle, p.runtime.Gmoribund, p.runtime.Gdead:
		return nil
	}

	// Find the OS thread for this G

	// TODO(austin) Ideally, we could look at the G's state and
	// figure out if it's on an OS thread or not.  However, this
	// is difficult because the state isn't updated atomically
	// with scheduling changes.
	for _, t := range p.proc.Threads() {
		regs, err := t.Regs()
		if err != nil {
			// TODO(austin) What to do?
			continue
		}
		thisg := p.G(regs)
		if thisg == g.addr().base {
			// Found this G's OS thread
			pc = regs.PC()
			sp = regs.SP()

			// If this thread crashed, try to recover it
			if pc == 0 {
				pc = p.peekUintptr(a, pc)
				sp += 8
			}

			break
		}
	}

	if pc == 0 && sp == 0 {
		// G is not mapped to an OS thread.  Use the
		// scheduler's stored PC and SP.
		sched := g.field(p.f.G.Sched).(remoteStruct)
		pc = proc.Word(sched.field(p.f.Gobuf.Pc).(remoteUint).aGet(a))
		sp = proc.Word(sched.field(p.f.Gobuf.Sp).(remoteUint).aGet(a))
	}

	// Get Stktop
	stk := g.field(p.f.G.Stackbase).(remotePtr).aGet(a).(remoteStruct)

	return prepareFrame(a, pc, sp, stk, nil)
}
Esempio n. 6
0
func (v remotePtr) aGet(a aborter) eval.Value {
	addr := proc.Word(v.r.Get(a, v.r.p.PtrSize()))
	if addr == 0 {
		return nil
	}
	return v.elemType.mk(remote{addr, v.r.p})
}
Esempio n. 7
0
func (ArchLSB) ToWord(data []byte) proc.Word {
	var v proc.Word
	for i, b := range data {
		v |= proc.Word(b) << (uint(i) * 8)
	}
	return v
}
Esempio n. 8
0
func (v remote) Set(a aborter, size int, x uint64) {
	var arr [8]byte
	buf := arr[0:size]
	v.p.FromWord(proc.Word(x), buf)
	_, err := v.p.Poke(v.base, buf)
	if err != nil {
		a.Abort(err)
	}
}
Esempio n. 9
0
func (v remoteSlice) aGet(a aborter) eval.Slice {
	rs := v.r.p.runtime.Slice.mk(v.r).(remoteStruct)
	base := proc.Word(rs.field(v.r.p.f.Slice.Array).(remoteUint).aGet(a))
	nel := rs.field(v.r.p.f.Slice.Len).(remoteInt).aGet(a)
	cap := rs.field(v.r.p.f.Slice.Cap).(remoteInt).aGet(a)
	if base == 0 {
		return eval.Slice{nil, nel, cap}
	}
	return eval.Slice{remoteArray{remote{base, v.r.p}, nel, v.elemType}, nel, cap}
}
Esempio n. 10
0
// typeOfSym returns the type associated with a symbol.  If the symbol
// has no type, returns nil.
func (p *Process) typeOfSym(s *gosym.Sym) (*remoteType, os.Error) {
	if s.GoType == 0 {
		return nil, nil
	}
	addr := proc.Word(s.GoType)
	var rt *remoteType
	err := try(func(a aborter) { rt = parseRemoteType(a, p.runtime.Type.mk(remote{addr, p}).(remoteStruct)) })
	if err != nil {
		return nil, err
	}
	return rt, nil
}
Esempio n. 11
0
func (v remoteString) aGet(a aborter) string {
	rs := v.r.p.runtime.String.mk(v.r).(remoteStruct)
	str := proc.Word(rs.field(v.r.p.f.String.Str).(remoteUint).aGet(a))
	len := rs.field(v.r.p.f.String.Len).(remoteInt).aGet(a)

	bytes := make([]uint8, len)
	_, err := v.r.p.Peek(str, bytes)
	if err != nil {
		a.Abort(err)
	}
	return string(bytes)
}
Esempio n. 12
0
func fnBpSet(t *eval.Thread, args []eval.Value, res []eval.Value) {
	// TODO(austin) This probably shouldn't take a symbol name.
	// Perhaps it should take an interface that provides PC's.
	// Functions and instructions can implement that interface and
	// we can have something to translate file:line pairs.
	if curProc == nil {
		t.Abort(NoCurrentGoroutine{})
	}
	name := args[0].(eval.StringValue).Get(t)
	fn := curProc.syms.LookupFunc(name)
	if fn == nil {
		t.Abort(UsageError("no such function " + name))
	}
	curProc.OnBreakpoint(proc.Word(fn.Entry)).AddHandler(EventStop)
}
Esempio n. 13
0
func readylockedBP(ev Event) (EventAction, os.Error) {
	b := ev.(*Breakpoint)
	p := b.Process()

	// The new g is the only argument to this function, so the
	// stack will have the return address, then the G*.
	regs, err := b.osThread.Regs()
	if err != nil {
		return EAStop, err
	}
	sp := regs.SP()
	addr := sp + proc.Word(p.PtrSize())
	arg := remotePtr{remote{addr, p}, p.runtime.G}
	var gp eval.Value
	err = try(func(a aborter) { gp = arg.aGet(a) })
	if err != nil {
		return EAStop, err
	}
	if gp == nil {
		return EAStop, UnknownGoroutine{b.osThread, 0}
	}
	gs := gp.(remoteStruct)
	g := &Goroutine{gs, nil, false}
	p.goroutines[gs.addr().base] = g

	// Enqueue goroutine creation event
	parent := b.Goroutine()
	if parent.isG0() {
		parent = nil
	}
	p.postEvent(&GoroutineCreate{commonEvent{p, g}, parent})

	// If we don't have any thread selected, select this one
	if p.curGoroutine == nil {
		p.curGoroutine = g
	}

	return EADefault, nil
}
Esempio n. 14
0
// causesToEvents translates the stop causes of the underlying process
// into an event queue.
func (p *Process) causesToEvents() ([]Event, os.Error) {
	// Count causes we're interested in
	nev := 0
	for _, t := range p.proc.Threads() {
		if c, err := t.Stopped(); err == nil {
			switch c := c.(type) {
			case proc.Breakpoint:
				nev++
			case proc.Signal:
				// TODO(austin)
				//nev++;
			}
		}
	}

	// Translate causes to events
	events := make([]Event, nev)
	i := 0
	for _, t := range p.proc.Threads() {
		if c, err := t.Stopped(); err == nil {
			switch c := c.(type) {
			case proc.Breakpoint:
				gt, err := p.osThreadToGoroutine(t)
				if err != nil {
					return nil, err
				}
				events[i] = &Breakpoint{commonEvent{p, gt}, t, proc.Word(c)}
				i++
			case proc.Signal:
				// TODO(austin)
			}
		}
	}

	return events, nil
}
Esempio n. 15
0
// prepareFrame creates a Frame from the PC and SP within that frame,
// as well as the active stack segment.  This function takes care of
// traversing stack breaks and unwinding closures.
func prepareFrame(a aborter, pc, sp proc.Word, stk remoteStruct, inner *Frame) *Frame {
	// Based on src/pkg/runtime/amd64/traceback.c:traceback
	p := stk.r.p
	top := inner == nil

	// Get function
	var path string
	var line int
	var fn *gosym.Func

	for i := 0; i < 100; i++ {
		// Traverse segmented stack breaks
		if p.sys.lessstack != nil && pc == proc.Word(p.sys.lessstack.Value) {
			// Get stk->gobuf.pc
			pc = proc.Word(stk.field(p.f.Stktop.Gobuf).(remoteStruct).field(p.f.Gobuf.Pc).(remoteUint).aGet(a))
			// Get stk->gobuf.sp
			sp = proc.Word(stk.field(p.f.Stktop.Gobuf).(remoteStruct).field(p.f.Gobuf.Sp).(remoteUint).aGet(a))
			// Get stk->stackbase
			stk = stk.field(p.f.Stktop.Stackbase).(remotePtr).aGet(a).(remoteStruct)
			continue
		}

		// Get the PC of the call instruction
		callpc := pc
		if !top && (p.sys.goexit == nil || pc != proc.Word(p.sys.goexit.Value)) {
			callpc--
		}

		// Look up function
		path, line, fn = p.syms.PCToLine(uint64(callpc))
		if fn != nil {
			break
		}

		// Closure?
		var buf = make([]byte, p.ClosureSize())
		if _, err := p.Peek(pc, buf); err != nil {
			break
		}
		spdelta, ok := p.ParseClosure(buf)
		if ok {
			sp += proc.Word(spdelta)
			pc = p.peekUintptr(a, sp-proc.Word(p.PtrSize()))
		}
	}
	if fn == nil {
		return nil
	}

	// Compute frame pointer
	var fp proc.Word
	if fn.FrameSize < p.PtrSize() {
		fp = sp + proc.Word(p.PtrSize())
	} else {
		fp = sp + proc.Word(fn.FrameSize)
	}
	// TODO(austin) To really figure out if we're in the prologue,
	// we need to disassemble the function and look for the call
	// to morestack.  For now, just special case the entry point.
	//
	// TODO(austin) What if we're in the call to morestack in the
	// prologue?  Then top == false.
	if top && pc == proc.Word(fn.Entry) {
		// We're in the function prologue, before SP
		// has been adjusted for the frame.
		fp -= proc.Word(fn.FrameSize - p.PtrSize())
	}

	return &Frame{pc, sp, fp, stk, fn, path, line, inner, nil}
}
Esempio n. 16
0
func (v remoteStruct) field(i int) eval.Value {
	f := &v.layout[i]
	return f.fieldType.mk(v.r.plus(proc.Word(f.offset)))
}
Esempio n. 17
0
func (v remoteArray) Sub(i int64, len int64) eval.ArrayValue {
	return remoteArray{v.r.plus(proc.Word(int64(v.elemType.size) * i)), len, v.elemType}
}
Esempio n. 18
0
func (v remoteArray) elem(i int64) eval.Value {
	return v.elemType.mk(v.r.plus(proc.Word(int64(v.elemType.size) * i)))
}
Esempio n. 19
0
// parseRemoteType parses a Type structure in a remote process to
// construct the corresponding interpreter type and remote type.
func parseRemoteType(a aborter, rs remoteStruct) *remoteType {
	addr := rs.addr().base
	p := rs.addr().p

	// We deal with circular types by discovering cycles at
	// NamedTypes.  If a type cycles back to something other than
	// a named type, we're guaranteed that there will be a named
	// type somewhere in that cycle.  Thus, we continue down,
	// re-parsing types until we reach the named type in the
	// cycle.  In order to still create one remoteType per remote
	// type, we insert an empty remoteType in the type map the
	// first time we encounter the type and re-use that structure
	// the second time we encounter it.

	rt, ok := p.types[addr]
	if ok && rt.Type != nil {
		return rt
	} else if !ok {
		rt = &remoteType{}
		p.types[addr] = rt
	}

	if debugParseRemoteType {
		sym := p.syms.SymByAddr(uint64(addr))
		name := "<unknown>"
		if sym != nil {
			name = sym.Name
		}
		log.Stderrf("%sParsing type at %#x (%s)", prtIndent, addr, name)
		prtIndent += " "
		defer func() { prtIndent = prtIndent[0 : len(prtIndent)-1] }()
	}

	// Get Type header
	itype := proc.Word(rs.field(p.f.Type.Typ).(remoteUint).aGet(a))
	typ := rs.field(p.f.Type.Ptr).(remotePtr).aGet(a).(remoteStruct)

	// Is this a named type?
	var nt *eval.NamedType
	uncommon := typ.field(p.f.CommonType.UncommonType).(remotePtr).aGet(a)
	if uncommon != nil {
		name := uncommon.(remoteStruct).field(p.f.UncommonType.Name).(remotePtr).aGet(a)
		if name != nil {
			// TODO(austin) Declare type in appropriate remote package
			nt = eval.NewNamedType(name.(remoteString).aGet(a))
			rt.Type = nt
		}
	}

	// Create type
	var t eval.Type
	var mk maker
	switch itype {
	case p.runtime.PBoolType:
		t = eval.BoolType
		mk = mkBool
	case p.runtime.PUint8Type:
		t = eval.Uint8Type
		mk = mkUint8
	case p.runtime.PUint16Type:
		t = eval.Uint16Type
		mk = mkUint16
	case p.runtime.PUint32Type:
		t = eval.Uint32Type
		mk = mkUint32
	case p.runtime.PUint64Type:
		t = eval.Uint64Type
		mk = mkUint64
	case p.runtime.PUintType:
		t = eval.UintType
		mk = mkUint
	case p.runtime.PUintptrType:
		t = eval.UintptrType
		mk = mkUintptr
	case p.runtime.PInt8Type:
		t = eval.Int8Type
		mk = mkInt8
	case p.runtime.PInt16Type:
		t = eval.Int16Type
		mk = mkInt16
	case p.runtime.PInt32Type:
		t = eval.Int32Type
		mk = mkInt32
	case p.runtime.PInt64Type:
		t = eval.Int64Type
		mk = mkInt64
	case p.runtime.PIntType:
		t = eval.IntType
		mk = mkInt
	case p.runtime.PFloat32Type:
		t = eval.Float32Type
		mk = mkFloat32
	case p.runtime.PFloat64Type:
		t = eval.Float64Type
		mk = mkFloat64
	case p.runtime.PFloatType:
		t = eval.FloatType
		mk = mkFloat
	case p.runtime.PStringType:
		t = eval.StringType
		mk = mkString

	case p.runtime.PArrayType:
		// Cast to an ArrayType
		typ := p.runtime.ArrayType.mk(typ.addr()).(remoteStruct)
		len := int64(typ.field(p.f.ArrayType.Len).(remoteUint).aGet(a))
		elem := parseRemoteType(a, typ.field(p.f.ArrayType.Elem).(remotePtr).aGet(a).(remoteStruct))
		t = eval.NewArrayType(len, elem.Type)
		mk = func(r remote) eval.Value { return remoteArray{r, len, elem} }

	case p.runtime.PStructType:
		// Cast to a StructType
		typ := p.runtime.StructType.mk(typ.addr()).(remoteStruct)
		fs := typ.field(p.f.StructType.Fields).(remoteSlice).aGet(a)

		fields := make([]eval.StructField, fs.Len)
		layout := make([]remoteStructField, fs.Len)
		for i := range fields {
			f := fs.Base.(remoteArray).elem(int64(i)).(remoteStruct)
			elemrs := f.field(p.f.StructField.Typ).(remotePtr).aGet(a).(remoteStruct)
			elem := parseRemoteType(a, elemrs)
			fields[i].Type = elem.Type
			name := f.field(p.f.StructField.Name).(remotePtr).aGet(a)
			if name == nil {
				fields[i].Anonymous = true
			} else {
				fields[i].Name = name.(remoteString).aGet(a)
			}
			layout[i].offset = int(f.field(p.f.StructField.Offset).(remoteUint).aGet(a))
			layout[i].fieldType = elem
		}

		t = eval.NewStructType(fields)
		mk = func(r remote) eval.Value { return remoteStruct{r, layout} }

	case p.runtime.PPtrType:
		// Cast to a PtrType
		typ := p.runtime.PtrType.mk(typ.addr()).(remoteStruct)
		elem := parseRemoteType(a, typ.field(p.f.PtrType.Elem).(remotePtr).aGet(a).(remoteStruct))
		t = eval.NewPtrType(elem.Type)
		mk = func(r remote) eval.Value { return remotePtr{r, elem} }

	case p.runtime.PSliceType:
		// Cast to a SliceType
		typ := p.runtime.SliceType.mk(typ.addr()).(remoteStruct)
		elem := parseRemoteType(a, typ.field(p.f.SliceType.Elem).(remotePtr).aGet(a).(remoteStruct))
		t = eval.NewSliceType(elem.Type)
		mk = func(r remote) eval.Value { return remoteSlice{r, elem} }

	case p.runtime.PMapType, p.runtime.PChanType, p.runtime.PFuncType, p.runtime.PInterfaceType, p.runtime.PUnsafePointerType, p.runtime.PDotDotDotType:
		// TODO(austin)
		t = eval.UintptrType
		mk = mkUintptr

	default:
		sym := p.syms.SymByAddr(uint64(itype))
		name := "<unknown symbol>"
		if sym != nil {
			name = sym.Name
		}
		err := fmt.Sprintf("runtime type at %#x has unexpected type %#x (%s)", addr, itype, name)
		a.Abort(FormatError(err))
	}

	// Fill in the remote type
	if nt != nil {
		nt.Complete(t)
	} else {
		rt.Type = t
	}
	rt.size = int(typ.field(p.f.CommonType.Size).(remoteUint).aGet(a))
	rt.mk = mk

	return rt
}
Esempio n. 20
0
func (p *Process) peekUintptr(a aborter, addr proc.Word) proc.Word {
	return proc.Word(mkUintptr(remote{addr, p}).(remoteUint).aGet(a))
}
Esempio n. 21
0
// populateWorld defines constants in the given world for each package
// in this process.  These packages are structs that, in turn, contain
// fields for each global and function in that package.
func (p *Process) populateWorld(w *eval.World) os.Error {
	type def struct {
		t eval.Type
		v eval.Value
	}
	packages := make(map[string]map[string]def)

	for _, s := range p.syms.Syms {
		if s.ReceiverName() != "" {
			// TODO(austin)
			continue
		}

		// Package
		pkgName := s.PackageName()
		switch pkgName {
		case "", "type", "extratype", "string", "go":
			// "go" is really "go.string"
			continue
		}
		pkg, ok := packages[pkgName]
		if !ok {
			pkg = make(map[string]def)
			packages[pkgName] = pkg
		}

		// Symbol name
		name := s.BaseName()
		if _, ok := pkg[name]; ok {
			log.Stderrf("Multiple definitions of symbol %s", s.Name)
			continue
		}

		// Symbol type
		rt, err := p.typeOfSym(&s)
		if err != nil {
			return err
		}

		// Definition
		switch s.Type {
		case 'D', 'd', 'B', 'b':
			// Global variable
			if rt == nil {
				continue
			}
			pkg[name] = def{rt.Type, rt.mk(remote{proc.Word(s.Value), p})}

		case 'T', 't', 'L', 'l':
			// Function
			s := s.Func
			// TODO(austin): Ideally, this would *also* be
			// callable.  How does that interact with type
			// conversion syntax?
			rt, err := p.makeFrameType(s)
			if err != nil {
				return err
			}
			pkg[name] = def{eval.NewPtrType(rt.Type), remoteFramePtr{p, s, rt}}
		}
	}

	// TODO(austin): Define remote types

	// Define packages
	for pkgName, defs := range packages {
		fields := make([]eval.StructField, len(defs))
		vals := make([]eval.Value, len(defs))
		i := 0
		for name, def := range defs {
			fields[i].Name = name
			fields[i].Type = def.t
			vals[i] = def.v
			i++
		}
		pkgType := eval.NewStructType(fields)
		pkgVal := remotePackage{vals}

		err := w.DefineConst(pkgName, pkgType, pkgVal)
		if err != nil {
			log.Stderrf("while defining package %s: %v", pkgName, err)
		}
	}

	return nil
}