|
|
1.1 root 1: Writing Debuggers for MiNT
2:
3:
4:
5: MiNT provides a special pseudo-drive, U:\PROC, in which processes appear
6:
7: as files. This drive may be used by debuggers much as the Unix /proc
8:
9: file system is used, although the specific details of implementation will
10:
11: be different.
12:
13:
14:
15: The entry for a process in U:\PROC has a name like SOMEPROG.nnn, where
16:
17: "nnn" is a 3 digit number which is the (decimal) process ID of the process,
18:
19: and "SOMEPROG" is the name of the process. When opening a process, only
20:
21: the process ID matters; a process does *not* need to know another process'
22:
23: name in order to open it, only its process id. So, for example,
24:
25: fd = Fopen("U:\\PROC\\FOO.032",2);
26:
27: fd = Fopen("U:\\PROC\\.032",2);
28:
29: and
30:
31: fd = Fopen("U:\\PROC\\TCSH.032",2);
32:
33: will all open the process with process id #32 for reading and writing,
34:
35: regardless of the actual name of that process.
36:
37:
38:
39: Also, note that a process id of -1 refers to the current process, and a
40:
41: process id of -2 refers to the parent of the current process; thus,
42:
43: fd = Fopen("U:\\PROC\\.-1",2);
44:
45: may always be used to open oneself.
46:
47:
48:
49: Before a process may be debugged (or "traced") it must first be marked
50:
51: as a traced process, and a specific process (often its parent) must
52:
53: be marked as the "tracer", the program which will be notified when the
54:
55: traced process receives signals.
56:
57:
58:
59: (1) If the process to be traced is started with Pexec, and the high bit
60:
61: of the Pexec mode is set, then the child process begins
62:
63: as a "traced" process automatically, with its parent as tracer.
64:
65:
66:
67: Example:
68:
69: childpid = Pexec(0x8000|100, "foo.prg", "", 0L);
70:
71:
72:
73: (2) The traced process may indicate that it wishes to be traced by opening
74:
75: its own entry in U:\PROC and performing an Fcntl(...PTRACESFLAGS) call
76:
77: which enables tracing. In this case, the child's parent will become
78:
79: the tracer automatically. Note that if the parent is not prepared to
80:
81: perform debugging functions, there could be undesireable results, so
82:
83: the child must be *certain* that the parent wishes to debug it; for
84:
85: example, this is the case if the child and parent are both executing
86:
87: from the same program image (e.g. the child was started with the
88:
89: Pvfork() system call). This method of indicating tracing is quite
90:
91: similar to Unix's ptrace(0,...) function.
92:
93:
94:
95: Example:
96:
97: if ((pid = Pvfork()) == 0) {
98:
99: short flag = 1;
100:
101: int fd;
102:
103:
104:
105: /* here we are in the child */
106:
107: /* open self; see notes above */
108:
109: fd = Fopen("U:\\PROC\\A.-1",2);
110:
111: /* perform Fcntl */
112:
113: Fcntl(fd, &flag, PTRACESFLAGS);
114:
115: /* close self, we don't need the handle any more */
116:
117: Fclose(fd);
118:
119: /* now overlay the child with a new program image */
120:
121: Pexec(200, "foo.prg", "", 0L);
122:
123: }
124:
125:
126:
127: (3) A process may force another process to be traced by opening that process'
128:
129: entry in U:\PROC, and doing an Fcntl(...PTRACESFLAGS) on it. This is
130:
131: the only way in which to trace a process that is not the
132:
133: child of the debugging process.
134:
135:
136:
137: Example:
138:
139: short flag = 1;
140:
141:
142:
143: /* open process "pid" for tracing */
144:
145: sprintf(name, "U:\\PROC\\A.%03d", pid);
146:
147: fd = Fopen(name, 2);
148:
149: Fcntl(fd, &flag, PTRACESFLAGS);
150:
151: /* leave fd open so that we can read and write it... */
152:
153:
154:
155:
156:
157: What Happens When A Traced Process Receives a Signal
158:
159:
160:
161: If a traced process receives a signal (for example, a bus error), then
162:
163: it is placed into a STOP condition and a SIGCHLD signal is sent to
164:
165: the tracer. If the tracer performs a Pwait3 or Pwaitpid system call,
166:
167: it can then retrieve the pid of the stopped process, and the signal which
168:
169: caused that process to stop. For example:
170:
171:
172:
173: #define WIFSTOPPED(x) (((int)((x) & 0xFF) == 0x7F) && ((int)(((x) >> 8) & 0xFF) != 0))
174:
175: #define WSTOPSIG(x) ((int)(((x) >> 8) & 0xFF))
176:
177: unsigned long r;
178:
179:
180:
181: /* 0x2 is a flag that indicates that we want to see stopped processes */
182:
183: r = Pwait3(0x2, 0L);
184:
185: if (WIFSTOPPED(r)) {
186:
187: pid = r >> 16;
188:
189: signal = WSTOPSIG(r);
190:
191: }
192:
193:
194:
195: The tracer can then examine the stopped process' address space and registers
196:
197: (using the Fread, Fwrite, and Fcntl system calls; see below) and/or make
198:
199: modifications to the stopped process' state. It can then restart that
200:
201: process with either the PTRACEGO, PTRACEFLOW, or PTRACESTEP Fcntl commands.
202:
203: The PTRACEGO command Fcntl(fd, &sig, PTRACEGO), where "sig" is a 16 bit
204:
205: integer, restarts the process. If sig == 0, then all pending signals in
206:
207: the traced process are cleared; otherwise, the signal represented by
208:
209: "sig" will be delivered to the process after it starts again. Normally,
210:
211: this signal would be the same one which caused the process to stop. Note
212:
213: that this second time the signal is delivered it will not cause the
214:
215: process to stop, but rather will cause whatever action is normally associated
216:
217: with the signal (for example, SIGKILL will kill the process).
218:
219:
220:
221: PTRACEFLOW and PTRACESTEP are similar to PTRACEGO, but they also set some
222:
223: bits in the status register of the stopped process which will cause a
224:
225: SIGTRAP (trace trap) signal to be raised either on the next instruction
226:
227: to be executed (PTRACESTEP) or the next branch or jump instruction to
228:
229: be executed (PTRACEFLOW; this only works on a 68030 or 68040 processor).
230:
231: Note that if the traced process was executing a system call at the time
232:
233: it stopped, the trace trap signal will not take effect until the process
234:
235: leaves the kernel. PTRACESTEP, then, makes it possible to single step
236:
237: through the traced program.
238:
239:
240:
241:
242:
243: Reading and Writing the Process' Address Space, and Setting Breakpoints
244:
245:
246:
247: Traditional debuggers for the ST have directly accessed the address space
248:
249: of the debugged process. This is undesireable because it assumes that
250:
251: both processes share a common address space, something which may not be
252:
253: the case if memory protection is enabled; also, in a virtual memory
254:
255: system logical addresses in the child and parent may be translated
256:
257: differently. The U:\PROC file system may be used to avoid both of these
258:
259: difficulties. If the debugger opens the process to be debugged for
260:
261: reading and writing, it may then use Fread and Fwrite system calls to
262:
263: transfer data (e.g. breakpoint instructions) to and from the debugged
264:
265: process' address space.
266:
267:
268:
269: To read 100 bytes from address 0x0123456 in the address space of
270:
271: process 12, for example, one would do:
272:
273: char buf[100];
274:
275:
276:
277: fd = Fopen("U:\\PROC\\A.012", 2);
278:
279: Fseek(0x0123456L, fd, 0);
280:
281: Fread(fd, 100L, buf);
282:
283: (Note that error checking should be performed in any real application;
284:
285: for example, the Fopen could very well fail if the process being
286:
287: opened is another user's process.)
288:
289:
290:
291:
292:
293: Reading and Writing the Process' Registers
294:
295:
296:
297: The registers of the stopped process are also available for inspection.
298:
299: The PPROCADDR and PCTXTSIZE Fcntl commands are used to find the
300:
301: address of the process context block in the traced process' address
302:
303: space; the registers of the process can then be read from the address
304:
305: space with Fread, or written to the address space with Fwrite.
306:
307:
308:
309: Example:
310:
311: long curprocaddr;
312:
313: long ctxtsize;
314:
315: struct context {
316:
317: long regs[15]; /* registers d0-d7, a0-a6 */
318:
319: long usp; /* user stack pointer */
320:
321: short sr; /* status register */
322:
323: long pc; /* program counter */
324:
325: long ssp; /* supervisor stack pointer */
326:
327: long tvec; /* GEMDOS terminate vector */
328:
329: char fstate[216]; /* internal FPU state */
330:
331: long fregs[3*8]; /* registers fp0-fp7 */
332:
333: long fctrl[3]; /* FPCR/FPSR/FPIAR */
334:
335: /* there are some more, undocumented, fields, in the
336:
337: * MiNT context structure
338:
339: */
340:
341: } c;
342:
343:
344:
345: /* get the address of the process control structure */
346:
347: Fcntl(fd, &curprocaddr, PPROCADDR);
348:
349:
350:
351: /* get the size of the process context structure.
352:
353: * there are 2 of these, located immediately before
354:
355: * the process control structure. The first one
356:
357: * is the one we're interested in (the second one
358:
359: * is used by MiNT, and should never be modified).
360:
361: */
362:
363: Fcntl(fd, &ctxtsize, PCTXTSIZE);
364:
365:
366:
367: /* make curprocaddr point to the first context struct */
368:
369: curprocaddr -= 2*ctxtsize;
370:
371:
372:
373: /* now read the context structure */
374:
375: Fseek(curprocaddr, fd, 0);
376:
377: Fread(fd, (long)sizeof(struct context), &c);
378:
379:
380:
381: /* the various fields of c are now set up properly */
382:
383: ... /* code omitted */
384:
385:
386:
387: /* now write back the context to reflect whatever changes
388:
389: * we made
390:
391: */
392:
393: Fseek(curprocaddr, fd, 0);
394:
395: Fwrite(fd, (long)sizeof(struct context), &c);
396:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.