main shrubtools / peek / peek.c
  1#define _POSIX_C_SOURCE 200809L
  2#include <arpa/inet.h>
  3#include <errno.h>
  4#include <netinet/in.h>
  5#include <signal.h>
  6#include <stdbool.h>
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <sys/ptrace.h>
 11#include <sys/socket.h>
 12#include <sys/syscall.h>
 13#include <sys/types.h>
 14#include <sys/un.h>
 15#include <sys/user.h>
 16#include <sys/wait.h>
 17#include <unistd.h>
 18
 19struct proc {
 20	pid_t pid;
 21	int entering;
 22};
 23
 24static struct proc *procs;
 25static size_t nprocs;
 26
 27static void
 28die(const char *msg)
 29{
 30	perror(msg);
 31	exit(1);
 32}
 33
 34static struct proc *
 35procget(pid_t pid)
 36{
 37	size_t i;
 38
 39	for (i = 0; i < nprocs; i++) {
 40		if (procs[i].pid == pid)
 41			return &procs[i];
 42	}
 43	procs = realloc(procs, (nprocs + 1) * sizeof(procs[0]));
 44	if (!procs)
 45		die("realloc");
 46	procs[nprocs] = (struct proc){.pid = pid};
 47	return &procs[nprocs++];
 48}
 49
 50static void
 51procdel(pid_t pid)
 52{
 53	size_t i;
 54
 55	for (i = 0; i < nprocs; i++) {
 56		if (procs[i].pid != pid)
 57			continue;
 58		procs[i] = procs[--nprocs];
 59		return;
 60	}
 61}
 62
 63static void
 64setopts(pid_t pid)
 65{
 66	long opts = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK |
 67	            PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE |
 68	            PTRACE_O_TRACEEXEC;
 69	if (ptrace(PTRACE_SETOPTIONS, pid, 0, opts) < 0)
 70		die("ptrace SETOPTIONS");
 71}
 72
 73static void
 74readmem(pid_t pid, unsigned long addr, void *buf, size_t n)
 75{
 76	size_t i;
 77	unsigned char *p = buf;
 78
 79	for (i = 0; i < n; i += sizeof(long)) {
 80		long v = ptrace(PTRACE_PEEKDATA, pid, addr + i, 0);
 81		if (v == -1 && errno)
 82			memset(p + i, 0, n - i);
 83		else
 84			memcpy(p + i, &v, n - i < sizeof(v) ? n - i : sizeof(v));
 85	}
 86}
 87
 88static char *
 89readstr(pid_t pid, unsigned long addr)
 90{
 91	char *s;
 92	size_t cap, len;
 93
 94	if (!addr)
 95		return strdup("?");
 96	cap = 64;
 97	len = 0;
 98	s = malloc(cap);
 99	if (!s)
100		die("malloc");
101	for (;;) {
102		long v = ptrace(PTRACE_PEEKDATA, pid, addr + len, 0);
103		size_t i;
104		char *b = (char *)&v;
105
106		if (v == -1 && errno) {
107			free(s);
108			return strdup("?");
109		}
110		for (i = 0; i < sizeof(v); i++) {
111			if (len + 1 >= cap) {
112				cap *= 2;
113				s = realloc(s, cap);
114				if (!s)
115					die("realloc");
116			}
117			s[len++] = b[i];
118			if (!b[i])
119				return s;
120		}
121	}
122}
123
124static void
125printnet(pid_t pid, unsigned long addr, unsigned long len)
126{
127	struct sockaddr_storage ss;
128	char host[INET6_ADDRSTRLEN + 32];
129
130	if (!addr || len < sizeof(sa_family_t))
131		return;
132	memset(&ss, 0, sizeof(ss));
133	readmem(pid, addr, &ss, len < sizeof(ss) ? len : sizeof(ss));
134	if (ss.ss_family == AF_UNIX) {
135		struct sockaddr_un *un = (struct sockaddr_un *)&ss;
136		printf("net  %d unix:%s\n", pid, un->sun_path[0] ? un->sun_path : "(anon)");
137	} else if (ss.ss_family == AF_INET) {
138		struct sockaddr_in *in = (struct sockaddr_in *)&ss;
139		if (!inet_ntop(AF_INET, &in->sin_addr, host, sizeof(host)))
140			return;
141		printf("net  %d %s:%u\n", pid, host, ntohs(in->sin_port));
142	} else if (ss.ss_family == AF_INET6) {
143		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&ss;
144		if (!inet_ntop(AF_INET6, &in6->sin6_addr, host, sizeof(host)))
145			return;
146		printf("net  %d [%s]:%u\n", pid, host, ntohs(in6->sin6_port));
147	}
148}
149
150static void
151tracecall(pid_t pid, struct user_regs_struct *r)
152{
153	char *s;
154
155	switch ((long)r->orig_rax) {
156	case SYS_execve:
157		s = readstr(pid, r->rdi);
158		printf("exec %d %s\n", pid, s);
159		free(s);
160		break;
161	case SYS_execveat:
162		s = readstr(pid, r->rsi);
163		printf("exec %d %s\n", pid, s);
164		free(s);
165		break;
166	case SYS_open:
167		s = readstr(pid, r->rdi);
168		printf("file %d %s\n", pid, s);
169		free(s);
170		break;
171	case SYS_openat:
172		s = readstr(pid, r->rsi);
173		printf("file %d %s\n", pid, s);
174		free(s);
175		break;
176	case SYS_openat2:
177		s = readstr(pid, r->rsi);
178		printf("file %d %s\n", pid, s);
179		free(s);
180		break;
181	case SYS_connect:
182		printnet(pid, r->rsi, r->rdx);
183		break;
184	}
185}
186
187int
188main(int argc, char *argv[])
189{
190	int status;
191	pid_t pid;
192
193	if (argc < 2) {
194		fprintf(stderr, "usage: %s cmd [args...]\n", argv[0]);
195		return 2;
196	}
197	pid = fork();
198	if (pid < 0)
199		die("fork");
200	if (pid == 0) {
201		if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
202			die("ptrace TRACEME");
203		raise(SIGSTOP);
204		execvp(argv[1], argv + 1);
205		die("execvp");
206	}
207	if (waitpid(pid, &status, 0) < 0)
208		die("waitpid");
209	procget(pid);
210	setopts(pid);
211	if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
212		die("ptrace SYSCALL");
213	while (nprocs > 0) {
214		unsigned int event;
215		struct proc *p;
216
217		pid = waitpid(-1, &status, __WALL);
218		if (pid < 0) {
219			if (errno == EINTR)
220				continue;
221			die("waitpid");
222		}
223		p = procget(pid);
224		if (WIFEXITED(status)) {
225			printf("exit %d %d\n", pid, WEXITSTATUS(status));
226			procdel(pid);
227			continue;
228		}
229		if (WIFSIGNALED(status)) {
230			printf("exit %d sig%d\n", pid, WTERMSIG(status));
231			procdel(pid);
232			continue;
233		}
234		if (!WIFSTOPPED(status))
235			continue;
236		if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
237			struct user_regs_struct r;
238
239			if (!p->entering && ptrace(PTRACE_GETREGS, pid, 0, &r) == 0)
240				tracecall(pid, &r);
241			p->entering = !p->entering;
242			ptrace(PTRACE_SYSCALL, pid, 0, 0);
243			continue;
244		}
245		event = (unsigned int)status >> 16;
246		if (WSTOPSIG(status) == SIGTRAP && event) {
247			unsigned long msg = 0;
248
249			if ((event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK ||
250			     event == PTRACE_EVENT_CLONE) &&
251			    ptrace(PTRACE_GETEVENTMSG, pid, 0, &msg) == 0) {
252				printf("fork %d -> %lu\n", pid, msg);
253				procget((pid_t)msg);
254			}
255			ptrace(PTRACE_SYSCALL, pid, 0, 0);
256			continue;
257		}
258		setopts(pid);
259		ptrace(PTRACE_SYSCALL, pid, 0, WSTOPSIG(status) == SIGSTOP ? 0 : WSTOPSIG(status));
260	}
261	free(procs);
262	return 0;
263}