2020-06-30 01:29:18 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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;
|
2020-06-30 18:31:43 +00:00
|
|
|
|
using System.Text;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
2020-06-30 18:31:43 +00:00
|
|
|
|
using Asm65;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
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;
|
2020-06-30 18:31:43 +00:00
|
|
|
|
private Formatter mFormatter;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2020-07-03 00:10:05 +00:00
|
|
|
|
private Dictionary<int, DisasmProject.RelocData> mRelocData =
|
|
|
|
|
new Dictionary<int, DisasmProject.RelocData>();
|
|
|
|
|
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="omfFile">OMF file to load.</param>
|
2020-07-03 00:10:05 +00:00
|
|
|
|
/// <param name="formatter">Text formatter.</param>
|
2020-06-30 18:31:43 +00:00
|
|
|
|
public Loader(OmfFile omfFile, Formatter formatter) {
|
2020-06-30 01:29:18 +00:00
|
|
|
|
Debug.Assert(omfFile.OmfFileKind == OmfFile.FileKind.Load);
|
|
|
|
|
|
|
|
|
|
mOmfFile = omfFile;
|
2020-06-30 18:31:43 +00:00
|
|
|
|
mFormatter = formatter;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-03 00:10:05 +00:00
|
|
|
|
if (!GenerateDataAndProject()) {
|
2020-06-30 01:29:18 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-03 00:10:05 +00:00
|
|
|
|
private bool GenerateDataAndProject() {
|
2020-06-30 01:29:18 +00:00
|
|
|
|
// 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");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChangeSet cs = new ChangeSet(mSegmentMap.Count * 2);
|
2020-06-30 18:31:43 +00:00
|
|
|
|
AddHeaderComment(proj, cs);
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
|
|
|
|
// Load the segments, and add entries to the project.
|
|
|
|
|
int bufOffset = 0;
|
|
|
|
|
foreach (SegmentMapEntry ent in mSegmentMap) {
|
2020-06-30 18:31:43 +00:00
|
|
|
|
if (ent == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-06-30 22:17:39 +00:00
|
|
|
|
|
|
|
|
|
if (ent.Segment.Kind == OmfSegment.SegmentKind.JumpTable) {
|
2020-07-02 20:41:44 +00:00
|
|
|
|
if (!RelocJumpTable(ent, data, bufOffset, cs)) {
|
2020-06-30 22:17:39 +00:00
|
|
|
|
// Could treat this as non-fatal, but it really ought to work.
|
|
|
|
|
Debug.WriteLine("Jump Table reloc failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Perform relocation.
|
|
|
|
|
if (!RelocSegment(ent, data, bufOffset)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-06-30 18:31:43 +00:00
|
|
|
|
}
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
2020-06-30 18:31:43 +00:00
|
|
|
|
// Add one or more address entries. (Normally one, but data segments
|
|
|
|
|
// can straddle multiple pages.)
|
|
|
|
|
AddAddressEntries(proj, ent, bufOffset, cs);
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
2020-07-02 20:41:44 +00:00
|
|
|
|
// Add a comment identifying the segment and its attributes.
|
|
|
|
|
string segCmt = string.Format(Res.Strings.OMF_SEG_COMMENT_FMT,
|
|
|
|
|
ent.Segment.SegNum, ent.Segment.Kind, ent.Segment.Attrs, ent.Segment.SegName);
|
|
|
|
|
UndoableChange uc = UndoableChange.CreateLongCommentChange(bufOffset, null,
|
2020-06-30 18:31:43 +00:00
|
|
|
|
new MultiLineComment(segCmt));
|
|
|
|
|
cs.Add(uc);
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
2020-07-02 20:41:44 +00:00
|
|
|
|
// Add a note identifying the segment.
|
|
|
|
|
string segNote = string.Format(Res.Strings.OMF_SEG_NOTE_FMT,
|
|
|
|
|
ent.Segment.SegNum, mFormatter.FormatAddress(ent.Address, true),
|
|
|
|
|
ent.Segment.SegName);
|
|
|
|
|
uc = UndoableChange.CreateNoteChange(bufOffset, null,
|
|
|
|
|
new MultiLineComment(segNote));
|
|
|
|
|
cs.Add(uc);
|
|
|
|
|
|
2020-06-30 18:31:43 +00:00
|
|
|
|
bufOffset += ent.Segment.Length;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proj.PrepForNew(data, "new_proj");
|
2020-07-03 00:10:05 +00:00
|
|
|
|
proj.ApplyChanges(cs, false, out _);
|
2020-07-04 00:37:04 +00:00
|
|
|
|
foreach (KeyValuePair<int, DisasmProject.RelocData> kvp in mRelocData) {
|
|
|
|
|
proj.RelocList.Add(kvp.Key, kvp.Value);
|
|
|
|
|
}
|
2020-06-30 01:29:18 +00:00
|
|
|
|
|
|
|
|
|
mLoadedData = data;
|
|
|
|
|
mNewProject = proj;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-30 18:31:43 +00:00
|
|
|
|
private void AddHeaderComment(DisasmProject proj, ChangeSet cs) {
|
|
|
|
|
// Add header comment.
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine(string.Format(Res.Strings.DEFAULT_HEADER_COMMENT_FMT,
|
|
|
|
|
App.ProgramVersion));
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
foreach (SegmentMapEntry ent in mSegmentMap) {
|
|
|
|
|
if (ent == null) {
|
|
|
|
|
continue;
|
2020-06-30 16:27:29 +00:00
|
|
|
|
}
|
2020-07-02 20:41:44 +00:00
|
|
|
|
string segCmt = string.Format(Res.Strings.OMF_SEG_HDR_COMMENT_FMT,
|
2020-06-30 18:31:43 +00:00
|
|
|
|
ent.Segment.SegNum, ent.Segment.Kind, ent.Segment.SegName,
|
|
|
|
|
mFormatter.FormatAddress(ent.Address, true));
|
|
|
|
|
sb.AppendLine(segCmt);
|
2020-06-30 16:27:29 +00:00
|
|
|
|
}
|
2020-06-30 18:31:43 +00:00
|
|
|
|
|
|
|
|
|
UndoableChange uc = UndoableChange.CreateLongCommentChange(
|
|
|
|
|
LineListGen.Line.HEADER_COMMENT_OFFSET,
|
|
|
|
|
null, new MultiLineComment(sb.ToString()));
|
|
|
|
|
cs.Add(uc);
|
2020-06-30 16:27:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-30 18:31:43 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Edits the data file, changing values based on the relocation dictionary.
|
|
|
|
|
/// </summary>
|
2020-06-30 01:29:18 +00:00
|
|
|
|
private bool RelocSegment(SegmentMapEntry ent, byte[] data, int bufOffset) {
|
2020-06-30 18:31:43 +00:00
|
|
|
|
const int INVALID_ADDR = 0x00ffffff;
|
|
|
|
|
|
2020-06-30 01:29:18 +00:00
|
|
|
|
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) {
|
2020-06-30 18:31:43 +00:00
|
|
|
|
// Some other file; not much we can do with this. Drop in an obviously
|
|
|
|
|
// invalid address and keep going.
|
2020-06-30 01:29:18 +00:00
|
|
|
|
Debug.WriteLine("Unable to process reloc with FileNum=" + omfRel.FileNum);
|
2020-06-30 18:31:43 +00:00
|
|
|
|
relocAddr = INVALID_ADDR;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
} 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) {
|
2020-06-30 18:31:43 +00:00
|
|
|
|
// Can't find the segment. Unlike the file case, this was expected to
|
|
|
|
|
// be something we could resolve with what we were given, so this is
|
|
|
|
|
// a hard failure.
|
2020-06-30 01:29:18 +00:00
|
|
|
|
Debug.WriteLine("Reloc SegNum=" + omfRel.SegNum + " not in map");
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
relocAddr += mSegmentMap[omfRel.SegNum].Address;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-03 00:10:05 +00:00
|
|
|
|
if (omfRel.Shift < -32 || omfRel.Shift > 32) {
|
|
|
|
|
Debug.WriteLine("Invalid reloc shift " + omfRel.Shift);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-07-04 00:37:04 +00:00
|
|
|
|
int adjRelocAddr = relocAddr;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
if (omfRel.Shift < 0) {
|
2020-07-04 00:37:04 +00:00
|
|
|
|
adjRelocAddr >>= -omfRel.Shift;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
} else if (omfRel.Shift > 0) {
|
2020-07-04 00:37:04 +00:00
|
|
|
|
adjRelocAddr <<= omfRel.Shift;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (omfRel.Width) {
|
|
|
|
|
case 1:
|
2020-07-04 00:37:04 +00:00
|
|
|
|
data[bufOffset + omfRel.Offset] = (byte)(adjRelocAddr);
|
2020-06-30 01:29:18 +00:00
|
|
|
|
break;
|
|
|
|
|
case 2:
|
2020-07-04 00:37:04 +00:00
|
|
|
|
data[bufOffset + omfRel.Offset] = (byte)(adjRelocAddr);
|
|
|
|
|
data[bufOffset + omfRel.Offset + 1] = (byte)(adjRelocAddr >> 8);
|
2020-06-30 01:29:18 +00:00
|
|
|
|
break;
|
|
|
|
|
case 3:
|
2020-07-04 00:37:04 +00:00
|
|
|
|
data[bufOffset + omfRel.Offset] = (byte)(adjRelocAddr);
|
|
|
|
|
data[bufOffset + omfRel.Offset + 1] = (byte)(adjRelocAddr >> 8);
|
|
|
|
|
data[bufOffset + omfRel.Offset + 2] = (byte)(adjRelocAddr >> 16);
|
2020-06-30 01:29:18 +00:00
|
|
|
|
break;
|
2020-06-30 16:27:29 +00:00
|
|
|
|
case 4:
|
2020-07-04 00:37:04 +00:00
|
|
|
|
data[bufOffset + omfRel.Offset] = (byte)(adjRelocAddr);
|
|
|
|
|
data[bufOffset + omfRel.Offset + 1] = (byte)(adjRelocAddr >> 8);
|
|
|
|
|
data[bufOffset + omfRel.Offset + 2] = (byte)(adjRelocAddr >> 16);
|
|
|
|
|
data[bufOffset + omfRel.Offset + 3] = (byte)(adjRelocAddr >> 24);
|
2020-06-30 16:27:29 +00:00
|
|
|
|
break;
|
2020-06-30 01:29:18 +00:00
|
|
|
|
default:
|
|
|
|
|
Debug.WriteLine("Invalid reloc width " + omfRel.Width);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-07-03 00:10:05 +00:00
|
|
|
|
|
2020-07-04 00:37:04 +00:00
|
|
|
|
mRelocData.Add(bufOffset + omfRel.Offset, new DisasmProject.RelocData(
|
|
|
|
|
(byte)omfRel.Width, (sbyte)omfRel.Shift, relocAddr));
|
2020-06-30 01:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-06-30 18:31:43 +00:00
|
|
|
|
|
2020-06-30 22:17:39 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Edits the data file, essentially putting the jump table entries into the
|
|
|
|
|
/// "loaded" state.
|
|
|
|
|
/// </summary>
|
2020-07-03 00:10:05 +00:00
|
|
|
|
/// <remarks>
|
|
|
|
|
/// We don't use ent.Segment.Relocs, as that is expected to be empty.
|
|
|
|
|
/// </remarks>
|
2020-07-02 20:41:44 +00:00
|
|
|
|
private bool RelocJumpTable(SegmentMapEntry ent, byte[] data, int bufOffset,
|
|
|
|
|
ChangeSet cs) {
|
2020-06-30 22:17:39 +00:00
|
|
|
|
const int ENTRY_LEN = 14;
|
|
|
|
|
|
2020-07-03 00:10:05 +00:00
|
|
|
|
if (ent.Segment.Relocs.Count != 0) {
|
|
|
|
|
Debug.WriteLine("WEIRD: jump table has reloc data?");
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-30 22:17:39 +00:00
|
|
|
|
byte[] srcData = ent.Segment.GetConstData();
|
|
|
|
|
Array.Copy(srcData, 0, data, bufOffset, srcData.Length);
|
|
|
|
|
|
|
|
|
|
// For no documented reason, jump tables start with 8 zero bytes.
|
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
|
|
|
if (data[bufOffset + i] != 0) {
|
|
|
|
|
Debug.WriteLine("JumpTab: missing 8-byte header");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-02 20:41:44 +00:00
|
|
|
|
TypedRangeSet newSet = new TypedRangeSet();
|
|
|
|
|
TypedRangeSet undoSet = new TypedRangeSet();
|
|
|
|
|
|
2020-06-30 22:17:39 +00:00
|
|
|
|
for (int i = 8; i + 4 <= ent.Segment.Length; i += ENTRY_LEN) {
|
2020-07-03 00:10:05 +00:00
|
|
|
|
//int userId = RawData.GetWord(data, bufOffset + i, 2, false);
|
2020-06-30 22:17:39 +00:00
|
|
|
|
int fileNum = RawData.GetWord(data, bufOffset + i + 2, 2, false);
|
|
|
|
|
|
|
|
|
|
if (fileNum == 0) {
|
|
|
|
|
// A zero file number indicates end of table.
|
|
|
|
|
Debug.WriteLine("JumpTab: found fileNum=0 at offset " + i + ", len=" +
|
|
|
|
|
ent.Segment.Length);
|
|
|
|
|
break;
|
|
|
|
|
} else if (fileNum != 1) {
|
|
|
|
|
// External file, ignore entry.
|
|
|
|
|
Debug.WriteLine("JumpTab: ignoring entry with FileNum=" + fileNum);
|
|
|
|
|
continue;
|
|
|
|
|
} else if (i + ENTRY_LEN > ent.Segment.Length) {
|
|
|
|
|
// Make sure the rest fits.
|
|
|
|
|
Debug.WriteLine("JumpTab: overran buffer");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-03 00:10:05 +00:00
|
|
|
|
// Note: segment might end right after FileNum, so don't try to read further
|
2020-06-30 22:17:39 +00:00
|
|
|
|
// until we've confirmed that FileNum != 0.
|
|
|
|
|
|
|
|
|
|
int segNum = RawData.GetWord(data, bufOffset + i + 4, 2, false);
|
|
|
|
|
int segOff = RawData.GetWord(data, bufOffset + i + 6, 4, false);
|
|
|
|
|
|
|
|
|
|
if (segNum < 0 || segNum >= mSegmentMap.Count || mSegmentMap[segNum] == null) {
|
|
|
|
|
Debug.WriteLine("JumpTab: invalid SegNum=" + segNum);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-07-02 20:41:44 +00:00
|
|
|
|
if (data[bufOffset + i + 10] != 0x22) {
|
|
|
|
|
Debug.WriteLine("JumpTab: did not find expected JSL at off=" + i);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int addr = mSegmentMap[segNum].Address + segOff;
|
|
|
|
|
int jmlOffset = bufOffset + i + 10;
|
|
|
|
|
data[jmlOffset] = 0x5c; // JML
|
|
|
|
|
data[jmlOffset + 1] = (byte)addr;
|
|
|
|
|
data[jmlOffset + 2] = (byte)(addr >> 8);
|
|
|
|
|
data[jmlOffset + 3] = (byte)(addr >> 16);
|
|
|
|
|
//Debug.WriteLine("JumpTab: off=" + i + " -> " +
|
|
|
|
|
// mFormatter.FormatAddress(addr, true));
|
|
|
|
|
|
|
|
|
|
// It seems to be fairly common for jump table entries to not be referenced
|
|
|
|
|
// from the program, which can leave whole dynamic segments unreferenced. Set
|
|
|
|
|
// a code hint on the JML instruction.
|
|
|
|
|
undoSet.Add(jmlOffset, (int)CodeAnalysis.TypeHint.NoHint);
|
|
|
|
|
newSet.Add(jmlOffset, (int)CodeAnalysis.TypeHint.Code);
|
2020-06-30 22:17:39 +00:00
|
|
|
|
}
|
2020-07-02 20:41:44 +00:00
|
|
|
|
|
|
|
|
|
UndoableChange uc = UndoableChange.CreateTypeHintChange(undoSet, newSet);
|
|
|
|
|
cs.Add(uc);
|
|
|
|
|
|
2020-06-30 22:17:39 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-30 18:31:43 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds one or more entries to the address map for the specified segment.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static void AddAddressEntries(DisasmProject proj, SegmentMapEntry ent,
|
|
|
|
|
int bufOffset, ChangeSet cs) {
|
|
|
|
|
int addr = ent.Address;
|
|
|
|
|
int segRem = ent.Segment.Length;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
// Generate an ORG directive.
|
|
|
|
|
int origAddr = proj.AddrMap.Get(bufOffset);
|
|
|
|
|
UndoableChange uc = UndoableChange.CreateAddressChange(bufOffset,
|
|
|
|
|
origAddr, addr);
|
|
|
|
|
cs.Add(uc);
|
|
|
|
|
|
|
|
|
|
// Compare amount of space in this bank to amount left in segment.
|
|
|
|
|
int bankRem = 0x00010000 - (addr & 0xffff);
|
|
|
|
|
if (bankRem > segRem) {
|
|
|
|
|
// All done, bail.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Advance to start of next bank.
|
|
|
|
|
addr += bankRem;
|
|
|
|
|
Debug.Assert((addr & 0x0000ffff) == 0);
|
|
|
|
|
bufOffset += bankRem;
|
|
|
|
|
segRem -= bankRem;
|
|
|
|
|
Debug.WriteLine("Adding additional ORG at " + addr);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-30 01:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
}
|