From fa500a2a4952d7360bcba61501d038a115ea3bec Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sat, 27 Jun 2020 18:06:26 -0700 Subject: [PATCH] Progress toward OMF file handling Added parsing of records from OMF segment bodies. These are displayed in the segment viewer window. --- SourceGen/SourceGen.csproj | 1 + SourceGen/Tools/Omf/OmfFile.cs | 44 +- SourceGen/Tools/Omf/OmfRecord.cs | 505 ++++++++++++++++++ SourceGen/Tools/Omf/OmfSegment.cs | 82 ++- .../Tools/Omf/WpfGui/OmfSegmentViewer.xaml | 11 +- .../Tools/Omf/WpfGui/OmfSegmentViewer.xaml.cs | 32 +- SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml | 2 +- SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs | 2 +- 8 files changed, 614 insertions(+), 65 deletions(-) create mode 100644 SourceGen/Tools/Omf/OmfRecord.cs diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index a5bbf86..adf408f 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -86,6 +86,7 @@ + OmfSegmentViewer.xaml diff --git a/SourceGen/Tools/Omf/OmfFile.cs b/SourceGen/Tools/Omf/OmfFile.cs index deab4c3..2c5ff34 100644 --- a/SourceGen/Tools/Omf/OmfFile.cs +++ b/SourceGen/Tools/Omf/OmfFile.cs @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +using Asm65; using System; using System.Collections.Generic; using System.Diagnostics; @@ -24,14 +25,18 @@ namespace SourceGen.Tools.Omf { /// /// OMF files are a series of segments. There is no file header or identifying information. /// In some cases the length is expected to be a multiple of 512 bytes, in others it isn't. + /// Each segment is comprised of a header, followed by a series of records. /// /// There's no structural limitation on mixing and matching segments, whether different /// versions or different types. The file format provides a structure in which various /// things may be stored, but does not provide a way to tell an observer what is contained - /// within (the ProDOS file type is supposed to do that). + /// within (the ProDOS file type is supposed to do that). A given file may be a Load + /// file (handled by the System Loader), Object file (fed to a linker), Library file + /// (also fed to a linker), or Run-Time Library (used by both the linker and the loader). /// /// References: - /// - (OMF "v0" is documented in an Orca/M manual?) + /// - IIgs Orca/M 2.0 manual. Appendix B documents OMF v0, v1, and v2.1 Load files. + /// (This is included with Opus ][.) /// - "Apple IIgs Programmer's Workshop Reference". Chapter 7, page 228, describes /// OMF v1.0 and v2.0. /// - "Apple IIgs GS/OS Reference, for GS/OS System Software Version 5.0 and later". @@ -50,22 +55,6 @@ namespace SourceGen.Tools.Omf { public const int MIN_FILE_SIZE = OmfSegment.MIN_HEADER_V0; public const int MAX_FILE_SIZE = (1 << 24) - 1; // cap at 16MB - // TODO: - // - has an overall file type (load, object, RTL) - // - determine with a prioritized series of "could this be ____" checks - // - holds list of OmfSegment - // - has a list of warnings and errors that arose during parsing - // - holds on to byte[] with data - // OmfSegment: - // - header (common data, plus name/value dict with version-specific fields for display) - // - ref back to OmfFile for byte[] access? - // - list of OmfRecord - // - file-type-specific stuff can be generated and cached in second pass, e.g. - // generate a full relocation dictionary for load files (can't do this until we - // know the overall file type, which we can't know until all segments have been - // processed a bit) - - private byte[] mFileData; /// /// Overall file contents, determined by analysis. @@ -81,6 +70,7 @@ namespace SourceGen.Tools.Omf { } public FileKind OmfFileKind { get; private set; } + private byte[] mFileData; private bool mIsDamaged; private List mSegmentList = new List(); @@ -102,13 +92,13 @@ namespace SourceGen.Tools.Omf { OmfFileKind = FileKind.Unknown; } - public void Analyze() { - OmfSegment.ParseResult result = DoAnalyze(false); + public void Analyze(Formatter formatter) { + OmfSegment.ParseResult result = DoAnalyze(formatter, false); if (result == OmfSegment.ParseResult.IsLibrary || result == OmfSegment.ParseResult.Failure) { // Failed; try again as a library. List firstFail = new List(MessageList); - result = DoAnalyze(true); + result = DoAnalyze(formatter, true); if (result == OmfSegment.ParseResult.Failure) { // Failed both ways. Report the failures from the non-library attempt. MessageList = firstFail; @@ -116,7 +106,7 @@ namespace SourceGen.Tools.Omf { } } - private OmfSegment.ParseResult DoAnalyze(bool parseAsLibrary) { + private OmfSegment.ParseResult DoAnalyze(Formatter formatter, bool parseAsLibrary) { bool first = true; int offset = 0; int len = mFileData.Length; @@ -124,8 +114,15 @@ namespace SourceGen.Tools.Omf { List msgs = new List(); while (len > 0) { - OmfSegment.ParseResult result = OmfSegment.ParseSegment(mFileData, offset, + OmfSegment.ParseResult result = OmfSegment.ParseHeader(mFileData, offset, parseAsLibrary, msgs, out OmfSegment seg); + if (result == OmfSegment.ParseResult.Success) { + if (!seg.ParseBody(formatter, msgs)) { + OmfSegment.AddErrorMsg(msgs, offset, "parsing of segment " + + seg.SegNum + " '" + seg.SegName + "' incomplete"); + //result = OmfSegment.ParseResult.Failure; + } + } MessageList.Clear(); foreach (string str in msgs) { @@ -149,6 +146,7 @@ namespace SourceGen.Tools.Omf { first = false; Debug.Assert(seg.FileLength > 0); + mSegmentList.Add(seg); offset += seg.FileLength; len -= seg.FileLength; diff --git a/SourceGen/Tools/Omf/OmfRecord.cs b/SourceGen/Tools/Omf/OmfRecord.cs new file mode 100644 index 0000000..f66f98a --- /dev/null +++ b/SourceGen/Tools/Omf/OmfRecord.cs @@ -0,0 +1,505 @@ +/* + * 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.Security.Cryptography; +using System.Text; +using Asm65; +using CommonUtil; + +namespace SourceGen.Tools.Omf { + /// + /// Apple IIgs OMF record. + /// + public class OmfRecord { + private const int NUMLEN = 4; // defined by NUMLEN field in header; always 4 for IIgs + + /// + /// Total length, in bytes, of this record. + /// + public int Length { get; private set; } + + /// + /// Opcode. + /// + public Opcode Op { get; private set; } + + /// + /// Opcode, in human-readable form. + /// + public string OpName { get; private set; } + + /// + /// Value, in human-readable form. + /// + public string Value { get; private set; } + + /// + /// Opcode byte definition. + /// + /// + /// Nearly all of the 256 possible values are assigned. 5 are unused, 5 are reserved. + /// + 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() { } + + /// + /// Creates a new OmfRecord instance from the data at the specified offset. + /// + /// + /// 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. + /// + /// Data to analyze. + /// Offset of start of record. + /// OMF segment version number. + /// Label length, defined in OMF segment header. + /// Output message holder. + /// New record instance. + /// True on success. + public static bool ParseRecord(byte[] data, int offset, + OmfSegment.SegmentVersion version, int labLen, Formatter formatter, + List msgs, out OmfRecord omfRec) { + omfRec = new OmfRecord(); + 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; + } + } + + /// + /// Parses OMF record data. + /// + /// Data to analyze. + /// Offset of start of record. + /// OMF segment version number. + /// Label length, defined in OMF segment header. + /// Output message holder. + /// Parse result code. + private bool DoParseRecord(byte[] data, int offset, + OmfSegment.SegmentVersion version, int labLen, Formatter formatter, + List 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); + len += count; + Value = (count - 1) + " bytes, type=" + + formatter.FormatHexValue(data[offset + NUMLEN], 2); + } + 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; + } + + + /// + /// Expression operations. + /// + 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 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; + } + } +} diff --git a/SourceGen/Tools/Omf/OmfSegment.cs b/SourceGen/Tools/Omf/OmfSegment.cs index 147437a..aa696ec 100644 --- a/SourceGen/Tools/Omf/OmfSegment.cs +++ b/SourceGen/Tools/Omf/OmfSegment.cs @@ -15,13 +15,10 @@ */ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; -using System.Linq; using System.Text; -using System.Threading.Tasks; -using System.Windows.Input; +using Asm65; using CommonUtil; namespace SourceGen.Tools.Omf { @@ -42,7 +39,7 @@ namespace SourceGen.Tools.Omf { /// /// Most IIgs binaries are v1.0 or v2.0. /// - /// You'd hope that parsing a segment would be unambiguous, but that is not the case. + /// You'd hope that parsing segments would be unambiguous, but that is not the case. /// From the same reference: /// /// "In Version 1.0, [the first] field is described as follows. For object files @@ -59,7 +56,7 @@ namespace SourceGen.Tools.Omf { /// /// Documentation bugs: /// - GS/OS ref: table F-2 says "blockCount" where it should say "SEGNAME", and shows the - /// offset of tempOrg as $2a (should be $2c). + /// offset of "tempOrg" as $2a (should be $2c). /// - GS/OS ref: appendix F refers to a "REVISION" field, which does not seem to exist. /// public class OmfSegment { @@ -70,7 +67,7 @@ namespace SourceGen.Tools.Omf { public const int MIN_HEADER_V1 = MIN_HEADER_V0 + 8 + LOAD_NAME_LEN; // v2.0: Updated IIgs OMF format. Removes LCBANK, redefines KIND, and embraces BYTECNT. public const int MIN_HEADER_V2 = MIN_HEADER_V1 + 4; - // v2.1: adds tempORG and a couple of attribute flags. No "min" constant needed. + // v2.1: adds TEMPORG and a couple of attribute flags. No "min" constant needed. // Length of LOADNAME field. private const int LOAD_NAME_LEN = 10; @@ -122,15 +119,17 @@ namespace SourceGen.Tools.Omf { Dynamic = 0x8000 // } + private byte[] mFileData; + // - // Header fields. + // Header fields and header-derived values. // public int FileOffset { get; private set; } public int FileLength { get; private set; } // from BLKCNT or BYTECNT + public int ResSpc { get; private set; } public int Length { get; private set; } - public int Type { get; private set; } public int LabLen { get; private set; } public SegmentVersion Version { get; private set; } public int BankSize { get; private set; } @@ -141,7 +140,8 @@ namespace SourceGen.Tools.Omf { public int LcBank { get; private set; } // v1.0 only public int SegNum { get; private set; } public int Entry { get; private set; } - public int TempOrg { get; private set; } // v2.1 only + public int DispData { get; private set; } + public int TempOrg { get; private set; } // v2.1; only used by MPW IIgs public string LoadName { get; private set; } // unused in load segments public string SegName { get; private set; } @@ -155,6 +155,10 @@ namespace SourceGen.Tools.Omf { // "The BANKSIZE and align restrictions are enforced by the linker, and violations // of them are unlikely in a load file." + public List Records = new List(); + + + // Constructor is private; use ParseHeader() to create an instance. private OmfSegment() { } public enum ParseResult { @@ -165,7 +169,7 @@ namespace SourceGen.Tools.Omf { } /// - /// Parses an OMF segment header. + /// Parses an OMF segment header. If successful, a new OmfSegment object is created. /// /// File data. /// Offset at which to start parsing. @@ -174,7 +178,7 @@ namespace SourceGen.Tools.Omf { /// Notes and errors generated by the parser. /// Completed object, or null on failure. /// Result code. - public static ParseResult ParseSegment(byte[] data, int offset, bool parseAsLibrary, + public static ParseResult ParseHeader(byte[] data, int offset, bool parseAsLibrary, List msgs, out OmfSegment segResult) { segResult = null; @@ -188,6 +192,7 @@ namespace SourceGen.Tools.Omf { } OmfSegment newSeg = new OmfSegment(); + newSeg.mFileData = data; newSeg.FileOffset = offset; // Start with the version number. The meaning of everything else depends on this. @@ -229,20 +234,20 @@ namespace SourceGen.Tools.Omf { newSeg.Org = RawData.GetWord(data, offset + 0x18, 4, false); newSeg.Align = RawData.GetWord(data, offset + 0x1c, 4, false); int numSex = data[offset + 0x20]; - int dispName, dispData; + int dispName; if (newSeg.Version == SegmentVersion.v0_0) { dispName = 0x24; if (newSeg.LabLen == 0) { - dispData = dispName + data[offset + dispName]; + newSeg.DispData = dispName + data[offset + dispName]; } else { - dispData = dispName + LOAD_NAME_LEN; + newSeg.DispData = dispName + LOAD_NAME_LEN; } } else { newSeg.LcBank = data[offset + 0x21]; newSeg.SegNum = RawData.GetWord(data, offset + 0x22, 2, false); newSeg.Entry = RawData.GetWord(data, offset + 0x24, 4, false); dispName = RawData.GetWord(data, offset + 0x28, 2, false); - dispData = RawData.GetWord(data, offset + 0x2a, 2, false); + newSeg.DispData = RawData.GetWord(data, offset + 0x2a, 2, false); } // The only way to detect a v2.1 segment is by checking DISPNAME. @@ -349,8 +354,9 @@ namespace SourceGen.Tools.Omf { expectedDispName + ", segLen=" + segLen + ")"); return ParseResult.Failure; } - if (dispData < expectedDispName + LOAD_NAME_LEN || dispData > (segLen - 1)) { - AddErrorMsg(msgs, offset, "invalid DISPDATA " + dispData + " (expected " + + if (newSeg.DispData < expectedDispName + LOAD_NAME_LEN || + newSeg.DispData > (segLen - 1)) { + AddErrorMsg(msgs, offset, "invalid DISPDATA " + newSeg.DispData + " (expected " + (expectedDispName + LOAD_NAME_LEN) + ", segLen=" + segLen + ")"); return ParseResult.Failure; } @@ -463,9 +469,9 @@ namespace SourceGen.Tools.Omf { newSeg.AddRaw("SEGNUM", newSeg.SegNum, 2, string.Empty); newSeg.AddRaw("ENTRY", newSeg.Entry, 4, string.Empty); newSeg.AddRaw("DISPNAME", dispName, 2, string.Empty); - newSeg.AddRaw("DISPDATA", dispData, 2, string.Empty); + newSeg.AddRaw("DISPDATA", newSeg.DispData, 2, string.Empty); if (newSeg.Version >= SegmentVersion.v2_1) { - newSeg.AddRaw("tempORG", newSeg.TempOrg, 4, string.Empty); + newSeg.AddRaw("TEMPORG", newSeg.TempOrg, 4, string.Empty); } newSeg.AddRaw("LOADNAME", loadName, 10, string.Empty); } @@ -475,6 +481,38 @@ namespace SourceGen.Tools.Omf { return ParseResult.Success; } + public bool ParseBody(Formatter formatter, List msgs) { + int offset = FileOffset + DispData; + while (true) { + bool result = OmfRecord.ParseRecord(mFileData, offset, Version, LabLen, + formatter, msgs, out OmfRecord omfRec); + if (!result) { + // Parsing failure. Bail out. + return false; + } + if (offset + omfRec.Length > FileOffset + FileLength) { + // Overrun. + AddErrorMsg(msgs, offset, "record ran off end of file (" + omfRec + ")"); + return false; + } + + if (omfRec.Op == OmfRecord.Opcode.END) { + // v0/v1 pad to 512-byte block boundaries, so this is expected there, but v2.x + // should be snug. Doesn't have to be, but might indicate a parsing error. + int remaining = (FileOffset + FileLength) - (offset + omfRec.Length); + Debug.Assert(remaining >= 0); + Debug.WriteLine("END record found, remaining space=" + remaining); + if (remaining >= 512 || (Version >= SegmentVersion.v2_0 && remaining != 0)) { + AddInfoMsg(msgs, offset, "found " + remaining + " bytes past END record"); + } + return true; + } + + Records.Add(omfRec); + offset += omfRec.Length; + } + } + // // Helper functions. // @@ -485,10 +523,10 @@ namespace SourceGen.Tools.Omf { } RawValues.Add(new NameValueNote(name, value, width, note)); } - private static void AddInfoMsg(List msgs, int offset, string msg) { + public static void AddInfoMsg(List msgs, int offset, string msg) { msgs.Add("Note (+" + offset.ToString("x6") + "): " + msg); } - private static void AddErrorMsg(List msgs, int offset, string msg) { + public static void AddErrorMsg(List msgs, int offset, string msg) { msgs.Add("Error (+" + offset.ToString("x6") + "): " + msg); } diff --git a/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml b/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml index c9c59f0..ee59e74 100644 --- a/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml +++ b/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml @@ -30,6 +30,7 @@ limitations under the License. File offset {0}, length {1} ({2}) + Records ({0}): @@ -44,7 +45,7 @@ limitations under the License. - + - + - - - + + + diff --git a/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml.cs b/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml.cs index 7bfe3a6..c541e92 100644 --- a/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml.cs +++ b/SourceGen/Tools/Omf/WpfGui/OmfSegmentViewer.xaml.cs @@ -26,7 +26,7 @@ namespace SourceGen.Tools.Omf.WpfGui { /// Apple IIgs OMF segment viewer. /// public partial class OmfSegmentViewer : Window, INotifyPropertyChanged { - private OmfFile mOmfFile; + //private OmfFile mOmfFile; private OmfSegment mOmfSeg; private Formatter mFormatter; @@ -36,10 +36,16 @@ namespace SourceGen.Tools.Omf.WpfGui { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - private string mFileOffsetLen; - public string FileOffsetLen { - get { return mFileOffsetLen; } - set { mFileOffsetLen = value; OnPropertyChanged(); } + private string mFileOffsetLenStr; + public string FileOffsetLenStr { + get { return mFileOffsetLenStr; } + set { mFileOffsetLenStr = value; OnPropertyChanged(); } + } + + private string mRecordHeaderStr; + public string RecordHeaderStr { + get { return mRecordHeaderStr; } + set { mRecordHeaderStr = value; OnPropertyChanged(); } } public class HeaderItem { @@ -55,12 +61,7 @@ namespace SourceGen.Tools.Omf.WpfGui { } public List HeaderItems { get; private set; } = new List(); - public class RecordItem { - public string Type { get; private set; } - public string Len { get; private set; } - public string Value { get; private set; } - } - public List RecordItems { get; private set; } = new List(); + public List RecordItems { get; private set; } public class RelocItem { public string Offset { get; private set; } @@ -84,17 +85,22 @@ namespace SourceGen.Tools.Omf.WpfGui { Owner = owner; DataContext = this; - mOmfFile = omfFile; + //mOmfFile = omfFile; mOmfSeg = omfSeg; mFormatter = formatter; string fmt = (string)FindResource("str_FileOffsetLenFmt"); - FileOffsetLen = string.Format(fmt, + FileOffsetLenStr = string.Format(fmt, mFormatter.FormatOffset24(omfSeg.FileOffset), omfSeg.FileLength, mFormatter.FormatHexValue(omfSeg.FileLength, 4)); GenerateHeaderItems(); + + RecordItems = omfSeg.Records; + + fmt = (string)FindResource("str_RecordHeaderFmt"); + RecordHeaderStr = string.Format(fmt, RecordItems.Count); } private void GenerateHeaderItems() { diff --git a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml index fe34d0b..fd60f4c 100644 --- a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml +++ b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml @@ -75,7 +75,7 @@ limitations under the License. - + diff --git a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs index ba6f167..7740562 100644 --- a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs +++ b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs @@ -105,7 +105,7 @@ namespace SourceGen.Tools.Omf.WpfGui { mFormatter = formatter; mOmfFile = new OmfFile(data); - mOmfFile.Analyze(); + mOmfFile.Analyze(mFormatter); foreach (OmfSegment omfSeg in mOmfFile.SegmentList) { SegmentListItems.Add(new SegmentListItem(omfSeg));