|
|
1.1 ! root 1: /*++ ! 2: ! 3: Copyright (c) 1993 Microsoft Corporation ! 4: ! 5: Module Name: ! 6: ! 7: regs.c ! 8: ! 9: Abstract: ! 10: ! 11: This file provides disassembly ( alpha ) ! 12: ! 13: Author: ! 14: ! 15: Miche Baker-Harvey (v-michbh) 1-May-1993 (ported from ntsd) ! 16: ! 17: Environment: ! 18: ! 19: User Mode ! 20: ! 21: --*/ ! 22: ! 23: ! 24: #include <windows.h> ! 25: #include <stddef.h> ! 26: #include <string.h> ! 27: ! 28: #include <alphaops.h> ! 29: #include "disasm.h" ! 30: #include "regs.h" ! 31: #include "optable.h" ! 32: ! 33: #include "drwatson.h" ! 34: #include "proto.h" ! 35: ! 36: void BlankFill(ULONG); ! 37: void OutputHex(ULONG, ULONG, BOOLEAN); ! 38: ! 39: void OutputEffectiveAddress(PDEBUGPACKET, ULONG); ! 40: void OutputString(char *); ! 41: void OutputReg(ULONG); ! 42: void OutputFReg(ULONG); ! 43: void GetNextOffset(PDEBUGPACKET, PULONG, BOOLEAN); ! 44: ! 45: ! 46: ALPHA_INSTRUCTION disinstr; ! 47: ! 48: extern PUCHAR pszReg[]; ! 49: ! 50: BOOLEAN disasm(PDEBUGPACKET, PDWORD, char *, BOOLEAN); ! 51: ! 52: ! 53: static char *pBufStart; ! 54: static char *pBuf; ! 55: ! 56: ! 57: #define OPRNDCOL 27 // Column for first operand ! 58: #define EACOL 40 // column for effective address ! 59: #define FPTYPECOL 40 // .. for the type of FP instruction ! 60: ! 61: ! 62: BOOLEAN ! 63: disasm (PDEBUGPACKET dp, PDWORD poffset, char *bufptr, BOOLEAN fEAout) ! 64: { ! 65: static initialized = 0; ! 66: ULONG opcode; ! 67: ULONG Ea; // Effective Address ! 68: POPTBLENTRY pEntry; ! 69: LONG displacement; ! 70: ! 71: if (!initialized) { ! 72: opTableInit(); ! 73: initialized = 1; ! 74: } ! 75: pBufStart = pBuf = bufptr; // Initialize pointers to buffer that ! 76: // will receive the disassembly text ! 77: OutputHex(*poffset, 8, FALSE);// Output Hex address of instruction ! 78: *pBuf++ = ':'; ! 79: *pBuf++ = ' '; ! 80: ! 81: if (!ReadProcessMemory (dp->hProcess, ! 82: (LPVOID)*poffset, ! 83: (LPVOID)&disinstr.Long, ! 84: sizeof(DWORD), ! 85: NULL ! 86: )) { ! 87: OutputString("???????? ????"); ! 88: *pBuf = '\0'; ! 89: return(FALSE); ! 90: } ! 91: ! 92: OutputHex(disinstr.Long, 8, FALSE); // Output instruction in Hex ! 93: *pBuf++ = ' '; ! 94: ! 95: opcode = disinstr.Memory.Opcode; // Select disassembly procedure from ! 96: ! 97: pEntry = findOpCodeEntry(opcode); // Get non-func entry for this code ! 98: if (pEntry == (POPTBLENTRY)-1) { ! 99: OutputString("??????? ????"); ! 100: *pBuf = '\0'; ! 101: return (FALSE); ! 102: } ! 103: ! 104: ! 105: switch (pEntry->iType) { ! 106: case ALPHA_UNKNOWN: ! 107: OutputString(pEntry->pszAlphaName); ! 108: break; ! 109: ! 110: case ALPHA_MEMORY: ! 111: OutputString(pEntry->pszAlphaName); ! 112: BlankFill(OPRNDCOL); ! 113: OutputReg(disinstr.Memory.Ra); ! 114: *pBuf++ = ','; ! 115: OutputHex(disinstr.Memory.MemDisp, (WIDTH_MEM_DISP + 3)/4, TRUE ); ! 116: *pBuf++ = '('; ! 117: OutputReg(disinstr.Memory.Rb); ! 118: *pBuf++ = ')'; ! 119: ! 120: break; ! 121: ! 122: case ALPHA_FP_MEMORY: ! 123: OutputString(pEntry->pszAlphaName); ! 124: BlankFill(OPRNDCOL); ! 125: OutputFReg(disinstr.Memory.Ra); ! 126: *pBuf++ = ','; ! 127: OutputHex(disinstr.Memory.MemDisp, (WIDTH_MEM_DISP + 3)/4, TRUE ); ! 128: *pBuf++ = '('; ! 129: OutputReg(disinstr.Memory.Rb); ! 130: *pBuf++ = ')'; ! 131: ! 132: break; ! 133: ! 134: case ALPHA_MEMSPC: ! 135: OutputString(findFuncName(pEntry, disinstr.Memory.MemDisp & BITS_MEM_DISP)); ! 136: // ! 137: // Some memory special instructions have an operand ! 138: // ! 139: ! 140: switch (disinstr.Memory.MemDisp & BITS_MEM_DISP) { ! 141: case FETCH_FUNC: ! 142: case FETCH_M_FUNC: ! 143: // one operand, in Rb ! 144: BlankFill(OPRNDCOL); ! 145: *pBuf++ = '0'; ! 146: *pBuf++ = '('; ! 147: OutputReg(disinstr.Memory.Rb); ! 148: *pBuf++ = ')'; ! 149: break; ! 150: ! 151: case RS_FUNC: ! 152: case RC_FUNC: ! 153: case RPCC_FUNC: ! 154: // one operand, in Ra ! 155: BlankFill(OPRNDCOL); ! 156: OutputReg(disinstr.Memory.Ra); ! 157: break; ! 158: ! 159: case MB_FUNC: ! 160: case WMB_FUNC: ! 161: case MB2_FUNC: ! 162: case MB3_FUNC: ! 163: case TRAPB_FUNC: ! 164: case EXCB_FUNC: ! 165: // no operands ! 166: break; ! 167: ! 168: default: ! 169: printf("we shouldn't get here \n"); ! 170: break; ! 171: } ! 172: ! 173: break; ! 174: ! 175: ! 176: case ALPHA_JUMP: ! 177: OutputString(findFuncName(pEntry, disinstr.Jump.Function)); ! 178: BlankFill(OPRNDCOL); ! 179: OutputReg(disinstr.Jump.Ra); ! 180: *pBuf++ = ','; ! 181: *pBuf++ = '('; ! 182: OutputReg(disinstr.Jump.Rb); ! 183: *pBuf++ = ')'; ! 184: *pBuf++ = ','; ! 185: OutputHex(disinstr.Jump.Hint, (WIDTH_HINT + 3)/4, TRUE); ! 186: ! 187: Ea = GetRegValue(dp, GetIntRegNumber(disinstr.Jump.Rb)) & (~3); ! 188: OutputEffectiveAddress(dp, Ea); ! 189: break; ! 190: ! 191: case ALPHA_BRANCH: ! 192: OutputString(pEntry->pszAlphaName); ! 193: BlankFill(OPRNDCOL); ! 194: OutputReg(disinstr.Branch.Ra); ! 195: *pBuf++ = ','; ! 196: ! 197: // ! 198: // The next line might be a call to GetNextOffset, but ! 199: // GetNextOffset assumes that it should work from REGFIR. ! 200: // ! 201: ! 202: Ea = *poffset + ! 203: sizeof(DWORD) + ! 204: (disinstr.Branch.BranchDisp << 2); ! 205: OutputHex(Ea, 8, FALSE); ! 206: OutputEffectiveAddress(dp, Ea); ! 207: ! 208: break; ! 209: ! 210: case ALPHA_FP_BRANCH: ! 211: OutputString(pEntry->pszAlphaName); ! 212: BlankFill(OPRNDCOL); ! 213: OutputFReg(disinstr.Branch.Ra); ! 214: *pBuf++ = ','; ! 215: ! 216: // ! 217: // The next line might be a call to GetNextOffset, but ! 218: // GetNextOffset assumes that it should work from REGFIR. ! 219: // ! 220: ! 221: Ea = *poffset + ! 222: sizeof(DWORD) + ! 223: (disinstr.Branch.BranchDisp << 2); ! 224: OutputHex(Ea, 8, FALSE); ! 225: OutputEffectiveAddress(dp, Ea); ! 226: ! 227: break; ! 228: ! 229: case ALPHA_OPERATE: ! 230: OutputString(findFuncName(pEntry, disinstr.OpReg.Function)); ! 231: BlankFill(OPRNDCOL); ! 232: OutputReg(disinstr.OpReg.Ra); ! 233: *pBuf++ = ','; ! 234: if (disinstr.OpReg.RbvType) { ! 235: *pBuf++ = '#'; ! 236: OutputHex(disinstr.OpLit.Literal, (WIDTH_LIT + 3)/4, TRUE); ! 237: } else ! 238: OutputReg(disinstr.OpReg.Rb); ! 239: *pBuf++ = ','; ! 240: OutputReg(disinstr.OpReg.Rc); ! 241: break; ! 242: ! 243: case ALPHA_FP_OPERATE: ! 244: ! 245: { ! 246: ULONG Function; ! 247: ULONG Flags; ! 248: ! 249: Flags = disinstr.FpOp.Function & MSK_FP_FLAGS; ! 250: Function = disinstr.FpOp.Function & MSK_FP_OP; ! 251: ! 252: // if (fVerboseOutput) { ! 253: // dprintf("In FP_OPERATE: Flags %08x Function %08x\n", ! 254: // Flags, Function); ! 255: // dprintf("opcode %d \n", opcode); ! 256: // } ! 257: ! 258: // ! 259: // CVTST and CVTST/S are different: they look like ! 260: // CVTTS with some flags set ! 261: // ! 262: if (Function == CVTTS_FUNC) { ! 263: if (disinstr.FpOp.Function == CVTST_S_FUNC) { ! 264: Function = CVTST_S_FUNC; ! 265: Flags = NONE_FLAGS; ! 266: } ! 267: if (disinstr.FpOp.Function == CVTST_FUNC) { ! 268: Function = CVTST_FUNC; ! 269: Flags = NONE_FLAGS; ! 270: } ! 271: } ! 272: ! 273: OutputString(findFuncName(pEntry, Function)); ! 274: ! 275: // ! 276: // Append the opcode qualifier, if any, to the opcode name. ! 277: // ! 278: ! 279: if ( (opcode == IEEEFP_OP) || (opcode == VAXFP_OP) ! 280: || (Function == CVTQL_FUNC) ) { ! 281: OutputString(findFlagName(Flags, Function)); ! 282: } ! 283: ! 284: BlankFill(OPRNDCOL); ! 285: // ! 286: // If this is a convert instruction, only Rb and Rc are used ! 287: // ! 288: if (strncmp("cvt", findFuncName(pEntry, Function), 3) != 0) { ! 289: OutputFReg(disinstr.FpOp.Fa); ! 290: *pBuf++ = ','; ! 291: } ! 292: OutputFReg(disinstr.FpOp.Fb); ! 293: *pBuf++ = ','; ! 294: OutputFReg(disinstr.FpOp.Fc); ! 295: ! 296: break; ! 297: } ! 298: ! 299: case ALPHA_FP_CONVERT: ! 300: OutputString(pEntry->pszAlphaName); ! 301: BlankFill(OPRNDCOL); ! 302: OutputFReg(disinstr.FpOp.Fa); ! 303: *pBuf++ = ','; ! 304: OutputFReg(disinstr.FpOp.Fb); ! 305: break; ! 306: ! 307: case ALPHA_CALLPAL: ! 308: OutputString(findFuncName(pEntry, disinstr.Pal.Function)); ! 309: break; ! 310: ! 311: case ALPHA_EV4_PR: ! 312: if ((disinstr.Long & MSK_EV4_PR) == 0) ! 313: OutputString("NOP"); ! 314: else { ! 315: OutputString(pEntry->pszAlphaName); ! 316: BlankFill(OPRNDCOL); ! 317: OutputReg(disinstr.EV4_PR.Ra); ! 318: *pBuf++ = ','; ! 319: if(disinstr.EV4_PR.Ra != disinstr.EV4_PR.Rb) { ! 320: OutputReg(disinstr.EV4_PR.Rb); ! 321: *pBuf++ = ','; ! 322: }; ! 323: OutputString(findFuncName(pEntry, (disinstr.Long & MSK_EV4_PR))); ! 324: }; ! 325: break; ! 326: case ALPHA_EV4_MEM: ! 327: OutputString(pEntry->pszAlphaName); ! 328: BlankFill(OPRNDCOL); ! 329: OutputReg(disinstr.EV4_MEM.Ra); ! 330: *pBuf++ = ','; ! 331: OutputReg(disinstr.EV4_MEM.Rb); ! 332: break; ! 333: case ALPHA_EV4_REI: ! 334: OutputString(pEntry->pszAlphaName); ! 335: break; ! 336: default: ! 337: OutputString("Invalid type"); ! 338: break; ! 339: }; ! 340: ! 341: ! 342: *poffset += sizeof(ULONG); ! 343: *pBuf = '\0'; ! 344: return(TRUE); ! 345: } ! 346: ! 347: /* BlankFill - blank-fill buffer ! 348: * ! 349: * Purpose: ! 350: * To fill the buffer at *pBuf with blanks until ! 351: * position count is reached. ! 352: * ! 353: * Input: ! 354: * None. ! 355: * ! 356: * Output: ! 357: * None. ! 358: * ! 359: ***********************************************************************/ ! 360: ! 361: void BlankFill(ULONG count) ! 362: { ! 363: do ! 364: *pBuf++ = ' '; ! 365: while (pBuf < pBufStart + count); ! 366: } ! 367: ! 368: /* OutputHex - output hex value ! 369: * ! 370: * Purpose: ! 371: * Output the value in outvalue into the buffer ! 372: * pointed by *pBuf. The value may be signed ! 373: * or unsigned depending on the value fSigned. ! 374: * ! 375: * Input: ! 376: * outvalue - value to output ! 377: * length - length in digits ! 378: * fSigned - TRUE if signed else FALSE ! 379: * ! 380: * Output: ! 381: * None. ! 382: * ! 383: ***********************************************************************/ ! 384: ! 385: UCHAR HexDigit[16] = { ! 386: '0', '1', '2', '3', '4', '5', '6', '7', ! 387: '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ! 388: }; ! 389: ! 390: void OutputHex (ULONG outvalue, ULONG length, BOOLEAN fSigned) ! 391: { ! 392: UCHAR digit[8]; ! 393: LONG index = 0; ! 394: ! 395: if (fSigned && (LONG)outvalue < 0) { ! 396: *pBuf++ = '-'; ! 397: outvalue = - (LONG)outvalue; ! 398: } ! 399: do { ! 400: digit[index++] = HexDigit[outvalue & 0xf]; ! 401: outvalue >>= 4; ! 402: } ! 403: ! 404: while ((fSigned && outvalue) || (!fSigned && index < (LONG)length)) ! 405: ; ! 406: while (--index >= 0) ! 407: *pBuf++ = digit[index]; ! 408: } ! 409: ! 410: /* OutputString - output string ! 411: * ! 412: * Purpose: ! 413: * Copy the string into the buffer pointed by pBuf. ! 414: * ! 415: * Input: ! 416: * *pStr - pointer to string ! 417: * ! 418: * Output: ! 419: * None. ! 420: * ! 421: ***********************************************************************/ ! 422: ! 423: void OutputString (char *pStr) ! 424: { ! 425: while (*pStr) ! 426: *pBuf++ = *pStr++; ! 427: } ! 428: ! 429: void OutputReg (ULONG regnum) ! 430: { ! 431: OutputString(pszReg[GetIntRegNumber(regnum)]); ! 432: } ! 433: ! 434: void OutputFReg (ULONG regnum) ! 435: { ! 436: *pBuf++ = 'f'; ! 437: if (regnum > 9) ! 438: *pBuf++ = (UCHAR)('0' + regnum / 10); ! 439: *pBuf++ = (UCHAR)('0' + regnum % 10); ! 440: } ! 441: ! 442: ! 443: /*** OutputEffectiveAddress - Print EA symbolically ! 444: * ! 445: * Purpose: ! 446: * Given the effective address (for a branch, jump or ! 447: * memory instruction, print it symbolically, if ! 448: * symbols are available. ! 449: * ! 450: * Input: ! 451: * offset - computed by the caller as ! 452: * for jumps, the value in Rb ! 453: * for branches, func(PC, displacement) ! 454: * for memory, func(PC, displacement) ! 455: * ! 456: * Returns: ! 457: * None ! 458: * ! 459: *************************************************************************/ ! 460: void OutputEffectiveAddress(PDEBUGPACKET dp, ULONG offset) ! 461: { ! 462: ULONG displacement; ! 463: PUCHAR pszTemp; ! 464: UCHAR ch; ! 465: ! 466: PSYMBOL sym; ! 467: ! 468: BlankFill(EACOL); ! 469: ! 470: sym = GetSymFromAddrAllContexts( offset, &displacement, dp); ! 471: ! 472: if (sym) { ! 473: pszTemp = UnDName( &sym->szName[1] ); ! 474: while (ch = *pszTemp++) ! 475: *pBuf++ = ch; ! 476: if (displacement) { ! 477: *pBuf++ = '+'; ! 478: OutputHex(displacement, 8, TRUE); ! 479: } ! 480: } ! 481: else { ! 482: OutputHex(offset, 8, FALSE); ! 483: } ! 484: } ! 485: ! 486: ! 487: /*** GetNextOffset - compute offset for trace or step ! 488: * ! 489: * Purpose: ! 490: * From a limited disassembly of the instruction pointed ! 491: * by the FIR register, compute the offset of the next ! 492: * instruction for either a trace or step operation. ! 493: * ! 494: * trace -> the next instruction to execute ! 495: * step -> the instruction in the next memory location or the ! 496: * next instruction executed due to a branch (step over ! 497: * subroutine calls). ! 498: * ! 499: * Input: ! 500: * result - where to put the next offset ! 501: * fStep - TRUE for step offset; FALSE for trace offset ! 502: * ! 503: * Returns: ! 504: * step or trace offset if input is TRUE or FALSE, respectively ! 505: * in result ! 506: * ! 507: *************************************************************************/ ! 508: /* ! 509: void ! 510: GetNextOffset (PDEBUGPACKET dp, PULONG result, BOOLEAN fStep) ! 511: { ! 512: ULONG rv; ! 513: ULONG opcode; ! 514: ULONG firaddr; ! 515: ULONG updatedpc; ! 516: ULONG branchTarget; ! 517: DWORD fir; ! 518: ! 519: // Canonical form to shorten tests; Abs is absolute value ! 520: ! 521: LONG Can, Abs; ! 522: ! 523: CONVERTED_DOUBLE Rav; ! 524: CONVERTED_DOUBLE Fav; ! 525: CONVERTED_DOUBLE Rbv; ! 526: ! 527: // ! 528: // Get current address ! 529: // ! 530: ! 531: firaddr = GetRegValue(dp, REGFIR); ! 532: ! 533: // ! 534: // relative addressing updates PC first ! 535: // Assume next sequential instruction is next offset ! 536: // ! 537: ! 538: updatedpc = firaddr + sizeof(ULONG); ! 539: rv = updatedpc; ! 540: ! 541: ReadProcessMemory (dp->hProcess, ! 542: (LPVOID)firaddr, ! 543: (LPVOID)&disinstr.Long, ! 544: sizeof(ULONG), ! 545: NULL ! 546: ); ! 547: ! 548: opcode = disinstr.Memory.Opcode; ! 549: ! 550: switch(findOpCodeEntry(opcode)->iType) { ! 551: ! 552: case ALPHA_JUMP: ! 553: ! 554: switch(disinstr.Jump.Function) { ! 555: ! 556: case JSR_FUNC: ! 557: case JSR_CO_FUNC: ! 558: ! 559: if (fStep) { ! 560: ! 561: // ! 562: // Step over the subroutine call; ! 563: // ! 564: ! 565: break; ! 566: } ! 567: ! 568: // ! 569: // fall through ! 570: // ! 571: ! 572: case JMP_FUNC: ! 573: case RET_FUNC: ! 574: ! 575: GetQuadRegValue(dp, GetIntRegNumber(disinstr.Jump.Rb), &Rbv); ! 576: rv = (Rbv.li.LowPart & (~3)); ! 577: break; ! 578: ! 579: } ! 580: ! 581: break; ! 582: ! 583: case ALPHA_BRANCH: ! 584: ! 585: branchTarget = (updatedpc + (disinstr.Branch.BranchDisp * 4)); ! 586: ! 587: GetQuadRegValue(dp, GetIntRegNumber(disinstr.Branch.Ra), &Rav.li); ! 588: ! 589: // ! 590: // set up a canonical value for computing the branch test ! 591: // - works with ALPHA, MIPS and 386 hosts ! 592: // ! 593: ! 594: Can = Rav.li.LowPart & 1; ! 595: ! 596: if ((LONG)Rav.li.HighPart < 0) { ! 597: Can |= 0x80000000; ! 598: } ! 599: ! 600: if ((Rav.li.LowPart & 0xfffffffe) || (Rav.li.HighPart & 0x7fffffff)) { ! 601: Can |= 2; ! 602: } ! 603: ! 604: // if (fVerboseOutput) { ! 605: // dprintf("Rav High %08lx Low %08lx Canonical %08lx\n", ! 606: // Rav.li.HighPart, Rav.li.LowPart, Can); ! 607: // dprintf("returnvalue %08lx branchTarget %08lx\n", ! 608: // rv, branchTarget); ! 609: // } ! 610: ! 611: switch(opcode) { ! 612: case BR_OP: rv = branchTarget; break; ! 613: case BSR_OP: if (!fStep) rv = branchTarget; break; ! 614: case BEQ_OP: if (Can == 0) rv = branchTarget; break; ! 615: case BLT_OP: if (Can < 0) rv = branchTarget; break; ! 616: case BLE_OP: if (Can <= 0) rv = branchTarget; break; ! 617: case BNE_OP: if (Can != 0) rv = branchTarget; break; ! 618: case BGE_OP: if (Can >= 0) rv = branchTarget; break; ! 619: case BGT_OP: if (Can > 0) rv = branchTarget; break; ! 620: case BLBC_OP: if ((Can & 0x1) == 0) rv = branchTarget; break; ! 621: case BLBS_OP: if ((Can & 0x1) == 1) rv = branchTarget; break; ! 622: }; ! 623: ! 624: break; ! 625: ! 626: ! 627: case ALPHA_FP_BRANCH: ! 628: ! 629: branchTarget = (updatedpc + (disinstr.Branch.BranchDisp * 4)); ! 630: ! 631: GetFloatingPointRegValue(dp, disinstr.Branch.Ra, &Fav); ! 632: ! 633: // ! 634: // Set up a canonical value for computing the branch test ! 635: // ! 636: ! 637: Can = Fav.li.HighPart & 0x80000000; ! 638: ! 639: // ! 640: // The absolute value is needed -0 and non-zero computation ! 641: // ! 642: ! 643: Abs = Fav.li.LowPart || (Fav.li.HighPart & 0x7fffffff); ! 644: ! 645: if (Can && (Abs == 0x0)) { ! 646: ! 647: // ! 648: // negative 0 should be considered as zero ! 649: // ! 650: ! 651: Can = 0x0; ! 652: ! 653: } else { ! 654: ! 655: Can |= Abs; ! 656: ! 657: } ! 658: ! 659: // if (fVerboseOutput) { ! 660: // dprintf("Fav High %08lx Low %08lx Canonical %08lx Absolute %08lx\n", ! 661: // Fav.li.HighPart, Fav.li.LowPart, Can, Abs); ! 662: // dprintf("returnvalue %08lx branchTarget %08lx\n", ! 663: // rv, branchTarget); ! 664: // } ! 665: ! 666: switch(opcode) { ! 667: case FBEQ_OP: if (Can == 0) rv = branchTarget; break; ! 668: case FBLT_OP: if (Can < 0) rv = branchTarget; break; ! 669: case FBNE_OP: if (Can != 0) rv = branchTarget; break; ! 670: case FBLE_OP: if (Can <= 0) rv = branchTarget; break; ! 671: case FBGE_OP: if (Can >= 0) rv = branchTarget; break; ! 672: case FBGT_OP: if (Can > 0) rv = branchTarget; break; ! 673: }; ! 674: ! 675: break; ! 676: }; ! 677: ! 678: // if (fVerboseOutput) { ! 679: // dprintf("GetNextOffset returning %08lx\n", rv); ! 680: // } ! 681: ! 682: *result = rv; ! 683: } ! 684: ! 685: ! 686: */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.