From 28eafef27c43ce851ddddf3cc29c318928eec995 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sat, 5 Oct 2019 19:51:34 -0700 Subject: [PATCH] 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. --- PluginCommon/Interfaces.cs | 48 ++-- PluginCommon/PluginException.cs | 30 +++ SourceGen/AddressMap.cs | 13 ++ SourceGen/CodeAnalysis.cs | 218 +++++++++++++++--- SourceGen/MainController.cs | 3 +- SourceGen/RuntimeData/Help/advanced.html | 2 + SourceGen/RuntimeData/Help/analysis.html | 1 + SourceGen/SGTestData/2022-extension-scripts | Bin 0 -> 156 bytes .../SGTestData/2022-extension-scripts.cs | 146 ++++++++++++ .../SGTestData/2022-extension-scripts.dis65 | 33 +++ .../SGTestData/2022-extension-scripts.sym65 | 7 + .../Expected/2022-extension-scripts_64tass.S | 59 +++++ .../2022-extension-scripts_Merlin32.S | 53 +++++ .../Expected/2022-extension-scripts_acme.S | 57 +++++ .../Expected/2022-extension-scripts_cc65.S | 58 +++++ .../Expected/2022-extension-scripts_cc65.cfg | 13 ++ .../Source/2022-extension-scripts.S | 61 +++++ 17 files changed, 748 insertions(+), 54 deletions(-) create mode 100644 PluginCommon/PluginException.cs create mode 100644 SourceGen/SGTestData/2022-extension-scripts create mode 100644 SourceGen/SGTestData/2022-extension-scripts.cs create mode 100644 SourceGen/SGTestData/2022-extension-scripts.dis65 create mode 100644 SourceGen/SGTestData/2022-extension-scripts.sym65 create mode 100644 SourceGen/SGTestData/Expected/2022-extension-scripts_64tass.S create mode 100644 SourceGen/SGTestData/Expected/2022-extension-scripts_Merlin32.S create mode 100644 SourceGen/SGTestData/Expected/2022-extension-scripts_acme.S create mode 100644 SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.S create mode 100644 SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.cfg create mode 100644 SourceGen/SGTestData/Source/2022-extension-scripts.S diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 497a545..0bc2a69 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -18,7 +18,7 @@ using System.Collections.Generic; namespace PluginCommon { /// - /// Script "plugins" must implement this interface. + /// Extension script "plugins" must implement this interface. /// public interface IPlugin { /// @@ -29,19 +29,23 @@ namespace PluginCommon { string Identifier { get; } /// - /// 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. /// /// Reference to application interface. /// 65xx code and data. - /// Symbols available to plugins, in no particular order. + /// Symbols available to plugins, in no particular order. All + /// platform, project, and user labels are included; auto-generated symbols and + /// local variables are not. void Prepare(IApplication appRef, byte[] fileData, List plSyms); } + /// + /// Extension scripts that want to handle inline JSRs must implement this interface. + /// public interface IPlugin_InlineJsr { /// /// 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); } + /// + /// Extension scripts that want to handle inline JSLs must implement this interface. + /// public interface IPlugin_InlineJsl { /// /// 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); } + /// + /// Extension scripts that want to handle inline BRKs must implement this interface. + /// public interface IPlugin_InlineBrk { /// /// Checks to see if code/data near a BRK instruction should be formatted. @@ -76,7 +86,8 @@ namespace PluginCommon { } /// - /// 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(). /// public interface IApplication { /// @@ -103,15 +114,12 @@ namespace PluginCommon { /// Type of item. Must be NumericLE, NumericBE, or Dense. /// Sub-type. Must be appropriate for type. /// Optional symbolic label. - /// True if the change was made, false if it was rejected. + /// True if the change was made, false if it was rejected (e.g. because + /// the area is already formatted, or contains code). + /// If something is really wrong, e.g. data runs + /// off end of file. 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) } /// @@ -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 } } diff --git a/PluginCommon/PluginException.cs b/PluginCommon/PluginException.cs new file mode 100644 index 0000000..7e2c416 --- /dev/null +++ b/PluginCommon/PluginException.cs @@ -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 { + /// + /// Custom exception class for cross-domain failures. + /// + [Serializable] + public class PluginException : Exception { + public PluginException() : base() { } + public PluginException(string msg) : base(msg) { } + public PluginException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + } +} diff --git a/SourceGen/AddressMap.cs b/SourceGen/AddressMap.cs index 47d36bb..05ae8e8 100644 --- a/SourceGen/AddressMap.cs +++ b/SourceGen/AddressMap.cs @@ -253,6 +253,19 @@ namespace SourceGen { return mAddrList[srcOffIndex].Addr + (offset - mAddrList[srcOffIndex].Offset); } + /// + /// 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. + /// + /// Start offset. + /// Length of region. + /// True if the data area is unbroken. + 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)); + } + /// /// Internal consistency checks. diff --git a/SourceGen/CodeAnalysis.cs b/SourceGen/CodeAnalysis.cs index d687cea..9315b30 100644 --- a/SourceGen/CodeAnalysis.cs +++ b/SourceGen/CodeAnalysis.cs @@ -936,30 +936,44 @@ namespace SourceGen { /// Instruction being examined. /// File offset of start of instruction. /// Set if any plugin declares the call to be no-continue. + /// Updated value for noContinue. 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; } + /// + /// Sets the format of an instruction operand. + /// + /// Offset of opcode. + /// Format sub-type. + /// Label, for subType=Symbol. + /// True if the format was applied. 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 { } /// - /// Handles a set data format call from an extension script. + /// Handles a set inline data format call from an extension script. /// + /// Offset of start of data item. + /// Length of data item. Must be greater than zero. + /// Data type. + /// Data sub-type. + /// Label, for type=Symbol. 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) { + /// + /// Verifies that the string data is what is expected. Does not attempt to check + /// the character encoding, just the structure. + /// + /// True if all is well. + 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); } } } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 95ec1b3..e577ca2 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -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 && diff --git a/SourceGen/RuntimeData/Help/advanced.html b/SourceGen/RuntimeData/Help/advanced.html index 5ae1c7e..7521fe1 100644 --- a/SourceGen/RuntimeData/Help/advanced.html +++ b/SourceGen/RuntimeData/Help/advanced.html @@ -110,6 +110,8 @@ useful for replacing immediate load operands with symbolic constants.

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.

+

A project may load multiple scripts. The order in which they are +invoked is not defined.

Development

diff --git a/SourceGen/RuntimeData/Help/analysis.html b/SourceGen/RuntimeData/Help/analysis.html index 60fd2e2..57bab12 100644 --- a/SourceGen/RuntimeData/Help/analysis.html +++ b/SourceGen/RuntimeData/Help/analysis.html @@ -275,6 +275,7 @@ out wrong more often than right.

  • Jumping to an address by pushing the location onto the stack, then executing an RTS.
  • Self-modifying code, e.g. overwriting a JMP instruction.
  • +
  • Addresses invoked by external code, e.g. interrupt handlers.
  • Sometimes the indirect jump targets are coming from a table of addresses in the file. If so, these can be formatted as addresses, diff --git a/SourceGen/SGTestData/2022-extension-scripts b/SourceGen/SGTestData/2022-extension-scripts new file mode 100644 index 0000000000000000000000000000000000000000..0cc2dd192783899d882117a52e9b2d028d0824c2 GIT binary patch literal 156 zcmb36{m4L}Qoz8_$k@cx%v_;Lz}(E##MsEtK%rV7uQVq|wKmOE1aLH&kL!U=(4%B4eb)V8H0&>7XFhhPE_0BuMr X%Fj;CQ(%Oep8zt4ivc8)nwJ6q2JS5m literal 0 HcmV?d00001 diff --git a/SourceGen/SGTestData/2022-extension-scripts.cs b/SourceGen/SGTestData/2022-extension-scripts.cs new file mode 100644 index 0000000..5a20271 --- /dev/null +++ b/SourceGen/SGTestData/2022-extension-scripts.cs @@ -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 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); + } + } + } +} diff --git a/SourceGen/SGTestData/2022-extension-scripts.dis65 b/SourceGen/SGTestData/2022-extension-scripts.dis65 new file mode 100644 index 0000000..0e41eb6 --- /dev/null +++ b/SourceGen/SGTestData/2022-extension-scripts.dis65 @@ -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":{ +}} diff --git a/SourceGen/SGTestData/2022-extension-scripts.sym65 b/SourceGen/SGTestData/2022-extension-scripts.sym65 new file mode 100644 index 0000000..ec014d1 --- /dev/null +++ b/SourceGen/SGTestData/2022-extension-scripts.sym65 @@ -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 diff --git a/SourceGen/SGTestData/Expected/2022-extension-scripts_64tass.S b/SourceGen/SGTestData/Expected/2022-extension-scripts_64tass.S new file mode 100644 index 0000000..772d4c9 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2022-extension-scripts_64tass.S @@ -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 diff --git a/SourceGen/SGTestData/Expected/2022-extension-scripts_Merlin32.S b/SourceGen/SGTestData/Expected/2022-extension-scripts_Merlin32.S new file mode 100644 index 0000000..bc4f79d --- /dev/null +++ b/SourceGen/SGTestData/Expected/2022-extension-scripts_Merlin32.S @@ -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 diff --git a/SourceGen/SGTestData/Expected/2022-extension-scripts_acme.S b/SourceGen/SGTestData/Expected/2022-extension-scripts_acme.S new file mode 100644 index 0000000..f1f8c40 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2022-extension-scripts_acme.S @@ -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 diff --git a/SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.S b/SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.S new file mode 100644 index 0000000..1866855 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.S @@ -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 diff --git a/SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.cfg b/SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.cfg new file mode 100644 index 0000000..e1fe3c3 --- /dev/null +++ b/SourceGen/SGTestData/Expected/2022-extension-scripts_cc65.cfg @@ -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 {} diff --git a/SourceGen/SGTestData/Source/2022-extension-scripts.S b/SourceGen/SGTestData/Source/2022-extension-scripts.S new file mode 100644 index 0000000..88d76cb --- /dev/null +++ b/SourceGen/SGTestData/Source/2022-extension-scripts.S @@ -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 *