From bb7998d1f040e7bba88fc0db9968134939790ed2 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Mon, 29 Jun 2020 18:29:18 -0700 Subject: [PATCH] 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. --- SourceGen/DisasmProject.cs | 3 +- SourceGen/Res/Strings.xaml | 2 + SourceGen/Res/Strings.xaml.cs | 4 + SourceGen/SourceGen.csproj | 1 + SourceGen/SystemDefs.cs | 15 +- SourceGen/Tools/Omf/Loader.cs | 389 +++++++++++++++++++ SourceGen/Tools/Omf/OmfFile.cs | 1 + SourceGen/Tools/Omf/OmfSegment.cs | 25 ++ SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml | 6 +- SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs | 46 +++ 10 files changed, 487 insertions(+), 5 deletions(-) create mode 100644 SourceGen/Tools/Omf/Loader.cs diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index a2b9d06..2d79ade 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -288,7 +288,8 @@ namespace SourceGen { /// Prepares the DisasmProject for use as a new project. /// /// 65xx data file contents. - /// Data file's filename (not pathname). + /// Data file's filename (not pathname). Only used for + /// cosmetic stuff, e.g. exporting to text; not stored in project. public void PrepForNew(byte[] fileData, string dataFileName) { Debug.Assert(fileData.Length == FileDataLength); diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml index c28d9ba..8b24a58 100644 --- a/SourceGen/Res/Strings.xaml +++ b/SourceGen/Res/Strings.xaml @@ -130,6 +130,7 @@ limitations under the License. Visualization ignored no files available No exported symbols found. + Segment {0}: Kind={2}, SegName='{1}' The file doesn't exist. File is empty Unable to load data file @@ -141,6 +142,7 @@ limitations under the License. The file is {0:N0} bytes long, but the project expected {1:N0}. The file has CRC {0}, but the project expected {1}. Failed + Success! (none) Failed while preparing the plugin directory {0} Failed Preparing Plugin Directory diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs index 86fd659..8a76d76 100644 --- a/SourceGen/Res/Strings.xaml.cs +++ b/SourceGen/Res/Strings.xaml.cs @@ -241,6 +241,8 @@ namespace SourceGen.Res { (string)Application.Current.FindResource("str_NoFilesAvailable"); public static string NO_EXPORTED_SYMBOLS_FOUND = (string)Application.Current.FindResource("str_NoExportedSymbolsFound"); + public static string OMF_SEG_COMMENT_FMT = + (string)Application.Current.FindResource("str_OmfSegCommentFmt"); public static string OPEN_DATA_DOESNT_EXIST = (string)Application.Current.FindResource("str_OpenDataDoesntExist"); public static string OPEN_DATA_EMPTY = @@ -263,6 +265,8 @@ namespace SourceGen.Res { (string)Application.Current.FindResource("str_OpenDataWrongLengthFmt"); public static string OPERATION_FAILED = (string)Application.Current.FindResource("str_OperationFailed"); + public static string OPERATION_SUCCEEDED = + (string)Application.Current.FindResource("str_OperationSucceeded"); public static string PARENTHETICAL_NONE = (string)Application.Current.FindResource("str_ParentheticalNone"); public static string PLUGIN_DIR_FAIL_FMT = diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index 0d668b6..1d342fc 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -85,6 +85,7 @@ GenTestRunner.xaml + diff --git a/SourceGen/SystemDefs.cs b/SourceGen/SystemDefs.cs index 8c79bd0..2431186 100644 --- a/SourceGen/SystemDefs.cs +++ b/SourceGen/SystemDefs.cs @@ -204,10 +204,19 @@ namespace SourceGen { "': Unexpected contents '" + sdf.Contents + "'"); } - foreach (SystemDef sd in sdf.Defs) { - Debug.WriteLine("### " + sd); - } + //foreach (SystemDef sd in sdf.Defs) { + // Debug.WriteLine("### " + sd); + //} return sdf; } + + public SystemDef FindEntryByName(string name) { + foreach (SystemDef sd in Defs) { + if (sd.Name == name) { + return sd; + } + } + return null; + } } } diff --git a/SourceGen/Tools/Omf/Loader.cs b/SourceGen/Tools/Omf/Loader.cs new file mode 100644 index 0000000..0bcb762 --- /dev/null +++ b/SourceGen/Tools/Omf/Loader.cs @@ -0,0 +1,389 @@ +/* + * 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.IO; + +using CommonUtil; + +namespace SourceGen.Tools.Omf { + /// + /// Apple IIgs OMF loader. This works like the GS/OS System Loader, reading the contents + /// of an executable file and resolving the relocation records. This only handles Load + /// files, as Object and Library files contain unresolved references. + /// + public class Loader { + private const string IIGS_SYSTEM_DEF = "Apple IIgs (GS/OS)"; + + private OmfFile mOmfFile; + + private byte[] mLoadedData; + private DisasmProject mNewProject; + + private class SegmentMapEntry { + public OmfSegment Segment { get; private set; } + public int Address { get; private set; } + + public SegmentMapEntry(OmfSegment omfSeg, int address) { + Segment = omfSeg; + Address = address; + } + } + private List mSegmentMap; + + + /// + /// Constructor. + /// + /// OMF file to load. + public Loader(OmfFile omfFile) { + Debug.Assert(omfFile.OmfFileKind == OmfFile.FileKind.Load); + + mOmfFile = omfFile; + } + + /// + /// Prepares the loaded form of the binary and the disassembly project. + /// + public bool Prepare() { + if (!CreateMap()) { + mSegmentMap = null; + return false; + } + + Debug.WriteLine("Segment map:"); + for (int i = 0; i < mSegmentMap.Count; i++) { + SegmentMapEntry ent = mSegmentMap[i]; + if (ent == null) { + Debug.Assert(i == 0 || i == 1); // initial hole and optional ~ExpressLoad + continue; + } + OmfSegment omfSeg = ent.Segment; + Debug.WriteLine(i + " " + ent.Address.ToString("x6") + " SegNum=" + omfSeg.SegNum + + " '" + omfSeg.SegName + "'"); + + Debug.Assert(i == ent.Segment.SegNum); + } + + if (!GenerateOutput()) { + mSegmentMap = null; + return false; + } + + return true; + } + + /// + /// Writes the data and disasm project files. + /// + /// + /// + public bool WriteProjectFiles(string dataPathName, string projectPathName, + out string errMsg) { + Debug.WriteLine("Writing " + dataPathName + " and " + projectPathName); + + using (FileStream fs = new FileStream(dataPathName, FileMode.Create)) { + fs.Write(mLoadedData, 0, mLoadedData.Length); + } + + if (!ProjectFile.SerializeToFile(mNewProject, projectPathName, out errMsg)) { + return false; + } + + return true; + } + + /// + /// Creates a map of file segments. The position of each segment in the list will + /// match the segment's position in the file, i.e. the segment in Map[5] will have + /// SEGNUM==5. + /// + /// + /// I'm assuming that the SEGNUM in the file matches the position. This seems to be + /// the case everywhere. ExpressLoad goes to some lengths to ensure this is still the + /// case after a file is "expressed", including a remap table so that loader calls made + /// by the application go to the right place (instead of, say, giving ~ExpressLoad a + /// SEGNUM of 255.) + /// + /// True on success. + private bool CreateMap() { + // Segments are numbered 1-N, so create a map with N+1 entries and leave first blank. + mSegmentMap = new List(mOmfFile.SegmentList.Count + 1); + mSegmentMap.Add(null); + + // Create a bank in-use map. + bool[] inUse = new bool[256]; + + // Flag special memory as in-use. + inUse[0x00] = inUse[0x01] = inUse[0xe0] = inUse[0xe1] = true; + + // Find segments that require specific addresses, and mark those banks as in use. + foreach (OmfSegment omfSeg in mOmfFile.SegmentList) { + if (omfSeg.Kind == OmfSegment.SegmentKind.DpStack) { + // This just allocates space in bank 0. + continue; + } + if (omfSeg.Length == 0) { + // Nothing to do here. + continue; + } + + int addr; + + if (omfSeg.Org == 0) { + // The docs say that a value of zero always means relocatable, but that + // would mean you can't set the "absolute bank" flag to position code or + // data in bank 0. I'm going to assume that's intentional, since people + // (a) shouldn't be doing that, and (b) can use DP/Stack instead (?). + continue; + } + + addr = omfSeg.Org; + if ((omfSeg.Attrs & OmfSegment.SegmentAttribute.AbsBank) != 0) { + // Bank is specified, rest of address is not. + addr &= 0x00ff0000; + } + + // Mark the banks as being in use. It's okay if multiple segments want the + // same space. + MarkBanks(addr, omfSeg.Length, inUse); + } + + // + // Assign segments to banks. Note we always start at offset $0000 within a bank. + // + + int nextBank = 0; + int dpAddr = 0x1000; // somewhat arbitrary + foreach (OmfSegment omfSeg in mOmfFile.SegmentList) { + if (omfSeg.Kind == OmfSegment.SegmentKind.DpStack || omfSeg.Length == 0) { + mSegmentMap.Add(new SegmentMapEntry(omfSeg, dpAddr)); + dpAddr += omfSeg.Length; + if (dpAddr > 0x00010000) { + Debug.WriteLine("Stack/DP overflow"); + return false; + } + continue; + } + if (omfSeg.IsExpressLoad) { + // We totally ignore these. Add a null ref as a placeholder. + mSegmentMap.Add(null); + continue; + } + + int addr; + + if (omfSeg.Org != 0) { + // Specific address requested. + addr = omfSeg.Org; + if ((omfSeg.Attrs & OmfSegment.SegmentAttribute.AbsBank) != 0) { + // just keep the bank + addr &= 0x00ff0000; + } + } else { + // Find next available spot with enough space. + while (true) { + while (nextBank < 256 && inUse[nextBank]) { + nextBank++; + } + if (nextBank == 256) { + // Should be impossible on any sane Apple IIgs Load file. + Debug.Assert(false); + return false; + } + if (!CheckBanks(nextBank << 16, omfSeg.Length, inUse)) { + // Didn't fit in the space. + nextBank++; + continue; + } + + // We only go forward, so no need to mark them. + + break; + } + + addr = nextBank << 16; + + // Advance nextBank. We do this by identifying the last address touched, + // and moving to the next bank. + int lastAddr = addr + omfSeg.Length - 1; + nextBank = (lastAddr >> 16) + 1; + } + + SegmentMapEntry ent = new SegmentMapEntry(omfSeg, addr); + mSegmentMap.Add(ent); + } + + return true; + } + + private static bool CheckBanks(int addr, int memLen, bool[] inUse) { + Debug.Assert(memLen > 0); + while (memLen > 0) { + if (inUse[(addr >> 16) & 0xff]) { + return false; + } + addr += 65536; + memLen -= 65536; + } + return true; + + } + + private static bool MarkBanks(int addr, int memLen, bool[] inUse) { + Debug.Assert(memLen > 0); + while (memLen > 0) { + inUse[(addr >> 16) & 0xff] = true; + addr += 65536; + memLen -= 65536; + } + return true; + } + + private bool GenerateOutput() { + // Sum up the segment lengths to get the total project size. + int totalLen = 0; + foreach (SegmentMapEntry ent in mSegmentMap) { + if (ent == null) { + continue; + } + totalLen += ent.Segment.Length; + } + Debug.WriteLine("Total length of loaded binary is " + totalLen); + + byte[] data = new byte[totalLen]; + + // Create the project object. + DisasmProject proj = new DisasmProject(); + proj.Initialize(data.Length); + + // Try to get the Apple IIgs system definition. This is fragile, because it + // relies on the name in the JSON file, but it's optional. (If the default CPU + // type stops being 65816, we should be sure to set that here.) + try { + // TODO(maybe): encapsulate this somewhere else + string sysDefsPath = RuntimeDataAccess.GetPathName("SystemDefs.json"); + SystemDefSet sds = SystemDefSet.ReadFile(sysDefsPath); + SystemDef sd = sds.FindEntryByName(IIGS_SYSTEM_DEF); + if (sd != null) { + proj.ApplySystemDef(sd); + } else { + Debug.WriteLine("Unable to find Apple IIgs system definition"); + } + } catch (Exception) { + // never mind + Debug.WriteLine("Failed to apply Apple IIgs system definition"); + } + + // Add header comment. + string cmt = string.Format(Res.Strings.DEFAULT_HEADER_COMMENT_FMT, App.ProgramVersion); + proj.LongComments.Add(LineListGen.Line.HEADER_COMMENT_OFFSET, + new MultiLineComment(cmt)); + + ChangeSet cs = new ChangeSet(mSegmentMap.Count * 2); + + // Load the segments, and add entries to the project. + int bufOffset = 0; + foreach (SegmentMapEntry ent in mSegmentMap) { + if (ent != null) { + // Perform relocation. + if (!RelocSegment(ent, data, bufOffset)) { + return false; + } + + // Add an address entry. + + // TODO: need to add multiple address entries if this straddles a segment. + + int origAddr = proj.AddrMap.Get(bufOffset); + UndoableChange uc = UndoableChange.CreateAddressChange(bufOffset, + origAddr, ent.Address); + cs.Add(uc); + + // Add a comment identifying the segment. + string segCmt = string.Format(Res.Strings.OMF_SEG_COMMENT_FMT, + ent.Segment.SegNum, ent.Segment.SegName, ent.Segment.Kind); + uc = UndoableChange.CreateLongCommentChange(bufOffset, null, + new MultiLineComment(segCmt)); + cs.Add(uc); + + bufOffset += ent.Segment.Length; + } + } + + proj.PrepForNew(data, "new_proj"); + proj.ApplyChanges(cs, false, out RangeSet unused); + + mLoadedData = data; + mNewProject = proj; + return true; + } + + private bool RelocSegment(SegmentMapEntry ent, byte[] data, int bufOffset) { + //const int INVALID_RELOC = 0x00ffffff; + byte[] srcData = ent.Segment.GetConstData(); + Array.Copy(srcData, 0, data, bufOffset, srcData.Length); + + foreach (OmfReloc omfRel in ent.Segment.Relocs) { + int relocAddr = omfRel.RelOffset; + if (omfRel.FileNum != -1 && omfRel.FileNum != 1) { + // Some other file; not much we can do with this. + Debug.WriteLine("Unable to process reloc with FileNum=" + omfRel.FileNum); + return false; + } else if (omfRel.SegNum == -1) { + // Within this segment. + relocAddr += ent.Address; + } else { + // Find other segment. This may fail if the file is damaged. + if (omfRel.SegNum < 0 || omfRel.SegNum >= mSegmentMap.Count || + mSegmentMap[omfRel.SegNum] == null) { + Debug.WriteLine("Reloc SegNum=" + omfRel.SegNum + " not in map"); + return false; + } else { + relocAddr += mSegmentMap[omfRel.SegNum].Address; + } + } + + if (omfRel.Shift < 0) { + relocAddr >>= -omfRel.Shift; + } else if (omfRel.Shift > 0) { + relocAddr <<= omfRel.Shift; + } + + switch (omfRel.Width) { + case 1: + data[bufOffset + omfRel.Offset] = (byte)(relocAddr); + break; + case 2: + data[bufOffset + omfRel.Offset] = (byte)(relocAddr); + data[bufOffset + omfRel.Offset + 1] = (byte)(relocAddr >> 8); + break; + case 3: + data[bufOffset + omfRel.Offset] = (byte)(relocAddr); + data[bufOffset + omfRel.Offset + 1] = (byte)(relocAddr >> 8); + data[bufOffset + omfRel.Offset + 2] = (byte)(relocAddr >> 16); + break; + default: + Debug.WriteLine("Invalid reloc width " + omfRel.Width); + return false; + } + } + + return true; + } + } +} diff --git a/SourceGen/Tools/Omf/OmfFile.cs b/SourceGen/Tools/Omf/OmfFile.cs index afe3b0e..7eb976b 100644 --- a/SourceGen/Tools/Omf/OmfFile.cs +++ b/SourceGen/Tools/Omf/OmfFile.cs @@ -95,6 +95,7 @@ namespace SourceGen.Tools.Omf { Debug.Assert(fileData.Length >= MIN_FILE_SIZE && fileData.Length <= MAX_FILE_SIZE); mFileData = fileData; + // Set to Unknown until analysis completes. OmfFileKind = FileKind.Unknown; } diff --git a/SourceGen/Tools/Omf/OmfSegment.cs b/SourceGen/Tools/Omf/OmfSegment.cs index b2576df..d87b1b2 100644 --- a/SourceGen/Tools/Omf/OmfSegment.cs +++ b/SourceGen/Tools/Omf/OmfSegment.cs @@ -174,6 +174,31 @@ namespace SourceGen.Tools.Omf { /// public List Relocs = new List(); + /// + /// True if this is an ExpressLoad segment. + /// + 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() { } diff --git a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml index c882068..773aef4 100644 --- a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml +++ b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml @@ -38,6 +38,9 @@ limitations under the License. This is {0}, with {1} segment This is {0}, with {1} segments This is not an Apple II OMF file + + Unable to prepare data file for project. + Data file and project created. @@ -93,7 +96,8 @@ limitations under the License. -