1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-27 18:29:30 +00:00
6502bench/SourceGen/Tools/Omf/OmfRecord.cs
Andy McFadden 463a0cc561 Progress toward OMF file handling
Added generation of the relocation dictionary and constant body for
segments in Load files.

Also, don't reject files with v1 segments (whose length is specified
as a block count) just because the EOF isn't a multiple of 512 bytes.
Some executables don't pad out the last block.

Various tweaks to output formatting.
2020-06-28 21:10:53 -07:00

519 lines
22 KiB
C#

/*
* Copyright 2020 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Asm65;
using CommonUtil;
namespace SourceGen.Tools.Omf {
/// <summary>
/// Apple IIgs OMF record.
/// </summary>
public class OmfRecord {
private const int NUMLEN = 4; // defined by NUMLEN field in header; always 4 for IIgs
/// <summary>
/// Offset of record start within file.
/// </summary>
public int FileOffset { get; private set; }
/// <summary>
/// Total length, in bytes, of this record.
/// </summary>
public int Length { get; private set; }
/// <summary>
/// Opcode.
/// </summary>
public Opcode Op { get; private set; }
/// <summary>
/// Opcode, in human-readable form.
/// </summary>
public string OpName { get; private set; }
/// <summary>
/// Value, in human-readable form.
/// </summary>
public string Value { get; private set; }
/// <summary>
/// Opcode byte definition.
/// </summary>
/// <remarks>
/// Nearly all of the 256 possible values are assigned. 5 are unused, 5 are reserved.
/// </remarks>
public enum Opcode : byte {
END = 0x00, // all
CONST_start = 0x01, // object
CONST_end = 0xdf, // object
ALIGN = 0xe0, // object
ORG = 0xe1, // object
RELOC = 0xe2, // load
INTERSEG = 0xe3, // load
USING = 0xe4, // object
STRONG = 0xe5, // object
GLOBAL = 0xe6, // object
GEQU = 0xe7, // object
MEM = 0xe8, // object ("not needed or supported on the Apple IIgs")
unused_e9 = 0xe9,
unused_ea = 0xea,
EXPR = 0xeb, // object
ZEXPR = 0xec, // object
BEXPR = 0xed, // object
RELEXPR = 0xee, // object
LOCAL = 0xef, // object
EQU = 0xf0, // object
DS = 0xf1, // all
LCONST = 0xf2, // all
LEXPR = 0xf3, // object
ENTRY = 0xf4, // RTL
cRELOC = 0xf5, // load
cINTERSEG = 0xf6, // load
SUPER = 0xf7, // load
unused_f8 = 0xf8,
unused_f9 = 0xf9,
unused_fa = 0xfa,
General = 0xfb, // reserved
Experimental1 = 0xfc, // reserved
Experimental2 = 0xfd, // reserved
Experimental3 = 0xfe, // reserved
Experimental4 = 0xff, // reserved
}
private OmfRecord() { }
/// <summary>
/// Creates a new OmfRecord instance from the data at the specified offset.
/// </summary>
/// <remarks>
/// This does not catch segment boundary overruns, unless they happen to overrun
/// the buffer entirely. The caller should either pass in a buffer that holds the
/// exact segment data, or check the return value for excess length.
/// </remarks>
/// <param name="data">Data to analyze.</param>
/// <param name="offset">Offset of start of record.</param>
/// <param name="version">OMF segment version number.</param>
/// <param name="labLen">Label length, defined in OMF segment header.</param>
/// <param name="msgs">Output message holder.</param>
/// <param name="omfRec">New record instance.</param>
/// <returns>True on success.</returns>
public static bool ParseRecord(byte[] data, int offset,
OmfSegment.SegmentVersion version, int labLen, Formatter formatter,
List<string> msgs, out OmfRecord omfRec) {
omfRec = new OmfRecord();
omfRec.FileOffset = offset;
try {
return omfRec.DoParseRecord(data, offset, version, labLen, formatter, msgs);
} catch (IndexOutOfRangeException ioore) {
OmfSegment.AddErrorMsg(msgs, offset, "buffer overrun while parsing record");
Debug.WriteLine("Exception thrown decoding record: " + ioore.Message);
return false;
}
}
/// <summary>
/// Parses OMF record data.
/// </summary>
/// <param name="data">Data to analyze.</param>
/// <param name="offset">Offset of start of record.</param>
/// <param name="version">OMF segment version number.</param>
/// <param name="labLen">Label length, defined in OMF segment header.</param>
/// <param name="msgs">Output message holder.</param>
/// <returns>Parse result code.</returns>
private bool DoParseRecord(byte[] data, int offset,
OmfSegment.SegmentVersion version, int labLen, Formatter formatter,
List<string> msgs) {
int len = 1; // 1 byte for the opcode
Opcode opcode = Op = (Opcode)data[offset++];
OpName = opcode.ToString();
Value = string.Empty;
if (opcode >= Opcode.CONST_start && opcode <= Opcode.CONST_end) {
// length determined by opcode value
int count = (int)opcode;
len += count;
OpName = "CONST";
Value = count + " bytes of data";
} else {
switch (opcode) {
case Opcode.END:
break;
case Opcode.ALIGN: {
int val = GetNum(data, ref offset, ref len);
Value = formatter.FormatHexValue(val, 6);
}
break;
case Opcode.ORG: {
int val = GetNum(data, ref offset, ref len);
Value = "loc " + formatter.FormatAdjustment(val);
}
break;
case Opcode.RELOC: {
len += 1 + 1 + 4 + 4; // 10
int width = data[offset];
int operandOff = RawData.GetWord(data, offset + 2, 4, false);
Value = width + " bytes @" + formatter.FormatHexValue(operandOff, 4);
}
break;
case Opcode.INTERSEG: {
len += 1 + 1 + 4 + 2 + 2 + 4; // 14
int width = data[offset];
int operandOff = RawData.GetWord(data, offset + 2, 4, false);
int segNum = RawData.GetWord(data, offset + 8, 2, false);
Value = width + " bytes @" + formatter.FormatHexValue(operandOff, 4) +
" (seg " + segNum + ")";
}
break;
case Opcode.USING:
case Opcode.STRONG: {
string label = GetLabel(data, ref offset, ref len, labLen);
Value = "'" + label + "'";
}
break;
case Opcode.GLOBAL:
case Opcode.LOCAL: {
string label = GetLabel(data, ref offset, ref len, labLen);
int bytes;
byte type;
byte priv = 0;
if (version == OmfSegment.SegmentVersion.v0_0) {
bytes = data[offset];
type = data[offset + 1];
offset += 2;
len += 2;
} else if (version == OmfSegment.SegmentVersion.v1_0) {
bytes = data[offset];
type = data[offset + 1];
priv = data[offset + 2];
offset += 3;
len += 3;
} else {
bytes = RawData.GetWord(data, offset, 2, false);
type = data[offset + 2];
priv = data[offset + 3];
offset += 4;
len += 4;
}
Value = (char)type + " '" + label + "' " +
formatter.FormatHexValue(bytes, 4) +
((priv == 0) ? "" : " private");
}
break;
case Opcode.GEQU:
case Opcode.EQU: {
string label = GetLabel(data, ref offset, ref len, labLen);
int bytes;
byte type;
byte priv = 0;
if (version == OmfSegment.SegmentVersion.v0_0) {
bytes = data[offset];
type = data[offset + 1];
offset += 2;
len += 2;
} else if (version == OmfSegment.SegmentVersion.v1_0) {
bytes = data[offset];
type = data[offset + 1];
priv = data[offset + 2];
offset += 3;
len += 3;
} else {
bytes = RawData.GetWord(data, offset, 2, false);
type = data[offset + 2];
priv = data[offset + 3];
offset += 4;
len += 4;
}
string expr = GetExpression(data, ref offset, ref len, labLen,
formatter, msgs);
Value = (char)type + " '" + label + "' " +
formatter.FormatHexValue(bytes, 4) +
((priv == 0) ? "" : " private") + " = " + expr;
}
break;
case Opcode.MEM: {
int addr1 = GetNum(data, ref offset, ref len);
int addr2 = GetNum(data, ref offset, ref len);
Value = formatter.FormatHexValue(addr1, 4) + ", " +
formatter.FormatHexValue(addr2, 4);
}
break;
case Opcode.EXPR:
case Opcode.ZEXPR:
case Opcode.BEXPR:
case Opcode.LEXPR: {
int cap = data[offset++];
len++;
string expr = GetExpression(data, ref offset, ref len, labLen,
formatter, msgs);
Value = "(" + cap + ") " + expr;
}
break;
case Opcode.RELEXPR: {
int cap = data[offset++];
len++;
int rel = GetNum(data, ref offset, ref len);
string expr = GetExpression(data, ref offset, ref len, labLen,
formatter, msgs);
Value = "(" + cap + ") " + formatter.FormatAdjustment(rel) + " " + expr;
}
break;
case Opcode.DS: {
int count = GetNum(data, ref offset, ref len);
Value = count + " bytes of $00";
}
break;
case Opcode.LCONST: {
int count = GetNum(data, ref offset, ref len);
len += count;
Value = count + " bytes of data";
}
break;
case Opcode.cRELOC: {
len += 1 + 1 + 2 + 2; // 6
int width = data[offset];
int operandOff = RawData.GetWord(data, offset + 2, 2, false);
Value = width + " bytes @" + formatter.FormatHexValue(operandOff, 4);
}
break;
case Opcode.cINTERSEG: {
len += 1 + 1 + 2 + 1 + 2; // 7
int width = data[offset];
int operandOff = RawData.GetWord(data, offset + 2, 2, false);
int segNum = data[offset + 4];
Value = width + " bytes @" + formatter.FormatHexValue(operandOff, 4) +
" (seg " + segNum + ")";
}
break;
case Opcode.SUPER: {
int count = GetNum(data, ref offset, ref len);
int type = data[offset];
len += count; // count includes type byte
Value = (count - 1) + " bytes, type=" +
formatter.FormatHexValue(type, 2);
if (type > 37) {
OmfSegment.AddErrorMsg(msgs, offset,
"found SUPER record with bogus type=$" + type.ToString("x2"));
// the length field allows us to skip it, so keep going
}
}
break;
case Opcode.General:
case Opcode.Experimental1:
case Opcode.Experimental2:
case Opcode.Experimental3:
case Opcode.Experimental4: {
OmfSegment.AddInfoMsg(msgs, offset, "found unusual record type " +
formatter.FormatHexValue((int)opcode, 2));
int count = GetNum(data, ref offset, ref len);
len += count;
}
break;
case Opcode.unused_e9:
case Opcode.unused_ea:
case Opcode.unused_f8:
case Opcode.unused_f9:
case Opcode.unused_fa:
// These are undefined, can't be parsed.
default:
Debug.Assert(false);
return false;
}
}
Length = len;
//Debug.WriteLine("REC +" + (offset-1).ToString("x6") + " " + this);
return true;
}
private static int GetNum(byte[] data, ref int offset, ref int len) {
int val = RawData.GetWord(data, offset, NUMLEN, false);
offset += NUMLEN;
len += NUMLEN;
return val;
}
private static string GetLabel(byte[] data, ref int offset, ref int len, int labLen) {
if (labLen == 0) {
labLen = data[offset++];
len++;
}
string str = Encoding.ASCII.GetString(data, offset, labLen).Trim();
offset += labLen;
len += labLen;
return str;
}
/// <summary>
/// Expression operations.
/// </summary>
private enum ExprOp : byte {
End = 0x00,
Addition = 0x01,
Subtraction = 0x02,
Multiplication = 0x03,
Division = 0x04,
IntegerRemainder = 0x05,
UnaryNegation = 0x06,
BitShift = 0x07,
AND = 0x08,
OR = 0x09,
EOR = 0x0a,
NOT = 0x0b,
LessThenEqualTo = 0x0c,
GreaterThanEqualTo = 0x0d,
NotEqual = 0x0e,
LessThan = 0x0f,
GreaterThan = 0x10,
EqualTo = 0x11,
BitAND = 0x12,
BitOR = 0x13,
BitEOR = 0x14,
BitNOT = 0x15,
PushLocation = 0x80,
PushConstant = 0x81,
PushLabelWeak = 0x82,
PushLabelValue = 0x83,
PushLabelLength = 0x84,
PushLabelType = 0x85,
PushLabelCount = 0x86,
PushRelOffset = 0x87,
}
private static readonly string[] ExprStrs = new string[] {
string.Empty, // 0x00 End
"+", // 0x01 Addition
"-", // 0x02 Subtraction
"*", // 0x03 Multiplication
"/", // 0x04 Division
"%", // 0x05 Integer Remainder
"neg", // 0x06 Unary Negation
"shift", // 0x07 Bit Shift
"&&", // 0x08 AND
"||", // 0x09 OR
"^^", // 0x0a EOR
"!", // 0x0b NOT
"<=", // 0x0c LE
">=", // 0x0d GE
"!=", // 0x0e NE
"<", // 0x0f LT
">", // 0x10 GT
"==", // 0x11 EQ
"&", // 0x12 Bit AND
"|", // 0x13 Bit OR
"^", // 0x14 Bit EOR
"~", // 0x15 Bit NOT
};
private static string GetExpression(byte[] data, ref int offset, ref int len, int labLen,
Formatter formatter, List<string> msgs) {
StringBuilder sb = new StringBuilder();
bool done = false;
while (!done) {
byte operVal = data[offset++];
len++;
// Generate an operand string, if appropriate.
if (operVal > 0 && operVal < ExprStrs.Length) {
sb.Append(' ');
sb.Append(ExprStrs[operVal]);
} else {
ExprOp oper = (ExprOp)operVal;
switch (oper) {
case ExprOp.End:
done = true;
break;
case ExprOp.PushLocation:
sb.Append(" [loc]");
break;
case ExprOp.PushConstant: {
int val = GetNum(data, ref offset, ref len);
sb.Append(' ');
sb.Append(formatter.FormatHexValue(val, 4));
}
break;
case ExprOp.PushLabelWeak: {
string label = GetLabel(data, ref offset, ref len, labLen);
sb.Append(" weak:'");
sb.Append(label);
sb.Append("'");
}
break;
case ExprOp.PushLabelValue: {
string label = GetLabel(data, ref offset, ref len, labLen);
sb.Append(" '");
sb.Append(label);
sb.Append("'");
}
break;
case ExprOp.PushLabelLength: {
string label = GetLabel(data, ref offset, ref len, labLen);
sb.Append(" len:'");
sb.Append(label);
sb.Append("'");
}
break;
case ExprOp.PushLabelType: {
string label = GetLabel(data, ref offset, ref len, labLen);
sb.Append(" typ:'");
sb.Append(label);
sb.Append("'");
}
break;
case ExprOp.PushLabelCount: {
string label = GetLabel(data, ref offset, ref len, labLen);
sb.Append(" cnt:'");
sb.Append(label);
sb.Append("'");
}
break;
case ExprOp.PushRelOffset: {
int adj = GetNum(data, ref offset, ref len);
sb.Append(" rel:");
sb.Append(formatter.FormatAdjustment(adj));
}
break;
default:
OmfSegment.AddErrorMsg(msgs, offset,
"Found unexpected expression operator " +
formatter.FormatHexValue((int)oper, 2));
sb.Append("???");
break;
}
}
}
if (sb.Length > 0) {
sb.Remove(0, 1); // remove leading space
}
return sb.ToString();
}
public override string ToString() {
return Length + " " + OpName + " " + Value;
}
}
}