Optionally treat BRKs as two-byte instructions

Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.

This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings.  In the system definitions it defaults to
true for Apple ///, false for all others.

ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.

Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.

To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.

Also, added/corrected some SOS constants.

See also issue #44.
This commit is contained in:
Andy McFadden 2019-10-09 14:55:56 -07:00
parent b8e11215fa
commit dfd5bcab1b
25 changed files with 127 additions and 58 deletions

View File

@ -146,7 +146,8 @@ namespace Asm65 {
/// <param name="includeUndocumented">Set to true if "undocumented" opcodes should
/// be included in the definition.</param>
/// <returns>Best CpuDef.</returns>
public static CpuDef GetBestMatch(CpuType type, bool includeUndocumented) {
public static CpuDef GetBestMatch(CpuType type, bool includeUndocumented,
bool twoByteBrk) {
// Many 65xx variants boil down to a 6502, 65C02, or 65816, at least as far as
// a disassembler needs to know. These do not, and would need full definitions:
//
@ -190,6 +191,16 @@ namespace Asm65 {
cpuDef = stripped;
}
// If we want two-byte BRKs, replace the entry with the StackInt form. Copy the rest.
if (twoByteBrk) {
CpuDef bigBrkDef = new CpuDef(cpuDef);
bigBrkDef.mOpDefs[0] = OpDef.OpBRK_StackInt;
for (int i = 1; i < 256; i++) {
bigBrkDef.mOpDefs[i] = cpuDef.mOpDefs[i];
}
cpuDef = bigBrkDef;
}
cpuDef.HasUndocumented = includeUndocumented;
return cpuDef;

View File

@ -733,7 +733,7 @@ namespace Asm65 {
case AddressMode.DP:
case AddressMode.PCRel:
case AddressMode.PCRelLong: // BRL
case AddressMode.StackInt: // COP
case AddressMode.StackInt: // COP and two-byte BRK
case AddressMode.StackPCRelLong: // PER
case AddressMode.WDM:
fmt = wdisStr + "{0}";

View File

@ -1478,15 +1478,16 @@ namespace Asm65 {
AddrMode = AddressMode.Unknown
};
public static readonly OpDef OpBRK_Implied = new OpDef(OpBRK) {
public static readonly OpDef OpBRK_Implied = new OpDef(OpBRK) { // 1-byte form
Opcode = 0x00,
// There should arguably be OpBRK_Implied for 6502/65C02 and OpBRK_StackInt for
// 65816, but in practice hardly any assemblers prefer (or even allow) it to be
// a two-byte instruction. The BRK does *act* like a two-byte instruction, but
// code rarely reflects this usage.
AddrMode = AddressMode.Implied,
CycDef = 7 | (int)(CycleMod.OneIfE0)
};
public static readonly OpDef OpBRK_StackInt = new OpDef(OpBRK) { // 2-byte form
Opcode = 0x00,
AddrMode = AddressMode.StackInt,
CycDef = 7 | (int)(CycleMod.OneIfE0)
};
public static readonly OpDef OpORA_DPIndexXInd = new OpDef(OpORA) {
Opcode = 0x01,
AddrMode = AddressMode.DPIndexXInd,

View File

@ -83,8 +83,9 @@ namespace PluginCommon {
/// The file data is only guaranteed to hold the BRK opcode byte.
/// </summary>
/// <param name="offset">Offset of the BRK instruction.</param>
/// <param name="isTwoBytes">True if the CPU is configured for two-byte BRKs.</param>
/// <param name="noContinue">Set to true if the BRK doesn't actually return.</param>
void CheckBrk(int offset, out bool noContinue);
void CheckBrk(int offset, bool isTwoBytes, out bool noContinue);
}
/// <summary>

View File

@ -123,7 +123,8 @@ namespace PluginCommon {
dict.Add(ps.Value, ps);
} catch (ArgumentException) {
appRef.DebugLog("WARNING: GenerateValueList: multiple entries with " +
"value " + ps.Value.ToString("x4"));
"value " + ps.Value.ToString("x4") + ": " + dict[ps.Value].Label +
" and " + ps.Label);
}
}
}

View File

@ -58,5 +58,31 @@ namespace PluginCommon {
public static uint ComputeBufferCRC(byte[] data) {
return CRC32.OnBuffer(0, data, 0, data.Length);
}
/// <summary>
/// Formats the byte that follows a BRK instruction. How we do this depends on
/// whether the system is configured for two-byte BRKs.
/// </summary>
/// <remarks>
/// We can actually apply the format both ways and let the app ignore the one it
/// doesn't like, but this is cleaner.
/// </remarks>
/// <param name="appRef">Reference to application object.</param>
/// <param name="twoByteBrk">True if BRKs are handled as two-byte instructions.</param>
/// <param name="brkOffset">Offset of BRK instruction.</param>
/// <param name="type">Data type to apply.</param>
/// <param name="subType">Data sub-type to apply.</param>
/// <param name="label">Label, for subType=Symbol.</param>
public static void FormatBrkByte(IApplication appRef, bool twoByteBrk, int brkOffset,
DataSubType subType, string label) {
if (twoByteBrk) {
// Two-byte BRK, so we want to apply the format to the instruction itself.
appRef.SetOperandFormat(brkOffset, subType, label);
} else {
// Single-byte BRK, so we want to format the byte that follows the
// instruction as inline data.
appRef.SetInlineDataFormat(brkOffset + 1, 1, DataType.NumericLE, subType, label);
}
}
}
}

View File

@ -314,8 +314,8 @@ namespace SourceGen.AsmGen {
return null;
}
}
if (op == OpDef.OpWDM_WDM) {
// ACME doesn't like this to have an operand. Output as hex.
if (op == OpDef.OpWDM_WDM || op == OpDef.OpBRK_StackInt) {
// ACME doesn't like these to have an operand. Output as hex.
return null;
}
return string.Empty; // indicate original is fine

View File

@ -325,8 +325,9 @@ namespace SourceGen.AsmGen {
// IGenerator
public string ModifyOpcode(int offset, OpDef op) {
if ((op == OpDef.OpWDM_WDM) && mAsmVersion < V2_18) {
if (op == OpDef.OpBRK_StackInt || (op == OpDef.OpWDM_WDM && mAsmVersion < V2_18)) {
// cc65 v2.17 doesn't support WDM, and assembles BRK <arg> to opcode $05.
// cc65 v2.18 only supports two-byte BRK on 65816 code.
// https://github.com/cc65/cc65/issues/715
// https://github.com/cc65/cc65/issues/716
return null;

View File

@ -672,7 +672,7 @@ namespace SourceGen {
// On first visit, check for BRK inline call.
if (firstVisit) {
if (op == OpDef.OpBRK_Implied) {
if (op == OpDef.OpBRK_Implied || op == OpDef.OpBRK_StackInt) {
bool noContinue = CheckForInlineCall(op, offset, !doContinue);
if (!noContinue) {
// We're expected to continue execution past the BRK.
@ -951,8 +951,10 @@ namespace SourceGen {
} 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);
} else if ((op == OpDef.OpBRK_Implied || op == OpDef.OpBRK_StackInt) &&
(mPluginCaps[i] & PluginCap.BRK) != 0) {
((IPlugin_InlineBrk)script).CheckBrk(offset, op == OpDef.OpBRK_StackInt,
out bool noCont);
noContinue &= noCont;
}
} catch (PluginException plex) {

View File

@ -239,6 +239,7 @@ namespace SourceGen {
// system definition.
ProjectProps.CpuType = CpuDef.CpuType.Cpu65816;
ProjectProps.IncludeUndocumentedInstr = false;
ProjectProps.TwoByteBrk = false;
UpdateCpuDef();
}
@ -281,7 +282,8 @@ namespace SourceGen {
public void ApplySystemDef(SystemDef sysDef) {
CpuDef.CpuType cpuType = CpuDef.GetCpuTypeFromName(sysDef.Cpu);
bool includeUndoc = SystemDefaults.GetUndocumentedOpcodes(sysDef);
CpuDef tmpDef = CpuDef.GetBestMatch(cpuType, includeUndoc);
bool twoByteBrk = SystemDefaults.GetTwoByteBrk(sysDef);
CpuDef tmpDef = CpuDef.GetBestMatch(cpuType, includeUndoc, twoByteBrk);
// Store the best-matched CPU in properties, rather than whichever was originally
// requested. This way the behavior of the project is the same for everyone, even
@ -289,6 +291,7 @@ namespace SourceGen {
// originally-specified CPU.
ProjectProps.CpuType = tmpDef.Type;
ProjectProps.IncludeUndocumentedInstr = includeUndoc;
ProjectProps.TwoByteBrk = twoByteBrk;
UpdateCpuDef();
ProjectProps.AnalysisParams.DefaultTextScanMode =
@ -325,7 +328,7 @@ namespace SourceGen {
public void UpdateCpuDef() {
CpuDef = CpuDef.GetBestMatch(ProjectProps.CpuType,
ProjectProps.IncludeUndocumentedInstr);
ProjectProps.IncludeUndocumentedInstr, ProjectProps.TwoByteBrk);
}
/// <summary>

View File

@ -188,6 +188,7 @@ namespace SourceGen {
public class SerProjectProperties {
public string CpuName { get; set; }
public bool IncludeUndocumentedInstr { get; set; }
public bool TwoByteBrk { get; set; }
public int EntryFlags { get; set; }
public string AutoLabelStyle { get; set; }
public SerAnalysisParameters AnalysisParams { get; set; }
@ -199,6 +200,7 @@ namespace SourceGen {
public SerProjectProperties(ProjectProperties props) {
CpuName = Asm65.CpuDef.GetCpuNameFromType(props.CpuType);
IncludeUndocumentedInstr = props.IncludeUndocumentedInstr;
TwoByteBrk = props.TwoByteBrk;
EntryFlags = props.EntryFlags.AsInt;
AutoLabelStyle = props.AutoLabelStyle.ToString();
AnalysisParams = new SerAnalysisParameters(props.AnalysisParams);
@ -495,6 +497,7 @@ namespace SourceGen {
// Deserialize ProjectProperties: misc items.
proj.ProjectProps.CpuType = Asm65.CpuDef.GetCpuTypeFromName(spf.ProjectProps.CpuName);
proj.ProjectProps.IncludeUndocumentedInstr = spf.ProjectProps.IncludeUndocumentedInstr;
proj.ProjectProps.TwoByteBrk = spf.ProjectProps.TwoByteBrk;
proj.ProjectProps.EntryFlags = Asm65.StatusFlags.FromInt(spf.ProjectProps.EntryFlags);
if (Enum.TryParse<AutoLabel.Style>(spf.ProjectProps.AutoLabelStyle,
out AutoLabel.Style als)) {

View File

@ -80,6 +80,11 @@ namespace SourceGen {
/// </summary>
public bool IncludeUndocumentedInstr { get; set; }
/// <summary>
/// Should we treat BRK instructions as 2 bytes?
/// </summary>
public bool TwoByteBrk { get; set; }
/// <summary>
/// Initial status flags at entry points.
/// </summary>
@ -129,6 +134,7 @@ namespace SourceGen {
public ProjectProperties(ProjectProperties src) : this() {
CpuType = src.CpuType;
IncludeUndocumentedInstr = src.IncludeUndocumentedInstr;
TwoByteBrk = src.TwoByteBrk;
EntryFlags = src.EntryFlags;
AutoLabelStyle = src.AutoLabelStyle;

View File

@ -54,7 +54,7 @@ namespace RuntimeData.Apple {
mFunctionList = PlSymbol.GeneratePlatformValueList(plSyms, SOS_MLI_TAG, appRef);
}
public void CheckBrk(int offset, out bool noContinue) {
public void CheckBrk(int offset, bool twoByteBrk, out bool noContinue) {
noContinue = true;
if (offset + 4 >= mFileData.Length) {
// ran off the end
@ -75,8 +75,7 @@ namespace RuntimeData.Apple {
if (!mFunctionList.TryGetValue(req, out sym)) {
return;
}
mAppRef.SetInlineDataFormat(offset + 1, 1, DataType.NumericLE,
DataSubType.Symbol, sym.Label);
Util.FormatBrkByte(mAppRef, twoByteBrk, offset, DataSubType.Symbol, sym.Label);
mAppRef.SetInlineDataFormat(offset + 2, 2, DataType.NumericLE,
DataSubType.Address, null);

View File

@ -2,8 +2,9 @@
; See the LICENSE.txt file for distribution terms (Apache 2.0).
;
; Source: SOS programmer's guide
; SOS Reference Manual, Volume 2 (Apple 1982)
*SYNOPSIS SOS constants.
*SYNOPSIS Apple /// Sophisticated Operating System constants.
; SOS MLI function codes.
*TAG SOS-MLI-Functions
@ -15,11 +16,17 @@ SOS_GET_SEG_INFO = $43
SOS_GET_SEG_NUM = $44
SOS_RELEASE_SEG = $45
SOS_SET_FENCE = $60
SOS_GET_FENCE = $61
SOS_SET_TIME = $62
SOS_GET_TIME = $63
SOS_GET_ANALOG = $64
; SOS ref: "TERMINATE"
; SOS ref: "No errors are possible. This is an excellent call for beginners."
SOS_QUIT = $65
SOS_READBLOCK = $80
SOS_WRITEBLOCK = $81
SOS_GET_TIME = $82
SOS_D_STATUS = $82
SOS_D_CONTROL = $83
SOS_GET_DEV_NUM = $84
@ -35,7 +42,9 @@ SOS_SET_PREFIX = $C6
SOS_GET_PREFIX = $C7
SOS_OPEN = $C8
SOS_NEWLINE = $C9
; SOS ref: "READ"
SOS_READFILE = $CA
; SOS ref: "WRITE"
SOS_WRITEFILE = $CB
SOS_CLOSE = $CC
SOS_FLUSH = $CD

View File

@ -196,7 +196,7 @@ code, but also needs to know how to handle the corner cases.</p>
outside bank zero cannot be assembled. SourceGen currently deals with
this by outputting the entire file as a hex dump.</li>
<li>Undocumented opcode $AB (<code>LAX #imm</code>) generates an error.</li>
<li>WDM is not allowed to have an operand.</li>
<li>BRK and WDM are not allowed to have operands.</li>
</ul>
<p>Quirks:</p>
@ -231,6 +231,7 @@ code, but also needs to know how to handle the corner cases.</p>
<p>Bugs:</p>
<ul>
<li>PC relative branches don't wrap around at bank boundaries.</li>
<li>BRK can only be given an argument in 65816 mode.</li>
<li>[Fixed in v2.18] The arguments to <code>MVN</code>/<code>MVP</code> are reversed.</li>
<li>[Fixed in v2.18] <code>BRK &lt;arg&gt;</code> is assembled to opcode
$05 rather than $00.</li>

View File

@ -93,7 +93,8 @@
"RT:Apple/SOS.cs"
],
"Parameters" : {
"load-address":"0x2000"
"load-address":"0x2000",
"two-byte-brk":"true"
}
},
{

View File

@ -27,7 +27,7 @@ namespace RuntimeData.Test2022 {
mAppRef.DebugLog("Test2022-B(id=" + AppDomain.CurrentDomain.Id + "): prepare()");
}
public void CheckBrk(int offset, out bool noContinue) {
public void CheckBrk(int offset, bool twoByteBrk, out bool noContinue) {
noContinue = true;
// need BRK, function byte, and two-byte address
@ -39,8 +39,7 @@ namespace RuntimeData.Test2022 {
return;
}
mAppRef.SetInlineDataFormat(offset + 1, 1, DataType.NumericLE,
DataSubType.Hex, null);
Util.FormatBrkByte(mAppRef, twoByteBrk, offset, DataSubType.Hex, null);
mAppRef.SetInlineDataFormat(offset + 2, 2, DataType.NumericLE,
DataSubType.Address, null);
noContinue = false;

View File

@ -1,7 +1,7 @@
### 6502bench SourceGen dis65 v1.0 ###
{
"_ContentVersion":2,"FileDataLength":203,"FileDataCrc32":-1621468157,"ProjectProps":{
"CpuName":"65816","IncludeUndocumentedInstr":false,"EntryFlags":32702671,"AutoLabelStyle":"Simple","AnalysisParams":{
"CpuName":"65816","IncludeUndocumentedInstr":false,"TwoByteBrk":true,"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-a.cs","PROJ:2022-extension-scripts-b.cs"],"ProjectSyms":{
"PrintInlineDciString":{

View File

@ -28,11 +28,9 @@ PrintInlineDciString = $013000
jsr L10AB
jsr L110F
jsr L1108
brk
.byte $01
brk #$01
.word data01
brk
.byte $02
brk #$02
.word data02
rts
@ -80,9 +78,7 @@ L10AB jsr PrintInlineNullString
L1108 jsl PrintInlineL2String
asl a
brk
.byte $60
brk #$60
L110F jsr PrintInlineNullString
adc $6e

View File

@ -21,11 +21,9 @@ PrintInlineDciString equ $013000
jsr L10AB
jsr L110F
jsr L1108
brk
dfb $01
brk $01
dw data01
brk
dfb $02
brk $02
dw data02
rts
@ -71,9 +69,7 @@ L10AB jsr PrintInlineNullString
L1108 jsl PrintInlineL2String
asl A
brk
dfb $60
brk $60
L110F jsr PrintInlineNullString
adc $6e

View File

@ -24,11 +24,9 @@ PrintInlineDciString = $013000
jsr L10AB
jsr L110F
jsr L1108
brk
!byte $01
!byte $00,$01
!word data01
brk
!byte $02
!byte $00,$02
!word data02
rts
@ -76,9 +74,7 @@ L10AB jsr PrintInlineNullString
L1108 jsl PrintInlineL2String
asl
brk
!byte $60
!byte $00,$60
L110F jsr PrintInlineNullString
adc $6e

View File

@ -25,11 +25,9 @@ PrintInlineDciString = $013000
jsr L10AB
jsr L110F
jsr L1108
brk
.byte $01
.byte $00,$01
.word data01
brk
.byte $02
.byte $00,$02
.word data02
rts
@ -81,9 +79,7 @@ L10AB: jsr PrintInlineNullString
L1108: jsl PrintInlineL2String
asl A
brk
.byte $60
.byte $00,$60
L110F: jsr PrintInlineNullString
adc $6e

View File

@ -28,6 +28,7 @@ namespace SourceGen {
private const string LOAD_ADDRESS = "load-address";
private const string ENTRY_FLAGS = "entry-flags";
private const string UNDOCUMENTED_OPCODES = "undocumented-opcodes";
private const string TWO_BYTE_BRK = "two-byte-brk";
private const string FIRST_WORD_IS_LOAD_ADDR = "first-word-is-load-addr";
private const string DEFAULT_TEXT_ENCODING = "default-text-encoding";
@ -105,6 +106,15 @@ namespace SourceGen {
return GetBoolParam(sysDef, UNDOCUMENTED_OPCODES, false);
}
/// <summary>
/// Gets the default setting for two-byte BRKs.
/// </summary>
/// <param name="sysDef">SystemDef instance.</param>
/// <returns>Enable/disable value.</returns>
public static bool GetTwoByteBrk(SystemDef sysDef) {
return GetBoolParam(sysDef, TWO_BYTE_BRK, false);
}
/// <summary>
/// Gets the default setting for using the first two bytes of the file as the
/// load address.

View File

@ -76,6 +76,8 @@ limitations under the License.
SelectionChanged="CpuComboBox_SelectionChanged"/>
<CheckBox Margin="0,4,0,0" Content="Enable undocumented instructions"
IsChecked="{Binding IncludeUndocumentedInstr}"/>
<CheckBox Margin="0,4,0,0" Content="Treat BRK as two-byte instruction"
IsChecked="{Binding TwoByteBrk}"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Entry Flags" Grid.Column="0" Grid.Row="1" Padding="2,4">

View File

@ -254,8 +254,9 @@ namespace SourceGen.WpfGui {
}
public AutoLabelItem[] AutoLabelItems { get; private set; }
//
// properties for checkboxes
//
public bool IncludeUndocumentedInstr {
get { return mWorkProps.IncludeUndocumentedInstr; }
set {
@ -264,6 +265,14 @@ namespace SourceGen.WpfGui {
IsDirty = true;
}
}
public bool TwoByteBrk {
get { return mWorkProps.TwoByteBrk; }
set {
mWorkProps.TwoByteBrk = value;
OnPropertyChanged();
IsDirty = true;
}
}
public bool AnalyzeUncategorizedData {
get { return mWorkProps.AnalysisParams.AnalyzeUncategorizedData; }
set {
@ -382,7 +391,7 @@ namespace SourceGen.WpfGui {
private void ChangeFlagButton_Click(object sender, RoutedEventArgs e) {
CpuDef cpuDef = CpuDef.GetBestMatch(mWorkProps.CpuType,
mWorkProps.IncludeUndocumentedInstr);
mWorkProps.IncludeUndocumentedInstr, mWorkProps.TwoByteBrk);
EditStatusFlags dlg =
new EditStatusFlags(this, mWorkProps.EntryFlags, cpuDef.HasEmuFlag);