Annotation of hatari/src/debug/debugcpu.c, revision 1.1

1.1     ! root        1: /*
        !             2:   Hatari - debugcpu.c
        !             3: 
        !             4:   This file is distributed under the GNU Public License, version 2 or at
        !             5:   your option any later version. Read the file gpl.txt for details.
        !             6: 
        !             7:   debugcpu.c - function needed for the CPU debugging tasks like memory
        !             8:   and register dumps.
        !             9: */
        !            10: const char DebugCpu_fileid[] = "Hatari debugcpu.c : " __DATE__ " " __TIME__;
        !            11: 
        !            12: #include <stdio.h>
        !            13: 
        !            14: #include "config.h"
        !            15: 
        !            16: #include "main.h"
        !            17: #include "breakcond.h"
        !            18: #include "configuration.h"
        !            19: #include "debugui.h"
        !            20: #include "debug_priv.h"
        !            21: #include "debugcpu.h"
        !            22: #include "evaluate.h"
        !            23: #include "hatari-glue.h"
        !            24: #include "log.h"
        !            25: #include "m68000.h"
        !            26: #include "memorySnapShot.h"
        !            27: #include "stMemory.h"
        !            28: #include "str.h"
        !            29: #include "symbols.h"
        !            30: 
        !            31: #define MEMDUMP_COLS   16      /* memdump, number of bytes per row */
        !            32: #define NON_PRINT_CHAR '.'     /* character to display for non-printables */
        !            33: 
        !            34: static Uint32 disasm_addr;     /* disasm address */
        !            35: static Uint32 memdump_addr;    /* memdump address */
        !            36: 
        !            37: static int nCpuActiveCBs = 0;  /* Amount of active conditional breakpoints */
        !            38: static int nCpuSteps = 0;      /* Amount of steps for CPU single-stepping */
        !            39: 
        !            40: 
        !            41: /**
        !            42:  * Load a binary file to a memory address.
        !            43:  */
        !            44: static int DebugCpu_LoadBin(int nArgc, char *psArgs[])
        !            45: {
        !            46:        FILE *fp;
        !            47:        unsigned char c;
        !            48:        Uint32 address;
        !            49:        int i=0;
        !            50: 
        !            51:        if (nArgc < 3)
        !            52:        {
        !            53:                DebugUI_PrintCmdHelp(psArgs[0]);
        !            54:                return DEBUGGER_CMDDONE;
        !            55:        }
        !            56: 
        !            57:        if (!Eval_Number(psArgs[2], &address))
        !            58:        {
        !            59:                fprintf(stderr, "Invalid address!\n");
        !            60:                return DEBUGGER_CMDDONE;
        !            61:        }
        !            62:        address &= 0x00FFFFFF;
        !            63: 
        !            64:        if ((fp = fopen(psArgs[1], "rb")) == NULL)
        !            65:        {
        !            66:                fprintf(stderr, "Cannot open file '%s'!\n", psArgs[1]);
        !            67:                return DEBUGGER_CMDDONE;
        !            68:        }
        !            69: 
        !            70:        c = fgetc(fp);
        !            71:        while (!feof(fp))
        !            72:        {
        !            73:                i++;
        !            74:                STMemory_WriteByte(address++, c);
        !            75:                c = fgetc(fp);
        !            76:        }
        !            77:        fprintf(stderr,"  Read 0x%x bytes.\n", i);
        !            78:        fclose(fp);
        !            79: 
        !            80:        return DEBUGGER_CMDDONE;
        !            81: }
        !            82: 
        !            83: 
        !            84: /**
        !            85:  * Dump memory from an address to a binary file.
        !            86:  */
        !            87: static int DebugCpu_SaveBin(int nArgc, char *psArgs[])
        !            88: {
        !            89:        FILE *fp;
        !            90:        unsigned char c;
        !            91:        Uint32 address;
        !            92:        Uint32 bytes, i = 0;
        !            93: 
        !            94:        if (nArgc < 4)
        !            95:        {
        !            96:                DebugUI_PrintCmdHelp(psArgs[0]);
        !            97:                return DEBUGGER_CMDDONE;
        !            98:        }
        !            99: 
        !           100:        if (!Eval_Number(psArgs[2], &address))
        !           101:        {
        !           102:                fprintf(stderr, "  Invalid address!\n");
        !           103:                return DEBUGGER_CMDDONE;
        !           104:        }
        !           105:        address &= 0x00FFFFFF;
        !           106: 
        !           107:        if (!Eval_Number(psArgs[3], &bytes))
        !           108:        {
        !           109:                fprintf(stderr, "  Invalid length!\n");
        !           110:                return DEBUGGER_CMDDONE;
        !           111:        }
        !           112: 
        !           113:        if ((fp = fopen(psArgs[1], "wb")) == NULL)
        !           114:        {
        !           115:                fprintf(stderr,"  Cannot open file '%s'!\n", psArgs[1]);
        !           116:                return DEBUGGER_CMDDONE;
        !           117:        }
        !           118: 
        !           119:        while (i < bytes)
        !           120:        {
        !           121:                c = STMemory_ReadByte(address++);
        !           122:                fputc(c, fp);
        !           123:                i++;
        !           124:        }
        !           125:        fclose(fp);
        !           126:        fprintf(stderr, "  Wrote 0x%x bytes.\n", bytes);
        !           127: 
        !           128:        return DEBUGGER_CMDDONE;
        !           129: }
        !           130: 
        !           131: 
        !           132: /**
        !           133:  * Check whether given address matches any CPU symbol, if yes,
        !           134:  * show the symbol information.
        !           135:  */
        !           136: static void DebugCpu_ShowMatchedSymbol(Uint32 addr)
        !           137: {
        !           138:        const char *symbol = Symbols_GetByCpuAddress(addr);
        !           139:        if (symbol)
        !           140:                fprintf(debugOutput, "%s:\n", symbol);
        !           141: }
        !           142: 
        !           143: /**
        !           144:  * Dissassemble - arg = starting address, or PC.
        !           145:  */
        !           146: int DebugCpu_DisAsm(int nArgc, char *psArgs[])
        !           147: {
        !           148:        Uint32 disasm_upper = 0;
        !           149:        int insts, max_insts;
        !           150:        uaecptr nextpc;
        !           151: 
        !           152:        if (nArgc > 1)
        !           153:        {
        !           154:                switch (Eval_Range(psArgs[1], &disasm_addr, &disasm_upper))
        !           155:                {
        !           156:                case -1:
        !           157:                        /* invalid value(s) */
        !           158:                        return DEBUGGER_CMDDONE;
        !           159:                case 0:
        !           160:                        /* single value */
        !           161:                        break;
        !           162:                case 1:
        !           163:                        /* range */
        !           164:                        disasm_upper &= 0x00FFFFFF;
        !           165:                        break;
        !           166:                }
        !           167:        }
        !           168:        else
        !           169:        {
        !           170:                /* continue */
        !           171:                if(!disasm_addr)
        !           172:                        disasm_addr = M68000_GetPC();
        !           173:        }
        !           174:        disasm_addr &= 0x00FFFFFF;
        !           175: 
        !           176:        /* limit is topmost address or instruction count */
        !           177:        if (disasm_upper)
        !           178:        {
        !           179:                max_insts = INT_MAX;
        !           180:        }
        !           181:        else
        !           182:        {
        !           183:                disasm_upper = 0x00FFFFFF;
        !           184:                max_insts = ConfigureParams.Debugger.nDisasmLines;
        !           185:        }
        !           186: 
        !           187:        /* output a range */
        !           188:        for (insts = 0; insts < max_insts && disasm_addr < disasm_upper; insts++)
        !           189:        {
        !           190:                DebugCpu_ShowMatchedSymbol(disasm_addr);
        !           191:                m68k_disasm(debugOutput, (uaecptr)disasm_addr, &nextpc, 1);
        !           192:                disasm_addr = nextpc;
        !           193:        }
        !           194:        fflush(debugOutput);
        !           195: 
        !           196:        return DEBUGGER_CMDCONT;
        !           197: }
        !           198: 
        !           199: 
        !           200: /**
        !           201:  * Readline match callback to list register names usable within debugger.
        !           202:  * STATE = 0 -> different text from previous one.
        !           203:  * Return next match or NULL if no matches.
        !           204:  */
        !           205: static char *DebugCpu_MatchRegister(const char *text, int state)
        !           206: {
        !           207:        static const char regs[][3] = {
        !           208:                "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
        !           209:                "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
        !           210:                "pc", "sr"
        !           211:        };
        !           212:        static int i, len;
        !           213:        
        !           214:        if (!state)
        !           215:        {
        !           216:                /* first match */
        !           217:                i = 0;
        !           218:                len = strlen(text);
        !           219:                if (len > 2)
        !           220:                        return NULL;
        !           221:        }
        !           222:        /* next match */
        !           223:        while (i < ARRAYSIZE(regs)) {
        !           224:                if (strncasecmp(regs[i++], text, len) == 0)
        !           225:                        return (strdup(regs[i-1]));
        !           226:        }
        !           227:        return NULL;
        !           228: }
        !           229: 
        !           230: 
        !           231: /**
        !           232:  * Set address of the named register to given argument.
        !           233:  * Return register size in bits or zero for uknown register name.
        !           234:  * Handles D0-7 data and A0-7 address registers, but not PC & SR
        !           235:  * registers as they need to be accessed using UAE accessors.
        !           236:  */
        !           237: int DebugCpu_GetRegisterAddress(const char *reg, Uint32 **addr)
        !           238: {
        !           239:        char r0, r1;
        !           240:        if (!reg[0] || !reg[1] || reg[2])
        !           241:                return 0;
        !           242:        
        !           243:        r0 = toupper(reg[0]);
        !           244:        r1 = toupper(reg[1]);
        !           245: 
        !           246:        if (r0 == 'D')  /* Data regs? */
        !           247:        {
        !           248:                if (r1 >= '0' && r1 <= '7')
        !           249:                {
        !           250:                        *addr = &(Regs[REG_D0 + r1 - '0']);
        !           251:                        return 32;
        !           252:                }
        !           253:                fprintf(stderr,"\tBad data register, valid values are 0-7\n");
        !           254:                return 0;
        !           255:        }
        !           256:        if(r0 == 'A')  /* Address regs? */
        !           257:        {
        !           258:                if (r1 >= '0' && r1 <= '7')
        !           259:                {
        !           260:                        *addr = &(Regs[REG_A0 + r1 - '0']);
        !           261:                        return 32;
        !           262:                }
        !           263:                fprintf(stderr,"\tBad address register, valid values are 0-7\n");
        !           264:                return 0;
        !           265:        }
        !           266:        return 0;
        !           267: }
        !           268: 
        !           269: 
        !           270: /**
        !           271:  * Dump or set CPU registers
        !           272:  */
        !           273: int DebugCpu_Register(int nArgc, char *psArgs[])
        !           274: {
        !           275:        char reg[3], *assign;
        !           276:        Uint32 value;
        !           277:        char *arg;
        !           278: 
        !           279:        /* If no parameter has been given, simply dump all registers */
        !           280:        if (nArgc == 1)
        !           281:        {
        !           282:                uaecptr nextpc;
        !           283:                /* use the UAE function instead */
        !           284:                m68k_dumpstate(debugOutput, &nextpc);
        !           285:                fflush(debugOutput);
        !           286:                return DEBUGGER_CMDDONE;
        !           287:        }
        !           288: 
        !           289:        arg = psArgs[1];
        !           290: 
        !           291:        assign = strchr(arg, '=');
        !           292:        if (!assign)
        !           293:        {
        !           294:                goto error_msg;
        !           295:        }
        !           296: 
        !           297:        *assign++ = '\0';
        !           298:        if (!Eval_Number(Str_Trim(assign), &value))
        !           299:        {
        !           300:                goto error_msg;
        !           301:        }
        !           302: 
        !           303:        arg = Str_Trim(arg);
        !           304:        if (strlen(arg) != 2)
        !           305:        {
        !           306:                goto error_msg;
        !           307:        }
        !           308:        reg[0] = toupper(arg[0]);
        !           309:        reg[1] = toupper(arg[1]);
        !           310:        reg[2] = '\0';
        !           311:        
        !           312:        /* set SR and update conditional flags for the UAE CPU core. */
        !           313:        if (reg[0] == 'S' && reg[1] == 'R')
        !           314:        {
        !           315:                M68000_SetSR(value);
        !           316:        }
        !           317:        else if (reg[0] == 'P' && reg[1] == 'C')   /* set PC? */
        !           318:        {
        !           319:                M68000_SetPC(value);
        !           320:        }
        !           321:        else
        !           322:        {
        !           323:                Uint32 *regaddr;
        !           324:                /* check&set data and address registers */
        !           325:                if (DebugCpu_GetRegisterAddress(reg, &regaddr))
        !           326:                {
        !           327:                        *regaddr = value;
        !           328:                }
        !           329:                else
        !           330:                {
        !           331:                        goto error_msg;
        !           332:                }
        !           333:        }
        !           334:        return DEBUGGER_CMDDONE;
        !           335: 
        !           336: error_msg:
        !           337:        fprintf(stderr,"\tError, usage: r or r xx=yyyy\n\tWhere: xx=A0-A7, D0-D7, PC or SR.\n");
        !           338:        return DEBUGGER_CMDDONE;
        !           339: }
        !           340: 
        !           341: 
        !           342: /**
        !           343:  * CPU wrapper for BreakAddr_Command/BreakPointCount.
        !           344:  */
        !           345: static int DebugCpu_BreakAddr(int nArgc, char *psArgs[])
        !           346: {
        !           347:        BreakAddr_Command(psArgs[1], false);
        !           348:        return DEBUGGER_CMDDONE;
        !           349: }
        !           350: 
        !           351: /**
        !           352:  * CPU wrapper for BreakCond_Command/BreakPointCount.
        !           353:  */
        !           354: static int DebugCpu_BreakCond(int nArgc, char *psArgs[])
        !           355: {
        !           356:        BreakCond_Command(psArgs[1], false);
        !           357:        return DEBUGGER_CMDDONE;
        !           358: }
        !           359: 
        !           360: 
        !           361: /**
        !           362:  * Do a memory dump, args = starting address.
        !           363:  */
        !           364: int DebugCpu_MemDump(int nArgc, char *psArgs[])
        !           365: {
        !           366:        int i;
        !           367:        char c;
        !           368:        Uint32 memdump_upper = 0;
        !           369: 
        !           370:        if (nArgc > 1)
        !           371:        {
        !           372:                switch (Eval_Range(psArgs[1], &memdump_addr, &memdump_upper))
        !           373:                {
        !           374:                case -1:
        !           375:                        /* invalid value(s) */
        !           376:                        return DEBUGGER_CMDDONE;
        !           377:                case 0:
        !           378:                        /* single value */
        !           379:                        break;
        !           380:                case 1:
        !           381:                        /* range */
        !           382:                        break;
        !           383:                }
        !           384:        } /* continue */
        !           385:        memdump_addr &= 0x00FFFFFF;
        !           386: 
        !           387:        if (!memdump_upper)
        !           388:        {
        !           389:                memdump_upper = memdump_addr + MEMDUMP_COLS * ConfigureParams.Debugger.nMemdumpLines;
        !           390:        }
        !           391:        memdump_upper &= 0x00FFFFFF;
        !           392: 
        !           393:        while (memdump_addr < memdump_upper)
        !           394:        {
        !           395:                fprintf(debugOutput, "%6.6X: ", memdump_addr); /* print address */
        !           396:                for (i = 0; i < MEMDUMP_COLS; i++)               /* print hex data */
        !           397:                        fprintf(debugOutput, "%2.2x ", STMemory_ReadByte(memdump_addr++));
        !           398:                fprintf(debugOutput, "  ");                     /* print ASCII data */
        !           399:                for (i = 0; i < MEMDUMP_COLS; i++)
        !           400:                {
        !           401:                        c = STMemory_ReadByte(memdump_addr-MEMDUMP_COLS+i);
        !           402:                        if(!isprint((unsigned)c))
        !           403:                                c = NON_PRINT_CHAR;             /* non-printable as dots */
        !           404:                        fprintf(debugOutput,"%c", c);
        !           405:                }
        !           406:                fprintf(debugOutput, "\n");            /* newline */
        !           407:        } /* while */
        !           408:        fflush(debugOutput);
        !           409: 
        !           410:        return DEBUGGER_CMDCONT;
        !           411: }
        !           412: 
        !           413: 
        !           414: /**
        !           415:  * Command: Write to memory, arg = starting address, followed by bytes.
        !           416:  */
        !           417: static int DebugCpu_MemWrite(int nArgc, char *psArgs[])
        !           418: {
        !           419:        int i, numBytes;
        !           420:        Uint32 write_addr, d;
        !           421:        unsigned char bytes[256]; /* store bytes */
        !           422: 
        !           423:        if (nArgc < 3)
        !           424:        {
        !           425:                DebugUI_PrintCmdHelp(psArgs[0]);
        !           426:                return DEBUGGER_CMDDONE;
        !           427:        }
        !           428: 
        !           429:        /* Read address */
        !           430:        if (!Eval_Number(psArgs[1], &write_addr))
        !           431:        {
        !           432:                fprintf(stderr, "Bad address!\n");
        !           433:                return DEBUGGER_CMDDONE;
        !           434:        }
        !           435: 
        !           436:        write_addr &= 0x00FFFFFF;
        !           437:        numBytes = 0;
        !           438: 
        !           439:        /* get bytes data */
        !           440:        for (i = 2; i < nArgc; i++)
        !           441:        {
        !           442:                if (!Eval_Number(psArgs[i], &d) || d > 255)
        !           443:                {
        !           444:                        fprintf(stderr, "Bad byte argument: '%s'!\n", psArgs[i]);
        !           445:                        return DEBUGGER_CMDDONE;
        !           446:                }
        !           447: 
        !           448:                bytes[numBytes] = d & 0x0FF;
        !           449:                numBytes++;
        !           450:        }
        !           451: 
        !           452:        /* write the data */
        !           453:        for (i = 0; i < numBytes; i++)
        !           454:                STMemory_WriteByte(write_addr + i, bytes[i]);
        !           455: 
        !           456:        return DEBUGGER_CMDDONE;
        !           457: }
        !           458: 
        !           459: 
        !           460: /**
        !           461:  * Command: Continue CPU emulation / single-stepping
        !           462:  */
        !           463: static int DebugCpu_Continue(int nArgc, char *psArgv[])
        !           464: {
        !           465:        int steps = 0;
        !           466:        
        !           467:        if (nArgc > 1)
        !           468:        {
        !           469:                steps = atoi(psArgv[1]);
        !           470:        }
        !           471:        if (steps <= 0)
        !           472:        {
        !           473:                nCpuSteps = 0;
        !           474:                fprintf(stderr,"Returning to emulation...\n");
        !           475:                return DEBUGGER_END;
        !           476:        }
        !           477:        nCpuSteps = steps;
        !           478:        fprintf(stderr,"Returning to emulation for %i CPU instructions...\n", steps);
        !           479:        return DEBUGGER_END;
        !           480: }
        !           481: 
        !           482: 
        !           483: /**
        !           484:  * This function is called after each CPU instruction when debugging is enabled.
        !           485:  */
        !           486: void DebugCpu_Check(void)
        !           487: {
        !           488:        if (LOG_TRACE_LEVEL(TRACE_CPU_DISASM))
        !           489:        {
        !           490:                DebugCpu_ShowMatchedSymbol(M68000_GetPC());
        !           491:        }
        !           492:        if (nCpuActiveCBs)
        !           493:        {
        !           494:                if (BreakCond_MatchCpu())
        !           495:                        DebugUI();
        !           496:        }
        !           497:        if (nCpuSteps)
        !           498:        {
        !           499:                nCpuSteps -= 1;
        !           500:                if (nCpuSteps == 0)
        !           501:                        DebugUI();
        !           502:        }
        !           503: }
        !           504: 
        !           505: /**
        !           506:  * Should be called before returning back emulation to tell the CPU core
        !           507:  * to call us after each instruction if "real-time" debugging like
        !           508:  * breakpoints has been set.
        !           509:  */
        !           510: void DebugCpu_SetDebugging(void)
        !           511: {
        !           512:        nCpuActiveCBs = BreakCond_BreakPointCount(false);
        !           513:        if (nCpuActiveCBs || nCpuSteps)
        !           514:                M68000_SetSpecial(SPCFLAG_DEBUGGER);
        !           515:        else
        !           516:                M68000_UnsetSpecial(SPCFLAG_DEBUGGER);
        !           517: }
        !           518: 
        !           519: 
        !           520: static const dbgcommand_t cpucommands[] =
        !           521: {
        !           522:        { NULL, NULL, "CPU commands", NULL, NULL, NULL, false },
        !           523:        /* NULL as match function will complete file names */
        !           524:        { DebugCpu_BreakAddr, Symbols_MatchCpuCodeAddress,
        !           525:          "address", "a",
        !           526:          "set CPU PC address breakpoints",
        !           527:          BreakAddr_Description,
        !           528:          true  },
        !           529:        { DebugCpu_BreakCond, BreakCond_MatchCpuVariable,
        !           530:          "breakpoint", "b",
        !           531:          "set/remove/list conditional CPU breakpoints",
        !           532:          BreakCond_Description,
        !           533:          true },
        !           534:        { DebugCpu_DisAsm, Symbols_MatchCpuCodeAddress,
        !           535:          "disasm", "d",
        !           536:          "disassemble from PC, or given address",
        !           537:          "[<start address>[-<end address>]]\n"
        !           538:          "\tIf no address is given, this command disassembles from the last\n"
        !           539:          "\tposition or from current PC if no last position is available.",
        !           540:          false },
        !           541:        { DebugCpu_Register, DebugCpu_MatchRegister,
        !           542:          "cpureg", "r",
        !           543:          "dump register values or set register to value",
        !           544:          "[REG=value]\n"
        !           545:          "\tSet CPU register to value or dumps all register if no parameter\n"
        !           546:          "\thas been specified.",
        !           547:          true },
        !           548:        { DebugCpu_MemDump, Symbols_MatchCpuDataAddress,
        !           549:          "memdump", "m",
        !           550:          "dump memory",
        !           551:          "[<start address>[-<end address>]]\n"
        !           552:          "\tdump memory at address or continue dump from previous address.",
        !           553:          false },
        !           554:        { DebugCpu_MemWrite, Symbols_MatchCpuAddress,
        !           555:          "memwrite", "w",
        !           556:          "write bytes to memory",
        !           557:          "address byte1 [byte2 ...]\n"
        !           558:          "\tWrite bytes to a memory address, bytes are space separated\n"
        !           559:          "\thexadecimals.",
        !           560:          false },
        !           561:        { DebugCpu_LoadBin, NULL,
        !           562:          "loadbin", "l",
        !           563:          "load a file into memory",
        !           564:          "filename address\n"
        !           565:          "\tLoad the file <filename> into memory starting at <address>.",
        !           566:          false },
        !           567:        { DebugCpu_SaveBin, NULL,
        !           568:          "savebin", "s",
        !           569:          "save memory to a file",
        !           570:          "filename address length\n"
        !           571:          "\tSave the memory block at <address> with given <length> to\n"
        !           572:          "\tthe file <filename>.",
        !           573:          false },
        !           574:        { Symbols_Command, NULL,
        !           575:          "symbols", "",
        !           576:          "load CPU symbols & their addresses",
        !           577:          Symbols_Description,
        !           578:          false },
        !           579:        { DebugCpu_Continue, NULL,
        !           580:          "cont", "c",
        !           581:          "continue emulation / CPU single-stepping",
        !           582:          "[steps]\n"
        !           583:          "\tLeave debugger and continue emulation for <steps> CPU instructions\n"
        !           584:          "\tor forever if no steps have been specified.",
        !           585:          false }
        !           586: };
        !           587: 
        !           588: 
        !           589: /**
        !           590:  * Should be called when debugger is first entered to initialize
        !           591:  * CPU debugging variables.
        !           592:  * 
        !           593:  * if you want disassembly or memdumping to start/continue from
        !           594:  * specific address, you can set them here.  If disassembly
        !           595:  * address is zero, disassembling starts from PC.
        !           596:  * 
        !           597:  * returns number of CPU commands and pointer to array of them.
        !           598:  */
        !           599: int DebugCpu_Init(const dbgcommand_t **table)
        !           600: {
        !           601:        memdump_addr = 0;
        !           602:        disasm_addr = 0;
        !           603:        
        !           604:        *table = cpucommands;
        !           605:        return ARRAYSIZE(cpucommands);
        !           606: }
        !           607: 
        !           608: /**
        !           609:  * Should be called when debugger is re-entered to reset
        !           610:  * relevant CPU debugging variables.
        !           611:  */
        !           612: void DebugCpu_InitSession(void)
        !           613: {
        !           614:        disasm_addr = M68000_GetPC();
        !           615: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.