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.
This commit is contained in:
Andy McFadden 2020-06-29 18:29:18 -07:00
parent 463a0cc561
commit bb7998d1f0
10 changed files with 487 additions and 5 deletions

View File

@ -288,7 +288,8 @@ namespace SourceGen {
/// Prepares the DisasmProject for use as a new project.
/// </summary>
/// <param name="fileData">65xx data file contents.</param>
/// <param name="dataFileName">Data file's filename (not pathname).</param>
/// <param name="dataFileName">Data file's filename (not pathname). Only used for
/// cosmetic stuff, e.g. exporting to text; not stored in project.</param>
public void PrepForNew(byte[] fileData, string dataFileName) {
Debug.Assert(fileData.Length == FileDataLength);

View File

@ -130,6 +130,7 @@ limitations under the License.
<system:String x:Key="str_MsgVisualizationIgnored">Visualization ignored</system:String>
<system:String x:Key="str_NoFilesAvailable">no files available</system:String>
<system:String x:Key="str_NoExportedSymbolsFound">No exported symbols found.</system:String>
<system:String x:Key="str_OmfSegCommentFmt" xml:space="preserve">&#x0d;Segment {0}: Kind={2}, SegName='{1}'&#x0d;&#x0d;</system:String>
<system:String x:Key="str_OpenDataDoesntExist">The file doesn't exist.</system:String>
<system:String x:Key="str_OpenDataEmpty">File is empty</system:String>
<system:String x:Key="str_OpenDataFailCaption">Unable to load data file</system:String>
@ -141,6 +142,7 @@ limitations under the License.
<system:String x:Key="str_OpenDataWrongLengthFmt">The file is {0:N0} bytes long, but the project expected {1:N0}.</system:String>
<system:String x:Key="str_OpenDataWrongCrcFmt">The file has CRC {0}, but the project expected {1}.</system:String>
<system:String x:Key="str_OperationFailed">Failed</system:String>
<system:String x:Key="str_OperationSucceeded">Success!</system:String>
<system:String x:Key="str_ParentheticalNone">(none)</system:String>
<system:String x:Key="str_PluginDirFailFmt">Failed while preparing the plugin directory {0}</system:String>
<system:String x:Key="str_PluginDirFailCaption">Failed Preparing Plugin Directory</system:String>

View File

@ -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 =

View File

@ -85,6 +85,7 @@
<DependentUpon>GenTestRunner.xaml</DependentUpon>
</Compile>
<Compile Include="Tools\ApplesoftToHtml.cs" />
<Compile Include="Tools\Omf\Loader.cs" />
<Compile Include="Tools\Omf\OmfFile.cs" />
<Compile Include="Tools\Omf\OmfRecord.cs" />
<Compile Include="Tools\Omf\OmfReloc.cs" />

View File

@ -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;
}
}
}

View File

@ -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 {
/// <summary>
/// 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.
/// </summary>
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<SegmentMapEntry> mSegmentMap;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="omfFile">OMF file to load.</param>
public Loader(OmfFile omfFile) {
Debug.Assert(omfFile.OmfFileKind == OmfFile.FileKind.Load);
mOmfFile = omfFile;
}
/// <summary>
/// Prepares the loaded form of the binary and the disassembly project.
/// </summary>
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;
}
/// <summary>
/// Writes the data and disasm project files.
/// </summary>
/// <param name="dataPathName"></param>
/// <param name="projectPathName"></param>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.)
/// </remarks>
/// <returns>True on success.</returns>
private bool CreateMap() {
// Segments are numbered 1-N, so create a map with N+1 entries and leave first blank.
mSegmentMap = new List<SegmentMapEntry>(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;
}
}
}

View File

@ -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;
}

View File

@ -174,6 +174,31 @@ namespace SourceGen.Tools.Omf {
/// </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() { }

View File

@ -38,6 +38,9 @@ limitations under the License.
<system:String x:Key="str_OmfFileSummaryFmt">This is {0}, with {1} segment</system:String>
<system:String x:Key="str_OmfFileSummaryPlFmt">This is {0}, with {1} segments</system:String>
<system:String x:Key="str_OmfFileNot">This is not an Apple II OMF file</system:String>
<system:String x:Key="str_OmfLoaderFail">Unable to prepare data file for project.</system:String>
<system:String x:Key="str_OmfConvertSuccessful">Data file and project created.</system:String>
</Window.Resources>
<Grid Margin="8">
@ -93,7 +96,8 @@ limitations under the License.
</TextBox>
<DockPanel Grid.Row="5" LastChildFill="False" Margin="0,16,0,0">
<Button DockPanel.Dock="Left" Content="Convert to SourceGen Project" Padding="4,0"/>
<Button DockPanel.Dock="Left" Content="Generate SourceGen Project" Padding="4,0"
IsEnabled="{Binding IsLoadFile}" Click="GenerateProject_Click"/>
<Button DockPanel.Dock="Right" Content="Cancel" Width="70" IsCancel="True"/>
</DockPanel>
</Grid>

View File

@ -17,10 +17,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Win32;
using Asm65;
@ -29,6 +31,8 @@ namespace SourceGen.Tools.Omf.WpfGui {
/// Apple IIgs OMF file viewer.
/// </summary>
public partial class OmfViewer : Window, INotifyPropertyChanged {
private const string OUT_FILE_SUFFIX = "_ld";
private OmfFile mOmfFile;
private Formatter mFormatter;
@ -93,6 +97,10 @@ namespace SourceGen.Tools.Omf.WpfGui {
set { mMessageStrings = value; OnPropertyChanged(); }
}
public bool IsLoadFile {
get { return mOmfFile.OmfFileKind == OmfFile.FileKind.Load; }
}
/// <summary>
/// Constructor.
@ -156,5 +164,43 @@ namespace SourceGen.Tools.Omf.WpfGui {
OmfSegmentViewer dlg = new OmfSegmentViewer(this, mOmfFile, item.OmfSeg, mFormatter);
dlg.ShowDialog();
}
private void GenerateProject_Click(object sender, RoutedEventArgs e) {
Loader loader = new Loader(mOmfFile);
if (!loader.Prepare()) {
// Unexpected. If there's a valid reason for this, we need to add details
// to the error message.
string msg = (string)FindResource("str_OmfLoaderFail");
MessageBox.Show(msg, Res.Strings.OPERATION_FAILED);
return;
}
SaveFileDialog fileDlg = new SaveFileDialog() {
Filter = Res.Strings.FILE_FILTER_ALL,
FilterIndex = 1,
ValidateNames = true,
AddExtension = true,
FileName = Path.GetFileName(PathName) + OUT_FILE_SUFFIX
};
if (fileDlg.ShowDialog() != true) {
Debug.WriteLine("Save canceled by user");
return;
}
string pathName = Path.GetFullPath(fileDlg.FileName);
if (!loader.WriteProjectFiles(pathName, pathName + ProjectFile.FILENAME_EXT,
out string errorMessage)) {
MessageBox.Show(Res.Strings.ERR_PROJECT_SAVE_FAIL + ": " + errorMessage,
Res.Strings.OPERATION_FAILED,
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
// Success! Show a message then close the dialog.
string smsg = (string)FindResource("str_OmfConvertSuccessful");
MessageBox.Show(smsg, Res.Strings.OPERATION_SUCCEEDED,
MessageBoxButton.OK, MessageBoxImage.Information);
DialogResult = true;
}
}
}