mirror of
https://github.com/fadden/6502bench.git
synced 2025-02-06 08:30:04 +00:00
Expand the set of things SetInlineDataFormat accepts
Extension scripts (a/k/a "plugins") can now apply any data format supported by FormatDescriptor to inline data. In particular, it can now handle variable-length inline strings. The code analyzer verifies the string structure (e.g. null-terminated strings have exactly one null byte, at the very end). Added PluginException to carry an exception back to the plugin code, for occasions when they're doing something so wrong that we just want to smack them. Added test 2022-extension-scripts to exercise the feature.
This commit is contained in:
parent
9a11ffea49
commit
28eafef27c
@ -18,7 +18,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace PluginCommon {
|
||||
/// <summary>
|
||||
/// Script "plugins" must implement this interface.
|
||||
/// Extension script "plugins" must implement this interface.
|
||||
/// </summary>
|
||||
public interface IPlugin {
|
||||
/// <summary>
|
||||
@ -29,19 +29,23 @@ namespace PluginCommon {
|
||||
string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the plugin with an application reference and a buffer with file
|
||||
/// data. Called before each analysis pass.
|
||||
/// Prepares the plugin for action. Called at the start of the code analysis pass.
|
||||
///
|
||||
/// In the current implementation, the file data will be the same every time,
|
||||
/// because plugins are discarded when a project is closed. However, this may
|
||||
/// because it doesn't change after the project is opened. However, this could
|
||||
/// change if we add a descramble feature.
|
||||
/// </summary>
|
||||
/// <param name="appRef">Reference to application interface.</param>
|
||||
/// <param name="fileData">65xx code and data.</param>
|
||||
/// <param name="plSyms">Symbols available to plugins, in no particular order.</param>
|
||||
/// <param name="plSyms">Symbols available to plugins, in no particular order. All
|
||||
/// platform, project, and user labels are included; auto-generated symbols and
|
||||
/// local variables are not.</param>
|
||||
void Prepare(IApplication appRef, byte[] fileData, List<PlSymbol> plSyms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension scripts that want to handle inline JSRs must implement this interface.
|
||||
/// </summary>
|
||||
public interface IPlugin_InlineJsr {
|
||||
/// <summary>
|
||||
/// Checks to see if code/data near a JSR instruction should be formatted.
|
||||
@ -53,6 +57,9 @@ namespace PluginCommon {
|
||||
void CheckJsr(int offset, out bool noContinue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension scripts that want to handle inline JSLs must implement this interface.
|
||||
/// </summary>
|
||||
public interface IPlugin_InlineJsl {
|
||||
/// <summary>
|
||||
/// Checks to see if code/data near a JSL instruction should be formatted.
|
||||
@ -64,6 +71,9 @@ namespace PluginCommon {
|
||||
void CheckJsl(int offset, out bool noContinue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension scripts that want to handle inline BRKs must implement this interface.
|
||||
/// </summary>
|
||||
public interface IPlugin_InlineBrk {
|
||||
/// <summary>
|
||||
/// Checks to see if code/data near a BRK instruction should be formatted.
|
||||
@ -76,7 +86,8 @@ namespace PluginCommon {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interfaces provided by the application for use by plugins.
|
||||
/// Interfaces provided by the application for use by plugins. An IApplication instance
|
||||
/// is passed to the plugin as an argument Prepare().
|
||||
/// </summary>
|
||||
public interface IApplication {
|
||||
/// <summary>
|
||||
@ -103,15 +114,12 @@ namespace PluginCommon {
|
||||
/// <param name="type">Type of item. Must be NumericLE, NumericBE, or Dense.</param>
|
||||
/// <param name="subType">Sub-type. Must be appropriate for type.</param>
|
||||
/// <param name="label">Optional symbolic label.</param>
|
||||
/// <returns>True if the change was made, false if it was rejected.</returns>
|
||||
/// <returns>True if the change was made, false if it was rejected (e.g. because
|
||||
/// the area is already formatted, or contains code).</returns>
|
||||
/// <exception cref="PluginException">If something is really wrong, e.g. data runs
|
||||
/// off end of file.</exception>
|
||||
bool SetInlineDataFormat(int offset, int length, DataType type,
|
||||
DataSubType subType, string label);
|
||||
|
||||
// Might want to add:
|
||||
// int AddressToOffset(int address) // returns 24-bit offset, or -1 if outside file
|
||||
// int OffsetToAddress(int offset) // returns 24-bit address
|
||||
// (although we could also just pass the address map in at Prepare() -- more efficient
|
||||
// if this gets called frequently)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -122,6 +130,11 @@ namespace PluginCommon {
|
||||
NumericLE,
|
||||
NumericBE,
|
||||
StringGeneric,
|
||||
StringReverse,
|
||||
StringNullTerm,
|
||||
StringL8,
|
||||
StringL16,
|
||||
StringDci,
|
||||
Dense,
|
||||
Fill
|
||||
}
|
||||
@ -137,8 +150,13 @@ namespace PluginCommon {
|
||||
Hex,
|
||||
Decimal,
|
||||
Binary,
|
||||
Ascii,
|
||||
Address,
|
||||
Symbol
|
||||
Symbol,
|
||||
|
||||
// Strings and NumericLE/BE (single character)
|
||||
Ascii,
|
||||
HighAscii,
|
||||
C64Petscii,
|
||||
C64Screen
|
||||
}
|
||||
}
|
||||
|
30
PluginCommon/PluginException.cs
Normal file
30
PluginCommon/PluginException.cs
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2019 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.Runtime.Serialization;
|
||||
|
||||
namespace PluginCommon {
|
||||
/// <summary>
|
||||
/// Custom exception class for cross-domain failures.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PluginException : Exception {
|
||||
public PluginException() : base() { }
|
||||
public PluginException(string msg) : base(msg) { }
|
||||
public PluginException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) { }
|
||||
}
|
||||
}
|
@ -253,6 +253,19 @@ namespace SourceGen {
|
||||
return mAddrList[srcOffIndex].Addr + (offset - mAddrList[srcOffIndex].Offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the specified range of offsets is in a contiguous range of
|
||||
/// addresses. Use this to see if something crosses an address-change boundary.
|
||||
/// </summary>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <param name="length">Length of region.</param>
|
||||
/// <returns>True if the data area is unbroken.</returns>
|
||||
public bool IsContiguous(int offset, int length) {
|
||||
Debug.Assert(offset >= 0 && offset < mTotalLength);
|
||||
Debug.Assert(length > 0 && offset + length <= mTotalLength);
|
||||
return (IndexForOffset(offset) == IndexForOffset(offset + length - 1));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal consistency checks.
|
||||
|
@ -936,30 +936,44 @@ namespace SourceGen {
|
||||
/// <param name="op">Instruction being examined.</param>
|
||||
/// <param name="offset">File offset of start of instruction.</param>
|
||||
/// <param name="noContinue">Set if any plugin declares the call to be no-continue.</param>
|
||||
/// <returns>Updated value for noContinue.</returns>
|
||||
private bool CheckForInlineCall(OpDef op, int offset, bool noContinue) {
|
||||
for (int i = 0; i < mScriptArray.Length; i++) {
|
||||
IPlugin script = mScriptArray[i];
|
||||
// The IPlugin object is a MarshalByRefObject, which doesn't define the
|
||||
// interface directly. A simple test showed it was fairly quick when the
|
||||
// interface was implemented but a bit slow when it wasn't. For performance
|
||||
// we query the capability flags instead.
|
||||
if (op == OpDef.OpJSR_Abs && (mPluginCaps[i] & PluginCap.JSR) != 0) {
|
||||
((IPlugin_InlineJsr)script).CheckJsr(offset, out bool noCont);
|
||||
noContinue |= noCont;
|
||||
} else if (op == OpDef.OpJSR_AbsLong && (mPluginCaps[i] & PluginCap.JSL) != 0) {
|
||||
((IPlugin_InlineJsl)script).CheckJsl(offset, out bool noCont);
|
||||
noContinue |= noCont;
|
||||
} else if (op == OpDef.OpBRK_Implied && (mPluginCaps[i] & PluginCap.BRK) != 0) {
|
||||
((IPlugin_InlineBrk)script).CheckBrk(offset, out bool noCont);
|
||||
noContinue &= noCont;
|
||||
try {
|
||||
IPlugin script = mScriptArray[i];
|
||||
// The IPlugin object is a MarshalByRefObject, which doesn't define the
|
||||
// interface directly. A simple test showed it was fairly quick when the
|
||||
// interface was implemented but a bit slow when it wasn't. For performance
|
||||
// we query the capability flags instead.
|
||||
if (op == OpDef.OpJSR_Abs && (mPluginCaps[i] & PluginCap.JSR) != 0) {
|
||||
((IPlugin_InlineJsr)script).CheckJsr(offset, out bool noCont);
|
||||
noContinue |= noCont;
|
||||
} else if (op == OpDef.OpJSR_AbsLong && (mPluginCaps[i] & PluginCap.JSL) != 0) {
|
||||
((IPlugin_InlineJsl)script).CheckJsl(offset, out bool noCont);
|
||||
noContinue |= noCont;
|
||||
} else if (op == OpDef.OpBRK_Implied && (mPluginCaps[i] & PluginCap.BRK) != 0) {
|
||||
((IPlugin_InlineBrk)script).CheckBrk(offset, out bool noCont);
|
||||
noContinue &= noCont;
|
||||
}
|
||||
} catch (PluginException plex) {
|
||||
LogW(offset, "Uncaught PluginException: " + plex.Message);
|
||||
} catch (Exception ex) {
|
||||
LogW(offset, "Plugin threw exception: " + ex);
|
||||
}
|
||||
}
|
||||
return noContinue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the format of an instruction operand.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of opcode.</param>
|
||||
/// <param name="subType">Format sub-type.</param>
|
||||
/// <param name="label">Label, for subType=Symbol.</param>
|
||||
/// <returns>True if the format was applied.</returns>
|
||||
private bool SetOperandFormat(int offset, DataSubType subType, string label) {
|
||||
if (offset <= 0 || offset > mFileData.Length) {
|
||||
throw new Exception("SOF: bad args: offset=+" + offset.ToString("x6") +
|
||||
throw new PluginException("SOF: bad args: offset=+" + offset.ToString("x6") +
|
||||
" subType=" + subType + " label='" + label + "'; file length is" +
|
||||
mFileData.Length);
|
||||
}
|
||||
@ -981,8 +995,8 @@ namespace SourceGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
FormatDescriptor.SubType subFmt = ConvertPluginSubType(subType, out bool isNumericSub);
|
||||
if (!isNumericSub && subFmt != FormatDescriptor.SubType.None) {
|
||||
FormatDescriptor.SubType subFmt = ConvertPluginSubType(subType, out bool isStringSub);
|
||||
if (subFmt == FormatDescriptor.SubType.None) {
|
||||
LogW(offset, "SOF: bad sub-type " + subType);
|
||||
return false;
|
||||
}
|
||||
@ -1003,16 +1017,26 @@ namespace SourceGen {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a set data format call from an extension script.
|
||||
/// Handles a set inline data format call from an extension script.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of start of data item.</param>
|
||||
/// <param name="length">Length of data item. Must be greater than zero.</param>
|
||||
/// <param name="type">Data type.</param>
|
||||
/// <param name="subType">Data sub-type.</param>
|
||||
/// <param name="label">Label, for type=Symbol.</param>
|
||||
private bool SetInlineDataFormat(int offset, int length, DataType type,
|
||||
DataSubType subType, string label) {
|
||||
if (offset <= 0 || offset + length > mFileData.Length) {
|
||||
throw new Exception("SIDF: bad args: offset=+" + offset.ToString("x6") +
|
||||
if (offset <= 0 || length <= 0 || offset + length > mFileData.Length) {
|
||||
throw new PluginException("SIDF: bad args: offset=+" + offset.ToString("x6") +
|
||||
" len=" + length + " type=" + type + " subType=" + subType +
|
||||
" label='" + label + "'; file length is" + mFileData.Length);
|
||||
}
|
||||
|
||||
if (!mAddrMap.IsContiguous(offset, length)) {
|
||||
LogW(offset, "SIDF: format crosses address map boundary (len=" + length + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Already formatted? We only check the initial offset -- overlapping format
|
||||
// descriptors aren't strictly illegal.
|
||||
if (mAnattribs[offset].DataDescriptor != null) {
|
||||
@ -1035,26 +1059,40 @@ namespace SourceGen {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert type to FormatDescriptor type, and do some validity checks.
|
||||
FormatDescriptor.Type fmt = ConvertPluginType(type);
|
||||
FormatDescriptor.SubType subFmt = ConvertPluginSubType(subType, out bool isNumericSub);
|
||||
//
|
||||
// Convert types to FormatDescriptor types, and do some validity checks.
|
||||
//
|
||||
FormatDescriptor.Type fmt = ConvertPluginType(type, out bool isStringType);
|
||||
FormatDescriptor.SubType subFmt = ConvertPluginSubType(subType, out bool isStringSub);
|
||||
|
||||
if (type == DataType.Dense && subType != DataSubType.None) {
|
||||
throw new Exception("SIDF rej: dense data must use subType=None");
|
||||
throw new PluginException("SIDF rej: dense data must use subType=None");
|
||||
}
|
||||
if (type == DataType.Fill && subType != DataSubType.None) {
|
||||
throw new PluginException("SIDF rej: fill data must use subType=None");
|
||||
}
|
||||
|
||||
if (isNumericSub && fmt != FormatDescriptor.Type.NumericLE &&
|
||||
fmt != FormatDescriptor.Type.NumericBE) {
|
||||
throw new Exception("SIDF rej: bad type/subType combo: type=" +
|
||||
if (isStringType && !isStringSub) {
|
||||
throw new PluginException("SIDF rej: bad type/subType combo: type=" +
|
||||
type + " subType= " + subType);
|
||||
}
|
||||
if ((type == DataType.NumericLE || type == DataType.NumericBE) &&
|
||||
(length < 1 || length > 4)) {
|
||||
throw new Exception("SIDF rej: bad length for numeric item (" +
|
||||
throw new PluginException("SIDF rej: bad length for numeric item (" +
|
||||
length + ")");
|
||||
}
|
||||
if (subType == DataSubType.Symbol && string.IsNullOrEmpty(label)) {
|
||||
throw new Exception("SIDF rej: label required for subType=" + subType);
|
||||
throw new PluginException("SIDF rej: label required for subType=" + subType);
|
||||
}
|
||||
|
||||
if (isStringType) {
|
||||
if (!VerifyStringData(offset, length, fmt)) {
|
||||
return false;
|
||||
}
|
||||
} else if (type == DataType.Fill) {
|
||||
if (!VerifyFillData(offset, length)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks good, create a descriptor, and mark all bytes as inline data.
|
||||
@ -1073,28 +1111,120 @@ namespace SourceGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
private FormatDescriptor.Type ConvertPluginType(DataType pluginType) {
|
||||
/// <summary>
|
||||
/// Verifies that the string data is what is expected. Does not attempt to check
|
||||
/// the character encoding, just the structure.
|
||||
/// </summary>
|
||||
/// <returns>True if all is well.</returns>
|
||||
private bool VerifyStringData(int offset, int length, FormatDescriptor.Type type) {
|
||||
switch (type) {
|
||||
case FormatDescriptor.Type.StringGeneric:
|
||||
case FormatDescriptor.Type.StringReverse:
|
||||
return true;
|
||||
case FormatDescriptor.Type.StringNullTerm:
|
||||
// must end in null byte, and have no null bytes before the end
|
||||
int chk = offset;
|
||||
while (length-- != 0) {
|
||||
byte val = mFileData[chk++];
|
||||
if (val == 0x00) {
|
||||
if (length != 0) {
|
||||
LogW(offset, "found null in middle of null-term string");
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
LogW(offset, "no null at end of null-term string");
|
||||
return false;
|
||||
case FormatDescriptor.Type.StringL8:
|
||||
if (mFileData[offset] != length - 1) {
|
||||
LogW(offset, "L1 string with mismatched length");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case FormatDescriptor.Type.StringL16:
|
||||
int len = RawData.GetWord(mFileData, offset, 2, false);
|
||||
if (len != length - 2) {
|
||||
LogW(offset, "L2 string with mismatched length");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case FormatDescriptor.Type.StringDci:
|
||||
if (length < 2) {
|
||||
LogW(offset, "DCI string is too short");
|
||||
return false;
|
||||
}
|
||||
byte first = (byte) (mFileData[offset] & 0x80);
|
||||
for (int i = offset + 1; i < offset + length - 1; i++) {
|
||||
if ((mFileData[i] & 0x80) != first) {
|
||||
LogW(offset, "mixed DCI string");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((mFileData[offset + length - 1] & 0x80) == first) {
|
||||
LogW(offset, "DCI string did not end");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool VerifyFillData(int offset, int length) {
|
||||
byte first = mFileData[offset];
|
||||
while (--length != 0) {
|
||||
if (mFileData[++offset] != first) {
|
||||
LogW(offset, "SIDF: mismatched fill data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private FormatDescriptor.Type ConvertPluginType(DataType pluginType,
|
||||
out bool isStringType) {
|
||||
isStringType = false;
|
||||
switch (pluginType) {
|
||||
case DataType.NumericLE:
|
||||
return FormatDescriptor.Type.NumericLE;
|
||||
case DataType.NumericBE:
|
||||
return FormatDescriptor.Type.NumericBE;
|
||||
case DataType.StringGeneric:
|
||||
isStringType = true;
|
||||
return FormatDescriptor.Type.StringGeneric;
|
||||
case DataType.StringReverse:
|
||||
isStringType = true;
|
||||
return FormatDescriptor.Type.StringReverse;
|
||||
case DataType.StringNullTerm:
|
||||
isStringType = true;
|
||||
return FormatDescriptor.Type.StringNullTerm;
|
||||
case DataType.StringL8:
|
||||
isStringType = true;
|
||||
return FormatDescriptor.Type.StringL8;
|
||||
case DataType.StringL16:
|
||||
isStringType = true;
|
||||
return FormatDescriptor.Type.StringL16;
|
||||
case DataType.StringDci:
|
||||
isStringType = true;
|
||||
return FormatDescriptor.Type.StringDci;
|
||||
case DataType.Fill:
|
||||
return FormatDescriptor.Type.Fill;
|
||||
case DataType.Dense:
|
||||
return FormatDescriptor.Type.Dense;
|
||||
case DataType.StringGeneric:
|
||||
case DataType.Fill:
|
||||
default:
|
||||
// not appropriate for operands, or inline data (?)
|
||||
throw new Exception("Instr format rej: unexpected format type " + pluginType);
|
||||
Debug.Assert(false);
|
||||
throw new PluginException("Instr format rej: unknown format type " + pluginType);
|
||||
}
|
||||
}
|
||||
|
||||
private FormatDescriptor.SubType ConvertPluginSubType(DataSubType pluginSubType,
|
||||
out bool isNumericSub) {
|
||||
isNumericSub = true;
|
||||
out bool isStringSub) {
|
||||
isStringSub = false;
|
||||
switch (pluginSubType) {
|
||||
case DataSubType.None:
|
||||
isNumericSub = false;
|
||||
return FormatDescriptor.SubType.None;
|
||||
case DataSubType.Hex:
|
||||
return FormatDescriptor.SubType.Hex;
|
||||
@ -1106,8 +1236,20 @@ namespace SourceGen {
|
||||
return FormatDescriptor.SubType.Address;
|
||||
case DataSubType.Symbol:
|
||||
return FormatDescriptor.SubType.Symbol;
|
||||
case DataSubType.Ascii:
|
||||
isStringSub = true;
|
||||
return FormatDescriptor.SubType.Ascii;
|
||||
case DataSubType.HighAscii:
|
||||
isStringSub = true;
|
||||
return FormatDescriptor.SubType.HighAscii;
|
||||
case DataSubType.C64Petscii:
|
||||
isStringSub = true;
|
||||
return FormatDescriptor.SubType.C64Petscii;
|
||||
case DataSubType.C64Screen:
|
||||
isStringSub = true;
|
||||
return FormatDescriptor.SubType.C64Screen;
|
||||
default:
|
||||
throw new Exception("Instr format rej: unexpected sub type " + pluginSubType);
|
||||
throw new PluginException("Instr format rej: unknown sub type " + pluginSubType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2947,7 +2947,8 @@ namespace SourceGen {
|
||||
firstOffset = tup.Value;
|
||||
break;
|
||||
}
|
||||
Debug.Assert(mProject.GetAnattrib(firstOffset).IsDataStart);
|
||||
Debug.Assert(mProject.GetAnattrib(firstOffset).IsDataStart ||
|
||||
mProject.GetAnattrib(firstOffset).IsInlineDataStart);
|
||||
bool toDefault = false;
|
||||
if (mProject.OperandFormats.TryGetValue(firstOffset, out FormatDescriptor curDfd)) {
|
||||
if (curDfd.FormatType == FormatDescriptor.Type.NumericLE &&
|
||||
|
@ -110,6 +110,8 @@ useful for replacing immediate load operands with symbolic constants.</p>
|
||||
<p>Scripts may be loaded from the RuntimeData directory, or from the directory
|
||||
where the project file lives. Attempts to load them from other locations
|
||||
will fail.</p>
|
||||
<p>A project may load multiple scripts. The order in which they are
|
||||
invoked is not defined.</p>
|
||||
|
||||
<h4>Development</h4>
|
||||
|
||||
|
@ -275,6 +275,7 @@ out wrong more often than right.</p>
|
||||
<li>Jumping to an address by pushing the location onto the stack,
|
||||
then executing an <code>RTS</code>.</li>
|
||||
<li>Self-modifying code, e.g. overwriting a <code>JMP</code> instruction.</li>
|
||||
<li>Addresses invoked by external code, e.g. interrupt handlers.</li>
|
||||
</ul>
|
||||
<p>Sometimes the indirect jump targets are coming from a table of
|
||||
addresses in the file. If so, these can be formatted as addresses,
|
||||
|
BIN
SourceGen/SGTestData/2022-extension-scripts
Normal file
BIN
SourceGen/SGTestData/2022-extension-scripts
Normal file
Binary file not shown.
146
SourceGen/SGTestData/2022-extension-scripts.cs
Normal file
146
SourceGen/SGTestData/2022-extension-scripts.cs
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright 2019 faddenSoft. All Rights Reserved.
|
||||
// See the LICENSE.txt file for distribution terms (Apache 2.0).
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using PluginCommon;
|
||||
|
||||
namespace RuntimeData.Test2022 {
|
||||
public class Test2022 : MarshalByRefObject, IPlugin, IPlugin_InlineJsr, IPlugin_InlineJsl {
|
||||
private IApplication mAppRef;
|
||||
private byte[] mFileData;
|
||||
|
||||
private int mInline8StringAddr; // jsr
|
||||
private int mInlineRev8StringAddr; // jsr
|
||||
private int mInlineNullStringAddr; // jsr
|
||||
private int mInlineL1StringAddr; // jsl
|
||||
private int mInlineL2StringAddr; // jsl
|
||||
private int mInlineDciStringAddr; // jsl
|
||||
|
||||
public string Identifier {
|
||||
get {
|
||||
return "Test 2022-extension-scripts";
|
||||
}
|
||||
}
|
||||
|
||||
public void Prepare(IApplication appRef, byte[] fileData, List<PlSymbol> plSymbols) {
|
||||
mAppRef = appRef;
|
||||
mFileData = fileData;
|
||||
|
||||
mAppRef.DebugLog("Test2022(id=" + AppDomain.CurrentDomain.Id + "): prepare()");
|
||||
|
||||
foreach (PlSymbol sym in plSymbols) {
|
||||
switch (sym.Label) {
|
||||
case "PrintInline8String":
|
||||
mInline8StringAddr = sym.Value;
|
||||
break;
|
||||
case "PrintInlineRev8String":
|
||||
mInlineRev8StringAddr = sym.Value;
|
||||
break;
|
||||
case "PrintInlineNullString":
|
||||
mInlineNullStringAddr = sym.Value;
|
||||
break;
|
||||
case "PrintInlineL1String":
|
||||
mInlineL1StringAddr = sym.Value;
|
||||
break;
|
||||
case "PrintInlineL2String":
|
||||
mInlineL2StringAddr = sym.Value;
|
||||
break;
|
||||
case "PrintInlineDciString":
|
||||
mInlineDciStringAddr = sym.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckJsr(int offset, out bool noContinue) {
|
||||
noContinue = false;
|
||||
int target = Util.GetWord(mFileData, offset + 1, 2, false);
|
||||
if (target == mInline8StringAddr) {
|
||||
if (offset + 3 + 8 > mFileData.Length) {
|
||||
mAppRef.DebugLog("8string ran off end at +" +
|
||||
(offset + 3).ToString("x6"));
|
||||
return;
|
||||
}
|
||||
mAppRef.SetInlineDataFormat(offset + 3, 8,
|
||||
DataType.StringGeneric, DataSubType.Ascii, null);
|
||||
} else if (target == mInlineRev8StringAddr) {
|
||||
if (offset + 3 + 8 > mFileData.Length) {
|
||||
mAppRef.DebugLog("rev8string ran off end at +" +
|
||||
(offset + 3).ToString("x6"));
|
||||
return;
|
||||
}
|
||||
mAppRef.SetInlineDataFormat(offset + 3, 8,
|
||||
DataType.StringReverse, DataSubType.Ascii, null);
|
||||
} else if (target == mInlineNullStringAddr) {
|
||||
// look for the terminating null byte
|
||||
int nullOff = offset + 3;
|
||||
while (nullOff < mFileData.Length) {
|
||||
if (mFileData[nullOff] == 0) {
|
||||
break;
|
||||
}
|
||||
nullOff++;
|
||||
}
|
||||
if (nullOff == mFileData.Length) {
|
||||
mAppRef.DebugLog("Unable to find end of null-terminated string at +" +
|
||||
(offset+3).ToString("x6"));
|
||||
return;
|
||||
}
|
||||
mAppRef.SetInlineDataFormat(offset + 3, nullOff - (offset + 3) + 1,
|
||||
DataType.StringNullTerm, DataSubType.Ascii, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckJsl(int offset, out bool noContinue) {
|
||||
noContinue = false;
|
||||
int target = Util.GetWord(mFileData, offset + 1, 3, false);
|
||||
if (target == mInlineL1StringAddr) {
|
||||
// 0 1 2 3 4 5
|
||||
// 22 00 10 01 01 66
|
||||
int len = mFileData[offset + 4]; // 1-byte len in first byte past 4-byte JSL
|
||||
if (offset + 5 + len > mFileData.Length) {
|
||||
// ran off the end
|
||||
mAppRef.DebugLog("L1 string ran off end of file at +" +
|
||||
(offset + 4).ToString("x6"));
|
||||
return;
|
||||
}
|
||||
mAppRef.SetInlineDataFormat(offset + 4, len + 1,
|
||||
DataType.StringL8, DataSubType.Ascii, null);
|
||||
} else if (target == mInlineL2StringAddr) {
|
||||
int len = Util.GetWord(mFileData, offset + 4, 2, false);
|
||||
if (offset + 6 + len > mFileData.Length) {
|
||||
// ran off the end
|
||||
mAppRef.DebugLog("L2 string ran off end of file at +" +
|
||||
(offset+4).ToString("x6"));
|
||||
return;
|
||||
}
|
||||
mAppRef.SetInlineDataFormat(offset + 4, len + 2,
|
||||
DataType.StringL16, DataSubType.Ascii, null);
|
||||
} else {
|
||||
// look for the first byte whose high bit doesn't match the first byte's bit
|
||||
// 0 1 2 3 4 5
|
||||
// 22 00 30 01 66 c1
|
||||
if (offset + 3 + 2 >= mFileData.Length) {
|
||||
// need at least two bytes
|
||||
return;
|
||||
}
|
||||
byte firstBit = (byte) (mFileData[offset + 4] & 0x80);
|
||||
int endOff = offset + 5;
|
||||
while (endOff < mFileData.Length) {
|
||||
if ((mFileData[endOff] & 0x80) != firstBit) {
|
||||
break;
|
||||
}
|
||||
endOff++;
|
||||
}
|
||||
if (endOff == mFileData.Length) {
|
||||
mAppRef.DebugLog("Unable to find end of DCI string at +" +
|
||||
(offset+4).ToString("x6"));
|
||||
return;
|
||||
}
|
||||
mAppRef.SetInlineDataFormat(offset + 4, endOff - (offset + 4) + 1,
|
||||
DataType.StringDci, DataSubType.Ascii, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
SourceGen/SGTestData/2022-extension-scripts.dis65
Normal file
33
SourceGen/SGTestData/2022-extension-scripts.dis65
Normal file
@ -0,0 +1,33 @@
|
||||
### 6502bench SourceGen dis65 v1.0 ###
|
||||
{
|
||||
"_ContentVersion":2,"FileDataLength":156,"FileDataCrc32":-741040917,"ProjectProps":{
|
||||
"CpuName":"65816","IncludeUndocumentedInstr":false,"EntryFlags":32702671,"AutoLabelStyle":"Simple","AnalysisParams":{
|
||||
"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":true,"SmartPlpHandling":true},
|
||||
"PlatformSymbolFileIdentifiers":["PROJ:2022-extension-scripts.sym65"],"ExtensionScriptFileIdentifiers":["PROJ:2022-extension-scripts.cs"],"ProjectSyms":{
|
||||
"PrintInlineDciString":{
|
||||
"DataDescriptor":{
|
||||
"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null},
|
||||
"Comment":"","HasWidth":false,"Label":"PrintInlineDciString","Value":77824,"Source":"Project","Type":"ExternalAddr"}}},
|
||||
"AddressMap":[{
|
||||
"Offset":0,"Addr":4096},
|
||||
{
|
||||
"Offset":135,"Addr":4352}],"TypeHints":[{
|
||||
"Low":0,"High":0,"Hint":"Code"}],"StatusFlagOverrides":{
|
||||
},
|
||||
"Comments":{
|
||||
},
|
||||
"LongComments":{
|
||||
},
|
||||
"Notes":{
|
||||
},
|
||||
"UserLabels":{
|
||||
"121":{
|
||||
"Label":"PrintInline8String","Value":4217,"Source":"User","Type":"LocalOrGlobalAddr"},
|
||||
"122":{
|
||||
"Label":"PrintInlineRev8String","Value":4218,"Source":"User","Type":"LocalOrGlobalAddr"},
|
||||
"123":{
|
||||
"Label":"PrintInlineNullString","Value":4219,"Source":"User","Type":"LocalOrGlobalAddr"}},
|
||||
"OperandFormats":{
|
||||
},
|
||||
"LvTables":{
|
||||
}}
|
7
SourceGen/SGTestData/2022-extension-scripts.sym65
Normal file
7
SourceGen/SGTestData/2022-extension-scripts.sym65
Normal file
@ -0,0 +1,7 @@
|
||||
; Copyright 2019 faddenSoft. All Rights Reserved.
|
||||
; See the LICENSE.txt file for distribution terms (Apache 2.0).
|
||||
|
||||
*SYNOPSIS Symbols test 2022-extension-scripts
|
||||
|
||||
PrintInlineL1String @ $011000
|
||||
PrintInlineL2String @ $012000
|
@ -0,0 +1,59 @@
|
||||
.cpu "65816"
|
||||
.enc sg_ascii
|
||||
.cdef $20,$7e,$20
|
||||
PrintInlineL1String = $011000
|
||||
PrintInlineL2String = $012000
|
||||
PrintInlineDciString = $013000
|
||||
|
||||
* = $1000
|
||||
.as
|
||||
.xs
|
||||
clc
|
||||
xce
|
||||
sep #$30
|
||||
jsr PrintInline8String
|
||||
.text "01234567"
|
||||
jsr PrintInlineRev8String
|
||||
.text "76543210"
|
||||
jsr PrintInlineNullString
|
||||
.null "null-term string"
|
||||
jsl PrintInlineL1String
|
||||
.ptext "string with length/1"
|
||||
jsl PrintInlineL2String
|
||||
.text $14,$00,"string with length/2"
|
||||
jsl PrintInlineDciString
|
||||
.shift "DCI string"
|
||||
jsr L107C
|
||||
jsr L110F
|
||||
jsr L1108
|
||||
rts
|
||||
|
||||
PrintInline8String rts
|
||||
|
||||
PrintInlineRev8String rts
|
||||
|
||||
PrintInlineNullString rts
|
||||
|
||||
L107C jsr PrintInlineNullString
|
||||
per $7ff4
|
||||
rtl
|
||||
|
||||
.byte $65
|
||||
.byte $6e
|
||||
.byte $20
|
||||
.byte $01
|
||||
.logical $1100
|
||||
.text "string"
|
||||
.byte $00
|
||||
.byte $60
|
||||
|
||||
L1108 jsl PrintInlineL2String
|
||||
asl a
|
||||
brk
|
||||
|
||||
.byte $60
|
||||
|
||||
L110F jsr PrintInlineNullString
|
||||
adc $6e
|
||||
.byte $64
|
||||
.here
|
@ -0,0 +1,53 @@
|
||||
PrintInlineL1String equ $011000
|
||||
PrintInlineL2String equ $012000
|
||||
PrintInlineDciString equ $013000
|
||||
|
||||
org $1000
|
||||
clc
|
||||
xce
|
||||
sep #$30
|
||||
jsr PrintInline8String
|
||||
asc '01234567'
|
||||
jsr PrintInlineRev8String
|
||||
rev '01234567'
|
||||
jsr PrintInlineNullString
|
||||
asc 'null-term string',00
|
||||
jsl PrintInlineL1String
|
||||
str 'string with length/1'
|
||||
jsl PrintInlineL2String
|
||||
strl 'string with length/2'
|
||||
jsl PrintInlineDciString
|
||||
dci 'DCI string'
|
||||
jsr L107C
|
||||
jsr L110F
|
||||
jsr L1108
|
||||
rts
|
||||
|
||||
PrintInline8String rts
|
||||
|
||||
PrintInlineRev8String rts
|
||||
|
||||
PrintInlineNullString rts
|
||||
|
||||
L107C jsr PrintInlineNullString
|
||||
per $7ff4
|
||||
rtl
|
||||
|
||||
dfb $65
|
||||
dfb $6e
|
||||
dfb $20
|
||||
dfb $01
|
||||
org $1100
|
||||
asc 'string'
|
||||
dfb $00
|
||||
dfb $60
|
||||
|
||||
L1108 jsl PrintInlineL2String
|
||||
asl A
|
||||
brk
|
||||
|
||||
dfb $60
|
||||
|
||||
L110F jsr PrintInlineNullString
|
||||
adc $6e
|
||||
dfb $64
|
57
SourceGen/SGTestData/Expected/2022-extension-scripts_acme.S
Normal file
57
SourceGen/SGTestData/Expected/2022-extension-scripts_acme.S
Normal file
@ -0,0 +1,57 @@
|
||||
!cpu 65816
|
||||
PrintInlineL1String = $011000
|
||||
PrintInlineL2String = $012000
|
||||
PrintInlineDciString = $013000
|
||||
|
||||
* = $1000
|
||||
!as
|
||||
!rs
|
||||
clc
|
||||
xce
|
||||
sep #$30
|
||||
jsr PrintInline8String
|
||||
!text "01234567"
|
||||
jsr PrintInlineRev8String
|
||||
!text "76543210"
|
||||
jsr PrintInlineNullString
|
||||
!text "null-term string",$00
|
||||
jsl PrintInlineL1String
|
||||
!text $14,"string with length/1"
|
||||
jsl PrintInlineL2String
|
||||
!text $14,$00,"string with length/2"
|
||||
jsl PrintInlineDciString
|
||||
!text "DCI strin",$e7
|
||||
jsr L107C
|
||||
jsr L110F
|
||||
jsr L1108
|
||||
rts
|
||||
|
||||
PrintInline8String rts
|
||||
|
||||
PrintInlineRev8String rts
|
||||
|
||||
PrintInlineNullString rts
|
||||
|
||||
L107C jsr PrintInlineNullString
|
||||
per $7ff4
|
||||
rtl
|
||||
|
||||
!byte $65
|
||||
!byte $6e
|
||||
!byte $20
|
||||
!byte $01
|
||||
!pseudopc $1100 {
|
||||
!text "string"
|
||||
!byte $00
|
||||
!byte $60
|
||||
|
||||
L1108 jsl PrintInlineL2String
|
||||
asl
|
||||
brk
|
||||
|
||||
!byte $60
|
||||
|
||||
L110F jsr PrintInlineNullString
|
||||
adc $6e
|
||||
!byte $64
|
||||
} ;!pseudopc
|
58
SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.S
Normal file
58
SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.S
Normal file
@ -0,0 +1,58 @@
|
||||
.setcpu "65816"
|
||||
PrintInlineL1String = $011000
|
||||
PrintInlineL2String = $012000
|
||||
PrintInlineDciString = $013000
|
||||
|
||||
; .segment "SEG000"
|
||||
.org $1000
|
||||
.a8
|
||||
.i8
|
||||
clc
|
||||
xce
|
||||
sep #$30
|
||||
jsr PrintInline8String
|
||||
.byte "01234567"
|
||||
jsr PrintInlineRev8String
|
||||
.byte "76543210"
|
||||
jsr PrintInlineNullString
|
||||
.asciiz "null-term string"
|
||||
jsl PrintInlineL1String
|
||||
.byte $14,"string with length/1"
|
||||
jsl PrintInlineL2String
|
||||
.byte $14,$00,"string with length/2"
|
||||
jsl PrintInlineDciString
|
||||
.byte "DCI strin",$e7
|
||||
jsr L107C
|
||||
jsr L110F
|
||||
jsr L1108
|
||||
rts
|
||||
|
||||
PrintInline8String: rts
|
||||
|
||||
PrintInlineRev8String: rts
|
||||
|
||||
PrintInlineNullString: rts
|
||||
|
||||
L107C: jsr PrintInlineNullString
|
||||
per $7ff4
|
||||
rtl
|
||||
|
||||
.byte $65
|
||||
.byte $6e
|
||||
.byte $20
|
||||
.byte $01
|
||||
; .segment "SEG001"
|
||||
.org $1100
|
||||
.byte "string"
|
||||
.byte $00
|
||||
.byte $60
|
||||
|
||||
L1108: jsl PrintInlineL2String
|
||||
asl A
|
||||
brk
|
||||
|
||||
.byte $60
|
||||
|
||||
L110F: jsr PrintInlineNullString
|
||||
adc $6e
|
||||
.byte $64
|
@ -0,0 +1,13 @@
|
||||
# 6502bench SourceGen generated linker script for 2022-extension-scripts
|
||||
MEMORY {
|
||||
MAIN: file=%O, start=%S, size=65536;
|
||||
# MEM000: file=%O, start=$1000, size=135;
|
||||
# MEM001: file=%O, start=$1100, size=21;
|
||||
}
|
||||
SEGMENTS {
|
||||
CODE: load=MAIN, type=rw;
|
||||
# SEG000: load=MEM000, type=rw;
|
||||
# SEG001: load=MEM001, type=rw;
|
||||
}
|
||||
FEATURES {}
|
||||
SYMBOLS {}
|
61
SourceGen/SGTestData/Source/2022-extension-scripts.S
Normal file
61
SourceGen/SGTestData/Source/2022-extension-scripts.S
Normal file
@ -0,0 +1,61 @@
|
||||
; Copyright 2019 faddenSoft. All Rights Reserved.
|
||||
; See the LICENSE.txt file for distribution terms (Apache 2.0).
|
||||
;
|
||||
; Assembler: Merlin 32
|
||||
|
||||
; EDIT: the project must include the platform symbol file and extension script
|
||||
|
||||
PrintInlineL1String equ $011000
|
||||
PrintInlineL2String equ $012000
|
||||
PrintInlineDciString equ $013000 ;EDIT: add to project symbols
|
||||
|
||||
org $1000
|
||||
|
||||
clc
|
||||
xce
|
||||
sep #$30
|
||||
mx %11
|
||||
|
||||
; check basics
|
||||
jsr Print8String
|
||||
asc '01234567'
|
||||
jsr PrintRev8String
|
||||
asc '76543210'
|
||||
jsr PrintInlineNullString
|
||||
asc 'null-term string',00
|
||||
|
||||
jsl PrintInlineL1String
|
||||
str 'string with length/1'
|
||||
jsl PrintInlineL2String
|
||||
strl 'string with length/2'
|
||||
jsl PrintInlineDciString
|
||||
dci 'DCI string'
|
||||
|
||||
; check errors
|
||||
jsr broken
|
||||
jsr off_end
|
||||
jsr too_long
|
||||
|
||||
rts
|
||||
|
||||
PrintInline8String rts ;EDIT: set label
|
||||
PrintInlineRev8String rts ;EDIT: set label
|
||||
PrintInlineNullString rts ;EDIT: set label
|
||||
|
||||
|
||||
; check address split across string
|
||||
broken jsr PrintInlineNullString
|
||||
asc 'broken ',01
|
||||
org $1100 ;EDIT: split address
|
||||
asc 'string',00
|
||||
rts
|
||||
|
||||
too_long jsl PrintInlineL2String
|
||||
dw END-*+1 ;should be 1 byte over; want it to be rejected by
|
||||
rts ; function in .cs (otherwise code overlap race)
|
||||
|
||||
; MUST be last
|
||||
off_end jsr PrintInlineNullString
|
||||
nonterm asc 'end'
|
||||
|
||||
END equ *
|
Loading…
x
Reference in New Issue
Block a user