// poor emulation of SVR5 truss command - traces system calls

include("syscall");

_stoprunning = 0;

defn stopped(pid) {
	local l;
	local pc;
	pc = *PC;
	if notes then {
		if (notes[0]!="sys: breakpoint") then
		{
			print(pid,": ",trapreason(),"\t");
			print(fmt(pc,97),"\t",fmt(pc,105),"\n");
			print("Notes pending:\n");
			l = notes;
			while l do
			{
				print("\t",head l,"\n");
				l = tail l;
			}
			_stoprunning = 1;
		}
	}
}

defn _addressof(pattern) {
	local s, l;
	l = symbols;
	pattern = "^\\$*"+pattern+"$";
	while l do
	{
		s = head l;
		if regexp(pattern, s[0]) && ((s[1] == 'T') || (s[1] == 'L')) then
			return s[2];
		l = tail l;
	}
	return 0;
}

stopPC = {};
readPC = {};
fd2pathPC = {};
errstrPC = {};
awaitPC = {};
_waitPC = {};
_errstrPC = {};
trusscalls = {
		"sysr1",
		"_errstr",
		"bind",
		"chdir",
		"close",
		"dup",
		"alarm",
		"exec",
		"_exits",
		"_fsession",
		"fauth",
		"_fstat",
		"segbrk",
		"_mount",
		"open",
		"_read",
		"oseek",
		"sleep",
		"_stat",
		"rfork",
		"_write",
		"pipe",
		"create",
		"fd2path",
		"brk_",
		"remove",
		"_wstat",
		"_fwstat",
		"notify",
		"noted",
		"segattach",
		"segdetach",
		"segfree",
		"segflush",
		"rendezvous",
		"unmount",
		"_wait",
		"seek",
		"fversion",
		"errstr",
		"stat",
		"fstat",
		"wstat",
		"fwstat",
		"mount",
		"await",
		"pread",
		"pwrite",
	};

trussapecalls = {
		"_SYSR1",
		"__ERRSTR",
		"_BIND",
		"_CHDIR",
		"_CLOSE",
		"_DUP",
		"_ALARM",
		"_EXEC",
		"_EXITS",
		"__FSESSION",
		"_FAUTH",
		"__FSTAT",
		"_SEGBRK",
		"__MOUNT",
		"_OPEN",
		"__READ",
		"_OSEEK",
		"_SLEEP",
		"__STAT",
		"_RFORK",
		"__WRITE",
		"_PIPE",
		"_CREATE",
		"_FD2PATH",
		"_BRK_",
		"_REMOVE",
		"__WSTAT",
		"__FWSTAT",
		"_NOTIFY",
		"_NOTED",
		"_SEGATTACH",
		"_SEGDETACH",
		"_SEGFREE",
		"_SEGFLUSH",
		"_RENDEZVOUS",
		"_UNMOUNT",
		"__WAIT",
		"_SEEK",
		"__NFVERSION",
		"__NERRSTR",
		"_STAT",
		"__NFSTAT",
		"__NWSTAT",
		"__NFWSTAT",
		"__NMOUNT",
		"__NAWAIT",
		"_PREAD",
		"_PWRITE",
	};

defn addressof(pattern) {
	// translate to ape system calls if we have an ape binary
	if _addressof("_EXITS") == 0 then
		return _addressof(pattern);
	return _addressof(trussapecalls[match(pattern, trusscalls)]);
}

defn setuptruss() {
	local lst, offset, name, addr;

	trussbpt = {};
	offset = trapoffset();
	lst = trusscalls;
	while lst do
	{
		name = head lst;
		lst = tail lst;
		addr = addressof(name);
		if addr then
		{
			bpset(addr+offset);
			trussbpt = append trussbpt, (addr+offset);
			// sometimes _exits is renamed $_exits
			if(regexp("exits|exec", name)) then stopPC = append stopPC, (addr+offset);
			if(regexp("read", name)) then readPC = append readPC, (addr+offset);
			if(regexp("fd2path", name)) then fd2pathPC = append fd2pathPC, (addr+offset);
			if(regexp("^\\$*await", name)) then awaitPC = append awaitPC, (addr+offset);
			if(regexp("^\\$*errstr", name)) then errstrPC = append errstrPC, (addr+offset);
			// compatibility hacks for old kernel
			if(regexp("_wait", name)) then _waitPC = append _waitPC, (addr+offset);
			if(regexp("_errstr", name)) then _errstrPC = append _errstrPC, (addr+offset);
		}
	}
}

defn trussflush() {
	stop(pid);		// already stopped, but flushes output
}

defn new() {
	bplist = {};
	newproc(progargs);
	bpset(follow(main)[0]);
	cont();
	bpdel(*PC);
	// clear the hang bit, which is left set by newproc, so programs we fork/exec don't hang
	printto("/proc/"+itoa(pid)+"/ctl", "nohang");
}

defn truss() {
	local pc, lst, offset, prevpc, pcspret, ret;

	offset = trapoffset();

	stop(pid);
	_stoprunning = 0;
	setuptruss();
	pcspret = UPCSPRET();

	while !_stoprunning do {
		cont();
		if notes[0]!="sys: breakpoint" then {
			cleantruss();
			return {};
		}
		pc = *PC;
		if match(*PC, stopPC)>=0 then {
			print(pid,": ",trapreason(),"\t");
			print(fmt(pc,'a'),"\t",fmt(pc,'i'),"\n");
			cleantruss();
			return {};
		}
		if match(*PC, trussbpt)>=0 then {
			usyscall();
			trussflush();
			prevpc = *PC;
			step();
			ret = eval pcspret[2];
			print("\treturn value: ", ret\D, "\n");
			if (ret>=0) && (match(prevpc, readPC)>=0) then {
				print("\tdata: ");
				printtextordata(*((eval pcspret[1])+4), ret);
				print("\n");
			}
			if (ret>=0) && (match(prevpc, fd2pathPC)>=0) then {
				print("\tdata: \"", *(*((eval pcspret[1])+4)\s), "\"\n");
			}
			if (ret>=0) && (match(prevpc, errstrPC)>=0) then {
				print("\tdata: \"", *(*(eval pcspret[1])\s), "\"\n");
			}
			if (ret>=0) && (match(prevpc, awaitPC)>=0) then {
				print("\tdata: ");
				printtextordata(*(eval pcspret[1]), ret);
				print("\n");
			}
			// compatibility hacks for old kernel:
			if (ret>=0) && (match(prevpc, _waitPC)>=0) then {
				print("\tdata: ");
				printtextordata(*(eval pcspret[1]), 12+3*12+64);
				print("\n");
			}
			if (ret>=0) && (match(prevpc, _errstrPC)>=0) then {
				print("\tdata: ");
				printtextordata(*(eval pcspret[1]), 64);
				print("\n");
			}
		}
		trussflush();
	}
}

defn cleantruss() {
	local lst, offset, addr;

	stop(pid);
	offset = trapoffset();
	lst = trussbpt;
	while lst do
	{
		addr = head lst;
		lst = tail lst;
		bpdel(addr);
	}
	trussbpt = {};
	**PC = @*PC;	// repair current instruction
}

defn untruss() {
	cleantruss();
	start(pid);
}

print(acidfile);
