/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Disassemble Apple II binaries. */ #include "StdAfx.h" #include "Disasm.h" /* * =========================================================================== * Common stuff for 65xxx-series CPU * =========================================================================== */ /* * The color scheme from Don Lancaster's "Enhancing your Apple II, Volume 1, * Second Edition": * (1) Paint all subroutine returns (RTS) green (entire line to the right * of the 4-character address field). * (2) Paint all subroutine calls (JSR) orange (with an extended swipe for * out-of-range calls, e.g. JSR $FDED). Paint the target address of * the JSR orange as well. Write the name of the target in brown felt * tip after the orange arrow, e.g. "COUT (output char)". * (3) Paint all absolute jumps pink. Highlight across the entire line * (everything but the address). Paint the address field of the target * location pink. * (4) Show branches in blue, with lines down the left-hand side. * (5) Paint constants green ("lda #$74") and variables pink ("sta $38"), * highlighting only the operation (i.e. not the hex values in the * middle of the listing). * (6) Paint the housekeeping yellow (inx, dex, dey, txa, cld, sec, tsx). * Draw yellow lines between pha/pla pairs. */ /* * Return the #of bytes required by the specified addressing mode, given a * particular CPU and values for the 'e', 'm', and 'x' flags. * * Width is always > 0. This guarantees that we don't loop forever even if * we hit stuff we don't recognize. */ int ReformatDisasm65xxx::GetOpWidth(OpCode opCode, AddrMode addrMode, CPU cpu, bool emul, bool shortM, bool shortX) { int width = 0; switch (addrMode) { case kAddrAcc: case kAddrImplied: case kAddrStackPull: case kAddrStackPush: case kAddrStackRTI: case kAddrStackRTL: case kAddrStackRTS: width = 1; break; case kAddrDP: case kAddrDPIndexX: case kAddrDPIndexY: case kAddrDPIndexXInd: case kAddrDPInd: case kAddrDPIndLong: case kAddrDPIndIndexY: case kAddrDPIndIndexYLong: case kAddrPCRel: case kAddrStackRel: case kAddrStackRelIndexY: case kAddrStackDPInd: width = 2; break; case kAddrAbs: case kAddrAbsIndexX: case kAddrAbsIndexY: case kAddrAbsIndexXInd: case kAddrAbsInd: case kAddrAbsIndLong: case kAddrBlockMove: case kAddrPCRelLong: case kAddrStackAbs: case kAddrStackPCRel: width = 3; break; case kAddrAbsLong: case kAddrAbsIndexXLong: width = 4; break; case kAddrStackInt: // BRK/COP if (emul || fOneByteBrkCop) width = 1; else width = 2; break; case kAddrImm: width = 2; if (!emul) { if (opCode == kOpCPX || opCode == kOpCPY || opCode == kOpLDX || opCode == kOpLDY) { if (!shortX) width = 3; } else if (opCode == kOpADC || opCode == kOpAND || opCode == kOpBIT || opCode == kOpCMP || opCode == kOpEOR || opCode == kOpLDA || opCode == kOpORA || opCode == kOpSBC) { if (!shortM) width = 3; } else if (opCode == kOpREP || opCode == kOpSEP) { /* keep width = 2 */ } else { assert(false); } } break; case kAddrWDM: // not really defined width = 2; break; case kAddrModeUnknown: // unknown opcode width = 1; break; default: assert(false); width = 1; break; } assert(width > 0); return width; } /* * Returns "true" if it looks like we're pointing at a ProDOS 8 MLI call. * * The caller is expected to check to see if we're in bank 0. */ bool ReformatDisasm65xxx::IsP8Call(const uint8_t* srcBuf, long srcLen) { if (srcLen >= 6 && srcBuf[0] == 0x20 && srcBuf[1] == 0x00 && srcBuf[2] == 0xbf) { return true; } return false; } /* * Returns "true" if it looks like we're pointing at a IIgs toolbox call. */ bool ReformatDisasm65xxx::IsToolboxCall(const uint8_t* srcBuf, long srcLen, long backState) { if (srcLen >= 4 && backState >= 3 && srcBuf[0] == 0x22 && srcBuf[1] == 0x00 && srcBuf[2] == 0x00 && srcBuf[3] == 0xe1 && srcBuf[-3] == 0xa2) // LDX #xxxx { return true; } return false; } /* * Returns "true" if it looks like we're pointing at an inline GS/OS call. */ bool ReformatDisasm65xxx::IsInlineGSOS(const uint8_t* srcBuf, long srcLen) { if (srcLen >= 10 && srcBuf[0] == 0x22 && srcBuf[1] == 0xa8 && srcBuf[2] == 0x00 && srcBuf[3] == 0xe1) { return true; } return false; } /* * Returns "true" if it looks like we're pointing at a stack GS/OS call. */ bool ReformatDisasm65xxx::IsStackGSOS(const uint8_t* srcBuf, long srcLen, long backState) { if (srcLen >= 4 && backState >= 3 && srcBuf[0] == 0x22 && srcBuf[1] == 0xb0 && srcBuf[2] == 0x00 && srcBuf[3] == 0xe1 && srcBuf[-3] == 0xf4) // PEA xxxx { return true; } return false; } /* * Output one or more lines of text similar to what the monitor would output * on an 8-bit Apple II. * * Returns the number of bytes consumed. */ int ReformatDisasm65xxx::OutputMonitor8(const uint8_t* srcBuf, long srcLen, long backState, uint16_t addr) { const CPU kCPU = kCPU65C02; // 6502 or 65C02 OpCode opCode; AddrMode addrMode; int bytesUsed; int opAndAddr; opAndAddr = kOpMap[*srcBuf].opAndAddr[kCPU]; opCode = (OpCode) (opAndAddr & 0xff); assert(opCode >= kOpCodeUnknown && opCode < kOpCodeMAX); addrMode = (AddrMode) (opAndAddr >> 8); assert(addrMode >= kAddrModeUnknown && addrMode < kAddrModeMAX); bytesUsed = GetOpWidth(opCode, addrMode, kCPU, true, true, true); if (IsP8Call(srcBuf, srcLen)) { /* print and skip P8 inline call stuff */ const char* callName; callName = NiftyList::LookupP8MLI(srcBuf[3]); if (callName == NULL) callName = "(Unknown P8 MLI)"; PrintMonitor8Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName); BufPrintf("%04X- %02X $%02X", addr+3, srcBuf[3], srcBuf[3]); RTFNewPara(); BufPrintf("%04X- %02X %02X $%02X%02X", addr+4, srcBuf[4], srcBuf[5], srcBuf[5], srcBuf[4]); RTFNewPara(); bytesUsed += 3; } else if (srcLen < bytesUsed) { assert(bytesUsed <= kMaxByteConsumption); uint8_t tmpBuf[kMaxByteConsumption]; memset(tmpBuf, 0, kMaxByteConsumption); memcpy(tmpBuf, srcBuf, srcLen); PrintMonitor8Line(opCode, addrMode, addr, tmpBuf, bytesUsed, NULL); } else { PrintMonitor8Line(opCode, addrMode, addr, srcBuf, bytesUsed, NULL); } return bytesUsed; } /* * Output one line of 8-bit monitor stuff. */ void ReformatDisasm65xxx::PrintMonitor8Line(OpCode opCode, AddrMode addrMode, uint16_t addr, const uint8_t* srcBuf, long srcLen, const char* comment) { char lineBuf[64]; // actual length is about 30 -- does not hold comment char* cp; const char* mnemonic = kOpCodeDetails[opCode].mnemonic; uint8_t byte0, byte1, byte2; cp = lineBuf; switch (srcLen) { case 1: byte0 = srcBuf[0]; byte1 = byte2 = 0xcc; // make bugs more obvious cp += sprintf(cp, "%04X- %02X %s", addr, byte0, mnemonic); break; case 2: byte0 = srcBuf[0]; byte1 = srcBuf[1]; byte2 = 0xcc; cp += sprintf(cp, "%04X- %02X %02X %s", addr, byte0, byte1, mnemonic); break; case 3: byte0 = srcBuf[0]; byte1 = srcBuf[1]; byte2 = srcBuf[2]; cp += sprintf(cp, "%04X- %02X %02X %02X %s", addr, byte0, byte1, byte2, mnemonic); break; default: assert(false); return; } switch (addrMode) { case kAddrImplied: case kAddrStackPull: case kAddrStackPush: case kAddrStackRTI: case kAddrStackRTS: break; case kAddrAcc: //cp += sprintf(cp, " A"); break; case kAddrDP: cp += sprintf(cp, " $%02X", byte1); break; case kAddrDPIndexX: cp += sprintf(cp, " $%02X,X", byte1); break; case kAddrDPIndexY: cp += sprintf(cp, " $%02X,Y", byte1); break; case kAddrDPIndexXInd: cp += sprintf(cp, " ($%02X,X)", byte1); break; case kAddrDPInd: cp += sprintf(cp, " ($%02X)", byte1); break; case kAddrDPIndIndexY: cp += sprintf(cp, " ($%02X),Y", byte1); break; case kAddrImm: cp += sprintf(cp, " #$%02X", byte1); break; case kAddrPCRel: cp += sprintf(cp, " $%04X", RelOffset(addr, byte1)); break; case kAddrAbs: cp += sprintf(cp, " $%02X%02X", byte2, byte1); if (comment == NULL) comment = NiftyList::Lookup00Addr(byte1 | byte2 << 8); break; case kAddrAbsIndexX: cp += sprintf(cp, " $%02X%02X,X", byte2, byte1); break; case kAddrAbsIndexY: cp += sprintf(cp, " $%02X%02X,Y", byte2, byte1); break; case kAddrAbsIndexXInd: cp += sprintf(cp, " ($%02X%02X,X)", byte2, byte1); break; case kAddrAbsInd: cp += sprintf(cp, " ($%02X%02X)", byte2, byte1); break; case kAddrStackInt: // BRK/COP if (srcLen != 1) cp += sprintf(cp, " $%02X", byte1); break; case kAddrAbsIndLong: // JML case kAddrAbsLong: case kAddrAbsIndexXLong: case kAddrBlockMove: case kAddrDPIndLong: case kAddrDPIndIndexYLong: case kAddrPCRelLong: // BRL case kAddrStackAbs: // PEA case kAddrStackDPInd: // PEI case kAddrStackPCRel: // PER case kAddrStackRTL: case kAddrStackRel: case kAddrStackRelIndexY: case kAddrWDM: // not for 8-bit mode assert(false); break; case kAddrModeUnknown: assert(srcLen == 1); break; default: assert(false); break; } assert(strlen(cp)+1 < sizeof(lineBuf)); if (comment == NULL) BufPrintf("%s", lineBuf); else BufPrintf("%s %s", lineBuf, comment); RTFNewPara(); } /* * Output one or more lines of text similar to what the monitor would output * on an 8-bit Apple II. * * Returns the number of bytes consumed. */ int ReformatDisasm65xxx::OutputMonitor16(const uint8_t* srcBuf, long srcLen, long backState, uint32_t addr, bool shortRegs) { const CPU kCPU = kCPU65816; OpCode opCode; AddrMode addrMode; int bytesUsed; int opAndAddr; const char* callName; opAndAddr = kOpMap[*srcBuf].opAndAddr[kCPU]; opCode = (OpCode) (opAndAddr & 0xff); assert(opCode >= kOpCodeUnknown && opCode < kOpCodeMAX); addrMode = (AddrMode) (opAndAddr >> 8); assert(addrMode >= kAddrModeUnknown && addrMode < kAddrModeMAX); bytesUsed = GetOpWidth(opCode, addrMode, kCPU, false, shortRegs, shortRegs); if (Bank(addr) == 0 && IsP8Call(srcBuf, srcLen)) { /* print and skip P8 inline call stuff */ callName = NiftyList::LookupP8MLI(srcBuf[3]); if (callName == NULL) callName = "(Unknown P8 MLI)"; PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName); BufPrintf("00/%04X: %02X %02X", addr+3, srcBuf[3], srcBuf[3]); RTFNewPara(); BufPrintf("00/%04X: %02X %02X %02X%02X", addr+4, srcBuf[4], srcBuf[5], srcBuf[5], srcBuf[4]); RTFNewPara(); bytesUsed += 3; } else if (IsToolboxCall(srcBuf, srcLen, backState)) { callName = NiftyList::LookupToolbox(srcBuf[-2] | srcBuf[-1] << 8); PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName); } else if (IsInlineGSOS(srcBuf, srcLen)) { callName = NiftyList::LookupGSOS(srcBuf[4] | srcBuf[5] << 8); PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName); BufPrintf("%02X/%04X: %02X %02X %02X%02X", Bank(addr), Offset(addr+4), srcBuf[4], srcBuf[5], srcBuf[5], srcBuf[4]); RTFNewPara(); BufPrintf("%02X/%04X: %02X %02X %02X %02X %02X%02X%02X%02X", Bank(addr), Offset(addr+6), srcBuf[6], srcBuf[7], srcBuf[8], srcBuf[9], srcBuf[9], srcBuf[8], srcBuf[7], srcBuf[6]); RTFNewPara(); bytesUsed += 6; } else if (IsStackGSOS(srcBuf, srcLen, backState)) { callName = NiftyList::LookupGSOS(srcBuf[-2] | srcBuf[-1] << 8); PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, callName); } else if (srcLen < bytesUsed) { assert(bytesUsed <= kMaxByteConsumption); uint8_t tmpBuf[kMaxByteConsumption]; memset(tmpBuf, 0, kMaxByteConsumption); memcpy(tmpBuf, srcBuf, srcLen); PrintMonitor16Line(opCode, addrMode, addr, tmpBuf, bytesUsed, NULL); } else { PrintMonitor16Line(opCode, addrMode, addr, srcBuf, bytesUsed, NULL); } return bytesUsed; } /* * Output one line of 16-bit monitor stuff. */ void ReformatDisasm65xxx::PrintMonitor16Line(OpCode opCode, AddrMode addrMode, uint32_t addr, const uint8_t* srcBuf, long srcLen, const char* comment) { char lineBuf[64]; // actual length is about 30 -- does not hold comment char* cp; const char* mnemonic = kOpCodeDetails[opCode].mnemonic; uint8_t byte0, byte1, byte2, byte3; int16_t offset; cp = lineBuf; cp += sprintf(cp, "%02X/%04X: ", addr >> 16, addr & 0xffff); switch (srcLen) { case 1: byte0 = srcBuf[0]; byte1 = byte2 = byte3 = 0xcc; // make bugs more obvious cp += sprintf(cp, "%02X %s", byte0, mnemonic); break; case 2: byte0 = srcBuf[0]; byte1 = srcBuf[1]; byte2 = byte3 = 0xcc; cp += sprintf(cp, "%02X %02X %s", byte0, byte1, mnemonic); break; case 3: byte0 = srcBuf[0]; byte1 = srcBuf[1]; byte2 = srcBuf[2]; byte3 = 0xcc; cp += sprintf(cp, "%02X %02X %02X %s", byte0, byte1, byte2, mnemonic); break; case 4: byte0 = srcBuf[0]; byte1 = srcBuf[1]; byte2 = srcBuf[2]; byte3 = srcBuf[3]; cp += sprintf(cp, "%02X %02X %02X %02X %s", byte0, byte1, byte2, byte3, mnemonic); break; default: assert(false); return; } switch (addrMode) { case kAddrImplied: case kAddrStackPull: case kAddrStackPush: case kAddrStackRTI: case kAddrStackRTS: case kAddrStackRTL: break; case kAddrAcc: //cp += sprintf(cp, " A"); break; case kAddrDP: cp += sprintf(cp, " %02X", byte1); break; case kAddrDPIndexX: cp += sprintf(cp, " %02X,X", byte1); break; case kAddrDPIndexY: cp += sprintf(cp, " %02X,Y", byte1); break; case kAddrDPIndexXInd: cp += sprintf(cp, " (%02X,X)", byte1); break; case kAddrDPInd: cp += sprintf(cp, " (%02X)", byte1); break; case kAddrDPIndIndexY: cp += sprintf(cp, " (%02X),Y", byte1); break; case kAddrImm: if (srcLen == 2) cp += sprintf(cp, " #%02X", byte1); else cp += sprintf(cp, " #%02X%02X", byte2, byte1); break; case kAddrPCRel: offset = (char) byte1; if (offset < 0) cp += sprintf(cp, " %04X {-%02X}", RelOffset((uint16_t) addr, byte1), -offset); else cp += sprintf(cp, " %04X {+%02X}", RelOffset((uint16_t) addr, byte1), offset); break; case kAddrAbs: cp += sprintf(cp, " %02X%02X", byte2, byte1); if (comment == NULL && Bank(addr) == 0) comment = NiftyList::Lookup00Addr(byte1 | byte2 << 8); break; case kAddrAbsIndexX: cp += sprintf(cp, " %02X%02X,X", byte2, byte1); break; case kAddrAbsIndexY: cp += sprintf(cp, " %02X%02X,Y", byte2, byte1); break; case kAddrAbsIndexXInd: cp += sprintf(cp, " (%02X%02X,X)", byte2, byte1); break; case kAddrAbsInd: cp += sprintf(cp, " (%02X%02X)", byte2, byte1); break; case kAddrWDM: case kAddrStackInt: // BRK/COP if (srcLen != 1) cp += sprintf(cp, " %02X", byte1); break; case kAddrAbsIndLong: // JML cp += sprintf(cp, " (%02X%02X)", byte2, byte1); break; case kAddrAbsLong: cp += sprintf(cp, " %02X%02X%02X", byte3, byte2, byte1); if (comment == NULL) { if (byte3 == 0x00) comment = NiftyList::Lookup00Addr(byte1 | byte2 << 8); else if (byte3 == 0x01) comment = NiftyList::Lookup01Vector(byte1 | byte2 << 8); else if (byte3 == 0xe0) comment = NiftyList::LookupE0Vector(byte1 | byte2 << 8); else if (byte3 == 0xe1) comment = NiftyList::LookupE1Vector(byte1 | byte2 << 8); } break; case kAddrAbsIndexXLong: cp += sprintf(cp, " %02X%02X%02X,X", byte3, byte2, byte1); break; case kAddrBlockMove: cp += sprintf(cp, " %02X%02X", byte2, byte1); break; case kAddrDPIndLong: cp += sprintf(cp, " [%02X]", byte1); break; case kAddrDPIndIndexYLong: cp += sprintf(cp, " [%02X],Y", byte1); break; case kAddrStackPCRel: // PER case kAddrPCRelLong: // BRL offset = (short) (byte1 | byte2 << 8); if (offset < 0) cp += sprintf(cp, " %04X {-%02X}", RelLongOffset((uint16_t) addr, offset), -offset); else cp += sprintf(cp, " %04X {+%02X}", RelLongOffset((uint16_t) addr, offset), offset); break; case kAddrStackAbs: // PEA cp += sprintf(cp, " %02X%02X", byte2, byte1); break; case kAddrStackDPInd: // PEI cp += sprintf(cp, " %02X", byte1); break; case kAddrStackRel: cp += sprintf(cp, " %02X,S", byte1); break; case kAddrStackRelIndexY: cp += sprintf(cp, " (%02X,S),Y", byte1); break; case kAddrModeUnknown: assert(srcLen == 1); break; default: assert(false); break; } assert(strlen(cp)+1 < sizeof(lineBuf)); if (comment == NULL) BufPrintf("%s", lineBuf); else { if (srcLen < 4) BufPrintf("%s %s", lineBuf, comment); else BufPrintf("%s %s", lineBuf, comment); } RTFNewPara(); } /* * =========================================================================== * Disassemble 8-bit code * =========================================================================== */ /* * For ProDOS 8 and DOS 3.3 stuff, we can't say for sure whether a "BIN" * file is code or, say, a hi-res graphic. We use "maybe" level to put * disassembly below other formats. */ void ReformatDisasm8::Examine(ReformatHolder* pHolder) { ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot; int fileType = pHolder->GetFileType(); if (fileType >= 0xf1 && fileType <= 0xf8) { applies = ReformatHolder::kApplicProbablyNot; } else if (fileType == 0x00 && pHolder->GetSourceFormat() != ReformatHolder::kSourceFormatCPM) { applies = ReformatHolder::kApplicProbablyNot; } else if (fileType == kTypeBIN) { applies = ReformatHolder::kApplicMaybe; } else if (fileType == kTypeSYS || fileType == kTypeCMD || fileType == kType8OB || fileType == kTypeP8C) { applies = ReformatHolder::kApplicYes; } pHolder->SetApplic(ReformatHolder::kReformatMonitor8, applies, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot); // not doing this yet pHolder->SetApplic(ReformatHolder::kReformatDisasmMerlin8, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot); } /* * Create a monitor listing or disassembly of a file. */ int ReformatDisasm8::Process(const ReformatHolder* pHolder, ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part, ReformatOutput* pOutput) { const uint8_t* srcBuf = pHolder->GetSourceBuf(part); long srcLen = pHolder->GetSourceLen(part); long fileType = pHolder->GetFileType(); long backState = 0; uint16_t addr; fUseRTF = false; if (!srcLen) return -1; RTFBegin(); /* pick a nice 16-bit start address */ if (fileType == kTypeSYS || fileType == kTypeP8C || fileType == kType8OB) addr = 0x2000; else if (fileType == kTypeBIN || fileType == kTypeCMD) addr = (uint16_t) pHolder->GetAuxType(); else addr = 0x0000; while (srcLen > 0) { int consumed; consumed = OutputMonitor8(srcBuf, srcLen, backState, addr); srcBuf += consumed; srcLen -= consumed; backState += consumed; addr += consumed; } RTFEnd(); SetResultBuffer(pOutput); return 0; } /* * =========================================================================== * Disassemble 16-bit code * =========================================================================== */ /* * For 16-bit stuff, the file types are always explicit. However, there are * instances of 65802/65816 code being written for 8-bit platforms, so we * want to keep 16-bit disassembly available but prioritized below "raw" for * BIN files. */ void ReformatDisasm16::Examine(ReformatHolder* pHolder) { ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot; long fileType = pHolder->GetFileType(); if (fileType == kTypeBIN || fileType == kTypeSYS || fileType == kTypeCMD || fileType == kType8OB || fileType == kTypeP8C) { applies = ReformatHolder::kApplicProbablyNot; } else if (fileType == 0x00 && pHolder->GetSourceFormat() != ReformatHolder::kSourceFormatCPM) { applies = ReformatHolder::kApplicProbablyNot; } else { /* * Interesting file types (GS/OS ref table F-1): * $B1 OBJ Object * $B2 LIB Library * $B3 S16 GS/OS or ProDOS 16 app * $B4 RTL Run-time library * $B5 EXE Shell application * $B6 PIF Permanent initialization * $B7 TIF Temporary initialization * $B8 NDA New desk accessory * $B9 CDA Classic desk accessory * $BA TOL Tool set file * $BB DVR Apple IIgs device driver * $BC LDF Generic load file (application-specific) * $BD FST GS/OS file system translator * * We also handle non-OMF files: * $F9 OS GS/OS System file */ if (fileType >= kTypeOBJ && fileType <= kTypeFST) applies = ReformatHolder::kApplicYes; else if (fileType == kTypeOS) applies = ReformatHolder::kApplicYes; } pHolder->SetApplic(ReformatHolder::kReformatMonitor16Long, applies, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot); pHolder->SetApplic(ReformatHolder::kReformatMonitor16Short, applies, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot); pHolder->SetApplicPreferred(ReformatHolder::kReformatMonitor16Long); // not doing this yet pHolder->SetApplic(ReformatHolder::kReformatDisasmOrcam16, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot); } /* * Disassemble or show monitor listing for 16-bit code. */ int ReformatDisasm16::Process(const ReformatHolder* pHolder, ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part, ReformatOutput* pOutput) { const uint8_t* srcBuf = pHolder->GetSourceBuf(part); long srcLen = pHolder->GetSourceLen(part); long fileType = pHolder->GetFileType(); uint32_t addr = 0; bool shortRegs; fUseRTF = false; if (!srcLen) return -1; RTFBegin(); if (id == ReformatHolder::kReformatMonitor16Long) shortRegs = false; else shortRegs = true; fOneByteBrkCop = pHolder->GetOption(ReformatHolder::kOptOneByteBrkCop) != 0; if (fileType >= kTypeOBJ && fileType <= kTypeFST) { if (!OutputOMF(srcBuf, srcLen, fileType, shortRegs)) { /* must not be OMF; do a generic list */ fExpBuf.Reset(); BufPrintf("[ Valid OMF expected but not found ]\r\n"); RTFNewPara(); OutputSection(srcBuf, srcLen, addr, shortRegs); } } else { /* pick a start address in bank 0 */ if (fileType == kTypeSYS) addr = 0x2000; else if (fileType == kTypeBIN || fileType == kTypeCMD) addr = (uint16_t) pHolder->GetAuxType(); OutputSection(srcBuf, srcLen, addr, shortRegs); } RTFEnd(); SetResultBuffer(pOutput); return 0; } /* * Output one section of a file. */ void ReformatDisasm16::OutputSection(const uint8_t* srcBuf, long srcLen, uint32_t addr, bool shortRegs) { long backState = 0; while (srcLen > 0) { int consumed; consumed = OutputMonitor16(srcBuf, srcLen, backState, addr, shortRegs); srcBuf += consumed; srcLen -= consumed; backState += consumed; addr += consumed; } } /* * Break an OMF file into sections, and output each individually. */ bool ReformatDisasm16::OutputOMF(const uint8_t* srcBuf, long srcLen, long fileType, bool shortRegs) { const uint8_t* origBuf = srcBuf; long origLen = srcLen; OMFSegmentHeader segHdr; int segmentNumber = 1; BufPrintf(";\r\n"); BufPrintf("; OMF segment summary:\r\n"); BufPrintf(";\r\n"); /* pass #1: print a preview */ while (srcLen > 0) { if (!segHdr.Unpack(srcBuf, srcLen, fileType)) { if (segmentNumber == 1) return false; else { BufPrintf("; (bad header found, ignoring last %ld bytes)\r\n", srcLen); break; // out of while; display what we have } } PrintHeader(&segHdr, segmentNumber, false); srcBuf += segHdr.GetSegmentLen(); srcLen -= segHdr.GetSegmentLen(); segmentNumber++; } BufPrintf(";\r\n"); RTFNewPara(); segmentNumber = 1; srcBuf = origBuf; srcLen = origLen; while (srcLen > 0) { if (!segHdr.Unpack(srcBuf, srcLen, fileType)) { BufPrintf("!!!\r\n"); BufPrintf("!!! Found bad OMF header at offset 0x%04x (remaining len=%ld)\r\n", srcBuf - origBuf, srcLen); BufPrintf("!!!\r\n"); RTFNewPara(); if (segmentNumber == 1) return false; else return true; } PrintHeader(&segHdr, segmentNumber, true); PrintSegment(&segHdr, srcBuf, srcLen, shortRegs); RTFNewPara(); srcBuf += segHdr.GetSegmentLen(); srcLen -= segHdr.GetSegmentLen(); segmentNumber++; } return true; } /* * Print the interesting bits of the header. */ void ReformatDisasm16::PrintHeader(const OMFSegmentHeader* pSegHdr, int segmentNumber, bool longFmt) { //pSegHdr->Dump(); const char* versStr; switch (pSegHdr->GetVersion()) { case 0: versStr = "0.0"; break; case 1: versStr = "1.0"; break; case 2: versStr = "2.0"; break; case 0x82: versStr = "2.1"; break; default: versStr = "(unknown)"; break; } const char* typeStr; switch (pSegHdr->GetSegmentType()) { case OMFSegmentHeader::kTypeCode: typeStr = "CODE"; break; case OMFSegmentHeader::kTypeData: typeStr = "DATA"; break; case OMFSegmentHeader::kTypeJumpTable: typeStr = "JumpTab"; break; case OMFSegmentHeader::kTypePathName: typeStr = "PathName"; break; case OMFSegmentHeader::kTypeLibraryDict: typeStr = "LibDict"; break; case OMFSegmentHeader::kTypeInit: typeStr = "Init"; break; case OMFSegmentHeader::kTypeAbsoluteBank: typeStr = "AbsBank"; break; case OMFSegmentHeader::kTypeDPStack: typeStr = "DP/Stack"; break; default: typeStr = "(unknown)"; break; } if (longFmt) { BufPrintf(";\r\n"); BufPrintf("; Segment #%d (%d): loadName='%s' segName='%s': \r\n", segmentNumber, pSegHdr->GetSegNum(), pSegHdr->GetLoadName(), pSegHdr->GetSegName()); BufPrintf("; type=%s length=%ld OMF v%s\r\n", typeStr, pSegHdr->GetSegmentLen(), versStr); BufPrintf("; flags:%s%s%s%s%s%s%s%s\r\n", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagBankRelative) ? " bankRel" : "", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagSkip) ? " skip" : "", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagReload) ? " reload" : "", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagAbsoluteBank) ? " absBank" : "", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagNoSpecialMem) ? " noSpecial" : "", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagPositionIndep) ? " posnIndep" : "", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagPrivate) ? " private" : "", pSegHdr->GetSegmentFlag(OMFSegmentHeader::kFlagDynamic) ? " dynamic" : ""); BufPrintf(";"); RTFNewPara(); } else { BufPrintf("; #%02d: %-8s len=0x%06x loadName='%s' segName='%s'\r\n", segmentNumber, typeStr, pSegHdr->GetSegmentLen(), pSegHdr->GetLoadName(), pSegHdr->GetSegName()); } } /* * Print the contents of the segment. */ void ReformatDisasm16::PrintSegment(const OMFSegmentHeader* pSegHdr, const uint8_t* srcBuf, long srcLen, bool shortRegs) { uint32_t subLen; int offset = 0; assert(pSegHdr != NULL); assert(srcBuf != NULL); assert(srcLen > 0); srcBuf += pSegHdr->GetDispData(); srcLen -= pSegHdr->GetDispData(); if (srcLen < 0) { BufPrintf("GLITCH: ran out of data\r\n"); return; } if (*srcBuf == 0xf2 && srcLen >= 5) { /* handle kSegOpLCONST just for fun */ subLen = Get32LE(srcBuf+1); BufPrintf("OMF: LCONST record (0x%04x bytes follow)\r\n", subLen); offset = 5; } OutputSection(srcBuf + offset, pSegHdr->GetSegmentLen() - pSegHdr->GetDispData() - offset, 0x000000, shortRegs); #if 0 OMFSegment seg; seg.Setup(pSegHdr, srcBuf, srcLen); const uint8_t* ptr; do { ptr = seg.ProcessNextChunk(); if (ptr == NULL) { BufPrintf("!!! bogus OMF values encountered\r\n"); return; } switch (*ptr) { case OMFSegment::kSegOpEND: BufPrintf("OMF: END (bytesLeft=%d)\r\n", (ptr+1) - srcBuf); break; } } while (*ptr != OMFSegment::kSegOpEND); #endif } /* * =========================================================================== * OMFSegmentHeader * =========================================================================== */ /* * Unpack an OMF segment header from the current offset. * * Returns "true" on success, "false" on failure. */ bool OMFSegmentHeader::Unpack(const uint8_t* srcBuf, long srcLen, int fileType) { if (srcLen < kHdrMinSize) { LOGI("OMF: Too short to be segment (%ld)", srcLen); return false; } fVersion = *(srcBuf + 0x0f); if (fVersion > kMaxVersion) { LOGI("OMF: Wrong version number to be OMF (%d)", fVersion); return false; } fRevision = 0; if (fVersion == 0) { /* unpack OMF v0.0 (not actually used for 16-bit code?) */ if (srcLen < kV0HdrMinSize) return false; fBlockCnt = Get32LE(srcBuf + 0x00); fResSpc = Get32LE(srcBuf + 0x04); fLength = Get32LE(srcBuf + 0x08); fType = *(srcBuf + 0x0c); fLabLen = *(srcBuf + 0x0d); fNumLen = *(srcBuf + 0x0e); fBankSize = Get32LE(srcBuf + 0x10); fOrg = Get32LE(srcBuf + 0x14); fAlign = Get32LE(srcBuf + 0x18); fNumSex = *(srcBuf + 0x1c); fByteCnt = fBlockCnt * 512; fKind = 0; fLCBank = 0; fSegNum = 0; fEntry = 0; fTempOrg = 0; fDispName = 0x24; if (fLabLen == 0) fDispData = fDispName + *(srcBuf + fDispName); else fDispData = fDispName + fLabLen; } else if (fVersion == 1) { /* unpack OMF v1.0 */ if (srcLen < kV1HdrMinSize) return false; fBlockCnt = Get32LE(srcBuf + 0x00); fResSpc = Get32LE(srcBuf + 0x04); fLength = Get32LE(srcBuf + 0x08); fType = *(srcBuf + 0x0c); fLabLen = *(srcBuf + 0x0d); fNumLen = *(srcBuf + 0x0e); fBankSize = Get32LE(srcBuf + 0x10); // unused32 at 0x14 fOrg = Get32LE(srcBuf + 0x18); fAlign = Get32LE(srcBuf + 0x1c); fNumSex = *(srcBuf + 0x20); fLCBank = *(srcBuf + 0x21); fSegNum = Get16LE(srcBuf + 0x22); fEntry = Get32LE(srcBuf + 0x24); fDispName = Get16LE(srcBuf + 0x28); fDispData = Get16LE(srcBuf + 0x2a); /* * Orca/APW libs seem to use version 1 with a byte count in the * first field, but the "LLRE" app has version 1 with a block count. * The spec is pretty clear, so it looks like somebody's library * builder screwed up. * * Special case type=LIB ($B2). */ if (fileType == 0xb2) { LOGI("NOTE: switching blockCount=%ld to byte count", fBlockCnt); fByteCnt = fBlockCnt; fBlockCnt = 0; } else { fByteCnt = fBlockCnt * 512; } fKind = 0; fTempOrg = 0; } else { /* unpack OMF v2.x */ if (srcLen < kV2HdrMinSize) return false; fByteCnt = Get32LE(srcBuf + 0x00); fResSpc = Get32LE(srcBuf + 0x04); fLength = Get32LE(srcBuf + 0x08); // unused at +0x0c fLabLen = *(srcBuf + 0x0d); fNumLen = *(srcBuf + 0x0e); fBankSize = Get32LE(srcBuf + 0x10); fKind = Get16LE(srcBuf + 0x14); // unused at +0x16 fOrg = Get32LE(srcBuf + 0x18); fAlign = Get32LE(srcBuf + 0x1c); fNumSex = *(srcBuf + 0x20); fSegNum = Get16LE(srcBuf + 0x22); // unused at +0x23 fEntry = Get32LE(srcBuf + 0x24); fDispName = Get16LE(srcBuf + 0x28); fDispData = Get16LE(srcBuf + 0x2a); if (fDispName > 0x2c) { // v2.1 adds a 4-byte "tempOrg" field fTempOrg = Get32LE(srcBuf + 0x2c); fRevision = 1; } else { fTempOrg = 0; } fBlockCnt = 0; fType = 0; fLCBank = 0; } /* validate fields */ if (fByteCnt < kHdrMinSize || fByteCnt > (uint32_t) srcLen) { LOGI("OMF: Bad value for byteCnt (%ld, srcLen=%ld min=%d)", fByteCnt, srcLen, kHdrMinSize); return false; } if (fDispName < 0x24 || fDispName > (srcLen - kLoadNameLen)) { LOGI("OMF: Bad value for dispName (%d, srcLen=%ld)", fDispName, srcLen); return false; } if (fDispData < 0x24 || fDispData > srcLen) { LOGI("OMF: Bad value for dispData (%d, srcLen=%ld)", fDispData, srcLen); return false; } if (fDispData < fDispName + kLoadNameLen) { LOGI("OMF: dispData is inside label region (%d / %d)", fDispData, fDispName); return false; } if (fBankSize != kExpectedBankSize && fBankSize != 0) { LOGI("OMF: NOTE: bankSize=%ld", fBankSize); /* allowed, just weird */ } if (fNumLen != kExpectedNumLen || fNumSex != 0) { LOGI("OMF: WARNING: numLen=%d numSex=%d", fNumLen, fNumSex); /* big endian odd-sized numbers?? keep going, I guess */ } const uint8_t* segName; int segLabelLen; /* copy the label entries over */ segName = srcBuf + fDispName; if (fVersion > 0) { memcpy(fLoadName, srcBuf + fDispName, kLoadNameLen); fLoadName[kLoadNameLen] = '\0'; segName += kLoadNameLen; } else { fLoadName[0] = '\0'; } if (fLabLen == 0) { /* pascal-style string */ segLabelLen = *segName++; memcpy(fSegName, segName, segLabelLen); fSegName[segLabelLen] = '\0'; LOGI(" OMF: Pascal segment label '%hs'", fSegName); } else { /* C-style or non-terminated string */ segLabelLen = fLabLen; memcpy(fSegName, segName, segLabelLen); fSegName[segLabelLen] = '\0'; LOGI(" OMF: Std segment label '%hs'", fSegName); } fReady = true; return true; } /* * Pry the segment type out of the "type" or "kind" field. */ OMFSegmentHeader::SegmentType OMFSegmentHeader::GetSegmentType(void) const { assert(fReady); if (fVersion < 2) return (SegmentType) (fType & 0x1f); else return (SegmentType) (fKind & 0x1f); } /* * Return the value of one of the segment header flags. */ bool OMFSegmentHeader::GetSegmentFlag(SegmentFlag flag) const { if (fVersion < 2) { switch (flag) { case kFlagPositionIndep: return (fType & 0x20) != 0; case kFlagPrivate: return (fType & 0x40) != 0; case kFlagDynamic: return (fType & 0x80) != 0; default: return false; } } else { switch (flag) { case kFlagBankRelative: return (fKind & 0x0100) != 0; case kFlagSkip: return (fKind & 0x0200) != 0; case kFlagReload: return (fKind & 0x0400) != 0; case kFlagAbsoluteBank: return (fKind & 0x0800) != 0; case kFlagNoSpecialMem: return (fKind & 0x1000) != 0; case kFlagPositionIndep: return (fKind & 0x2000) != 0; case kFlagPrivate: return (fKind & 0x4000) != 0; case kFlagDynamic: return (fKind & 0x8000) != 0; default: assert(false); return false; } } } /* * Dump the contents of the segment header struct. */ void OMFSegmentHeader::Dump(void) const { LOGI("OMF segment header:"); LOGI(" segNum=%d loadName='%hs' segName='%hs'", fSegNum, fLoadName, fSegName); LOGI(" blockCnt=%ld byteCnt=%ld resSpc=%ld length=%ld", fBlockCnt, fByteCnt, fResSpc, fLength); LOGI(" version=%d type=0x%02x kind=0x%04x", fVersion, fType, fKind); LOGI(" labLen=%d numLen=%d bankSize=%ld lcBank=%d", fLabLen, fNumLen, fBankSize, fLCBank); LOGI(" align=%ld numSex=%d org=%ld tempOrg=%ld", fAlign, fNumSex, fOrg, fTempOrg); LOGI(" entry=%ld dispName=%d dispData=%d", fEntry, fDispName, fDispData); } #if 0 /* * =========================================================================== * OMFSegment * =========================================================================== */ /* * Prepare to roll through a segment. */ void OMFSegment::Setup(const OMFSegmentHeader* pSegHdr, const uint8_t* srcBuf, long srcLen) { fSegBuf = fCurPtr = srcBuf + pSegHdr->GetDispData(); fSegLen = srcLen - pSegHdr->GetDispData(); fLabLen = pSegHdr->GetLabLen(); fNumLen = pSegHdr->GetNumLen(); } /* * Process the next chunk from the segment. * * Returns a pointer to the start of the chunk, or "NULL" if we've encountered * some bogus condition (e.g. running off the end). */ const uint8_t* OMFSegment::ProcessNextChunk(void) { const uint8_t* prevPtr = fCurPtr; long remLen = fSegLen - (fCurPtr - fSegBuf); uint32_t subLen; int len = 1; // one byte at least (for the opcode) assert(fLabLen >= 0); assert(fNumLen > 0); switch (*fCurPtr) { case kSegOpEND: LOGI(" OMF END reached, remaining len = %d", fSegLen - (fCurPtr - fSegBuf)); assert(len == 1); break; case kSegOpALIGN: len += fNumLen; break; case kSegOpORG: len += fNumLen; break; case kSegOpRELOC: len += 10; break; case kSegOpINTERSEG: len += 14; break; case kSegOpUSING: case kSegOpSTRONG: len += LabelLength(fCurPtr + 1); break; case kSegOpGLOBAL: len += LabelLength(fCurPtr + 1) + 4; break; case kSegOpGEQU: len += LabelLength(fCurPtr + 1) + 4; len += ExpressionLength(fCurPtr + len); break; case kSegOpMEM: // not used on IIgs? len += fNumLen*2; break; case kSegOpEXPR: case kSegOpZEXPR: case kSegOpBEXPR: len += 1; len += ExpressionLength(fCurPtr + len); break; case kSegOpRELEXPR: len += 1 + fNumLen; len += ExpressionLength(fCurPtr + len); break; case kSegOpLOCAL: len += LabelLength(fCurPtr+1) + 4; break; case kSegOpEQU: len += LabelLength(fCurPtr+1) + 4; len += ExpressionLength(fCurPtr + len); break; case kSegOpDS: len += fNumLen; break; case kSegOpLCONST: subLen = Get32LE(fCurPtr+1); len += fNumLen + subLen; break; case kSegOpLEXPR: len += 1; len += ExpressionLength(fCurPtr + len); break; case kSegOpENTRY: len += 6; len += LabelLength(fCurPtr + len); break; case kSegOpcRELOC: len += 6; break; case kSegOpcINTERSEG: len += 7; break; case kSegOpSUPER: subLen = Get32LE(fCurPtr+1); len += 4 + subLen; break; case kSegOpGeneral: case kSegOpExperimental1: case kSegOpExperimental2: case kSegOpExperimental3: case kSegOpExperimental4: subLen = Get32LE(fCurPtr+1); // assumes fNumLen==4 LOGI(" OMF found 'reserved' len=%lu (remLen=%ld)", subLen, remLen); if (subLen > (uint32_t) remLen) return NULL; len += subLen + fNumLen; break; default: assert(len == 1); if (*fCurPtr >= kSegOpCONSTStart && *fCurPtr <= kSegOpCONSTEnd) len += *fCurPtr; break; } fCurPtr += len; return prevPtr; } /* * Determine the length of an OMF expression. * * Pass a pointer to the start of the expression. */ int OMFSegment::ExpressionLength(const uint8_t* ptr) { // do this someday return 1; } #endif