1
0
mirror of https://github.com/fadden/6502bench.git synced 2026-04-26 12:18:26 +00:00

Add jump table formatting

When asked to tag an absolute JMP instruction ($4C) as code, we now
scan forward to see if it's the start of a series of JMPs.  If we
find more than one, we offer the opportunity to tag the entire set
all at once.

A series of unformatted JMPs has been added to 20200-ui-edge-cases
for manual testing.

(issue #22)
This commit is contained in:
Andy McFadden
2025-07-10 16:14:17 -07:00
parent 213f7cc205
commit eb6999b1b0
11 changed files with 208 additions and 22 deletions
+100
View File
@@ -4310,6 +4310,14 @@ namespace SourceGen {
public void MarkAsType(CodeAnalysis.AnalyzerTag atag, bool firstByteOnly) {
RangeSet sel;
if (atag == CodeAnalysis.AnalyzerTag.Code && SelectionAnalysis.mNumItemsSelected == 1) {
// We're applying a code tag to a single line. Analyze the file to see if special
// handling for jump tables can be applied here.
if (TryMarkJumpTable(out bool cancel) || cancel) {
return;
}
}
if (firstByteOnly) {
sel = new RangeSet();
foreach (int index in mMainWin.CodeDisplayList.SelectedIndices) {
@@ -4335,6 +4343,98 @@ namespace SourceGen {
sel = OffsetSetFromSelected();
}
DoMarkAsType(atag, sel);
}
/// <summary>
/// Attempts special handling for "jump tables", i.e. chunks of code with multiple
/// consecutive JMP abs instructions.
/// </summary>
/// <remarks>
/// The current scan will skip over already-formatted JMP instructions to look for more
/// beyond. This is extended behavior to make it easier to tag something when the first
/// JMP is already tagged. However, if it only finds one unformatted entry it won't fire.
/// The user really ought to be tagging the first unformatted $4c byte; it might be better
/// to require this.
/// </remarks>
/// <param name="cancel">Result: true if user asked to cancel the operation.</param>
/// <returns>True if a jump table was found and processed.</returns>
private bool TryMarkJumpTable(out bool cancel) {
cancel = false;
int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
int offset = CodeLineList[selIndex].FileOffset;
byte JMP = OpDef.OpJMP_Abs.Opcode; // 0x4c
RangeSet sel = new RangeSet();
while (offset + 2 < mProject.FileDataLength) {
if (mProject.FileData[offset] != JMP) {
break;
}
Anattrib attr0 = mProject.GetAnattrib(offset);
bool isInst = attr0.IsInstructionStart;
bool halt = false;
// Confirm that all bytes are data/inline-data, or are part of a previously-known
// JMP instruction.
for (int i = 0; i < 2; i++) {
Anattrib attr = mProject.GetAnattrib(offset + i);
if (attr.IsInstruction && !isInst) {
halt = true; // found instruction, but offset+0 wasn't an inst start
break;
}
// Don't continue if a user label is defined here, unless it's on the JMP
// opcode byte.
if (i != 0 && mProject.UserLabels.TryGetValue(offset + i, out Symbol unused)) {
halt = true;
break;
}
// Don't continue if any byte has been formatted.
if (mProject.OperandFormats.TryGetValue(offset + i, out FormatDescriptor unu)) {
halt = true;
break;
}
}
if (halt) {
break;
}
// If not already marked as an instruction, add the JMP opcode byte to the set.
if (!attr0.IsInstructionStart) {
Debug.WriteLine("JumpTab: adding offset +" + offset.ToString("x6"));
sel.Add(offset);
}
offset += 3;
}
if (sel.Count <= 1) {
// Didn't find anything to do, or found only one entry. Let the general code
// handle it.
return false;
}
// Ask the user to confirm.
string msg = string.Format(Res.Strings.ANALYZER_TAG_JMP_TABLE_FMT, sel.Count);
MessageBoxResult result =
MessageBox.Show(msg, Res.Strings.ANALYZER_TAG_JMP_TABLE_CAPTION,
MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
switch (result) {
case MessageBoxResult.Cancel:
default:
cancel = true;
return false;
case MessageBoxResult.No:
return false;
case MessageBoxResult.Yes:
DoMarkAsType(CodeAnalysis.AnalyzerTag.Code, sel);
return true;
}
}
/// <summary>
/// Generates the change set for analyzer tag updates.
/// </summary>
private void DoMarkAsType(CodeAnalysis.AnalyzerTag atag, RangeSet sel) {
TypedRangeSet newSet = new TypedRangeSet();
TypedRangeSet undoSet = new TypedRangeSet();
+2
View File
@@ -25,6 +25,8 @@ limitations under the License.
<system:String x:Key="str_AbbrevConstant">Const</system:String>
<system:String x:Key="str_AbbrevStackRelative">StkRl</system:String>
<system:String x:Key="str_AnalyzerTagMultiChk">Setting start/stop tags on multiple consecutive bytes is rarely a good idea. Continue?</system:String>
<system:String x:Key="str_AnalyzerTagJmpTableCaption">Format As Jump Table?</system:String>
<system:String x:Key="str_AnalyzerTagJmpTableFmt">Found {0} consecutive JMP instructions. Do you want to format all of them, rather than just the first?</system:String>
<system:String x:Key="str_AsmLatestVersion">[latest version]</system:String>
<system:String x:Key="str_AsmMatchFailure">output DOES NOT match data file</system:String>
<system:String x:Key="str_AsmMatchSuccess">output matches data file</system:String>
+4
View File
@@ -31,6 +31,10 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_AbbrevStackRelative");
public static string ANALYZER_TAG_MULTI_CHK =
(string)Application.Current.FindResource("str_AnalyzerTagMultiChk");
public static string ANALYZER_TAG_JMP_TABLE_CAPTION =
(string)Application.Current.FindResource("str_AnalyzerTagJmpTableCaption");
public static string ANALYZER_TAG_JMP_TABLE_FMT =
(string)Application.Current.FindResource("str_AnalyzerTagJmpTableFmt");
public static string ASM_LATEST_VERSION =
(string)Application.Current.FindResource("str_AsmLatestVersion");
public static string ASM_MATCH_FAILURE =
Binary file not shown.
+3 -10
View File
@@ -1,8 +1,8 @@
### 6502bench SourceGen dis65 v1.0 ###
{
"_ContentVersion":6,
"FileDataLength":193,
"FileDataCrc32":960553569,
"FileDataLength":222,
"FileDataCrc32":1648676931,
"ProjectProps":{
"CpuName":"6502",
"IncludeUndocumentedInstr":false,
@@ -132,7 +132,7 @@
{
"Offset":53,
"Addr":8704,
"Length":140,
"Length":169,
"PreLabel":"",
"DisallowInward":false,
"DisallowOutward":false,
@@ -197,13 +197,6 @@
"Type":"GlobalAddr",
"LabelAnno":"None"},
"192":{
"Label":"done",
"Value":8843,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"163":{
"Label":"bitsy",
"Value":8814,
@@ -64,7 +64,7 @@ next1 lda addr1
bcc bitsy+1
bitsy .byte $2c
lda #$ff
jmp done
jmp _L228B
.word FOO
.word FOO_5
@@ -79,6 +79,22 @@ bitsy .byte $2c
.word zf4
.byte $80
done rts
_L228B nop
jmp _L22A8
.byte $4c
.byte $8b
.text $22,"L9",$22,"L"
.byte $95
.byte $22
.byte $4c
.byte $00
.byte $30
.byte $ea
.text "L9",$22,"L"
.byte $00
.text "1LLLLLL"
_L22A8 rts
.here
@@ -63,7 +63,7 @@ next1 lda addr1
bcc bitsy+1
bitsy !byte $2c
lda #$ff
jmp done
jmp @L228B
!word FOO
!word FOO_5
@@ -78,6 +78,22 @@ bitsy !byte $2c
!word zf4
!byte $80
done rts
@L228B nop
jmp @L22A8
!byte $4c
!byte $8b
!text $22,"L9",$22,"L"
!byte $95
!byte $22
!byte $4c
!byte $00
!byte $30
!byte $ea
!text "L9",$22,"L"
!byte $00
!text "1LLLLLL"
@L22A8 rts
}
@@ -59,7 +59,7 @@ next1: lda addr1
bcc bitsy+1
bitsy: .byte $2c
lda #$ff
jmp done
jmp @L228B
.word FOO
.word FOO_5
@@ -74,5 +74,21 @@ bitsy: .byte $2c
.word zf4
.byte $80
done: rts
@L228B: nop
jmp @L22A8
.byte $4c
.byte $8b
.byte $22,"L9",$22,"L"
.byte $95
.byte $22
.byte $4c
.byte $00
.byte $30
.byte $ea
.byte "L9",$22,"L"
.byte $00
.byte "1LLLLLL"
@L22A8: rts
@@ -58,7 +58,7 @@ next1 lda addr1
bcc bitsy+1
bitsy dfb $2c
lda #$ff
jmp done
jmp :L228B
dw FOO
dw FOO_5
@@ -73,5 +73,21 @@ bitsy dfb $2c
dw zf4
dfb $80
done rts
:L228B nop
jmp :L22A8
dfb $4c
dfb $8b
asc '"L9"L'
dfb $95
dfb $22
dfb $4c
dfb $00
dfb $30
dfb $ea
asc 'L9"L'
dfb $00
asc '1LLLLLL'
:L22A8 rts
@@ -86,7 +86,7 @@ next1
bcc bitsy+1 ;EDIT: set symbol "bitsy"
bitsy bit $ffa9 ;EDIT: set label "bitsy" on BIT instruction
jmp done
jmp next2
; EDIT: format as 16-bit addresses
dw FOO
@@ -102,4 +102,18 @@ bitsy bit $ffa9 ;EDIT: set label "bitsy" on BIT instruction
dw zf4
dfb $80
done rts
next2 nop
; jump table
jmp next3
jmp next2
jmp next1
self jmp self
jmp plataddr
nop
jmp next1
jmp OVERL
jmp $4c4c
jmp $4c4c
next3 rts
+11 -2
View File
@@ -532,8 +532,17 @@ or simply by hitting <kbd class="key">Ctrl+D</kbd>. Hit that, tag the
byte or bytes, then hit it again to re-enable the
string &amp; fill analyzer.</p>
<p>Another approach is to use the <samp>Toggle Single-Byte Format</samp>
menu item to "flatten" the item, explicitly formatting everything as
individual hex bytes.</p>
action (<kbd class="key">Ctrl+B</kbd>) to "flatten" the item, explicitly
formatting everything as individual hex bytes. This formats the bytes
as data, however, so it's not recommended for sections that you think
are actually code.</p>
<p>As a special case, if you tag the start of an absolute JMP instruction
($4C) as code, the next several bytes will be checked to see if they hold
a consecutive series of JMPs. If so, you will be offered the opportunity
to tag all of them at once. This can be convenient for "jump tables".
The forward scan will halt if formatted data or mis-placed labels are
encountered.</p>
<h3 id="address-table">Format Address Table</h3>