|
|
1.1 root 1: /*
2: * i8086a.c
3: *
4: * The information contained herein is a trade secret of Mark Williams
5: * Company, and is confidential information. It is provided under a
6: * license agreement, and may be copied or disclosed only under the
7: * terms of that agreement. Any reproduction or disclosure of this
8: * material without the express written authorization of Mark Williams
9: * Company or persuant to the license agreement is unlawful.
10: *
11: * COHERENT Version 2.3.35
12: * Copyright (c) 1982, 1983, 1984.
13: * An unpublished work by Mark Williams Company, Chicago.
14: * All rights reserved.
15: */
16:
17: #include <stdio.h>
18: #include <sys/param.h>
19: #include <l.out.h>
20: #include <signal.h>
21: #include <sys/ptrace.h>
22: #include <sys/timeout.h>
23: #include <sys/uproc.h>
24: #include "trace.h"
25: #include "i8086.h"
26:
27: extern errno;
28:
29: #define MAGICTEST hdrinfo.magic == L_MAGIC
30:
31: /*
32: * Given an l.out header, set up segmentation for an l.out.
33: */
34: setaseg(ldhp)
35: register struct ldheader *ldhp;
36: {
37: register long fbase;
38: register long si;
39: register long pi;
40: register long bi;
41: register long sd;
42: register long pd;
43: register long rs;
44:
45: fbase = sizeof(*ldhp);
46: si = ldhp->l_ssize[L_SHRI];
47: pi = ldhp->l_ssize[L_PRVI];
48: bi = ldhp->l_ssize[L_BSSI];
49: sd = ldhp->l_ssize[L_SHRD];
50: pd = ldhp->l_ssize[L_PRVD];
51: switch (ldhp->l_flag & (LF_SEP|LF_SHR)) {
52: case 0:
53: endpure = NULL;
54: DSPACE = setsmap(NULL, (long)0, si+pi, fbase,
55: getf, putf, 0);
56: DSPACE = setsmap(DSPACE, si+pi+bi, sd+pd, fbase+si+pi,
57: getf, putf, 0);
58: ISPACE = DSPACE;
59: break;
60:
61: case LF_SHR:
62: endpure = NULL;
63: rs = (si+sd+0xF) & ~0xF;
64: DSPACE = setsmap(NULL, (long)0, si, fbase, getf, putf, 0);
65: DSPACE = setsmap(DSPACE, si, sd, fbase+si+pi, getf, putf, 0);
66: DSPACE = setsmap(DSPACE, rs, pi, fbase+si, getf, putf, 0);
67: DSPACE = setsmap(DSPACE, rs+pi, pd, fbase+si+pi+sd,
68: getf, putf, 0);
69: ISPACE = DSPACE;
70: break;
71:
72: case LF_SEP:
73: endpure = NULL;
74: ISPACE = setsmap(NULL, (long)0, si+pi, fbase, getf, putf, 0);
75: DSPACE = setsmap(NULL, (long)0, sd+pd, fbase+si+pi,
76: getf, putf, 0);
77: break;
78:
79: case LF_SEP|LF_SHR:
80: endpure = NULL; /* No pure data */
81: rs = (sd+0xF) & ~0xF;
82: ISPACE = setsmap(NULL, (long)0, si+pi, fbase, getf, putf, 0);
83: DSPACE = setsmap(NULL, (long)0, sd, fbase+si+pi, getf, putf, 0);
84: DSPACE = setsmap(DSPACE, rs, pd, fbase+si+pi+sd, getf, putf, 0);
85: break;
86: }
87: }
88:
89: /*
90: * Set up segmentation for kernel memory.
91: */
92: setkmem(np)
93: char *np;
94: {
95: register off_t l;
96: unsigned n;
97: struct LDSYM lds;
98:
99: cfp = openfil(np, rflag);
100: USPACE = setsmap(NULL, (off_t)0, (off_t)LI, (off_t)0,
101: getf, putf, 1);
102: add = 2;
103: if (getb(USEG, &n, sizeof(n)) == 0)
104: panic("Bad memory file");
105: l = (off_t)n << 4;
106: ISPACE = setsmap(NULL, (off_t)0, (off_t)LI, (off_t)l,
107: getf, putf, 1);
108: strncpy(lds.ls_id, "etext_", NCPLN);
109: if (nameval(&lds) == 0)
110: DSPACE = ISPACE;
111: else {
112: n = (lds.ls_addr+0xF) & ~0xF;
113: DSPACE = setsmap(NULL, (off_t)0, (off_t)LI, (off_t)l+n,
114: getf, putf, 1);
115: }
116: }
117:
118: /*
119: * Set up segmentation for a system dump.
120: */
121: setdump(np)
122: char *np;
123: {
124: setkmem(np);
125: }
126:
127: #if FPTEST
128: void
129: fpdump(fstp)
130: struct _fpstate * fstp;
131: {
132: int i;
133: int tagbits;
134: unsigned char opfirst, opsecond;
135: unsigned int fcs = fstp->cssel;
136: int tos = (fstp->sw >> 11) & 7;
137:
138: fflush(stdout);
139:
140: printf("fstp=%08x tos=%d ", fstp, tos);
141: opfirst = 0xD8 | ((fcs >> 24) & 7);
142: opsecond = fcs >> 16;
143: printf("cw=%04x sw=%04x tag=%04x\n",
144: fstp->cw&0xFFFF, fstp->sw&0xFFFF, fstp->tag&0xffff);
145: printf("op=%02x %02x fcs=%04x fip=%08x fos=%04x foo=%08x\n",
146: opfirst, opsecond,
147: fcs&0xFFFF, fstp->ipoff, fstp->datasel&0xFFFF, fstp->dataoff);
148: for (i = 0; i < 8; i++) {
149: tagbits = (fstp->tag >> (((tos + i) & 7) * 2)) & 3;
150: printf("st[%d]:(%d) at %08x ", i, tagbits,
151: fstp->_st + i);
152: switch(tagbits) {
153: case 0:
154: {
155: int j;
156: for (j = 0; j < 4; j++)
157: printf("%04x ", fstp->_st[i].significand[j]);
158: printf(":%04x ", fstp->_st[i].exponent);
159: }
160: printf("\n");
161: break;
162: case 1:
163: printf("Zero\n");
164: break;
165: case 2:
166: printf("Special: %08x %08x %08x %08x : %08x\n",
167: fstp->_st[i].significand[0],
168: fstp->_st[i].significand[1],
169: fstp->_st[i].significand[2],
170: fstp->_st[i].significand[3],
171: fstp->_st[i].exponent);
172: break;
173: case 3:
174: printf("Empty\n");
175: break;
176: }
177: }
178: fflush(stdout);
179: }
180: #endif
181:
182: /*
183: * Update register structure in memory.
184: */
185: setregs()
186: {
187: struct ureg ureg;
188:
189: regflag = 0;
190: add = 0; /* UGLY! FIX THIS! */
191: if (getb(USEG, (char *)&ureg, sizeof(ureg)) == 0) {
192: printr("Cannot read registers");
193: return 0;
194: }
195:
196: #if FPTEST
197: fpdump(&ureg.ur_fpstate);
198: #endif
199: copyreg(&ureg);
200: regflag = 1;
201: return 1;
202: }
203:
204: /*
205: * Copy registers from the User area structure to the register
206: * structure.
207: */
208: copyreg(up)
209: register struct ureg *up;
210: {
211: reg.r_ax = up->ur_ax;
212: reg.r_bx = up->ur_bx;
213: reg.r_cx = up->ur_cx;
214: reg.r_dx = up->ur_dx;
215: reg.r_sp = up->ur_sp;
216: reg.r_bp = up->ur_bp;
217: reg.r_si = up->ur_si;
218: reg.r_di = up->ur_di;
219: reg.r_cs = up->ur_cs;
220: reg.r_ds = up->ur_ds;
221: reg.r_ss = up->ur_ss;
222: reg.r_es = up->ur_es;
223: reg.r_ip = up->ur_ip;
224: reg.r_fw = up->ur_fw;
225: reg.r_fs = up->ur_fs;
226: reg.r_gs = up->ur_gs;
227: reg.valid = 1;
228: }
229:
230: /*
231: * If the given name matches a register, set up `ldp' with the address
232: * of a register in the user area.
233: */
234: regaddr(ldp)
235: struct LDSYM *ldp;
236: {
237: register char *cp;
238: register int c0;
239: register int c1;
240: register int a;
241:
242: a = 0; /* UGLY! FIX THIS */
243: cp = ldp->ls_id;
244: if ((c0=*cp++) == '\0')
245: return 0;
246: if ((c1=*cp++) == '\0')
247: return 0;
248: if (*cp != '\0')
249: return 0;
250: switch (c1) {
251: case 'h':
252: a++;
253: case 'l':
254: c1 = 'x';
255: }
256: cp = regitab;
257: do {
258: if (cp[0]==c0 && cp[1]==c1) {
259: ldp->ls_addr = a;
260: ldp->ls_type = L_REG;
261: return 1;
262: }
263: a += 2;
264: cp += 2;
265: } while (*cp != '\0');
266: return 0;
267: }
268:
269: /*
270: * Return the program counter.
271: */
272: caddr_t
273: getpc()
274: {
275: return reg.r_ip;
276: }
277:
278: /*
279: * Set the program counter.
280: */
281: setpc(ip)
282: caddr_t ip;
283: {
284: fprintf(stderr, "setpc(%x)\n", ip);
285: reg.r_ip = ip;
286: add = PTRACE_EIP;
287: putb(USEG, ®.r_ip, sizeof(reg.r_ip));
288: }
289:
290: /*
291: * Return the frame pointer.
292: */
293: getfp()
294: {
295: return reg.r_bp;
296: }
297:
298: /*
299: * Print out a constant in a reasonable format.
300: */
301: concons(sp, l)
302: register char *sp;
303: unsigned long l;
304: {
305: /* Another bug in unsigned long on the PDP11
306: if (l < 0x10)
307: */
308: if ((unsigned)l < 0x10)
309: sprintf(sp, "%d", (int)l);
310: else
311: sprintf(sp, "0x%lx", l);
312: }
313:
314: /*
315: * Display registers.
316: */
317: dispreg()
318: {
319: if (testint())
320: return;
321: if (hdrinfo.defsegatt == DSA16)
322: printf("%%ip=%04x %%fw=%04x\n", reg.r_ip, reg.r_fw);
323: else
324: printf("%%eip=%08x %%efw=%08x\n", reg.r_ip, reg.r_fw);
325:
326: if (testint())
327: return;
328: if (hdrinfo.defsegatt == DSA16)
329: printf("%%ax=%08x %%bx=%08x %%cx=%08x %%dx=%08x\n",
330: reg.r_ax, reg.r_bx, reg.r_cx, reg.r_dx);
331: else
332: printf("%%eax=%08x %%ebx=%08x %%ecx=%08x %%edx=%08x\n",
333: reg.r_ax, reg.r_bx, reg.r_cx, reg.r_dx);
334:
335: if (testint())
336: return;
337: if (hdrinfo.defsegatt == DSA16)
338: printf("%%sp=%08x %%bp=%08x %%si=%08x %%di=%08x\n",
339: reg.r_sp, reg.r_bp, reg.r_si, reg.r_di);
340: else
341: printf("%%esp=%08x %%ebp=%08x %%esi=%08x %%edi=%08x\n",
342: reg.r_sp, reg.r_bp, reg.r_si, reg.r_di);
343:
344: if (testint())
345: return;
346: printf("%%cs=%04x %%ds=%04x %%ss=%04x %%es=%04x %%fs=%04x %%gs=%04x\n",
347: reg.r_cs&0xffff, reg.r_ds&0xffff, reg.r_ss&0xffff,
348: reg.r_es&0xffff, reg.r_fs&0xffff, reg.r_gs&0xffff);
349: }
350:
351: /*
352: * Stack traceback.
353: */
354: dispsbt(l)
355: {
356: register int argc;
357: register int iflag;
358: register int sflag;
359: register int qflag;
360: unsigned amain;
361: unsigned int pc;
362: unsigned int rpc;
363: unsigned int fp;
364: unsigned int n;
365: unsigned char e[1];
366: unsigned char b[3];
367: struct LDSYM ldsym;
368:
369: amain = 1;
370: strcpy(ldsym.ls_id, MAGICTEST ? "main_" : "main");
371: if (nameval(&ldsym) != 0)
372: amain = ldsym.ls_addr;
373: add = MAGICTEST ? (unsigned short)reg.r_ip : reg.r_ip;
374: if (getb(ISEG, b, 1) == 0) {
375: printe("Illegal ip");
376: return;
377: }
378: sflag = 0;
379: fp = reg.r_bp;
380: e[0] = MAGICTEST ? 0x56 : 0xc8; /* push si : enter */
381: if (valname(1, (off_t)reg.r_ip, &ldsym) != 0) {
382: add = (long)ldsym.ls_addr;
383: getb(ISEG, (char *)e, 1);
384: }
385: if ( MAGICTEST ? (b[0]==0x56 || e[0]!=0x56) /* push si */
386: : (b[0]==0xc8 || e[0]!=0xc8) ) { /* enter */
387: sflag = 1;
388: fp = reg.r_sp - (MAGICTEST ? 6 : 4);
389: }
390: pc = reg.r_ip;
391: qflag = 0;
392: for (;;) {
393: add = MAGICTEST ? (unsigned short)fp + 6 : fp + 4;
394: if (getb(DSEG, &rpc, MAGICTEST ? 2 : 4) == 0)
395: break;
396: argc = 0;
397: add = MAGICTEST ? (unsigned short)rpc : rpc;
398: if (getb(ISEG, b, 3) != 0) {
399: if (b[0]==0x83 && b[1]==0xC4) /* add sp,$? */
400: argc = b[2]/(MAGICTEST ? 2 : 4);
401: }
402: add = MAGICTEST ? (unsigned short)rpc - 3 : rpc - 5;
403: if (getb(ISEG, b, 1) == 0)
404: break;
405: if (getb(ISEG, &n, (MAGICTEST ? 2 : 4)) == 0)
406: break;
407: printx(DAFMT, (long)fp);
408: printx(" ");
409: printx(DAFMT, (long)pc);
410: printx(" ");
411: if (b[0] == 0xE8) { /* call ? */
412: n += MAGICTEST ? (unsigned short) rpc : rpc;
413: putaddr(1, (long)n, I);
414: if (n == amain) {
415: qflag = 1;
416: argc = 3;
417: }
418: } else {
419: if (valname(1, (off_t)pc, &ldsym) == 0)
420: printx("*");
421: else {
422: printx("%.*s", NCPLN, ldsym.ls_id);
423: if (strcmp(ldsym.ls_id
424: , MAGICTEST ? "main_" : "main") == 0) {
425: qflag = 1;
426: argc = 3;
427: }
428: }
429: }
430: putx('(');
431: iflag = 0;
432: add = (MAGICTEST ? (unsigned short)fp : fp) + 8;
433: /* offset 8 coincidently works for both file formats */
434: while (argc--) {
435: if (getb(DSEG, &n, MAGICTEST ? 2 : 4) == 0)
436: break;
437: if (iflag++ != 0)
438: printx(", ");
439: putaddr(1, (long)n, -1);
440: }
441: printx(")\n");
442: if (testint())
443: return;
444: if (qflag)
445: break;
446: if (sflag) {
447: sflag = 0;
448: fp = reg.r_bp;
449: } else {
450: add = MAGICTEST ? (unsigned short)fp : fp;
451: if (getb(DSEG, &fp, (MAGICTEST ? 2 : 4)) == 0)
452: break;
453: }
454: if (--l == 0)
455: return;
456: pc = MAGICTEST ? (unsigned short)rpc : rpc;
457: }
458: }
459:
460: /*
461: * Get the return pc and frame pointer to set a return breakpoint.
462: */
463: setretf(pcp, fpp)
464: register caddr_t *pcp;
465: register caddr_t *fpp;
466: {
467: unsigned char b[1];
468:
469: add = reg.r_ip;
470: if (getb(ISEG, b, sizeof(b)) == 0)
471: return 0;
472: if (b[0] == 0x56) { /* push si */
473: *fpp = reg.r_bp;
474: add = reg.r_sp;
475: } else {
476: add = reg.r_bp;
477: if (getb(DSEG, (char *)fpp, sizeof(*fpp)) == 0)
478: return 0;
479: add = reg.r_bp + 6;
480: }
481: if (getb(DSEG, (char *)pcp, sizeof(*pcp)) == 0)
482: return 0;
483: }
484:
485: /*
486: * Initialise after getting a trap.
487: */
488: trapint()
489: {
490: caddr_t pc;
491: caddr_t fw;
492:
493: #if 0
494: /* new trap handler clears TF in PSW */
495: add = PTRACE_EFL;
496: if (getb(USEG, &fw, sizeof(int)) == 0) {
497: printr("Cannot get psw");
498: return 0;
499: }
500: fw &= ~TBIT;
501: add = PTRACE_EFL;
502: if (putb(USEG, &fw, sizeof(int)) == 0) {
503: printr("Cannot set psw");
504: return 0;
505: }
506: #endif
507: if (sysflag) {
508: add = PTRACE_EIP;
509: if (getb(USEG, &pc, sizeof(pc)) == 0) {
510: printr("Cannot get eip");
511: return 0;
512: }
513: pc -= 2;
514: add = PTRACE_EIP;
515: if (putb(USEG, &pc, sizeof(pc)) == 0) {
516: printr("Cannot set eip");
517: return 0;
518: }
519: add = (long)pc;
520: if (putb(ISEG, (char *)sin, sizeof(sin)) == 0) {
521: printr("Cannot set sin");
522: return 0;
523: }
524: bitflag = 1;
525: }
526: return 1;
527: }
528:
529: /*
530: * Set up before returning from a trap. Single stepping over a system
531: * call is handled here.
532: */
533: restret()
534: {
535: unsigned char w[1];
536:
537: sysflag = 0;
538: if (bitflag == 0)
539: return 1;
540: add = reg.r_ip;
541: if (getb(ISEG, (char *)w, sizeof(w)) == 0)
542: return 1;
543:
544: /* FIX THIS FOR 386 */
545: if (w[0] != 0xCD) /* Interrupt (system call) */
546: return 1;
547: add = reg.r_ip + 2;
548: if (getb(ISEG, (char *)sin, sizeof(sin)) == 0) {
549: printr("Cannot get breakpoint");
550: return 0;
551: }
552: add -= sizeof(sin);
553: if (putb(ISEG, (char *)bin, sizeof(bin)) == 0) {
554: printr("Cannot set breakpoint");
555: return 0;
556: }
557: bitflag = 0;
558: sysflag = 1;
559: return 1;
560: }
561:
562: /*
563: * Change sinmode to SCSET if instruction at EIP is a call.
564: */
565: setcont()
566: {
567: unsigned char w[4];
568: int xx;
569:
570: add = reg.r_ip;
571: if (getb(ISEG, &xx, sizeof(xx)) == 0) {
572: return;
573: }
574: w[0] = xx & 0xff;
575: w[1] = (xx>>8) & 0xff;
576: if (w[0] == 0xE8) { /* Direct call */
577: sinmode = SCSET;
578: }
579: if (w[0]==0xFF && (w[1]&0x38)==0x10) { /* Indirect call */
580: sinmode = SCSET;
581: }
582: }
583:
584: /*
585: * Get return address from ESP and set BSIN breakpoint there.
586: */
587: intcont()
588: {
589: unsigned return_addr;
590:
591: add = reg.r_sp;
592: if (getb(DSEG, &return_addr, sizeof(return_addr))) {
593: setibpt(BSIN, return_addr, reg.r_bp, NULL);
594: }
595: }
596:
597: /*
598: * See if the given instruction is a breakpoint.
599: */
600: testbpt(pc)
601: {
602: unsigned char w[1];
603:
604: add = pc;
605: return (getb(ISEG, w, sizeof(w))!=0 && w[0]==0xCC);
606: }
607:
608: /*
609: * Read `n' characters into the buffer `bp' starting at address `a'
610: * from the traced process, segment `f'.
611: */
612: getp(f, a, bp, n)
613: long a;
614: register char *bp;
615: register int n;
616: {
617: int d, n1;
618: char *memcpy();
619: int pcmd;
620:
621: for (errno = 0; n; n -= n1, a += n1, bp += n1) {
622: n1 =n>sizeof(long) ? sizeof(long) : n;
623: switch(f) {
624: case ISEG: pcmd = PTRACE_RD_TXT; break;
625: case DSEG: pcmd = PTRACE_RD_DAT; break;
626: case USEG: pcmd = PTRACE_RD_USR; break;
627: }
628: d = ptrace(pcmd, pid, (int *) a, 0);
629: if (errno)
630: return 0;
631: memcpy(bp, &d, n1);
632: }
633: return 1;
634: }
635:
636: /*
637: * Write `n' characters from the buffer `bp' starting at address `a' in
638: * the traced process to segment `f'.
639: */
640: putp(f, a, bp, n)
641: long a;
642: register char *bp;
643: register int n;
644: {
645: int d, n1;
646: int pcmd;
647:
648: for (errno = 0; n; n -= n1, a += n1, bp += n1) {
649: if (n < sizeof(long)) {
650: n1 = n;
651: switch(f) {
652: case ISEG: pcmd = PTRACE_RD_TXT; break;
653: case DSEG: pcmd = PTRACE_RD_DAT; break;
654: case USEG: pcmd = PTRACE_RD_USR; break;
655: }
656: d = ptrace(pcmd, pid, (int *)a, 0);
657: } else
658: n1 = sizeof(long);
659: memcpy(&d, bp, n1);
660: switch(f) {
661: case ISEG: pcmd = PTRACE_WR_TXT; break;
662: case DSEG: pcmd = PTRACE_WR_DAT; break;
663: case USEG: pcmd = PTRACE_WR_USR; break;
664: }
665: ptrace(pcmd, pid, (int *) a, d);
666: if (errno) {
667: perror("db: putp()");
668: return 0;
669: }
670: }
671: return 1;
672: }
673:
674: /******************************************************************************
675: *
676: * Note some #defines in these include files interferes with
677: * items in preceeding include files. Hence the strange
678: * program order.
679: *
680: ******************************************************************************/
681:
682: #include <coff.h>
683:
684: /*
685: * Given an l.out header, set up segmentation for an l.out.
686: */
687: setcoffseg(fp, coffhp)
688: FILE *fp;
689: FILEHDR *coffhp;
690: {
691: off_t fbase, tbase, tsize, dbase, dsize, bbase, bsize;
692: off_t f_tscnp, f_dscnp, f_bscnp;
693: SCNHDR *scnhp;
694: short i;
695:
696: if (coffhp->f_opthdr)
697: fbase = sizeof(FILEHDR) + coffhp->f_opthdr;
698: else
699: fbase = sizeof (FILEHDR);
700: if ( (scnhp=nalloc(coffhp->f_nscns*sizeof(SCNHDR))) == NULL ) {
701: printf("No memory to alloc to coff.h section headers");
702: return;
703: }
704:
705: fseek(fp, fbase, 0);
706: for (i=0; i<coffhp->f_nscns; scnhp++, i++) {
707: if (fread(scnhp, sizeof(SCNHDR), 1, fp) != 1)
708: printf("Cannot read coff.h section headers");
709:
710: switch((int)scnhp->s_flags) {
711: case STYP_TEXT:
712: tbase = scnhp->s_vaddr;
713: tsize = scnhp->s_size;
714: f_tscnp = scnhp->s_scnptr;
715: break;
716: case STYP_DATA:
717: dbase = scnhp->s_vaddr;
718: dsize = scnhp->s_size;
719: f_dscnp = scnhp->s_scnptr;
720: break;
721: case STYP_BSS:
722: bbase = scnhp->s_vaddr;
723: bsize = scnhp->s_size;
724: f_bscnp = scnhp->s_scnptr;
725: break;
726: default:
727: continue;
728: }
729: }
730:
731: #if 0 /* Assume NOT separate */
732: endpure = NULL;
733: DSPACE = setsmap(NULL, tbase, tsize, f_tscnp, getf, putf, 0);
734: /* dbase = (dbase + (off_t)0xF) & ~(off_t)0xF;
735: */ DSPACE = setsmap(DSPACE, dbase, dsize, f_dscnp, getf, putf, 0);
736: DSPACE = setsmap(DSPACE, bbase, bsize+4L, f_bscnp, getf, putf, 0);
737: ISPACE = DSPACE;
738:
739: #else /* Assume separate */
740: endpure = NULL;
741: ISPACE = setsmap(NULL, tbase, tsize, f_tscnp, getf, putf, 0);
742: /* dbase = (dbase + (off_t)0xF) & ~(off_t)0xF;
743: */ DSPACE = setsmap(NULL, dbase, dsize, f_dscnp, getf, putf, 0);
744: DSPACE = setsmap(DSPACE, bbase, bsize+4L, f_bscnp, getf, putf, 0);
745: #endif
746: nfree(scnhp);
747: }
748:
749: /* end of i8086a.c */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.