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:
Andy McFadden 2019-10-05 19:51:34 -07:00
parent 9a11ffea49
commit 28eafef27c
17 changed files with 748 additions and 54 deletions

View File

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

View 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) { }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View 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);
}
}
}
}

View 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":{
}}

View 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

View File

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

View File

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

View 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

View 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

View File

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

View 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 *