ciderpress/reformat/Disasm.h
Andy McFadden e5ac0ac631 Fix OMF version confusion
VERSION=0/1/2 corresponds, simply, to v0/v1/v2, where v0 was only
used for some older 8-bit Orca/M stuff.  v2.1 can be detected by
looking for the optional "tempOrg" field.

Also, allow the disk version number to be set to zero in 2IMG images.
2020-06-25 14:12:29 -07:00

455 lines
15 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* 6502/65C02/65802/65816 disassembly.
*
* Lots of credit to the classic "Programming the 65816" book by David Eyes
* and Ron Lichty.
*/
#ifndef REFORMAT_DISASM_H
#define REFORMAT_DISASM_H
#include "ReformatBase.h"
/*
* Disassembly base class. Common code and data for all formats.
*/
class ReformatDisasm65xxx : public ReformatText {
public:
ReformatDisasm65xxx(void) {
fOneByteBrkCop = false;
ValidateOpMap();
ValidateOpCodeDetails();
}
virtual ~ReformatDisasm65xxx(void) {}
/*
* OpCode enumeration, must fit in 0-255.
*/
typedef enum OpCode {
kOpCodeUnknown = 0,
kOpADC, kOpAND, kOpASL, kOpBCC,
kOpBCS, kOpBEQ, kOpBIT, kOpBMI,
kOpBNE, kOpBPL, kOpBRA, kOpBRK,
kOpBRL, kOpBVC, kOpBVS, kOpCLC,
kOpCLD, kOpCLI, kOpCLV, kOpCMP,
kOpCOP, kOpCPX, kOpCPY, kOpDEC,
kOpDEX, kOpDEY, kOpEOR, kOpINC,
kOpINX, kOpINY, kOpJML, kOpJMP,
kOpJSL, kOpJSR, kOpLDA, kOpLDX,
kOpLDY, kOpLSR, kOpMVN, kOpMVP,
kOpNOP, kOpORA, kOpPEA, kOpPEI,
kOpPER, kOpPHA, kOpPHB, kOpPHD,
kOpPHK, kOpPHP, kOpPHX, kOpPHY,
kOpPLA, kOpPLB, kOpPLD, kOpPLP,
kOpPLX, kOpPLY, kOpREP, kOpROL,
kOpROR, kOpRTI, kOpRTL, kOpRTS,
kOpSBC, kOpSEC, kOpSED, kOpSEI,
kOpSEP, kOpSTA, kOpSTP, kOpSTX,
kOpSTY, kOpSTZ, kOpTAX, kOpTAY,
kOpTCD, kOpTCS, kOpTDC, kOpTRB,
kOpTSB, kOpTSC, kOpTSX, kOpTXA,
kOpTXS, kOpTXY, kOpTYA, kOpTYX,
kOpWAI, kOpWDM, kOpXBA, kOpXCE,
kOpCodeMAX
} OpCode;
/*
* Address mode enumeration, must fit in 0-255.
*/
typedef enum AddrMode {
kAddrModeUnknown = 0,
kAddrAbs,
kAddrAbsIndexX,
kAddrAbsIndexY,
kAddrAbsIndexXInd,
kAddrAbsInd,
kAddrAbsIndLong,
kAddrAbsLong,
kAddrAbsIndexXLong,
kAddrAcc,
kAddrBlockMove,
kAddrDP,
kAddrDPIndexX,
kAddrDPIndexY,
kAddrDPIndexXInd,
kAddrDPInd,
kAddrDPIndLong,
kAddrDPIndIndexY,
kAddrDPIndIndexYLong,
kAddrImm,
kAddrImplied,
kAddrPCRel,
kAddrPCRelLong,
kAddrStackAbs,
kAddrStackDPInd,
kAddrStackInt,
kAddrStackPCRel,
kAddrStackPull,
kAddrStackPush,
kAddrStackRTI,
kAddrStackRTL,
kAddrStackRTS,
kAddrStackRel,
kAddrStackRelIndexY,
kAddrWDM,
kAddrModeMAX
} AddrMode;
/*
* Specify the CPU to emulate when disassembling.
*/
typedef enum CPU {
kCPU6502,
kCPU65C02,
kCPU65802,
kCPU65816,
kCPUCount
} CPU;
protected:
/*
* Table mapping instruction byte values to an opcode and address mode.
*
* There are 256 entries, each of which is a combination of OpCode and
* AddrMode.
*/
typedef struct OpMap {
int opAndAddr[kCPUCount];
} OpMap;
/*
* We have a table of these, with one entry in the table for each
* entry in the OpCode enum. We use a 1:1 mapping, so it's vital that
* the entries line up.
*/
typedef struct OpCodeDetails {
OpCode opCode; // sanity check
char mnemonic[4]; // 3-letter mnemonic
} OpCodeDetails;
bool fOneByteBrkCop;
/*
* Output one or more lines of code in a manner appropriate for a
* monitor listing on an 8-bit machine. Returns the #of bytes consumed.
*/
int OutputMonitor8(const uint8_t* srcBuf, long srcLen,
long backState, uint16_t addr);
int OutputMonitor16(const uint8_t* srcBuf, long srcLen,
long backState, uint32_t addr, bool shortRegs);
private:
bool ValidateOpMap(void);
static const OpMap kOpMap[];
bool ValidateOpCodeDetails(void);
static const OpCodeDetails kOpCodeDetails[];
inline uint16_t RelOffset(uint16_t addr, uint8_t off) {
int8_t shift = (int8_t) off; // offset is signed
return addr +2 + shift;
}
inline uint16_t RelLongOffset(uint16_t addr, uint16_t off) {
int16_t shift = (int16_t) off; // offset is signed
return addr +3 + shift;
}
enum {
kMaxByteConsumption = 4, // max #of bytes for one output
};
int GetOpWidth(OpCode opCode, AddrMode addrMode, CPU cpu,
bool emul, bool shortM, bool shortX);
bool IsP8Call(const uint8_t* srcBuf, long srcLen);
bool IsToolboxCall(const uint8_t* srcBuf, long srcLen,
long backState);
bool IsInlineGSOS(const uint8_t* srcBuf, long srcLen);
bool IsStackGSOS(const uint8_t* srcBuf, long srcLen,
long backState);
void PrintMonitor8Line(OpCode opCode, AddrMode addrMode,
uint16_t addr, const uint8_t* srcBuf, long srcLen,
const char* comment);
void PrintMonitor16Line(OpCode opCode, AddrMode addrMode,
uint32_t addr, const uint8_t* srcBuf, long srcLen,
const char* comment);
/* 24-bit address helpers */
inline uint8_t Bank(uint32_t addr) {
return (uint8_t) ((addr >> 16) & 0xff);
}
inline uint16_t Offset(uint32_t addr) {
return (uint16_t) (addr & 0xffff);
}
};
/*
* 8-bit code disassembly.
*
* Used for DOS 'B' files and ProDOS BIN/SYS.
*/
class ReformatDisasm8 : public ReformatDisasm65xxx {
public:
ReformatDisasm8(void) {}
virtual ~ReformatDisasm8(void) {}
virtual void Examine(ReformatHolder* pHolder) override;
virtual int Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput) override;
};
/*
* This holds an OMF segment header, and abstracts away version
* differences.
*/
class OMFSegmentHeader {
public:
OMFSegmentHeader(void) : fReady(false) {}
virtual ~OMFSegmentHeader(void) {}
bool Unpack(const uint8_t* srcBuf, long srcLen, int fileType);
void Dump(void) const;
typedef enum SegmentType {
kTypeCode = 0x00,
kTypeData = 0x01,
kTypeJumpTable = 0x02,
kTypePathName = 0x04,
kTypeLibraryDict = 0x08,
kTypeInit = 0x10,
kTypeAbsoluteBank = 0x11, // v1.0 only
kTypeDPStack = 0x12,
} SegmentType;
typedef enum SegmentFlag {
kFlagBankRelative,
kFlagSkip,
kFlagReload,
kFlagAbsoluteBank,
kFlagNoSpecialMem,
kFlagPositionIndep,
kFlagPrivate,
kFlagDynamic,
} SegmentFlag;
uint8_t GetVersion(void) const { return fVersion + (fRevision << 7); }
uint32_t GetSegmentLen(void) const { return fByteCnt; }
SegmentType GetSegmentType(void) const;
bool GetSegmentFlag(SegmentFlag flag) const;
uint16_t GetSegNum(void) const { return fSegNum; }
uint8_t GetLabLen(void) const { return fLabLen; }
uint8_t GetNumLen(void) const { return fNumLen; }
uint16_t GetDispData(void) const { return fDispData; }
const char* GetLoadName(void) const { return (const char*) fLoadName; }
const char* GetSegName(void) const { return (const char*) fSegName; }
// some of these must be public in VC++6.0
enum {
kMaxVersion = 2, // v0/1/2 == OMF 0.0 / 1.0 / 2.x
kLoadNameLen = 10,
kV0HdrMinSize = 0x25,
kV1HdrMinSize = 0x2c + kLoadNameLen,
kV2HdrMinSize = kV1HdrMinSize, // 2.1 is slightly larger; handle that elsewhere
kHdrMinSize = kV0HdrMinSize, // smallest of the three
kExpectedNumLen = 4,
kExpectedBankSize = 65536,
kSegNameLen = 255,
};
private:
bool fReady;
inline uint16_t Get16LE(const uint8_t* buf) {
return *buf | *(buf+1) << 8;
}
inline uint32_t Get32LE(const uint8_t* buf) {
return *buf | *(buf+1) << 8 | *(buf+2) << 16 | *(buf+3) << 24;
}
uint32_t fBlockCnt; // v0/v1
uint32_t fByteCnt; // [v1]/v2
uint32_t fResSpc;
uint32_t fLength;
uint8_t fType; // v0/v1
uint8_t fLabLen;
uint8_t fNumLen; // (always 4)
uint8_t fVersion; // (0, 1, or 2)
uint8_t fRevision; // fictitious field; holds 0 or 1
uint32_t fBankSize; // (always 65536)
uint16_t fKind; // v2
uint32_t fOrg;
uint32_t fAlign;
uint8_t fNumSex;
uint8_t fLCBank; // v1
uint16_t fSegNum; // v1/v2
uint32_t fEntry; // v1/v2
uint16_t fDispName; // v1/v2
uint16_t fDispData; // v1/v2
uint32_t fTempOrg; // v2
uint8_t fLoadName[kLoadNameLen + 1]; // 10 chars, space-padded
uint8_t fSegName[kSegNameLen + 1]; // 1-255 chars
};
#if 0
/*
* Constants and functions for manipulating an OMF segment.
*/
class OMFSegment {
public:
OMFSegment(void) : fSegBuf(NULL), fSegLen(-1), fCurPtr(NULL),
fNumLen(-1), fLabLen(-1)
{}
virtual ~OMFSegment(void) {}
void Setup(const OMFSegmentHeader* pSegHdr, const uint8_t* srcBuf,
long srcLen);
const uint8_t* ProcessNextChunk(void);
/*
* Segment op codes.
*/
typedef enum SegmentOp {
kSegOpEND = 0x00, // all
kSegOpCONSTStart = 0x01, // object
kSegOpCONSTEnd = 0xdf, // object
kSegOpALIGN = 0xe0, // object
kSegOpORG = 0xe1, // org
kSegOpRELOC = 0xe2, // load
kSegOpINTERSEG = 0xe3, // load
kSegOpUSING = 0xe4, // object
kSegOpSTRONG = 0xe5, // object
kSegOpGLOBAL = 0xe6, // object
kSegOpGEQU = 0xe7, // object
kSegOpMEM = 0xe8, // object
// 0xe9, 0xea unused
kSegOpEXPR = 0xeb, // object
kSegOpZEXPR = 0xec, // object
kSegOpBEXPR = 0xed, // object
kSegOpRELEXPR = 0xee, // object
kSegOpLOCAL = 0xef, // object
kSegOpEQU = 0xf0, // object
kSegOpDS = 0xf1, // all
kSegOpLCONST = 0xf2, // all
kSegOpLEXPR = 0xf3, // object
kSegOpENTRY = 0xf4, // RTL
kSegOpcRELOC = 0xf5, // load
kSegOpcINTERSEG = 0xf6, // load
kSegOpSUPER = 0xf7, // load
kSegOpGeneral = 0xfb, // reserved
kSegOpExperimental1 = 0xfc, // reserved
kSegOpExperimental2 = 0xfd, // reserved
kSegOpExperimental3 = 0xfe, // reserved
kSegOpExperimental4 = 0xff, // reserved
} SegmentOp;
/*
* Expression operands.
*/
typedef enum ExprOp {
kExprOpEnd = 0x00,
kExprOpAddition = 0x01,
kExprOpSubtraction = 0x02,
kExprOpMultiplication = 0x03,
kExprOpDivision = 0x04,
kExprOpIntegerRemainder = 0x05,
kExprOpUnaryNegation = 0x06,
kExprOpBitShift = 0x07,
kExprOpAND = 0x08,
kExprOpOR = 0x09,
kExprOpEOR = 0x0a,
kExprOpNOT = 0x0b,
kExprOpLessThenEqualTo = 0x0c,
kExprOpGreaterThanEqualTo = 0x0d,
kExprOpNotEqual = 0x0e,
kExprOpLessThan = 0x0f,
kExprOpGreaterThan = 0x10,
kExprOpEqualTo = 0x11,
kExprOpBitAND = 0x12,
kExprOpBitOR = 0x13,
kExprOpBitEOR = 0x14,
kExprOpBitNOT = 0x15,
kExprOpPushLocation = 0x80,
kExprOpPushConstant = 0x81,
kExprOpPushLabelWeak = 0x82,
kExprOpPushLabelValue = 0x83,
kExprOpPushLabelLength = 0x84,
kExprOpPushLabelType = 0x85,
kExprOpPushLabelCount = 0x86,
kExprOpPushRelOffset = 0x87,
} ExprOp;
private:
// inline uint16_t Get16LE(const uint8_t* buf) {
// return *buf | *(buf+1) << 8;
// }
inline uint32_t Get32LE(const uint8_t* buf) {
return *buf | *(buf+1) << 8 | *(buf+2) << 16 | *(buf+3) << 24;
}
/*
* Given a pointer to the start of a label, return the label's len.
* The length returned includes the length byte (if present).
*/
int LabelLength(const uint8_t* ptr) {
if (fLabLen != 0)
return fLabLen;
else
return (*ptr) +1;
}
/* determine the length of an expression */
int ExpressionLength(const uint8_t* ptr);
const uint8_t* fSegBuf;
long fSegLen;
const uint8_t* fCurPtr;
int fNumLen;
int fLabLen;
};
#endif
/*
* 16-bit code disassembly.
*
* Used for GS/OS S16, EXE, PIF, and others.
*
* OMF information comes from Appendix F of the Apple IIgs GS/OS Reference
* manual and Appendix B of the Orca/M assembler manual.
*/
class ReformatDisasm16 : public ReformatDisasm65xxx {
public:
ReformatDisasm16(void) {}
virtual ~ReformatDisasm16(void) {}
virtual void Examine(ReformatHolder* pHolder) override;
virtual int Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput) override;
private:
void OutputSection(const uint8_t* srcBuf, long srcLen,
uint32_t addr, bool shortRegs);
bool OutputOMF(const uint8_t* srcBuf, long srcLen,
long fileType, bool shortRegs);
void PrintHeader(const OMFSegmentHeader* pSegHdr,
int segmentNumber, bool longFmt);
void PrintSegment(const OMFSegmentHeader* pSegHdr,
const uint8_t* srcBuf, long srcLen, bool shortRegs);
};
#endif /*REFORMAT_DISASM_H*/