mirror of
https://github.com/fadden/6502bench.git
synced 2024-11-25 14:34:27 +00:00
Progress toward OMF file handling
Added parsing of records from OMF segment bodies. These are displayed in the segment viewer window.
This commit is contained in:
parent
d1526e5f25
commit
fa500a2a49
@ -86,6 +86,7 @@
|
||||
</Compile>
|
||||
<Compile Include="Tools\ApplesoftToHtml.cs" />
|
||||
<Compile Include="Tools\Omf\OmfFile.cs" />
|
||||
<Compile Include="Tools\Omf\OmfRecord.cs" />
|
||||
<Compile Include="Tools\Omf\OmfSegment.cs" />
|
||||
<Compile Include="Tools\Omf\WpfGui\OmfSegmentViewer.xaml.cs">
|
||||
<DependentUpon>OmfSegmentViewer.xaml</DependentUpon>
|
||||
|
@ -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 {
|
||||
/// <remarks>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// 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<OmfSegment> mSegmentList = new List<OmfSegment>();
|
||||
@ -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<string> firstFail = new List<string>(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<string> msgs = new List<string>();
|
||||
|
||||
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;
|
||||
|
505
SourceGen/Tools/Omf/OmfRecord.cs
Normal file
505
SourceGen/Tools/Omf/OmfRecord.cs
Normal file
@ -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 {
|
||||
/// <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>
|
||||
/// 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();
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
/// </remarks>
|
||||
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<OmfRecord> Records = new List<OmfRecord>();
|
||||
|
||||
|
||||
// Constructor is private; use ParseHeader() to create an instance.
|
||||
private OmfSegment() { }
|
||||
|
||||
public enum ParseResult {
|
||||
@ -165,7 +169,7 @@ namespace SourceGen.Tools.Omf {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an OMF segment header.
|
||||
/// Parses an OMF segment header. If successful, a new OmfSegment object is created.
|
||||
/// </summary>
|
||||
/// <param name="data">File data.</param>
|
||||
/// <param name="offset">Offset at which to start parsing.</param>
|
||||
@ -174,7 +178,7 @@ namespace SourceGen.Tools.Omf {
|
||||
/// <param name="msgs">Notes and errors generated by the parser.</param>
|
||||
/// <param name="segResult">Completed object, or null on failure.</param>
|
||||
/// <returns>Result code.</returns>
|
||||
public static ParseResult ParseSegment(byte[] data, int offset, bool parseAsLibrary,
|
||||
public static ParseResult ParseHeader(byte[] data, int offset, bool parseAsLibrary,
|
||||
List<string> 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<string> 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<string> msgs, int offset, string msg) {
|
||||
public static void AddInfoMsg(List<string> msgs, int offset, string msg) {
|
||||
msgs.Add("Note (+" + offset.ToString("x6") + "): " + msg);
|
||||
}
|
||||
private static void AddErrorMsg(List<string> msgs, int offset, string msg) {
|
||||
public static void AddErrorMsg(List<string> msgs, int offset, string msg) {
|
||||
msgs.Add("Error (+" + offset.ToString("x6") + "): " + msg);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ limitations under the License.
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
|
||||
<system:String x:Key="str_FileOffsetLenFmt">File offset {0}, length {1} ({2})</system:String>
|
||||
<system:String x:Key="str_RecordHeaderFmt">Records ({0}):</system:String>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="8">
|
||||
@ -44,7 +45,7 @@ limitations under the License.
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{Binding FileOffsetLen, FallbackValue=File Offset / Length}" Margin="0,8,0,0"/>
|
||||
<TextBlock Grid.Row="0" Text="{Binding FileOffsetLenStr, FallbackValue=File Offset / Length}" Margin="0,8,0,0"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Text="Header fields:" Margin="0,8,0,0"/>
|
||||
<DataGrid Name="headerList" Grid.Row="2" Height="130" Margin="0,8,0,0"
|
||||
@ -73,7 +74,7 @@ limitations under the License.
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<TextBlock Grid.Row="3" Text="Records {N}:" Margin="0,8,0,0"/>
|
||||
<TextBlock Grid.Row="3" Text="{Binding RecordHeaderStr, FallbackValue=Records (123):}" Margin="0,8,0,0"/>
|
||||
<DataGrid Name="recordList" Grid.Row="4" Height="130" Margin="0,8,0,0"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding RecordItems}"
|
||||
@ -94,9 +95,9 @@ limitations under the License.
|
||||
Color="{x:Static SystemColors.HighlightTextColor}"/>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Type" Width="100" Binding="{Binding Type}"/>
|
||||
<DataGridTextColumn Header="Len" Width="40" Binding="{Binding Len}"/>
|
||||
<DataGridTextColumn Header="Value" Width="300" Binding="{Binding Value}"/>
|
||||
<DataGridTextColumn Header="OpName" Width="80" Binding="{Binding OpName}"/>
|
||||
<DataGridTextColumn Header="Length" Width="80" Binding="{Binding Length}"/>
|
||||
<DataGridTextColumn Header="Value" Width="380" Binding="{Binding Value}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
|
@ -26,7 +26,7 @@ namespace SourceGen.Tools.Omf.WpfGui {
|
||||
/// Apple IIgs OMF segment viewer.
|
||||
/// </summary>
|
||||
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<HeaderItem> HeaderItems { get; private set; } = new List<HeaderItem>();
|
||||
|
||||
public class RecordItem {
|
||||
public string Type { get; private set; }
|
||||
public string Len { get; private set; }
|
||||
public string Value { get; private set; }
|
||||
}
|
||||
public List<RecordItem> RecordItems { get; private set; } = new List<RecordItem>();
|
||||
public List<OmfRecord> 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() {
|
||||
|
@ -75,7 +75,7 @@ limitations under the License.
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<TextBlock Grid.Row="3" Text="Notes and errors:" Margin="0,8,0,0"/>
|
||||
<TextBlock Grid.Row="3" Text="Notes and error messages:" Margin="0,8,0,0"/>
|
||||
<TextBox Grid.Row="4" Margin="0,4,0,0" Height="60"
|
||||
Text="{Binding MessageStrings}"
|
||||
IsReadOnly="True" VerticalScrollBarVisibility="Auto">
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user