main shrub/shrubtools / peek / peek.c
  1#define _POSIX_C_SOURCE 200809L
  2#include <arpa/inet.h>
  3#include <alloc.h>
  4#include <buffer.h>
  5#include <byte.h>
  6#include <errno.h>
  7#include <fmt.h>
  8#include <netinet/in.h>
  9#include <signal.h>
 10#include <stdbool.h>
 11#include <strerr.h>
 12#include <sys/ptrace.h>
 13#include <sys/socket.h>
 14#include <sys/syscall.h>
 15#include <sys/types.h>
 16#include <sys/un.h>
 17#include <sys/user.h>
 18#include <sys/wait.h>
 19#include <unistd.h>
 20
 21struct proc {
 22	pid_t pid;
 23	int entering;
 24};
 25
 26static struct proc *procs;
 27static size_t nprocs;
 28
 29static void
 30out(const char *s)
 31{
 32	buffer_puts(buffer_1, s);
 33}
 34
 35static void
 36outc(char c)
 37{
 38	buffer_put(buffer_1, &c, 1);
 39}
 40
 41static void
 42out_ulong(unsigned long u)
 43{
 44	char buf[FMT_ULONG];
 45	unsigned int n;
 46
 47	n = fmt_ulong(buf, u);
 48	buffer_put(buffer_1, buf, n);
 49}
 50
 51static void
 52die(const char *msg)
 53{
 54	strerr_die2sys(111, "peek: ", msg);
 55}
 56
 57static char *
 58unknown_string(void)
 59{
 60	char *s;
 61
 62	s = alloc(2);
 63	if (!s)
 64		die("alloc");
 65	s[0] = '?';
 66	s[1] = '\0';
 67	return s;
 68}
 69
 70static struct proc *
 71procget(pid_t pid)
 72{
 73	size_t i;
 74
 75	for (i = 0; i < nprocs; i++) {
 76		if (procs[i].pid == pid)
 77			return &procs[i];
 78	}
 79	if (!alloc_re((char **)&procs, nprocs * sizeof(procs[0]),
 80	    (nprocs + 1) * sizeof(procs[0])))
 81		die("alloc_re");
 82	procs[nprocs] = (struct proc){.pid = pid};
 83	return &procs[nprocs++];
 84}
 85
 86static void
 87procdel(pid_t pid)
 88{
 89	size_t i;
 90
 91	for (i = 0; i < nprocs; i++) {
 92		if (procs[i].pid != pid)
 93			continue;
 94		procs[i] = procs[--nprocs];
 95		return;
 96	}
 97}
 98
 99static void
100setopts(pid_t pid)
101{
102	long opts = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK |
103	            PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE |
104	            PTRACE_O_TRACEEXEC;
105	if (ptrace(PTRACE_SETOPTIONS, pid, 0, opts) < 0)
106		die("ptrace SETOPTIONS");
107}
108
109static void
110readmem(pid_t pid, unsigned long addr, void *buf, size_t n)
111{
112	size_t i;
113	unsigned char *p = buf;
114
115	for (i = 0; i < n; i += sizeof(long)) {
116		long v = ptrace(PTRACE_PEEKDATA, pid, addr + i, 0);
117		if (v == -1 && errno)
118			byte_zero(p + i, n - i);
119		else
120			byte_copy(p + i, n - i < sizeof(v) ? n - i : sizeof(v), (char *)&v);
121	}
122}
123
124static char *
125readstr(pid_t pid, unsigned long addr)
126{
127	char *s;
128	size_t cap, len;
129
130	if (!addr)
131		return unknown_string();
132	cap = 64;
133	len = 0;
134	s = alloc(cap);
135	if (!s)
136		die("alloc");
137	for (;;) {
138		long v = ptrace(PTRACE_PEEKDATA, pid, addr + len, 0);
139		size_t i;
140		char *b = (char *)&v;
141
142		if (v == -1 && errno) {
143			alloc_free(s);
144			return unknown_string();
145		}
146		for (i = 0; i < sizeof(v); i++) {
147			if (len + 1 >= cap) {
148				size_t oldcap = cap;
149				cap *= 2;
150				if (!alloc_re(&s, oldcap, cap))
151					s = NULL;
152				if (!s)
153					die("alloc_re");
154			}
155			s[len++] = b[i];
156			if (!b[i])
157				return s;
158		}
159	}
160}
161
162static void
163printnet(pid_t pid, unsigned long addr, unsigned long len)
164{
165	struct sockaddr_storage ss;
166	char host[INET6_ADDRSTRLEN + 32];
167
168	if (!addr || len < sizeof(sa_family_t))
169		return;
170	byte_zero(&ss, sizeof(ss));
171	readmem(pid, addr, &ss, len < sizeof(ss) ? len : sizeof(ss));
172	if (ss.ss_family == AF_UNIX) {
173		struct sockaddr_un *un = (struct sockaddr_un *)&ss;
174		out("net  ");
175		out_ulong((unsigned long)pid);
176		out(" unix:");
177		out(un->sun_path[0] ? un->sun_path : "(anon)");
178		out("\n");
179	} else if (ss.ss_family == AF_INET) {
180		struct sockaddr_in *in = (struct sockaddr_in *)&ss;
181		if (!inet_ntop(AF_INET, &in->sin_addr, host, sizeof(host)))
182			return;
183		out("net  ");
184		out_ulong((unsigned long)pid);
185		out(" ");
186		out(host);
187		out(":");
188		out_ulong((unsigned long)ntohs(in->sin_port));
189		out("\n");
190	} else if (ss.ss_family == AF_INET6) {
191		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&ss;
192		if (!inet_ntop(AF_INET6, &in6->sin6_addr, host, sizeof(host)))
193			return;
194		out("net  ");
195		out_ulong((unsigned long)pid);
196		out(" [");
197		out(host);
198		out("]:");
199		out_ulong((unsigned long)ntohs(in6->sin6_port));
200		out("\n");
201	}
202}
203
204static void
205tracecall(pid_t pid, struct user_regs_struct *r)
206{
207	char *s;
208
209	switch ((long)r->orig_rax) {
210	case SYS_execve:
211		s = readstr(pid, r->rdi);
212		out("exec ");
213		out_ulong((unsigned long)pid);
214		out(" ");
215		out(s);
216		out("\n");
217		alloc_free(s);
218		break;
219	case SYS_execveat:
220		s = readstr(pid, r->rsi);
221		out("exec ");
222		out_ulong((unsigned long)pid);
223		out(" ");
224		out(s);
225		out("\n");
226		alloc_free(s);
227		break;
228	case SYS_open:
229		s = readstr(pid, r->rdi);
230		out("file ");
231		out_ulong((unsigned long)pid);
232		out(" ");
233		out(s);
234		out("\n");
235		alloc_free(s);
236		break;
237	case SYS_openat:
238		s = readstr(pid, r->rsi);
239		out("file ");
240		out_ulong((unsigned long)pid);
241		out(" ");
242		out(s);
243		out("\n");
244		alloc_free(s);
245		break;
246	case SYS_openat2:
247		s = readstr(pid, r->rsi);
248		out("file ");
249		out_ulong((unsigned long)pid);
250		out(" ");
251		out(s);
252		out("\n");
253		alloc_free(s);
254		break;
255	case SYS_connect:
256		printnet(pid, r->rsi, r->rdx);
257		break;
258	}
259}
260
261int
262main(int argc, char *argv[])
263{
264	int status;
265	pid_t pid;
266
267	if (argc < 2) {
268		buffer_puts(buffer_2, "usage: ");
269		buffer_puts(buffer_2, argv[0]);
270		buffer_puts(buffer_2, " cmd [args...]\n");
271		buffer_flush(buffer_2);
272		return 2;
273	}
274	pid = fork();
275	if (pid < 0)
276		die("fork");
277	if (pid == 0) {
278		if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
279			die("ptrace TRACEME");
280		raise(SIGSTOP);
281		execvp(argv[1], argv + 1);
282		die("execvp");
283	}
284	if (waitpid(pid, &status, 0) < 0)
285		die("waitpid");
286	procget(pid);
287	setopts(pid);
288	if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
289		die("ptrace SYSCALL");
290	while (nprocs > 0) {
291		unsigned int event;
292		struct proc *p;
293
294		pid = waitpid(-1, &status, __WALL);
295		if (pid < 0) {
296			if (errno == EINTR)
297				continue;
298			die("waitpid");
299		}
300		p = procget(pid);
301		if (WIFEXITED(status)) {
302			out("exit ");
303			out_ulong((unsigned long)pid);
304			out(" ");
305			out_ulong((unsigned long)WEXITSTATUS(status));
306			out("\n");
307			procdel(pid);
308			continue;
309		}
310		if (WIFSIGNALED(status)) {
311			out("exit ");
312			out_ulong((unsigned long)pid);
313			out(" sig");
314			out_ulong((unsigned long)WTERMSIG(status));
315			out("\n");
316			procdel(pid);
317			continue;
318		}
319		if (!WIFSTOPPED(status))
320			continue;
321		if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
322			struct user_regs_struct r;
323
324			if (!p->entering && ptrace(PTRACE_GETREGS, pid, 0, &r) == 0)
325				tracecall(pid, &r);
326			p->entering = !p->entering;
327			ptrace(PTRACE_SYSCALL, pid, 0, 0);
328			continue;
329		}
330		event = (unsigned int)status >> 16;
331		if (WSTOPSIG(status) == SIGTRAP && event) {
332			unsigned long msg = 0;
333
334			if ((event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK ||
335			     event == PTRACE_EVENT_CLONE) &&
336			    ptrace(PTRACE_GETEVENTMSG, pid, 0, &msg) == 0) {
337				out("fork ");
338				out_ulong((unsigned long)pid);
339				out(" -> ");
340				out_ulong(msg);
341				out("\n");
342				procget((pid_t)msg);
343			}
344			ptrace(PTRACE_SYSCALL, pid, 0, 0);
345			continue;
346		}
347		setopts(pid);
348		ptrace(PTRACE_SYSCALL, pid, 0, WSTOPSIG(status) == SIGSTOP ? 0 : WSTOPSIG(status));
349	}
350	buffer_flush(buffer_1);
351	alloc_free((char *)procs);
352	return 0;
353}