code: purgatorio

ref: a920c765f2b4130590fb5971a50690b21664957a
dir: /appl/lib/debug.b/

View raw version
implement Debug;

include "sys.m";
sys: Sys;
sprint, FD: import sys;

include "string.m";
str: String;

include "draw.m";

include "debug.m";

include "dis.m";
	dism: Dis;

Command: module
{
	init:	fn(ctxt: ref Draw->Context, argv: list of string);
};

Spin: adt
{
	spin:	int;
	pspin:	int;
};

SrcState: adt
{
	files:	array of string;
	lastf:	int;
	lastl:	int;
	vers:	int;			# version number
					# 11 => more source states
};

typenames := array[] of {
	Terror => "error",
	Tid => "id",
	Tadt => "adt",
	Tadtpick => "adtpick",
	Tarray => "array",
	Tbig => "big",
	Tbyte => "byte",
	Tchan => "chan",
	Treal => "real",
	Tfn => "fn",
	Targ => "arg",
	Tlocal => "local",
	Tglobal => "global",
	Tint => "int",
	Tlist => "list",
	Tmodule => "module",
	Tnil => "nil",
	Tnone => "none",
	Tref => "ref",
	Tstring => "string",
	Ttuple => "tuple",
	Tend => "end",
	Targs => "args",
	Tslice => "slice",
	Tpoly => "poly",
};

tnone:		ref Type;
tnil:		ref Type;
tint:		ref Type;
tbyte:		ref Type;
tbig:		ref Type;
treal:		ref Type;
tstring:	ref Type;
tpoly:	ref Type;

IBY2WD:		con 4;
IBY2LG:		con 8;
H:		con int 16rffffffff;

ModHash:	con 32;
SymHash:	con 32;
mods:=		array[ModHash] of list of ref Module;
syms:=		array[SymHash] of list of ref Sym;

sblpath :=	array[] of
{
	("/dis/",	"/appl/cmd/"),
	("/dis/",	"/appl/"),
};

init(): int
{
	sys = load Sys Sys->PATH;
	str = load String String->PATH;
	if(sys == nil || str == nil)
		return 0;
	tnone = ref Type(nil, Tnone, 0, "", nil, nil, nil);
	tnil = ref Type(nil, Tnil, IBY2WD, "nil", nil, nil, nil);
	tint = ref Type(nil, Tint, IBY2WD, "int", nil, nil, nil);
	tbyte = ref Type(nil, Tbyte, 1, "byte", nil, nil, nil);
	tbig = ref Type(nil, Tbig, IBY2LG, "big", nil, nil, nil);
	treal = ref Type(nil, Treal, IBY2LG, "real", nil, nil, nil);
	tstring = ref Type(nil, Tstring, IBY2WD, "string", nil, nil, nil);
	tpoly = ref Type(nil, Tpoly, IBY2WD, "polymorphic", nil, nil, nil);
	return 1;
}

prog(pid: int): (ref Prog, string)
{
	spid := string pid;
	h := sys->open("/prog/"+spid+"/heap", sys->ORDWR);
	if(h == nil)
		return (nil, sprint("can't open heap file: %r"));
	c := sys->open("/prog/"+spid+"/ctl", sys->OWRITE);
	if(c == nil)
		return (nil, sprint("can't open ctl file: %r"));
	d := sys->open("/prog/"+spid+"/dbgctl", sys->ORDWR);
	if(d == nil)
		return (nil, sprint("can't open debug ctl file: %r"));
	s := sys->open("/prog/"+spid+"/stack", sys->OREAD);
	if(s == nil)
		return (nil, sprint("can't open stack file: %r"));
	return (ref Prog(pid, h, c, d, s), "");
}

startprog(dis, dir: string, ctxt: ref Draw->Context, argv: list of string): (ref Prog, string)
{
	c := load Command dis;
	if(c == nil)
		return (nil, "module not loaded");

	ack := chan of int;
	spin := ref Spin(1, 1);
	end := chan of int;
	spawn execer(ack, dir, c, ctxt, argv, spin, end);
	kid := <-ack;

	fd := sys->open("/prog/"+string kid+"/dbgctl", sys->ORDWR);
	if(fd == nil){
		spin.spin = -1;
		<- end;
		return (nil, sprint("can't open debug ctl file: %r"));
	}
	done := chan of string;
	spawn stepper(done, fd, spin);

wait:	for(;;){
		alt{
		<-ack =>
			sys->sleep(0);
		err := <-done =>
			if(err != ""){
				<- end;
				return(nil, err);
			}
			break wait;
		}
	}

	b := array[20] of byte;
	n := sys->read(fd, b, len b);
	if(n <= 0){
		<- end;
		return(nil, sprint("%r"));
	}
	msg := string b[:n];
	if(!str->prefix("new ", msg)){
		<- end;
		return (nil, msg);
	}

	kid = int msg[len "new ":];

	# clean up the execer slave
	b = array of byte "start";
	sys->write(fd, b, len b);

	<- end;
	return prog(kid);
}

stepper(done: chan of string, ctl: ref FD, spin: ref Spin)
{
	b := array of byte "step1";
	while(spin.pspin){
		if(sys->write(ctl, b, len b) != len b)
			done <-= sprint("can't start new thread: %r");
		spin.spin = 0;
	}
	done <-= "";
}

execer(ack: chan of int, dir: string, c: Command, ctxt: ref Draw->Context, args: list of string, spin: ref Spin, end: chan of int)
{
	pid := sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->NEWFD, 0::1::2::nil);
	sys->chdir(dir);
	while(spin.spin == 1)
		ack <-= pid;
	if(spin.spin == -1){
		end <-= 0;
		exit;
	}
	spawn c->init(ctxt, args);
	spin.pspin = 0;
	end <-= 0;
	exit;
}

# format of each line is
# fp pc mp prog compiled path
# fp, pc, mp, and prog are %.8lux
# compile is  or 1
# path is a string
Prog.stack(p: self ref Prog): (array of ref Exp, string)
{
	buf := array[8192] of byte;
	sys->seek(p.stk, big 0, 0);
	n := sys->read(p.stk, buf, len buf - 1);
	if(n < 0)
		return (nil, sprint("can't read stack file: %r"));
	buf[n] = byte 0;

	t := 0;
	nf := 0;
	for(s := 0; s < n; s = t+1){
		t = strchr(buf, s, '\n');
		if(buf[t] != byte '\n' || t-s < 40)
			continue;
		nf++;
	}

	e := array[nf] of ref Exp;
	nf = 0;
	for(s = 0; s < n; s = t+1){
		t = strchr(buf, s, '\n');
		if(buf[t] != byte '\n' || t-s < 40)
			continue;
		e[nf] = ref Exp("unknown fn",
				hex(buf[s+0:s+8]), 
				hex(buf[s+9:s+17]),
				mkmod(hex(buf[s+18:s+26]), hex(buf[s+27:s+35]), buf[36] != byte '0', string buf[s+38:t]),
				p,
				nil);
		nf++;
	}

	return (e, "");
}

Prog.step(p: self ref Prog, how: int): string
{
	(stack, nil) := p.stack();
	if(stack == nil)
		return "can't find initial pc";
	src := stack[0].srcstr();
	stmt := ftostmt(stack[0]);

	if(stack[0].m.sym == nil)
		how = -1;

	buf := array of byte("step1");
	if(how == StepOut)
		buf = array of byte("toret");
	while(sys->write(p.dbgctl, buf, len buf) == len buf){
		(stk, err) := p.stack();
		if(err != nil)
			return "";
		case how{
		StepExp =>
			if(src != stk[0].srcstr())
				return "";
		StepStmt =>
			if(stmt != ftostmt(stk[0]))
				return "";
			if(stk[0].offset != stack[0].offset)
				return "";
		StepOut =>
			if(returned(stack, stk))
				return "";
		StepOver =>
			if(stk[0].offset == stack[0].offset){
				if(stmt != ftostmt(stk[0]))
					return "";
				buf = array of byte("step1");
				break;
			}
			if(returned(stack, stk))
				return "";
			buf = array of byte("toret");
		* =>
			return "";
		}
	}
	return sprint("%r");
}

Prog.stop(p: self ref Prog): string
{
	return dbgctl(p, "stop");
}

Prog.unstop(p: self ref Prog): string
{
	return dbgctl(p, "unstop");
}

Prog.grab(p: self ref Prog): string
{
	return dbgctl(p, "step0");
}

Prog.start(p: self ref Prog): string
{
	return dbgctl(p, "start");
}

Prog.cont(p: self ref Prog): string
{
	return dbgctl(p, "cont");
}

dbgctl(p: ref Prog, msg: string): string
{
	b := array of byte msg;
	while(sys->write(p.dbgctl, b, len b) != len b)
		return sprint("%r");
	return "";
}

returned(old, new: array of ref Exp): int
{
	n := len old;
	if(n > len new)
		return 1;
	return 0;
}

Prog.setbpt(p: self ref Prog, dis: string, pc:int): string
{
	b := array of byte("bpt set "+dis+" "+string pc);
	if(sys->write(p.dbgctl, b, len b) != len b)
		return sprint("can't set breakpoint: %r");
	return "";
}

Prog.delbpt(p: self ref Prog, dis: string, pc:int): string
{
	b := array of byte("bpt del "+dis+" "+string pc);
	if(sys->write(p.dbgctl, b, len b) != len b)
		return sprint("can't del breakpoint: %r");
	return "";
}

Prog.kill(p: self ref Prog): string
{
	b := array of byte "kill";
	if(sys->write(p.ctl, b, len b) != len b)
		return sprint("can't kill process: %r");
	return "";
}

Prog.event(p: self ref Prog): string
{
	b := array[100] of byte;
	n := sys->read(p.dbgctl, b, len b);
	if(n < 0)
		return sprint("error: %r");
	return string b[:n];
}

ftostmt(e: ref Exp): int
{
	m := e.m;
	if(!m.comp && m.sym != nil && e.pc < len m.sym.srcstmt)
		return m.sym.srcstmt[e.pc];
	return -1;
}

Exp.srcstr(e: self ref Exp): string
{
	m := e.m;
	if(!m.comp && m.sym != nil && e.pc < len m.sym.src){
		src := m.sym.src[e.pc];
		ss := src.start.file+":"+string src.start.line+"."+string src.start.pos+", ";
		if(src.stop.file != src.start.file)
			ss += src.stop.file+":"+string src.stop.line+".";
		else if(src.stop.line != src.start.line)
			ss += string src.stop.line+".";
		return ss+string src.stop.pos;
	}
	return sprint("Module %s PC %d", e.m.path, e.pc);
}

Exp.findsym(e: self ref Exp): string
{
	m := e.m;
	if(m.comp)
		return "compiled module";
	if(m.sym != nil){
		n := e.pc;
		fns := m.sym.fns;
		for(i := 0; i < len fns; i++){
			if(n >= fns[i].offset && n < fns[i].stoppc){
				e.name = fns[i].name;
				e.id = fns[i];
				return "";
			}
		}
		return "pc out of bounds";
	}
	return "no symbol file";
}

Exp.src(e: self ref Exp): ref Src
{
	m := e.m;
	if(e.id == nil || m.sym == nil)
		return nil;
	src := e.id.src;
	if(src != nil)
		return src;
	if(e.id.t.kind == Tfn && !m.comp && e.pc < len m.sym.src && e.pc >= 0)
		return m.sym.src[e.pc];
	return nil;
}

Type.getkind(t: self ref Type, sym: ref Sym): int
{
	if(t == nil)
		return -1;
	if(t.kind == Tid)
		return sym.adts[int t.name].getkind(sym);
	return t.kind;
}

Type.text(t: self ref Type, sym: ref Sym): string
{
	if (t == nil)
		return "no type";
	s := typenames[t.kind];
	case t.kind {
	Tadt or
	Tadtpick or
	Tmodule =>
		s = t.name;
	Tid =>
		return sym.adts[int t.name].text(sym);
	Tarray or
	Tlist or
	Tchan or
	Tslice =>
		s += " of " + t.Of.text(sym);
	Tref =>
		s += " " + t.Of.text(sym);
	Tfn =>
		s += "(";	
		for(i := 0; i < len t.ids; i++)
			s += t.ids[i].name + ": " + t.ids[i].t.text(sym);
		s += "): " + t.Of.text(sym);
	Ttuple or
	Tlocal or
	Tglobal or
	Targ =>
		if(t.kind == Ttuple)
			s = "";
		s += "(";
		for (i := 0; i < len t.ids; i++) {
			s += t.ids[i].t.text(sym);
			if (i < len t.ids - 1)
				s += ", ";
		}
		s += ")";
	}
	return s;
}

Exp.typename(e: self ref Exp): string
{
	if (e.id == nil)
		return "no info";
	return e.id.t.text(e.m.sym);
}

Exp.kind(e: self ref Exp): int
{
	if(e.id == nil)
		return -1;
	return e.id.t.getkind(e.m.sym);
}

EXPLISTMAX : con	32;	# what's a good value for this ?

Exp.expand(e: self ref Exp): array of ref Exp
{
	if(e.id == nil)
		return nil;

	t := e.id.t;
	if(t.kind == Tid)
		t = e.m.sym.adts[int t.name];

	off := e.offset;
	ids := t.ids;
	case t.kind{
	Tadt or Tfn or Targ or Tlocal or Ttuple =>
		break;
	Tadtpick =>
		break;
	Tglobal =>
		ids = e.m.sym.vars;
		off = e.m.data;
	Tmodule =>
		(s, err) := pdata(e.p, off, "M");
		if(s == "nil" || err != "")
			return nil;
		off = hex(array of byte s);
	Tref =>
		(s, err) := pdata(e.p, off, "P");
		if(s == "nil" || err != "")
			return nil;
		off = hex(array of byte s);
		et := t.Of;
		if(et.kind == Tid)
			et = e.m.sym.adts[int et.name];
		ids = et.ids;
		if(et.kind == Tadtpick){
			(s, err) = pdata(e.p, off, "W");
			tg := int s;
			if(tg < 0 || tg > len et.tags || err != "" )
				return nil;
			k := array[1 + len ids + len et.tags[tg].ids] of ref Exp;
			k[0] = ref Exp(et.tags[tg].name, off+0, e.pc, e.m, e.p, ref Id(et.src, et.tags[tg].name, 0, 0, tint));
			x := 1;
			for(i := 0; i < len ids; i++){
				id := ids[i];
				k[i+x] = ref Exp(id.name, off+id.offset, e.pc, e.m, e.p, id);
			}
			x += len ids;
			ids = et.tags[tg].ids;
			for(i = 0; i < len ids; i++){
				id := ids[i];
				k[i+x] = ref Exp(id.name, off+id.offset, e.pc, e.m, e.p, id);
			}
			return k;
		}
	Tlist =>
		(s, err) := pdata(e.p, off, "L");
		if(err != "")
			return nil;
		(tloff, hdoff) := str->splitl(s, ".");
		hdoff = hdoff[1:];
		k := array[2] of ref Exp;
		k[0] = ref Exp("hd", hex(array of byte hdoff), e.pc, e.m, e.p, ref Id(nil, "hd", H, H, t.Of));
		k[1] = ref Exp("tl", hex(array of byte tloff), e.pc, e.m, e.p, ref Id(nil, "tl", H, H, t));
		return k;
	Tarray =>
		(s, nil) := pdata(e.p, e.offset, "A");
		if(s == "nil")
			return nil;
		(sn, sa) := str->splitl(s, ".");
		n := int sn;
		if(sa == "" || n <= 0)
			return nil;
		(off, nil) = str->toint(sa[1:], 16);
		et := t.Of;
		if(et.kind == Tid)
			et = e.m.sym.adts[int et.name];
		esize := et.size;
		if (n <= EXPLISTMAX || EXPLISTMAX == 0) {
			k := array[n] of ref Exp;
			for(i := 0; i < n; i++){
				name := string i;
				k[i] = ref Exp(name, off+i*esize, e.pc, e.m, e.p, ref Id(nil, name, H, H, et));
			}
			return k;
		}
		else {
			# slice it
			(p, q, r) := partition(n, EXPLISTMAX);
			lb := 0;
			k := array[p] of ref Exp;
			st := ref Type(et.src, Tslice, 0, nil, et, nil, nil);
			for (i := 0; i < p; i++){
				ub := lb+q-1;
				if (--r >= 0)
					ub++;
				name := string lb + ".." + string ub;
				k[i] = ref Exp(name, off+lb*esize, e.pc, e.m, e.p, ref Id(nil, name, H, H, st));
				lb = ub+1;
			}
			return k;	
		}
	Tslice =>
		(lb, ub) := bounds(e.name);
		if (lb > ub)
			return nil;
		n := ub-lb+1;
		et := t.Of;
		if(et.kind == Tid)
			et = e.m.sym.adts[int et.name];
		esize := et.size;
		if (n <= EXPLISTMAX || EXPLISTMAX == 0) {
			k := array[n] of ref Exp;
			for(i := 0; i < n; i++){
				name := string (i+lb);
				k[i] = ref Exp(name, off+i*esize, e.pc, e.m, e.p, ref Id(nil, name, H, H, et));
			}
			return k;
		}
		else {
			# slice it again
			(p, q, r) := partition(n, EXPLISTMAX);
			lb0 := lb;
			k := array[p] of ref Exp;
			st := ref Type(et.src, Tslice, 0, nil, et, nil, nil);
			for (i := 0; i < p; i++){
				ub = lb+q-1;
				if (--r >= 0)
					ub++;
				name := string lb + ".." + string ub;
				k[i] = ref Exp(name, off+(lb-lb0)*esize, e.pc, e.m, e.p, ref Id(nil, name, H, H, st));
				lb = ub+1;
			}
			return k;
		}	
	Tchan =>
		(s, nil) := pdata(e.p, e.offset, "c");
		if(s == "nil")
			return nil;
		(sn, sa) := str->splitl(s, ".");
		n := int sn;
		if(sa == "" || n <= 0)
			return nil;
		(off, nil) = str->toint(sa[1:], 16);
		(nil, sa) = str->splitl(sa[1:], ".");
		(sn, sa) = str->splitl(sa[1:], ".");
		f := int sn;
		sz := int sa[1:];
		et := t.Of;
		if(et.kind == Tid)
			et = e.m.sym.adts[int et.name];
		esize := et.size;
		k := array[sz] of ref Exp;
		for(i := 0; i < sz; i++){
			name := string i;
			j := (f+i)%n;
			k[i] = ref Exp(name, off+j*esize, e.pc, e.m, e.p, ref Id(nil, name, H, H, et));
		}
		return k;
	* =>
		return nil;
	}
	k := array[len ids] of ref Exp;
	for(i := 0; i < len k; i++){
		id := ids[i];
		k[i] = ref Exp(id.name, off+id.offset, e.pc, e.m, e.p, id);
	}
	return k;
}

Exp.val(e: self ref Exp): (string, int)
{
	if(e.id == nil)
		return (e.m.path+" unknown fn", 0);
	t := e.id.t;
	if(t.kind == Tid)
		t = e.m.sym.adts[int t.name];

	w := 0;
	s := "";
	err := "";
	p := e.p;
	case t.kind{
	Tfn =>
		if(t.ids != nil)
			w = 1;
		src := e.m.sym.src[e.pc];
		ss := src.start.file+":"+string src.start.line+"."+string src.start.pos+", ";
		if(src.stop.file != src.start.file)
			ss += src.stop.file+":"+string src.stop.line+".";
		else if(src.stop.line != src.start.line)
			ss += string src.stop.line+".";
		return (ss+string src.stop.pos, w);
	Targ or Tlocal or Tglobal or Tadtpick or Ttuple =>
		return ("", 1);
	Tadt =>
		return ("#" + string e.offset, 1);
	Tnil =>
		s = "nil";
	Tbyte =>
		(s, err) = pdata(p, e.offset, "B");
	Tint =>
		(s, err) = pdata(p, e.offset, "W");
	Tbig =>
		(s, err) = pdata(p, e.offset, "V");
	Treal =>
		(s, err) = pdata(p, e.offset, "R");
	Tarray =>
		(s, err) = pdata(p, e.offset, "A");
		if(s == "nil")
			break;
		(n, a) := str->splitl(s, ".");
		if(a == "")
			return ("", 0);
		s = "["+n+"] @"+a[1:];
		w = 1;
	Tslice =>
		(lb, ub) := bounds(e.name);
		s = sys->sprint("[:%d] @ %x", ub-lb+1, e.offset);
		w = 1;
	Tstring =>
		n : int;
		(n, s, err) = pstring(p, e.offset);
		if(err != "")
			return ("", 0);
		for(i := 0; i < len s; i++)
			if(s[i] == '\n')
				s[i] = '\u008a';
		s = "["+string n+"] \""+s+"\"";
	Tref or Tlist or Tmodule or Tpoly=>
		(s, err) = pdata(p, e.offset, "P");
		if(s == "nil")
			break;
		s = "@" + s;
		w = 1;
	Tchan =>
		(s, err) = pdata(p, e.offset, "c");
		if(s == "nil")
			break;
		(n, a) := str->splitl(s, ".");
		if(a == "")
			return ("", 0);
		if(n == "0"){
			s = "@" + a[1:];
			w = 0;
		}
		else{
			(a, nil) = str->splitl(a[1:], ".");
			s = "["+n+"] @"+a;
			w = 1;
		}
	}
	if(err != "")
		return ("", 0);
	return (s, w);
}

Sym.srctopc(s: self ref Sym, src: ref Src): int
{
	srcs := s.src;
	line := src.start.line;
	pos := src.start.pos;
	(nil, file) := str->splitr(src.start.file, "/");
	backup := -1;
	delta := 80;
	for(i := 0; i < len srcs; i++){
		ss := srcs[i];
		if(ss.start.file != file)
			continue;
		if(ss.start.line <= line && ss.start.pos <= pos
		&& ss.stop.line >= line && ss.stop.pos >= pos)
			return i;
		d := ss.start.line - line;
		if(d >= 0 && d < delta){
			delta = d;
			backup = i;
		}
	}
	return backup;
}

Sym.pctosrc(s: self ref Sym, pc: int): ref Src
{
	if(pc < 0 || pc >= len s.src)
		return nil;
	return s.src[pc];
}

sym(sbl: string): (ref Sym, string)
{
	h := 0;
	for(i := 0; i < len sbl; i++)
		h = (h << 1) + sbl[i];
	h &= SymHash - 1;
	for(sl := syms[h]; sl != nil; sl = tl sl){
		s := hd sl;
		if(sbl == s.path)
			return (s, "");
	}
	(sy, err) := loadsyms(sbl);
	if(err != "")
		return (nil, err);
	syms[h] = sy :: syms[h];
	return (sy, "");
}

Module.addsym(m: self ref Module, sym: ref Sym)
{
	m.sym = sym;
}

Module.sbl(m: self ref Module): string
{
	if(m.sym != nil)
		return m.sym.path;
	return "";
}

Module.dis(m: self ref Module): string
{
	return m.path;
}

findsbl(dis: string): string
{
	n  := len dis;
	if(n <= 4 || dis[n-4: n] != ".dis")
		dis += ".dis";
	if(dism == nil){
		dism = load Dis Dis->PATH;
		if(dism != nil)
			dism->init();
	}
	if(dism != nil && (b := dism->src(dis)) != nil){
		n = len b;
		if(n > 2 && b[n-2: n] == ".b"){
			sbl := b[0: n-2] + ".sbl";
			if(sys->open(sbl, Sys->OREAD) != nil)
				return sbl;
		}
	}	
	return nil;	
}

Module.stdsym(m: self ref Module)
{
	if(m.sym != nil)
		return;
	if((sbl := findsbl(m.path)) != nil){
		(m.sym, nil) = sym(sbl);
		return;
	}
	sbl = m.path;
	n := len sbl;
	if(n > 4 && sbl[n-4:n] == ".dis")
		sbl = sbl[:n-4]+".sbl";
	else
		sbl = sbl+".sbl";
	path := sbl;
	fd := sys->open(sbl, sys->OREAD);
	for(i := 0; fd == nil && i < len sblpath; i++){
		(dis, src) := sblpath[i];
		nd := len dis;
		if(len sbl > nd && sbl[:nd] == dis){
			path = src + sbl[nd:];
			fd = sys->open(path, sys->OREAD);
		}
	}
	if(fd == nil)
		return;
	(m.sym, nil) = sym(path);
}

mkmod(data, code, comp: int, dis: string): ref Module
{
	h := 0;
	for(i := 0; i < len dis; i++)
		h = (h << 1) + dis[i];
	h &= ModHash - 1;
	sym : ref Sym;
	for(ml := mods[h]; ml != nil; ml = tl ml){
		m := hd ml;
		if(m.path == dis && m.code == code && m.comp == comp){
			sym = m.sym;
			if(m.data == data)
				return m;
		}
	}
	m := ref Module(dis, code, data, comp, sym);
	mods[h] = m :: mods[h];
	return m;
}

pdata(p: ref Prog, a: int, fmt: string): (string, string)
{
	b := array of byte sprint("0x%ux.%s1", a, fmt);
	if(sys->write(p.heap, b, len b) != len b)
		return ("", sprint("can't write heap: %r"));

	buf := array[64] of byte;
	sys->seek(p.heap, big 0, 0);
	n := sys->read(p.heap, buf, len buf);
	if(n <= 1)
		return ("", sprint("can't read heap: %r"));
	return (string buf[:n-1], "");
}

pstring0(p: ref Prog, a: int, blen: int): (int, string, string)
{
	b := array of byte sprint("0x%ux.C1", a);
	if(sys->write(p.heap, b, len b) != len b)
		return (-1, "", sprint("can't write heap: %r"));

	buf := array[blen] of byte;
	sys->seek(p.heap, big 0, 0);
	n := sys->read(p.heap, buf, len buf-1);
	if(n <= 1)
		return (-1, "", sprint("can't read heap: %r"));
	buf[n] = byte 0;
	m := strchr(buf, 0, '.');
	if(buf[m++] != byte '.')
		m = 0;
	return (int string buf[0:m], string buf[m:n], "");
}

pstring(p: ref Prog, a: int): (int, string, string)
{
	m, n: int;
	s, err: string;

	m = 64;
	for(;;){
		(n, s, err) = pstring0(p, a, m);
		if(err != "" || n <= len s)
			break;
		# guard against broken devprog
		if(m >= 3 * n)
			return (-1, nil, "bad string");
		m *= 2;
	}
	return (n, s, err);
}

Prog.status(p: self ref Prog): (int, string, string, string)
{
	fd := sys->open(sprint("/prog/%d/status", p.id), sys->OREAD);
	if(fd == nil)
		return (-1, "", sprint("can't open status file: %r"), "");
	buf := array[256] of byte;
	n := sys->read(fd, buf, len buf);
	if(n <= 0)
		return (-1, "", sprint("can't read status file: %r"), "");
	(ni, info) := sys->tokenize(string buf[:n], " \t");
	if(ni != 6 && ni != 7)
		return (-1, "", "can't parse status file", "");
	info = tl info;
	if(ni == 6)
		return (int hd info, hd tl info, hd tl tl info, hd tl tl tl tl info);
	return (int hd info, hd tl info, hd tl tl tl info, hd tl tl tl tl tl info);
}

loadsyms(sbl: string): (ref Sym, string)
{
	fd := sys->open(sbl, sys->OREAD);
	if(fd == nil)
		return (nil, sprint("Can't open symbol file '%s': %r", sbl));

	(ok, dir) := sys->fstat(fd);
	if(ok < 0)
		return (nil, sprint("Can't read symbol file '%s': %r", sbl));
	n := int dir.length;
	buf := array[n+1] of byte;
	if(sys->read(fd, buf, n) != n)
		return (nil, sprint("Can't read symbol file '%s': %r", sbl));
	fd = nil;
	buf[n] = byte 0;

	s := ref Sym;
	s.path = sbl;

	n = strchr(buf, 0, '\n');
	vers := 0;
	if(string buf[:n] == "limbo .sbl 1.")
		vers = 10;
	else if(string buf[:n] == "limbo .sbl 1.1")
		vers = 11;
	else if(string buf[:n] == "limbo .sbl 2.0")
		vers = 20;
	else if(string buf[:n] == "limbo .sbl 2.1")
		vers = 21;
	else
		return (nil, "Symbol file "+sbl+" out of date");
	o := n += 1;
	n = strchr(buf, o, '\n');
	if(buf[n] != byte '\n')
		return (nil, "Corrupted symbol file "+sbl);
	s.name = string buf[o:n++];
	ss := ref SrcState(nil, 0, 0, vers);
	err : string;
	if(n >= 0){
		err = "file";
		n = debugfiles(ss, buf, n);
	}
	if(n >= 0){
		err = "pc";
		n = debugpc(ss, s, buf, n);
	}
	if(n >= 0){
		err = "types";
		n = debugtys(ss, s, buf, n);
	}
	if(n >= 0){
		err = "fn";
		n = debugfns(ss, s, buf, n);
	}
	vs: array of ref Id;
	if(n >= 0){
		err = "global";
		(vs, n) = debugid(ss, buf, n);
	}
	if(n < 0)
		return (nil, "Corrupted "+err+" symbol table in "+sbl);
	s.vars = vs;
	return (s, "");
}

#
# parse a source location
# format[file:][line.]pos,[file:][line.]pos' '
#
debugsrc(ss: ref SrcState, buf: array of byte, p: int): (ref Src, int)
{
	n: int;
	src: ref Src;

	(n, p) = strtoi(buf, p);
	if(buf[p] == byte ':'){
		ss.lastf = n;
		(n, p) = strtoi(buf, p + 1);
	}
	if(buf[p] == byte '.'){
		ss.lastl = n;
		(n, p) = strtoi(buf, p + 1);
	}
	if(buf[p++] != byte ',' || ss.lastf >= len ss.files || ss.lastf < 0)
		return (nil, -1);
	src = ref Src;
	src.start.file = ss.files[ss.lastf];
	src.start.line = ss.lastl;
	src.start.pos = n;

	(n, p) = strtoi(buf, p);
	if(buf[p] == byte ':'){
		ss.lastf = n;
		(n, p) = strtoi(buf, p+1);
	}
	if(buf[p] == byte '.'){
		ss.lastl = n;
		(n, p) = strtoi(buf, p + 1);
	}
	if(buf[p++] != byte ' ' || ss.lastf >= len ss.files || ss.lastf < 0)
		return (nil, -1);
	src.stop.file = ss.files[ss.lastf];
	src.stop.line = ss.lastl;
	src.stop.pos = n;
	return (src, p);
}

#
# parse the file table
# item format: file: string
#
debugfiles(ss: ref SrcState, buf: array of byte, p: int): int
{
	n, q: int;

	(n, p) = strtoi(buf, p);
	if(buf[p++] != byte '\n')
		return -1;
	ss.files = array[n] of string;
	for(i := 0; i < n; i++){
		q = strchr(buf, p, '\n');
		ss.files[i] = string buf[p:q];
		p = q + 1;
	}
	return p;
}

#
# parse the pc to source table
# item format: Source stmt
#
debugpc(ss: ref SrcState, s: ref Sym, buf: array of byte, p: int): int
{
	ns: int;

	(ns, p) = strtoi(buf, p);
	if(buf[p++] != byte '\n')
		return -1;
	s.src = array[ns] of ref Src;
	s.srcstmt = array[ns] of int;
	for(i := 0; i < ns; i++){
		(s.src[i], p) = debugsrc(ss, buf, p);
		if(p < 0)
			return -1;
		(s.srcstmt[i], p) = strtoi(buf, p);
		if(buf[p++] != byte '\n')
			return -1;
	}
	return p;
}

#
# parse the type table
# format: linear list of types
#
debugtys(ss: ref SrcState, s: ref Sym, buf: array of byte, p: int): int
{
	na: int;

	(na, p) = strtoi(buf, p);
	if(buf[p++] != byte '\n')
		return -1;
	s.adts = array[na] of ref Type;
	adts := s.adts;
	for(i := 0; i < na; i++){
		if(ss.vers < 20)
			(adts[i], p) = debugadt(ss, buf, p);
		else
			(adts[i], p) = debugtype(ss, buf, p);
		if(p < 0)
			return -1;
	}
	return p;
}

#
# parse the function table
# format: pc:name:argids localids rettype
#
debugfns(ss: ref SrcState, s: ref Sym, buf: array of byte, p: int): int
{
	t: ref Type;
	args, locals: array of ref Id;
	nf, pc, q: int;

	(nf, p) = strtoi(buf, p);
	if(buf[p++] != byte '\n')
		return -1;
	s.fns = array[nf] of ref Id;
	fns := s.fns;
	for(i := 0; i < nf; i++){
		(pc, p) = strtoi(buf, p);
		if(buf[p++] != byte ':')
			return -2;
		q = strchr(buf, p, '\n');
		if(buf[q] != byte '\n')
			return -3;
		name := string buf[p:q];
		(args, p) = debugid(ss, buf, q + 1);
		if(p == -1)
			return -4;
		(locals, p) = debugid(ss, buf, p);
		if(p == -1)
			return -5;
		(t, p) = debugtype(ss, buf, p);
		if(p == -1)
			return -6;
		nk := 1 + (len args != 0) + (len locals != 0);
		kids := array[nk] of ref Id;
		nk = 0;
		if(len locals != 0)
			kids[nk++] = ref Id(nil, "locals", 0, 0, ref Type(nil, Tlocal, 0, nil, nil, locals, nil));
		if(len args != 0)
			kids[nk++] = ref Id(nil, "args", 0, 0, ref Type(nil, Targ, 0, nil, nil, args, nil));
		kids[nk++] = ref Id(nil, "module", 0, 0, ref Type(nil, Tglobal, 0, nil, nil, nil, nil));
		args = nil;
		locals = nil;
		fns[i] = ref Id(nil, name, pc, 0, ref Type(nil, Tfn, 0, name, t, kids, nil));
	}
	for(i = 1; i < nf; i++)
		fns[i-1].stoppc = fns[i].offset;
	fns[i-1].stoppc = len s.src;
	return p;
}

#
# parse a list of ids
# format: offset ':' name ':' src type '\n'
#
debugid(ss: ref SrcState, buf: array of byte, p: int): (array of ref Id, int)
{
	t: ref Type;
	off, nd, q, qq, tq: int;
	src: ref Src;

	(nd, p) = strtoi(buf, p);
	if(buf[p++] != byte '\n')
		return (nil, -1);
	d := array[nd] of ref Id;
	for(i := 0; i < nd; i++){
		(off, q) = strtoi(buf, p);
		if(buf[q++] != byte ':')
			return (nil, -1);
		qq = strchr(buf, q, ':');
		if(buf[qq] != byte ':')
			return (nil, -1);
		tq = qq + 1;
		if(ss.vers > 10){
			(src, tq) = debugsrc(ss, buf, tq);
			if(tq < 0)
				return (nil, -1);
		}
		(t, p) = debugtype(ss, buf, tq);
		if(p == -1 || buf[p++] != byte '\n')
			return (nil, -1);
		d[i] = ref Id(src, string buf[q:qq], off, 0, t);
	}
	return (d, p);
}

idlist(a: array of ref Id): list of ref Id
{
	n := len a;
	ids : list of ref Id = nil;
	while(n-- > 0)
		ids = a[n] :: ids;
	return ids;
}

#
# parse a type description
#
debugtype(ss: ref SrcState, buf: array of byte, p: int): (ref Type, int)
{
	t: ref Type;
	d: array of ref Id;
	q, k: int;
	src: ref Src;

	size := 0;
	case int buf[p++]{
	'@' =>
		k = Tid;
	'A' =>
		k = Tarray;
		size = IBY2WD;
	'B' =>
		return (tbig, p);
	'C' =>	k = Tchan;
		size = IBY2WD;
	'L' =>
		k = Tlist;
		size = IBY2WD;
	'N' =>
		return (tnil, p);
	'R' =>
		k = Tref;
		size = IBY2WD;
	'a' =>
		k = Tadt;
		if(ss.vers < 20)
			size = -1;
	'b' =>
		return (tbyte, p);
	'f' =>
		return (treal, p);
	'i' =>
		return (tint, p);
	'm' =>
		k = Tmodule;
		size = IBY2WD;
	'n' =>
		return (tnone, p);
	'p' =>
		k = Tadtpick;
	's' =>
		return (tstring, p);
	't' =>
		k = Ttuple;
	 	size = -1;
	'F' =>
		k = Tfn;
		size = IBY2WD;
	'P' =>
		return (tpoly, p);
	* =>
		k = Terror;
	}

	if(size == -1){
		q = strchr(buf, p, '.');
		if(buf[q] == byte '.'){
			size = int string buf[p:q];
			p = q+1;
		}
	}

	case k{
	Tid =>
		q = strchr(buf, p, '\n');
		if(buf[q] != byte '\n')
			return (nil, -1);
		t = ref Type(nil, Tid, -1, string buf[p:q], nil, nil, nil);
		p = q + 1;
	Tadt =>
		if(ss.vers < 20){
			q = strchr(buf, p, '\n');
			if(buf[q] != byte '\n')
				return (nil, -1);
			t = ref Type(nil, Tid, size, string buf[p:q], nil, nil, nil);
			p = q + 1;
		}else
			(t, p) = debugadt(ss, buf, p);
	Tadtpick =>
		(t, p) = debugadt(ss, buf, p);
		t.kind = Tadtpick;
		(t.tags, p) = debugtag(ss, buf, p);
	Tmodule =>
		q = strchr(buf, p, '\n');
		if(buf[q] != byte '\n')
			return (nil, -1);
		t = ref Type(nil, k, size, string buf[p:q], nil, nil, nil);
		p = q + 1;
		if(ss.vers > 10){
			(src, p) = debugsrc(ss, buf, p);
			t.src = src;
		}
		if(ss.vers > 20)
			(t.ids, p) = debugid(ss, buf, p);
	Tref or Tarray or Tlist or Tchan =>		# ref, array, list, chan
		(t, p) = debugtype(ss, buf, p);
		t = ref Type(nil, k, size, "", t, nil, nil);

	Ttuple =>						# tuple
		(d, p) = debugid(ss, buf, p);
		t = ref Type(nil, k, size, "", nil, d, nil);

	Tfn =>						# fn
		(d, p) = debugid(ss, buf, p);
		(t, p) = debugtype(ss, buf, p);
		t = ref Type(nil, k, size, "", t, d, nil);

	* =>
		p = -1;
	}
	return (t, p);
}

#
# parse an adt type spec
# format: name ' ' src size '\n' ids
#
debugadt(ss: ref SrcState, buf: array of byte, p: int): (ref Type, int)
{
	src: ref Src;

	q := strchr(buf, p, ' ');
	if(buf[q] != byte ' ')
		return (nil, -1);
	sq := q + 1;
	if(ss.vers > 10){
		(src, sq) = debugsrc(ss, buf, sq);
		if(sq < 0)
			return (nil, -1);
	}
	qq := strchr(buf, sq, '\n');
	if(buf[qq] != byte '\n')
		return (nil, -1);
	(d, pp) := debugid(ss, buf, qq + 1);
	if(pp == -1)
		return (nil, -1);
	t := ref Type(src, Tadt, int string buf[sq:qq], string buf[p:q], nil, d, nil);
	return (t, pp);
}

#
# parse a list of tags
# format:
#	name ':' src size '\n' ids
# or	
#	name ':' src '\n'
#
debugtag(ss: ref SrcState, buf: array of byte, p: int): (array of ref Type, int)
{
	d: array of ref Id;
	ntg, q, pp, np: int;
	src: ref Src;

	(ntg, p) = strtoi(buf, p);
	if(buf[p++] != byte '\n')
		return (nil, -1);
	tg := array[ntg] of ref Type;
	for(i := 0; i < ntg; i++){
		pp = strchr(buf, p, ':');
		if(buf[pp] != byte ':')
			return (nil, -1);
		q = pp + 1;
		(src, q) = debugsrc(ss, buf, q);
		if(q < 0)
			return (nil, -1);
		if(buf[q] == byte '\n'){
			np = q + 1;
			if(i <= 0)
				return (nil, -1);
			tg[i] = ref Type(src, Tadt, tg[i-1].size, string buf[p:pp], nil, tg[i-1].ids, nil);
		}else{
			np = strchr(buf, q, '\n');
			if(buf[np] != byte '\n')
				return (nil, -1);
			size := int string buf[q:np];
			(d, np) = debugid(ss, buf, np+1);
			if(np == -1)
				return (nil, -1);
			tg[i] = ref Type(src, Tadt, size, string buf[p:pp], nil, d, nil);
		}
		p = np;
	}
	return (tg, p);
}

strchr(a: array of byte, p, c: int): int
{
	bc := byte c;
	while((b := a[p]) != byte 0 && b != bc)
		p++;
	return p;
}

strtoi(a: array of byte, start: int): (int, int)
{
	p := start;
	for(; c := int a[p]; p++){
		case c{
		' ' or '\t' or '\n' or '\r' =>
			continue;
		}
		break;
	}

	# sign
	neg := c == '-';
	if(neg || c == '+')
		p++;

	# digits
	n := 0;
	nn := 0;
	ndig := 0;
	over := 0;
	for(; c = int a[p]; p++){
		if(c < '0' || c > '9')
			break;
		ndig++;
		nn = n * 10 + (c - '0');
		if(nn < n)
			over = 1;
		n = nn;
	}
	if(ndig == 0)
		return (0, start);
	if(neg)
		n = -n;
	if(over)
		if(neg)
			n = 2147483647;
		else
			n = int -2147483648;
	return (n, p);
}

hex(a: array of byte): int
{
	n := 0;
	for(i := 0; i < len a; i++){
		c := int a[i];
		if(c >= '0' && c <= '9')
			c -= '0';
		else
			c -= 'a' - 10;
		n = (n << 4) + (c & 15);
	}
	return n;
}

partition(n : int, max : int) : (int, int, int)
{
	p := n/max; 
	if (n%max != 0)
		p++;
	if (p > max)
		p = max;
	q := n/p;
	r := n-p*q;
	return (p, q, r);
}

bounds(s : string) : (int, int)
{
	lb := int s;
	for (i := 0; i < len s; i++)
		if (s[i] == '.')
			break;
	if (i+1 >= len s || s[i] != '.' || s[i+1] != '.')
		return (1, 0);
	ub := int s[i+2:];
	return (lb, ub);
}