xref: /plan9/sys/src/cmd/ratrace.c (revision aaf0c99d0234073f3ddc814e3b2ce1542f14fa95)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <thread.h>
5 
6 enum {
7 	Stacksize	= 8*1024,
8 	Bufsize		= 8*1024,
9 };
10 
11 Channel *out;
12 Channel *quit;
13 Channel *forkc;
14 int nread = 0;
15 
16 typedef struct Str Str;
17 struct Str {
18 	char	*buf;
19 	int	len;
20 };
21 
22 void
die(char * s)23 die(char *s)
24 {
25 	fprint(2, "%s\n", s);
26 	exits(s);
27 }
28 
29 void
cwrite(int fd,char * path,char * cmd,int len)30 cwrite(int fd, char *path, char *cmd, int len)
31 {
32 	werrstr("");
33 	if (write(fd, cmd, len) < len) {
34 		fprint(2, "cwrite: %s: failed writing %d bytes: %r\n",
35 			path, len);
36 		sendp(quit, nil);
37 		threadexits(nil);
38 	}
39 }
40 
41 Str *
newstr(void)42 newstr(void)
43 {
44 	Str *s;
45 
46 	s = mallocz(sizeof(Str) + Bufsize, 1);
47 	if (s == nil)
48 		sysfatal("malloc");
49 	s->buf = (char *)&s[1];
50 	return s;
51 }
52 
53 void
reader(void * v)54 reader(void *v)
55 {
56 	int cfd, tfd, forking = 0, exiting, pid, newpid;
57 	char *ctl, *truss;
58 	Str *s;
59 	static char start[] = "start";
60 	static char waitstop[] = "waitstop";
61 
62 	pid = (int)(uintptr)v;
63 	ctl = smprint("/proc/%d/ctl", pid);
64 	if ((cfd = open(ctl, OWRITE)) < 0)
65 		die(smprint("%s: %r", ctl));
66 	truss = smprint("/proc/%d/syscall", pid);
67 	if ((tfd = open(truss, OREAD)) < 0)
68 		die(smprint("%s: %r", truss));
69 
70 	/* child was stopped by hang msg earlier */
71 	cwrite(cfd, ctl, waitstop, sizeof waitstop - 1);
72 
73 	cwrite(cfd, ctl, "startsyscall", 12);
74 	s = newstr();
75 	exiting = 0;
76 	while((s->len = pread(tfd, s->buf, Bufsize - 1, 0)) >= 0){
77 		if (forking && s->buf[1] == '=' && s->buf[3] != '-') {
78 			forking = 0;
79 			newpid = strtol(&s->buf[3], 0, 0);
80 			sendp(forkc, (void*)newpid);
81 			procrfork(reader, (void*)newpid, Stacksize, 0);
82 		}
83 
84 		/*
85 		 * There are three tests here and they (I hope) guarantee
86 		 * no false positives.
87 		 */
88 		if (strstr(s->buf, " Rfork") != nil) {
89 			char *a[8];
90 			char *rf;
91 
92 			rf = strdup(s->buf);
93          		if (tokenize(rf, a, 8) == 5 &&
94 			    strtoul(a[4], 0, 16) & RFPROC)
95 				forking = 1;
96 			free(rf);
97 		} else if (strstr(s->buf, " Exits") != nil)
98 			exiting = 1;
99 
100 		sendp(out, s);	/* print line from /proc/$child/syscall */
101 		if (exiting) {
102 			s = newstr();
103 			strcpy(s->buf, "\n");
104 			sendp(out, s);
105 			break;
106 		}
107 
108 		/* flush syscall trace buffer */
109 		cwrite(cfd, ctl, "startsyscall", 12);
110 		s = newstr();
111 	}
112 
113 	sendp(quit, nil);
114 	threadexitsall(nil);
115 }
116 
117 void
writer(void *)118 writer(void *)
119 {
120 	int newpid;
121 	Alt a[4];
122 	Str *s;
123 
124 	a[0].op = CHANRCV;
125 	a[0].c = quit;
126 	a[0].v = nil;
127 	a[1].op = CHANRCV;
128 	a[1].c = out;
129 	a[1].v = &s;
130 	a[2].op = CHANRCV;
131 	a[2].c = forkc;
132 	a[2].v = &newpid;
133 	a[3].op = CHANEND;
134 
135 	for(;;)
136 		switch(alt(a)){
137 		case 0:			/* quit */
138 			nread--;
139 			if(nread <= 0)
140 				goto done;
141 			break;
142 		case 1:			/* out */
143 			/* it's a nice null terminated thing */
144 			fprint(2, "%s", s->buf);
145 			free(s);
146 			break;
147 		case 2:			/* forkc */
148 			// procrfork(reader, (void*)newpid, Stacksize, 0);
149 			nread++;
150 			break;
151 		}
152 done:
153 	exits(nil);
154 }
155 
156 void
usage(void)157 usage(void)
158 {
159 	fprint(2, "Usage: ratrace [-c cmd [arg...]] | [pid]\n");
160 	exits("usage");
161 }
162 
163 void
hang(void)164 hang(void)
165 {
166 	int me;
167 	char *myctl;
168 	static char hang[] = "hang";
169 
170 	myctl = smprint("/proc/%d/ctl", getpid());
171 	me = open(myctl, OWRITE);
172 	if (me < 0)
173 		sysfatal("can't open %s: %r", myctl);
174 	cwrite(me, myctl, hang, sizeof hang - 1);
175 	close(me);
176 	free(myctl);
177 }
178 
179 void
threadmain(int argc,char ** argv)180 threadmain(int argc, char **argv)
181 {
182 	int pid;
183 	char *cmd = nil;
184 	char **args = nil;
185 
186 	/*
187 	 * don't bother with fancy arg processing, because it picks up options
188 	 * for the command you are starting.  Just check for -c as argv[1]
189 	 * and then take it from there.
190 	 */
191 	if (argc < 2)
192 		usage();
193 	while (argv[1][0] == '-') {
194 		switch(argv[1][1]) {
195 		case 'c':
196 			if (argc < 3)
197 				usage();
198 			cmd = strdup(argv[2]);
199 			args = &argv[2];
200 			break;
201 		default:
202 			usage();
203 		}
204 		++argv;
205 		--argc;
206 	}
207 
208 	/* run a command? */
209 	if(cmd) {
210 		pid = fork();
211 		if (pid < 0)
212 			sysfatal("fork failed: %r");
213 		if(pid == 0) {
214 			hang();
215 			exec(cmd, args);
216 			if(cmd[0] != '/')
217 				exec(smprint("/bin/%s", cmd), args);
218 			sysfatal("exec %s failed: %r", cmd);
219 		}
220 	} else {
221 		if(argc != 2)
222 			usage();
223 		pid = atoi(argv[1]);
224 	}
225 
226 	out   = chancreate(sizeof(char*), 0);
227 	quit  = chancreate(sizeof(char*), 0);
228 	forkc = chancreate(sizeof(ulong *), 0);
229 	nread++;
230 	procrfork(writer, nil, Stacksize, 0);
231 	reader((void*)pid);
232 }
233