1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-12-01 07:50:37 +00:00
6502bench/SourceGen/Tools/Omf/OmfSegment.cs
Andy McFadden bb7998d1f0 Progress toward OMF file handling
Added generation of data and project files.  We're applying the
relocation dictionary, but not using the information to inform the
formatting.
2020-06-30 08:20:12 -07:00

778 lines
34 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 file segment.
/// </summary>
/// <remarks>
/// Three versions of OMF were used for Apple IIgs binaries: v1.0, v2.0, and v2.1. (There's
/// also a "v0" used for older Orca/M 8-bit products.) The Apple IIgs Programmer's
/// Workshop Reference says:
///
/// "This section describes Version 2.0 of the Apple IIGS object module
/// format(OMF). The System Loader supports files written in either Version 2.0 or
/// Version 1.0 of the OMF. The APW Linker, however, creates load files that
/// conform to Version 1.0 of the OMF. Notes in this section describe the differences
/// between Version 1.0 and Version 2.0 of the OMF. The Compact utility program,
/// described in Chapter 3, converts load files from Version 1.0 to Version 2.0."
///
/// Most IIgs binaries are v1.0 or v2.0.
///
/// 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
/// and load files, BLKCNT is a 4-byte field containing the number of blocks in the file
/// that the segment requires. Each block is 512 bytes. The segment header is part of
/// the first block of the segment. Segments in an object file or load file start on block
/// boundaries. For library files (ProDOS 16 file type $B2), this field is BYTECNT,
/// indicating the number of bytes in the segment. Library-file segments are not
/// aligned to block boundaries."
///
/// This choice means it's impossible to unambiguously parse a v1 OMF file without knowing
/// its ProDOS file type, which we don't have access to. In most cases we can make a
/// reasonable guess.
///
/// 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).
/// - GS/OS ref: appendix F refers to a "REVISION" field, which does not seem to exist.
/// </remarks>
public class OmfSegment {
// v0.0: Original Orca/M OMF format. 0x24 bytes followed by variable-length SEGNAME.
public const int MIN_HEADER_V0 = 0x24 + 1;
// v1.0: Initial IIgs OMF format. Adds LCBANK, SEGNUM, ENTRY, DISPNAME, DISPDATA, and
// LOADNAME. Ambiguates BLKCNT/BYTECNT.
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.
// Length of LOADNAME field.
private const int LOAD_NAME_LEN = 10;
private const int DISK_BLOCK_SIZE = 512;
public class NameValueNote {
public string Name { get; private set; }
public object Value { get; private set; }
public int Width { get; private set; }
public string Note { get; private set; }
public NameValueNote(string name, object value, int width, string note) {
Name = name;
Value = value;
Width = width;
Note = note;
}
}
/// <summary>
/// Values pulled from file header. Useful for display.
/// </summary>
public List<NameValueNote> RawValues = new List<NameValueNote>();
/// <summary>
/// All known OMF versions.
/// </summary>
public enum SegmentVersion { v0_0, v1_0, v2_0, v2_1 }
/// <summary>
/// All known segment kinds.
/// </summary>
public enum SegmentKind {
Code = 0x00,
Data = 0x01,
JumpTable = 0x02,
PathName = 0x04,
LibraryDict = 0x08,
Init = 0x10,
AbsoluteBank = 0x11, // v1.0 only; became a flag
DpStack = 0x12
}
/// <summary>
/// Segment attribute flags, included in the Kind field.
/// </summary>
[Flags]
public enum SegmentAttribute {
BankRel = 0x0100, // v2.1
Skip = 0x0200, // v2.1
Reloadable = 0x0400, // v2.0
AbsBank = 0x0800, // v2.0
NoSpecial = 0x1000, // v2.0
PosnIndep = 0x2000, //
Private = 0x4000, //
Dynamic = 0x8000 //
}
private byte[] mFileData;
//
// Header fields and header-derived values.
//
public int FileOffset { get; private set; }
public int RawFileLength { get; private set; } // from BLKCNT or BYTECNT
public int FileLength { get; private set; } // last block may be short
public int ResSpc { get; private set; }
public int Length { get; private set; }
public int LabLen { get; private set; }
public SegmentVersion Version { get; private set; }
public int BankSize { get; private set; }
public SegmentKind Kind { get; private set; }
public SegmentAttribute Attrs { get; private set; }
public int Org { get; private set; }
public int Align { get; private set; }
public int LcBank { get; private set; } // v1.0 only
public int SegNum { get; private set; }
public int Entry { get; private set; }
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; }
// According to GS/OS ref, an OMF file is considered "foreign" unless:
// - the NUMSEX field is 0
// - the NUMLEN field is 4
// - the BANKSIZE field is <= $10000
// - the ALIGN field is <= $10000
//
// So we don't need to store NUMLEN or NUMSEX. According to the GS/OS ref,
// "The BANKSIZE and align restrictions are enforced by the linker, and violations
// of them are unlikely in a load file."
/// <summary>
/// Record list, from body of segment.
/// </summary>
public List<OmfRecord> Records = new List<OmfRecord>();
/// <summary>
/// Relocation list, for segments in Load files.
/// </summary>
public List<OmfReloc> Relocs = new List<OmfReloc>();
/// <summary>
/// True if this is an ExpressLoad segment.
/// </summary>
public bool IsExpressLoad {
get {
if (Kind != SegmentKind.Data) {
return false;
}
if ((Attrs & SegmentAttribute.Dynamic) == 0) {
return false;
}
// Should be case-insensitive? I'm assuming it's not padded with spaces since
// it's longer than 10 chars.
if (!(SegName == EXPRESSLOAD || SegName == EXPRESSLOAD_OLD)) {
return false;
}
if (SegNum != 1) {
Debug.WriteLine("WEIRD: ~ExpressLoad not first segment");
}
return true;
}
}
private const string EXPRESSLOAD = "~ExpressLoad";
private const string EXPRESSLOAD_OLD = "ExpressLoad";
// Constructor is private; use ParseHeader() to create an instance.
private OmfSegment() { }
public enum ParseResult {
Unknown = 0,
Success,
Failure,
IsLibrary
}
/// <summary>
/// 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>
/// <param name="parseAsLibrary">Set to true to parse the header as if it were part
/// of a library file. Affects parsing of v1 headers.</param>
/// <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 ParseHeader(byte[] data, int offset, bool parseAsLibrary,
List<string> msgs, out OmfSegment segResult) {
segResult = null;
//Debug.WriteLine("PARSE offset=" + offset);
Debug.Assert(offset < data.Length);
if (data.Length - offset < MIN_HEADER_V0) {
// Definitely too small.
AddErrorMsg(msgs, offset, "remaining file space too small to hold segment");
return ParseResult.Failure;
}
OmfSegment newSeg = new OmfSegment();
newSeg.mFileData = data;
newSeg.FileOffset = offset;
// Start with the version number. The meaning of everything else depends on this.
int minLen, expectedDispName;
switch (data[offset + 0x0f]) {
case 0:
newSeg.Version = SegmentVersion.v0_0;
minLen = MIN_HEADER_V0;
expectedDispName = 0x24;
break;
case 1:
newSeg.Version = SegmentVersion.v1_0;
minLen = MIN_HEADER_V1;
expectedDispName = 0x2c;
break;
case 2:
newSeg.Version = SegmentVersion.v2_0;
minLen = MIN_HEADER_V2;
expectedDispName = 0x2c;
break;
default:
// invalid version, this is probably not OMF
AddErrorMsg(msgs, offset, "invalid segment type " + data[offset + 0x0f]);
return ParseResult.Failure;
}
if (data.Length - offset < minLen) {
// Too small for this version of the header.
AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
newSeg.Version + " segment");
return ParseResult.Failure;
}
int blkByteCnt = RawData.GetWord(data, offset + 0x00, 4, false);
newSeg.ResSpc = RawData.GetWord(data, offset + 0x04, 4, false);
newSeg.Length = RawData.GetWord(data, offset + 0x08, 4, false);
newSeg.LabLen = data[offset + 0x0d];
int numLen = data[offset + 0x0e];
newSeg.BankSize = RawData.GetWord(data, offset + 0x10, 4, false);
int numSex, dispName;
if (newSeg.Version == SegmentVersion.v0_0) {
newSeg.Org = RawData.GetWord(data, offset + 0x14, 4, false);
newSeg.Align = RawData.GetWord(data, offset + 0x18, 4, false);
numSex = data[offset + 0x1c];
// 7 unused bytes follow
dispName = 0x24;
if (newSeg.LabLen == 0) {
newSeg.DispData = dispName + data[offset + dispName];
} else {
newSeg.DispData = dispName + LOAD_NAME_LEN;
}
} else {
newSeg.BankSize = RawData.GetWord(data, offset + 0x10, 4, false);
newSeg.Org = RawData.GetWord(data, offset + 0x18, 4, false);
newSeg.Align = RawData.GetWord(data, offset + 0x1c, 4, false);
numSex = data[offset + 0x20];
newSeg.LcBank = data[offset + 0x21]; // v1.0 only
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);
newSeg.DispData = RawData.GetWord(data, offset + 0x2a, 2, false);
}
// The only way to detect a v2.1 segment is by checking DISPNAME.
if (newSeg.Version == SegmentVersion.v2_0 && dispName > 0x2c) {
newSeg.Version = SegmentVersion.v2_1;
expectedDispName += 4;
if (data.Length - offset < minLen + 4) {
AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
newSeg.Version + " segment");
return ParseResult.Failure;
}
newSeg.TempOrg = RawData.GetWord(data, offset + 0x2c, 4, false);
}
// Extract Kind and its attributes. The Orca/M 2.0 manual refers to the 1-byte
// field in v0/v1 as "TYPE" and the 2-byte field as "KIND", but we're generally
// following the GS/OS reference nomenclature.
int kindByte, kindWord;
if (newSeg.Version <= SegmentVersion.v1_0) {
kindByte = data[offset + 0x0c];
if (!Enum.IsDefined(typeof(SegmentKind), kindByte & 0x1f)) {
// Example: Moria GS has a kind of $1F for its GLOBALS segment.
AddErrorMsg(msgs, offset, "invalid segment kind $" + kindByte.ToString("x2"));
return ParseResult.Failure;
}
newSeg.Kind = (SegmentKind)(kindByte & 0x1f);
int kindAttrs = 0;
if ((kindByte & 0x20) != 0) {
kindAttrs |= (int)SegmentAttribute.PosnIndep;
}
if ((kindByte & 0x40) != 0) {
kindAttrs |= (int)SegmentAttribute.Private;
}
if ((kindByte & 0x80) != 0) {
kindAttrs |= (int)SegmentAttribute.Dynamic;
}
newSeg.Attrs = (SegmentAttribute)kindAttrs;
} else {
// Yank all the attribute bits out at once. Don't worry about v2.0 vs. v2.1.
kindWord = RawData.GetWord(data, offset + 0x14, 2, false);
if (!Enum.IsDefined(typeof(SegmentKind), kindWord & 0x001f)) {
AddErrorMsg(msgs, offset, "invalid segment kind $" + kindWord.ToString("x4"));
return ParseResult.Failure;
}
newSeg.Kind = (SegmentKind)(kindWord & 0x001f);
newSeg.Attrs = (SegmentAttribute)(kindWord & 0xff00);
}
// If we found a library dictionary segment, and we're not currently handling the
// file as a library, reject this and try again.
if (newSeg.Kind == SegmentKind.LibraryDict && !parseAsLibrary) {
AddInfoMsg(msgs, offset, "found Library Dictionary segment, retrying as library");
return ParseResult.IsLibrary;
}
// We've got the basic pieces. Handle the block-vs-byte debacle.
int segLen;
bool asBlocks = false;
if (newSeg.Version == SegmentVersion.v0_0) {
// Always block count.
segLen = blkByteCnt * DISK_BLOCK_SIZE;
asBlocks = true;
} else if (newSeg.Version >= SegmentVersion.v2_0) {
// Always byte count.
segLen = blkByteCnt;
} else /*v1.0*/ {
// Only Library files should treat the field as bytes. We can eliminate Load
// files by checking for a nonzero SegNum field, but there's no reliable way
// to tell the difference between Object and Library while looking at a segment
// in isolation.
if (parseAsLibrary) {
segLen = blkByteCnt;
} else {
segLen = blkByteCnt * DISK_BLOCK_SIZE;
asBlocks = true;
}
}
newSeg.RawFileLength = newSeg.FileLength = segLen;
//
// Perform validity checks. If any of these fail, we're probably reading something
// that isn't OMF (or, if this isn't the first segment, we might have gone off the
// rails at some point).
//
if (numLen != 4) {
AddErrorMsg(msgs, offset, "NUMLEN must be 4, was " + numLen);
return ParseResult.Failure;
}
if (numSex != 0) {
AddErrorMsg(msgs, offset, "NUMSEX must be 0, was " + numSex);
return ParseResult.Failure;
}
if (offset + segLen > data.Length) {
if (asBlocks && offset + segLen - data.Length < DISK_BLOCK_SIZE) {
// I have found a few examples (e.g. BRIDGE.S16 in Davex v1.23, SYSTEM:START
// on an old Paintworks GS disk) where the file's length doesn't fill out
// the last block in the file. If we continue, and the segment actually
// does pass EOF, we'll fail while reading the records.
AddInfoMsg(msgs, offset,
"file EOF is not a multiple of 512; last segment may be truncated");
newSeg.FileLength = data.Length - offset;
} else {
// Segment is longer than the file. (This can happen easily in a static lib if
// we're not parsing it as such.)
AddErrorMsg(msgs, offset, "segment file length exceeds EOF (segLen=" + segLen +
", remaining=" + (data.Length - offset) + ")");
return ParseResult.Failure;
}
}
if (dispName < expectedDispName || dispName > (segLen - LOAD_NAME_LEN)) {
AddErrorMsg(msgs, offset, "invalid DISPNAME " + dispName + " (expected " +
expectedDispName + ", segLen=" + segLen + ")");
return ParseResult.Failure;
}
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;
}
if (newSeg.BankSize > 0x00010000) {
AddErrorMsg(msgs, offset, "invalid BANKSIZE $" + newSeg.BankSize.ToString("x"));
return ParseResult.Failure;
}
if (newSeg.Align > 0x00010000) {
AddErrorMsg(msgs, offset, "invalid ALIGN $" + newSeg.Align.ToString("x"));
return ParseResult.Failure;
}
if (newSeg.BankSize != 0x00010000 && newSeg.BankSize != 0) {
// This is fine, just a little weird.
AddInfoMsg(msgs, offset, "unusual BANKSIZE $" + newSeg.BankSize.ToString("x6"));
}
if (newSeg.Align != 0 && newSeg.Align != 0x0100 && newSeg.Align != 0x00010000) {
// Unexpected; the loader will round up.
AddInfoMsg(msgs, offset, "unusual ALIGN $" + newSeg.Align.ToString("x6"));
}
if (newSeg.Entry != 0 && newSeg.Entry >= newSeg.Length) {
// This is invalid, but if we got this far we might as well keep going.
AddInfoMsg(msgs, offset, "invalid ENTRY $" + newSeg.Entry.ToString("x6"));
}
// Extract LOADNAME. Fixed-width field, padded with spaces. Except for the
// times when it's filled with zeroes instead.
string loadName = string.Empty;
int segNameStart = dispName;
if (newSeg.Version != SegmentVersion.v0_0) {
loadName = ExtractString(data, offset + dispName, LOAD_NAME_LEN);
segNameStart += LOAD_NAME_LEN;
}
// Extract SEGNAME. May be fixed- or variable-width.
string segName;
if (newSeg.LabLen == 0) {
// string preceded by length byte
int segNameLen = data[offset + segNameStart];
if (segNameStart + 1 + segNameLen > segLen) {
AddInfoMsg(msgs, offset, "var-width SEGNAME ran off end of segment (len=" +
segNameLen + ", segLen=" + segLen + ")");
return ParseResult.Failure;
}
segName = Encoding.ASCII.GetString(data, offset + segNameStart + 1, segNameLen);
} else {
// fixed-width string
if (segNameStart + newSeg.LabLen > segLen) {
AddInfoMsg(msgs, offset, "fixed-width SEGNAME ran off end of segment (LABLEN=" +
newSeg.LabLen + ", segLen=" + segLen + ")");
return ParseResult.Failure;
}
segName = ExtractString(data, offset + segNameStart, newSeg.LabLen);
}
//AddInfoMsg(msgs, offset, "GOT LOADNAME='" + loadName + "' SEGNAME='" + segName + "'");
newSeg.LoadName = loadName;
newSeg.SegName = segName;
//
// Populate the "raw data" table. We add the fields shown in the specification in
// the order in which they appear.
//
if (newSeg.Version == SegmentVersion.v0_0 ||
(newSeg.Version == SegmentVersion.v1_0 && !parseAsLibrary)) {
newSeg.AddRaw("BLKCNT", blkByteCnt, 4, "blocks");
} else {
newSeg.AddRaw("BYTECNT", blkByteCnt, 4, "bytes");
}
newSeg.AddRaw("RESSPC", newSeg.ResSpc, 4, string.Empty);
newSeg.AddRaw("LENGTH", newSeg.Length, 4, string.Empty);
if (newSeg.Version <= SegmentVersion.v1_0) {
string attrStr = AttrsToString(newSeg.Attrs);
if (!string.IsNullOrEmpty(attrStr)) {
attrStr = " -" + attrStr;
}
newSeg.AddRaw("KIND", data[offset+0x0c], 1,
KindToString(newSeg.Kind) + attrStr);
} else {
newSeg.AddRaw("undefined", data[offset + 0x0c], 1, string.Empty);
}
newSeg.AddRaw("LABLEN", newSeg.LabLen, 1,
(newSeg.LabLen == 0 ? "variable length" : "fixed length"));
newSeg.AddRaw("NUMLEN", numLen, 1, "must be 4");
newSeg.AddRaw("VERSION", data[offset + 0x0f], 1, VersionToString(newSeg.Version));
newSeg.AddRaw("BANKSIZE", newSeg.BankSize, 4, string.Empty);
if (newSeg.Version >= SegmentVersion.v2_0) {
string attrStr = AttrsToString(newSeg.Attrs);
if (!string.IsNullOrEmpty(attrStr)) {
attrStr = " -" + attrStr;
}
newSeg.AddRaw("KIND", RawData.GetWord(data, offset + 0x14, 2, false), 2,
KindToString(newSeg.Kind) + attrStr);
newSeg.AddRaw("undefined", RawData.GetWord(data, offset + 0x16, 2, false), 2,
string.Empty);
} else {
newSeg.AddRaw("undefined", RawData.GetWord(data, offset + 0x14, 4, false), 4,
string.Empty);
}
newSeg.AddRaw("ORG", newSeg.Org, 4, (newSeg.Org != 0 ? "" : "relocatable"));
// alignment is rounded up to page/bank
string alignStr;
if (newSeg.Align == 0) {
alignStr = "no alignment";
} else if (newSeg.Align <= 0x0100) {
alignStr = "align to page";
} else {
alignStr = "align to bank";
}
newSeg.AddRaw("ALIGN", newSeg.Align, 4, alignStr);
newSeg.AddRaw("NUMSEX", numSex, 1, "must be 0");
if (newSeg.Version == SegmentVersion.v1_0) {
newSeg.AddRaw("LCBANK", newSeg.LcBank, 1, string.Empty);
} else {
newSeg.AddRaw("undefined", data[offset + 0x21], 1, string.Empty);
}
if (newSeg.Version >= SegmentVersion.v1_0) {
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", newSeg.DispData, 2, string.Empty);
if (newSeg.Version >= SegmentVersion.v2_1) {
newSeg.AddRaw("TEMPORG", newSeg.TempOrg, 4, string.Empty);
}
newSeg.AddRaw("LOADNAME", loadName, 10, string.Empty);
}
newSeg.AddRaw("SEGNAME", segName, 0, string.Empty);
segResult = newSeg;
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 + RawFileLength) {
// 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 some slop is expected there,
// but v2.x should be snug. Doesn't have to be, but might indicate a
// bug in the parser.
int remaining = (FileOffset + FileLength) - (offset + omfRec.Length);
Debug.Assert(remaining >= 0);
Debug.WriteLine("END record found, remaining space=" + remaining);
if (remaining >= DISK_BLOCK_SIZE ||
(Version >= SegmentVersion.v2_0 && remaining != 0)) {
AddInfoMsg(msgs, offset, "found " + remaining + " bytes past END record");
}
return true;
}
Records.Add(omfRec);
offset += omfRec.Length;
}
}
/// <summary>
/// Tests to see whether the record collection is congruent with a Load file.
/// </summary>
public bool CheckRecords_LoadFile() {
bool constSection = true;
foreach (OmfRecord omfRec in Records) {
switch (omfRec.Op) {
case OmfRecord.Opcode.LCONST:
case OmfRecord.Opcode.DS:
if (!constSection) {
Debug.WriteLine("Found LCONST/DS past const section");
return false;
}
break;
case OmfRecord.Opcode.RELOC:
case OmfRecord.Opcode.cRELOC:
case OmfRecord.Opcode.INTERSEG:
case OmfRecord.Opcode.cINTERSEG:
case OmfRecord.Opcode.SUPER:
constSection = false;
break;
default:
// incompatible record
return false;
}
}
return true;
}
/// <summary>
/// Tests to see whether the record collection is congruent with an Object or Library file.
/// </summary>
public bool CheckRecords_ObjectOrLib() {
foreach (OmfRecord omfRec in Records) {
switch (omfRec.Op) {
case OmfRecord.Opcode.RELOC:
case OmfRecord.Opcode.cRELOC:
case OmfRecord.Opcode.INTERSEG:
case OmfRecord.Opcode.cINTERSEG:
case OmfRecord.Opcode.SUPER:
case OmfRecord.Opcode.ENTRY:
return false;
default:
break;
}
}
return true;
}
public void GenerateRelocDict() {
Debug.Assert(CheckRecords_LoadFile());
foreach (OmfRecord omfRec in Records) {
switch (omfRec.Op) {
case OmfRecord.Opcode.RELOC:
case OmfRecord.Opcode.cRELOC:
case OmfRecord.Opcode.INTERSEG:
case OmfRecord.Opcode.cINTERSEG:
case OmfRecord.Opcode.SUPER:
OmfReloc.GenerateRelocs(this, omfRec, mFileData, Relocs);
break;
default:
break;
}
}
}
private byte[] mConstData;
/// <summary>
/// Returns a reference to the unpacked constant data from the body of a Load segment
/// (i.e. the LCONST/DS part).
/// </summary>
public byte[] GetConstData() {
if (mConstData != null) {
return mConstData;
}
// We haven't generated this yet; do it now. Start by determining the length.
int totalLen = 0;
foreach (OmfRecord omfRec in Records) {
if (omfRec.Op != OmfRecord.Opcode.LCONST && omfRec.Op != OmfRecord.Opcode.DS) {
break;
}
// safe to assume NUMLEN=4, NUMSEX=0
totalLen += RawData.GetWord(mFileData, omfRec.FileOffset + 1, 4, false);
}
byte[] data = new byte[totalLen];
int bufOffset = 0;
foreach (OmfRecord omfRec in Records) {
if (omfRec.Op == OmfRecord.Opcode.DS) {
int len = RawData.GetWord(mFileData, omfRec.FileOffset + 1, 4, false);
bufOffset += len; // new buffers are zero-filled
} else if (omfRec.Op == OmfRecord.Opcode.LCONST) {
int len = RawData.GetWord(mFileData, omfRec.FileOffset + 1, 4, false);
Array.Copy(mFileData, omfRec.FileOffset + 5, data, bufOffset, len);
bufOffset += len;
} else {
break;
}
}
Debug.Assert(bufOffset == totalLen);
Debug.WriteLine("Generated " + totalLen + " bytes of LCONST/DS data for " + this);
mConstData = data;
return data;
}
//
// Helper functions.
//
private void AddRaw(string name, object value, int width, string note) {
if (value is byte) {
value = (int)(byte)value;
}
RawValues.Add(new NameValueNote(name, value, width, note));
}
public static void AddInfoMsg(List<string> msgs, int offset, string msg) {
msgs.Add("Note (+" + offset.ToString("x6") + "): " + msg);
}
public static void AddErrorMsg(List<string> msgs, int offset, string msg) {
msgs.Add("Error (+" + offset.ToString("x6") + "): " + msg);
}
/// <summary>
/// Extracts a fixed-length ASCII string, stopping early if a '\0' is encountered.
/// </summary>
private static string ExtractString(byte[] data, int offset, int len) {
StringBuilder sb = new StringBuilder();
for (int i = offset; i < offset + len; i++) {
byte b = data[i];
if (b == 0) {
break;
}
sb.Append((char)b);
}
return sb.ToString();
}
/// <summary>
/// Converts a segment version to a human-readable string.
/// </summary>
public static string VersionToString(SegmentVersion vers) {
switch (vers) {
case SegmentVersion.v0_0: return "v0.0";
case SegmentVersion.v1_0: return "v1.0";
case SegmentVersion.v2_0: return "v2.0";
case SegmentVersion.v2_1: return "v2.1";
default: return "v?.?";
}
}
/// <summary>
/// Converts a segment kind to a human-readable string.
/// </summary>
public static string KindToString(SegmentKind kind) {
switch (kind) {
case SegmentKind.Code: return "Code";
case SegmentKind.Data: return "Data";
case SegmentKind.JumpTable: return "Jump Table";
case SegmentKind.PathName: return "Pathname";
case SegmentKind.LibraryDict: return "Library Dict";
case SegmentKind.Init: return "Init";
case SegmentKind.AbsoluteBank: return "Abs Bank";
case SegmentKind.DpStack: return "DP/Stack";
default: return "???";
}
}
public static string AttrsToString(SegmentAttribute attrs) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
int bit = 1 << i;
if (((int)attrs & bit) != 0) {
SegmentAttribute attr = (SegmentAttribute)bit;
sb.Append(' ');
sb.Append(attr.ToString());
}
}
return sb.ToString();
}
public override string ToString() {
return "[OmfSegment " + SegNum + " '" + LoadName + "' '" + SegName + "']";
}
}
}