From 575f834b1d7d42b2b6b6b5f4d5678cb92b451656 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Thu, 2 May 2019 15:45:40 -0700 Subject: [PATCH] Copy some non-UI code over Mostly a straight copy & paste of the files. The only significant change was to move the localizable strings from Properties/Resources (RESX) to Res/Strings.xaml (Resource Dictionary). I expect a number of strings will no longer be needed, since WPF lets you put more of the UI/UX logic into the design side. I also renamed the namespace to SourceGenWPF, and put the app icon into the Res directory so it can be a resource rather than a loose file. I'm merging the "Setup" directory contents into the main app since there wasn't a whole lot going on there. The WPF Color class lacks conversions to/from a 32-bit integer, so I added those. None of the stuff is wired up yet. --- SourceGenWPF/AddressMap.cs | 296 ++++ SourceGenWPF/Anattrib.cs | 355 +++++ SourceGenWPF/App.xaml | 6 + SourceGenWPF/AppSettings.cs | 355 +++++ SourceGenWPF/AutoLabel.cs | 164 ++ SourceGenWPF/ChangeSet.cs | 109 ++ SourceGenWPF/CodeAnalysis.cs | 1070 +++++++++++++ SourceGenWPF/DataAnalysis.cs | 1139 ++++++++++++++ SourceGenWPF/DefSymbol.cs | 104 ++ SourceGenWPF/DisasmProject.cs | 1767 ++++++++++++++++++++++ SourceGenWPF/DisplayList.cs | 1224 +++++++++++++++ SourceGenWPF/ExternalFile.cs | 264 ++++ SourceGenWPF/FormatDescriptor.cs | 423 ++++++ SourceGenWPF/HelpAccess.cs | 90 ++ SourceGenWPF/MultiLineComment.cs | 276 ++++ SourceGenWPF/NavStack.cs | 157 ++ SourceGenWPF/PlatformSymbols.cs | 254 ++++ SourceGenWPF/ProjWin/MainWindow.xaml | 4 +- SourceGenWPF/ProjectFile.cs | 743 +++++++++ SourceGenWPF/ProjectProperties.cs | 134 ++ SourceGenWPF/PseudoOp.cs | 897 +++++++++++ SourceGenWPF/Res/SourceGenIcon.ico | Bin 0 -> 142787 bytes SourceGenWPF/Res/Strings.xaml | 38 + SourceGenWPF/Res/Strings.xaml.cs | 71 + SourceGenWPF/RuntimeDataAccess.cs | 113 ++ SourceGenWPF/Sandbox/DomainManager.cs | 255 ++++ SourceGenWPF/Sandbox/PluginDllCache.cs | 237 +++ SourceGenWPF/Sandbox/ScriptManager.cs | 226 +++ SourceGenWPF/Sandbox/Sponsor.cs | 177 +++ SourceGenWPF/SourceGenWPF.csproj | 55 + SourceGenWPF/Symbol.cs | 156 ++ SourceGenWPF/SymbolTable.cs | 215 +++ SourceGenWPF/SystemDefaults.cs | 140 ++ SourceGenWPF/SystemDefs.cs | 213 +++ SourceGenWPF/UndoableChange.cs | 460 ++++++ SourceGenWPF/VirtualListViewSelection.cs | 146 ++ SourceGenWPF/WeakSymbolRef.cs | 94 ++ SourceGenWPF/XrefSet.cs | 139 ++ 38 files changed, 12564 insertions(+), 2 deletions(-) create mode 100644 SourceGenWPF/AddressMap.cs create mode 100644 SourceGenWPF/Anattrib.cs create mode 100644 SourceGenWPF/AppSettings.cs create mode 100644 SourceGenWPF/AutoLabel.cs create mode 100644 SourceGenWPF/ChangeSet.cs create mode 100644 SourceGenWPF/CodeAnalysis.cs create mode 100644 SourceGenWPF/DataAnalysis.cs create mode 100644 SourceGenWPF/DefSymbol.cs create mode 100644 SourceGenWPF/DisasmProject.cs create mode 100644 SourceGenWPF/DisplayList.cs create mode 100644 SourceGenWPF/ExternalFile.cs create mode 100644 SourceGenWPF/FormatDescriptor.cs create mode 100644 SourceGenWPF/HelpAccess.cs create mode 100644 SourceGenWPF/MultiLineComment.cs create mode 100644 SourceGenWPF/NavStack.cs create mode 100644 SourceGenWPF/PlatformSymbols.cs create mode 100644 SourceGenWPF/ProjectFile.cs create mode 100644 SourceGenWPF/ProjectProperties.cs create mode 100644 SourceGenWPF/PseudoOp.cs create mode 100644 SourceGenWPF/Res/SourceGenIcon.ico create mode 100644 SourceGenWPF/Res/Strings.xaml create mode 100644 SourceGenWPF/Res/Strings.xaml.cs create mode 100644 SourceGenWPF/RuntimeDataAccess.cs create mode 100644 SourceGenWPF/Sandbox/DomainManager.cs create mode 100644 SourceGenWPF/Sandbox/PluginDllCache.cs create mode 100644 SourceGenWPF/Sandbox/ScriptManager.cs create mode 100644 SourceGenWPF/Sandbox/Sponsor.cs create mode 100644 SourceGenWPF/Symbol.cs create mode 100644 SourceGenWPF/SymbolTable.cs create mode 100644 SourceGenWPF/SystemDefaults.cs create mode 100644 SourceGenWPF/SystemDefs.cs create mode 100644 SourceGenWPF/UndoableChange.cs create mode 100644 SourceGenWPF/VirtualListViewSelection.cs create mode 100644 SourceGenWPF/WeakSymbolRef.cs create mode 100644 SourceGenWPF/XrefSet.cs diff --git a/SourceGenWPF/AddressMap.cs b/SourceGenWPF/AddressMap.cs new file mode 100644 index 0000000..e8e7c3f --- /dev/null +++ b/SourceGenWPF/AddressMap.cs @@ -0,0 +1,296 @@ +/* + * 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.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace SourceGenWPF { + /// + /// Map file offsets to 65xx addresses and vice-versa. Useful for sources with + /// multiple ORG directives. + /// + /// It's possible to generate code that would overlap once relocated at run time, + /// which means a given address could map to multiple offsets. For this reason + /// it's useful to know the offset of the referring code when evaluating a + /// reference, so that a "local" match can take priority. + /// + public class AddressMap : IEnumerable { + /// + /// Code starting at the specified offset will have the specified address. + /// + /// The entries are held in the list in order, sorted by offset, with no gaps. + /// This makes the "length" field redundant, as it can be computed by + /// (entry[N+1].mOffset - entry[N].mOffset), with a special case for the last + /// entry in the list. It's convenient to maintain it explicitly however, as + /// the list is read far more often than it is updated. + /// + /// Entries are mutable, but must only be altered by AddressMap. Don't retain + /// instances of this across other activity. + /// + public class AddressMapEntry { + public int Offset { get; set; } + public int Addr { get; set; } + public int Length { get; set; } + + public AddressMapEntry(int offset, int addr, int len) { + Offset = offset; + Addr = addr; + Length = len; + } + } + + /// + /// Total length, in bytes, spanned by this map. + /// + private int mTotalLength; + + /// + /// List of definitions, in sorted order. + /// + private List mAddrList = new List(); + + /// + /// Constructor. + /// + /// Total length, in bytes, spanned by this map. + public AddressMap(int length) { + /// There must always be at least one entry, defining the target address + /// for file offset 0. This can be changed, but can't be removed. + mTotalLength = length; + mAddrList.Add(new AddressMapEntry(0, 0, length)); + } + + // IEnumerable + public IEnumerator GetEnumerator() { + return ((IEnumerable)mAddrList).GetEnumerator(); + } + + // IEnumerable + IEnumerator IEnumerable.GetEnumerator() { + return ((IEnumerable)mAddrList).GetEnumerator(); + } + + /// + /// Returns the Nth entry in the address map. + /// + public AddressMapEntry this[int i] { + get { return mAddrList[i]; } + } + + /// + /// Number of entries in the address map. + /// + public int Count { get { return mAddrList.Count; } } + + /// + /// Returns the Address value of the address map entry associated with the specified + /// offset, or -1 if there is no address map entry there. The offset must match exactly. + /// + public int Get(int offset) { + foreach (AddressMapEntry ad in mAddrList) { + if (ad.Offset == offset) { + return ad.Addr; + } + } + return -1; + } + + /// + /// Returns the index of the address map entry that contains the given offset. + /// We assume the offset is valid. + /// + private int IndexForOffset(int offset) { + for (int i = 1; i < mAddrList.Count; i++) { + if (mAddrList[i].Offset > offset) { + return i - 1; + } + } + + return mAddrList.Count - 1; + } + + /// + /// Adds, updates, or removes a map entry. + /// + /// File offset at which the address changes. + /// 24-bit address. + public void Set(int offset, int addr) { + Debug.Assert(offset >= 0); + if (addr == -1) { + if (offset != 0) { // ignore attempts to remove entry at offset zero + Remove(offset); + } + return; + } + Debug.Assert(addr >= 0 && addr < 0x01000000); // 24-bit address space + + int i; + for (i = 0; i < mAddrList.Count; i++) { + AddressMapEntry ad = mAddrList[i]; + if (ad.Offset == offset) { + // update existing + ad.Addr = addr; + mAddrList[i] = ad; + return; + } else if (ad.Offset > offset) { + // The i'th entry is one past the interesting part. + break; + } + } + + // Carve a chunk out of the previous entry. + AddressMapEntry prev = mAddrList[i - 1]; + int prevOldLen = prev.Length; + int prevNewLen = offset - prev.Offset; + prev.Length = prevNewLen; + mAddrList[i - 1] = prev; + + mAddrList.Insert(i, + new AddressMapEntry(offset, addr, prevOldLen - prevNewLen)); + + DebugValidate(); + } + + /// + /// Removes an entry from the set. + /// + /// The initial offset of the mapping to remove. This + /// must be the initial value, not a mid-range value. + /// True if something was removed. + public bool Remove(int offset) { + if (offset == 0) { + throw new Exception("Not allowed to remove entry 0"); + } + + for (int i = 1; i < mAddrList.Count; i++) { + if (mAddrList[i].Offset == offset) { + // Add the length to the previous entry. + AddressMapEntry prev = mAddrList[i - 1]; + prev.Length += mAddrList[i].Length; + mAddrList[i - 1] = prev; + + mAddrList.RemoveAt(i); + DebugValidate(); + return true; + } + } + return false; + } + + /// + /// Returns true if the given address falls into the range spanned by the + /// address map entry. + /// + /// Address map entry index. + /// Address to check. + /// + private bool IndexContainsAddress(int index, int addr) { + return addr >= mAddrList[index].Addr && + addr < mAddrList[index].Addr + mAddrList[index].Length; + } + + /// + /// Determines the file offset that best contains the specified target address. + /// + /// Offset of the address reference. + /// Address to look up. + /// The file offset, or -1 if the address falls outside the file. + public int AddressToOffset(int srcOffset, int targetAddr) { + if (mAddrList.Count == 1) { + // Trivial case. + if (IndexContainsAddress(0, targetAddr)) { + Debug.Assert(targetAddr >= mAddrList[0].Addr); + return targetAddr - mAddrList[0].Addr; + } else { + return -1; + } + } + + // We have multiple, potentially overlapping address ranges. Start by + // looking for a match in the srcOffset range; if that fails, scan + // forward from the start. + int srcOffIndex = IndexForOffset(srcOffset); + if (IndexContainsAddress(srcOffIndex, targetAddr)) { + Debug.Assert(targetAddr >= mAddrList[srcOffIndex].Addr); + return (targetAddr - mAddrList[srcOffIndex].Addr) + mAddrList[srcOffIndex].Offset; + } + + for (int i = 0; i < mAddrList.Count; i++) { + if (i == srcOffIndex) { + // optimization -- we already checked this one + continue; + } + if (IndexContainsAddress(i, targetAddr)) { + Debug.Assert(targetAddr >= mAddrList[i].Addr); + return (targetAddr - mAddrList[i].Addr) + mAddrList[i].Offset; + } + } + + return -1; + } + + /// + /// Converts a file offset to an address. + /// + /// File offset. + /// 24-bit address. + public int OffsetToAddress(int offset) { + int srcOffIndex = IndexForOffset(offset); + return mAddrList[srcOffIndex].Addr + (offset - mAddrList[srcOffIndex].Offset); + } + + + /// + /// Internal consistency checks. + /// + private void DebugValidate() { + if (mAddrList.Count < 1) { + throw new Exception("AddressMap: empty"); + } + if (mAddrList[0].Offset != 0) { + throw new Exception("AddressMap: bad offset 0"); + } + + if (mAddrList.Count == 1) { + if (mAddrList[0].Length != mTotalLength) { + throw new Exception("AddressMap: single entry len bad"); + } + } else { + int totalLen = 0; + for (int i = 0; i < mAddrList.Count; i++) { + AddressMapEntry ent = mAddrList[i]; + if (i != 0) { + if (ent.Offset != mAddrList[i - 1].Offset + mAddrList[i - 1].Length) { + throw new Exception("Bad offset step to " + i); + } + } + + totalLen += ent.Length; + } + + if (totalLen != mTotalLength) { + throw new Exception("AddressMap: bad length sum (" + totalLen + " vs " + + mTotalLength + ")"); + } + } + } + + public override string ToString() { + return "[AddressMap: " + mAddrList.Count + " entries]"; + } + } +} diff --git a/SourceGenWPF/Anattrib.cs b/SourceGenWPF/Anattrib.cs new file mode 100644 index 0000000..9a45e9d --- /dev/null +++ b/SourceGenWPF/Anattrib.cs @@ -0,0 +1,355 @@ +/* + * 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.Diagnostics; +using System.Text; + +using Asm65; + +namespace SourceGenWPF { + /// + /// Analyzer attribute holder. Contains the output of the instruction and data analyzers. + /// Every byte in the input file has one of these associated with it. + /// + /// (Yes, it's a mutable struct. Yes, that fact has bitten me a few times. The array + /// of these may have millions of elements, so the reduction in overhead seems worthwhile.) + /// + public struct Anattrib { + [FlagsAttribute] + private enum AttribFlags { + InstrStart = 1 << 0, // byte is first of an instruction + Instruction = 1 << 1, // byte is part of an instruction or inline data + InlineData = 1 << 2, // byte is inline data + Data = 1 << 3, // byte is data + + EntryPoint = 1 << 8, // external code branches here + BranchTarget = 1 << 9, // internal code branches here + ExternalBranch = 1 << 10, // this abs/rel branch lands outside input file + + NoContinue = 1 << 12, // execution does not continue to following instruction + + Visited = 1 << 16, // has the analyzer visited this byte? + Changed = 1 << 17, // set/cleared as the analyzer works + + Hinted = 1 << 18, // was this byte affected by a type hint? + } + + // Flags indicating what type of data is here. Use the following Is* properties + // to set/clear. + private AttribFlags mAttribFlags; + + public bool IsInstructionStart { + get { + return (mAttribFlags & AttribFlags.InstrStart) != 0; + } + set { + IsInstruction = value; + if (value) { + mAttribFlags |= AttribFlags.InstrStart; + } else { + mAttribFlags &= ~AttribFlags.InstrStart; + } + } + } + public bool IsInstruction { + get { + return (mAttribFlags & AttribFlags.Instruction) != 0; + } + set { + Debug.Assert(value == false || + (mAttribFlags & (AttribFlags.InlineData | AttribFlags.Data)) == 0); + if (value) { + mAttribFlags |= AttribFlags.Instruction; + } else { + mAttribFlags &= ~AttribFlags.Instruction; + } + } + } + public bool IsInlineData { + get { + return (mAttribFlags & AttribFlags.InlineData) != 0; + } + set { + Debug.Assert(value == false || + (mAttribFlags & (AttribFlags.Instruction | AttribFlags.Data)) == 0); + if (value) { + mAttribFlags |= AttribFlags.InlineData; + } else { + mAttribFlags &= ~AttribFlags.InlineData; + } + } + } + public bool IsData { + get { + return (mAttribFlags & AttribFlags.Data) != 0; + } + set { + Debug.Assert(value == false || + (mAttribFlags & (AttribFlags.InlineData | AttribFlags.Instruction)) == 0); + if (value) { + mAttribFlags |= AttribFlags.Data; + } else { + mAttribFlags &= ~AttribFlags.Data; + } + } + } + public bool IsStart { + get { + return IsInstructionStart || IsDataStart || IsInlineDataStart; + } + } + public bool IsEntryPoint { + get { + return (mAttribFlags & AttribFlags.EntryPoint) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.EntryPoint; + } else { + mAttribFlags &= ~AttribFlags.EntryPoint; + } + } + } + public bool IsBranchTarget { + get { + return (mAttribFlags & AttribFlags.BranchTarget) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.BranchTarget; + } else { + mAttribFlags &= ~AttribFlags.BranchTarget; + } + } + } + public bool IsExternalBranch { + get { + return (mAttribFlags & AttribFlags.ExternalBranch) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.ExternalBranch; + } else { + mAttribFlags &= ~AttribFlags.ExternalBranch; + } + } + } + public bool DoesNotContinue { + get { + return (mAttribFlags & AttribFlags.NoContinue) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.NoContinue; + } else { + mAttribFlags &= ~AttribFlags.NoContinue; + } + } + } + public bool DoesNotBranch { + get { + return (BranchTaken == OpDef.BranchTaken.Never); + } + } + public bool IsVisited { + get { + return (mAttribFlags & AttribFlags.Visited) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.Visited; + } else { + mAttribFlags &= ~AttribFlags.Visited; + } + } + } + public bool IsChanged { + get { + return (mAttribFlags & AttribFlags.Changed) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.Changed; + } else { + mAttribFlags &= ~AttribFlags.Changed; + } + } + } + public bool IsHinted { + get { + return (mAttribFlags & AttribFlags.Hinted) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.Hinted; + } else { + mAttribFlags &= ~AttribFlags.Hinted; + } + } + } + + public bool IsDataStart { + get { + return IsData && DataDescriptor != null; + } + } + public bool IsInlineDataStart { + get { + return IsInlineData && DataDescriptor != null; + } + } + + /// + /// Get the target memory address for this byte. + /// + public int Address { get; set; } + + /// + /// Instructions: length of the instruction (for InstrStart). If a FormatDescriptor + /// is assigned, the length must match. + /// Inline data: FormatDescriptor length, or zero if no descriptor is defined. + /// Data: FormatDescriptor length, or zero if no descriptor is defined. + /// + /// This field should only be set by CodeAnalysis methods, although the "get" value + /// can be changed for data/inline-data by setting the DataDescriptor field. + /// + public int Length { + get { + // For data we don't even use the field; this ensures that we're always + // using the FormatDescriptor's length. + if (IsData || IsInlineData) { + Debug.Assert(mLength == 0); + if (DataDescriptor != null) { + return DataDescriptor.Length; + } else { + return 0; + } + } + return mLength; + } + set { + Debug.Assert(!IsData); + mLength = value; + } + } + private int mLength; + + /// + /// Instructions only: processor status flags. + /// + /// Note this returns a copy of a struct, so modifications to the returned value + /// (including calls to Merge and Apply) are not permanent. + /// + public StatusFlags StatusFlags { + get { return mStatusFlags; } + set { mStatusFlags = value; } + } + private StatusFlags mStatusFlags; + + public void MergeStatusFlags(StatusFlags other) { + mStatusFlags.Merge(other); + } + public void ApplyStatusFlags(StatusFlags other) { + mStatusFlags.Apply(other); + } + + /// + /// Branch instructions only: outcome of branch. + /// + public OpDef.BranchTaken BranchTaken { get; set; } + + /// + /// Instructions only: decoded operand address value. Will be -1 if not + /// yet computed or not applicable. For a relative branch instruction, + /// this will have the absolute branch target address. On the 65816, this + /// will be a 24-bit address. + /// + public int OperandAddress { + get { return mOperandAddressSet ? mOperandAddress : -1; } + set { + Debug.Assert(mOperandAddress >= -1); + mOperandAddress = value; + mOperandAddressSet = (value >= 0); + } + } + private int mOperandAddress; + private bool mOperandAddressSet; + + /// + /// Instructions only: offset referenced by OperandAddress. Will be -1 if not + /// yet computed, not applicable, or if OperandAddress refers to a location + /// outside the scope of the file. + /// + public int OperandOffset { + get { return mOperandOffsetSet ? mOperandOffset : -1; } + set { + Debug.Assert(mOperandOffset >= -1); + mOperandOffset = value; + mOperandOffsetSet = (value >= 0); + } + } + private int mOperandOffset; + private bool mOperandOffsetSet; + + /// + /// Instructions only: is OperandOffset a direct target offset? (This is used when + /// tracing jump instructions, to know if we should add the offset to the scan list. + /// It's determined by the opcode, e.g. "JMP addr" -> true, "JMP (addr,X)" -> false.) + /// + public bool IsOperandOffsetDirect { get; set; } + + /// + /// Symbol defined as the label for this offset. All offsets that are instruction + /// or data target offsets will have one of these defined. Users can define additional + /// symbols as well. + /// + /// Will be null if no label is defined for this offset. + /// + public Symbol Symbol { get; set; } + + /// + /// Format descriptor for operands and data items. Will be null if no descriptor + /// is defined for this offset. + /// + public FormatDescriptor DataDescriptor { get; set; } + + /// + /// Is this an instruction with an operand (i.e. not impl/acc)? + /// + public bool IsInstructionWithOperand { + get { + if (!IsInstructionStart) { + return false; + } + return Length != 1; + } + } + + /// + /// Returns a fixed-width string with indicators for items of interest. + /// + public string ToAttrString() { + StringBuilder sb = new StringBuilder(5); + char blank = '.'; + sb.Append(IsEntryPoint ? '@' : blank); + sb.Append(IsHinted ? 'H' : blank); + sb.Append(DoesNotBranch ? '!' : blank); + sb.Append(DoesNotContinue ? '#' : blank); + sb.Append(IsBranchTarget ? '>' : blank); + return sb.ToString(); + } + } +} diff --git a/SourceGenWPF/App.xaml b/SourceGenWPF/App.xaml index 7037803..73ab575 100644 --- a/SourceGenWPF/App.xaml +++ b/SourceGenWPF/App.xaml @@ -20,5 +20,11 @@ limitations under the License. StartupUri="ProjWin/MainWindow.xaml"> Consolas + + + + + + diff --git a/SourceGenWPF/AppSettings.cs b/SourceGenWPF/AppSettings.cs new file mode 100644 index 0000000..d30aa68 --- /dev/null +++ b/SourceGenWPF/AppSettings.cs @@ -0,0 +1,355 @@ +/* + * 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.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Web.Script.Serialization; + +namespace SourceGenWPF { + /// + /// Application settings registry. This holds both user-accessible settings and saved + /// values like window widths. + /// + /// Everything is stored as name/value pairs, where the value is serialized as a string. + /// Names are case-sensitive. + /// + /// We don't discard things we don't recognize. If we somehow end up reading a config + /// file from a newer version of the app, the various settings will be retained. + /// + public class AppSettings { + #region Names + + // Name constants. Having them defined here avoids collisions and misspellings, and + // makes it easy to find all uses. + + // Main window. + public const string MAIN_WINDOW_WIDTH = "main-window-width"; + public const string MAIN_WINDOW_HEIGHT = "main-window-height"; + public const string MAIN_WINDOW_LOC_X = "main-window-loc-x"; + public const string MAIN_WINDOW_LOC_Y = "main-window-loc-y"; + public const string MAIN_WINDOW_MAXIMIZED = "main-window-maximized"; + public const string MAIN_LEFT_PANEL_WIDTH = "main-left-panel-width"; + public const string MAIN_RIGHT_PANEL_WIDTH = "main-right-panel-width"; + public const string MAIN_LEFT_SIDE_SPLITTER_DIST = "main-left-side-splitter-dist"; + public const string MAIN_RIGHT_SIDE_SPLITTER_DIST = "main-right-side-splitter-dist"; + + // New project dialog. + public const string NEWP_SELECTED_SYSTEM = "newp-selected-system"; + + // Formatting choices. + public const string FMT_UPPER_HEX_DIGITS = "fmt-upper-hex-digits"; + public const string FMT_UPPER_OP_MNEMONIC = "fmt-upper-op-mnemonic"; + public const string FMT_UPPER_PSEUDO_OP_MNEMONIC = "fmt-upper-pseudo-op-mnemonic"; + public const string FMT_UPPER_OPERAND_A = "fmt-upper-operand-a"; + public const string FMT_UPPER_OPERAND_S = "fmt-upper-operand-s"; + public const string FMT_UPPER_OPERAND_XY = "fmt-upper-operand-xy"; + public const string FMT_ADD_SPACE_FULL_COMMENT = "fmt-add-space-full-comment"; + public const string FMT_SPACES_BETWEEN_BYTES = "fmt-spaces-between-bytes"; + + public const string FMT_OPCODE_SUFFIX_ABS = "fmt-opcode-suffix-abs"; + public const string FMT_OPCODE_SUFFIX_LONG = "fmt-opcode-suffix-long"; + public const string FMT_OPERAND_PREFIX_ABS = "fmt-operand-prefix-abs"; + public const string FMT_OPERAND_PREFIX_LONG = "fmt-operand-prefix-long"; + public const string FMT_EXPRESSION_MODE = "fmt-expression-mode"; + + public const string FMT_PSEUDO_OP_NAMES = "fmt-pseudo-op-names"; + + public const string CLIP_LINE_FORMAT = "clip-line-format"; + + // Symbol-list window options. + public const string SYMWIN_SHOW_USER = "symwin-show-user"; + public const string SYMWIN_SHOW_AUTO = "symwin-show-auto"; + public const string SYMWIN_SHOW_PROJECT = "symwin-show-project"; + public const string SYMWIN_SHOW_PLATFORM = "symwin-show-platform"; + public const string SYMWIN_SHOW_CONST = "symwin-show-const"; + public const string SYMWIN_SHOW_ADDR = "symwin-show-addr"; + public const string SYMWIN_SORT_ASCENDING = "symwin-sort-ascending"; + public const string SYMWIN_SORT_COL = "symwin-sort-col"; + + public const string SYMWIN_COL_WIDTHS = "symwin-col-widths"; + + // References window options. + public const string REFWIN_COL_WIDTHS = "refwin-col-widths"; + + // Notes window options. + public const string NOTEWIN_COL_WIDTHS = "notewin-col-widths"; + + // Code List View settings. + public const string CDLV_COL_WIDTHS = "cdlv-col-widths"; + public const string CDLV_FONT = "cdlv-font"; + + // Hex dump viewer settings. + public const string HEXD_ASCII_ONLY = "hexd-ascii-only"; + public const string HEXD_CHAR_CONV = "hexd-char-conv"; + + // ASCII chart viewer settings. + public const string ASCCH_MODE = "ascch-mode"; + + // Source generation settings. + public const string SRCGEN_DEFAULT_ASM = "srcgen-default-asm"; + public const string SRCGEN_ADD_IDENT_COMMENT = "srcgen-add-ident-comment"; + public const string SRCGEN_DISABLE_LABEL_LOCALIZATION = "srcgen-disable-label-localization"; + public const string SRCGEN_LONG_LABEL_NEW_LINE = "srcgen-long-label-new-line"; + public const string SRCGEN_SHOW_CYCLE_COUNTS = "srcgen-show-cycle-counts"; + + // Main project view settings. + public const string PRVW_RECENT_PROJECT_LIST = "prvw-recent-project-list"; + + // Assembler settings prefix + public const string ASM_CONFIG_PREFIX = "asm-config-"; + + // Internal debugging features. + public const string DEBUG_MENU_ENABLED = "debug-menu-enabled"; + + #endregion Names + + #region Implementation + + // App settings file header. + public const string MAGIC = "### 6502bench SourceGen settings v1.0 ###"; + + + /// + /// Single global instance of app settings. + /// + public static AppSettings Global { + get { + return sSingleton; + } + } + private static AppSettings sSingleton = new AppSettings(); + + + /// + /// Dirty flag, set to true by every "set" call. + /// + public bool Dirty { get; set; } + + /// + /// Settings storage. + /// + private Dictionary mSettings = new Dictionary(); + + + private AppSettings() { } + + /// + /// Creates a copy of this object. + /// + /// + public AppSettings GetCopy() { + AppSettings copy = new AppSettings(); + //copy.mSettings.EnsureCapacity(mSettings.Count); + foreach (KeyValuePair kvp in mSettings) { + copy.mSettings.Add(kvp.Key, kvp.Value); + } + return copy; + } + + /// + /// Replaces the existing list of settings with a new list. + /// + /// This can be used to replace the contents of the global settings object without + /// discarding the object itself, which is useful in case something has cached a + /// reference to the singleton. + /// + /// + public void ReplaceSettings(AppSettings newSettings) { + // Clone the new list, and stuff it into the old object. This way the + // objects aren't sharing lists. + mSettings = newSettings.GetCopy().mSettings; + Dirty = true; + } + + /// + /// Merges settings from another settings object into this one. + /// + /// + /// + public void MergeSettings(AppSettings newSettings) { + foreach (KeyValuePair kvp in newSettings.mSettings) { + mSettings[kvp.Key] = kvp.Value; + } + Dirty = true; + } + + /// + /// Retrieves an integer setting. + /// + /// Setting name. + /// Setting default value. + /// The value found, or the default value if no setting with the specified + /// name exists, or the stored value is not an integer. + public int GetInt(string name, int defaultValue) { + if (!mSettings.TryGetValue(name, out string valueStr)) { + return defaultValue; + } + if (!int.TryParse(valueStr, out int value)) { + Debug.WriteLine("Warning: int parse failed on " + name + "=" + valueStr); + return defaultValue; + } + return value; + } + + /// + /// Sets an integer setting. + /// + /// Setting name. + /// Setting value. + public void SetInt(string name, int value) { + mSettings[name] = value.ToString(); + Dirty = true; + } + + /// + /// Retrieves a boolean setting. + /// + /// Setting name. + /// Setting default value. + /// The value found, or the default value if no setting with the specified + /// name exists, or the stored value is not a boolean. + public bool GetBool(string name, bool defaultValue) { + if (!mSettings.TryGetValue(name, out string valueStr)) { + return defaultValue; + } + if (!bool.TryParse(valueStr, out bool value)) { + Debug.WriteLine("Warning: bool parse failed on " + name + "=" + valueStr); + return defaultValue; + } + return value; + } + + /// + /// Sets a boolean setting. + /// + /// Setting name. + /// Setting value. + public void SetBool(string name, bool value) { + mSettings[name] = value.ToString(); + Dirty = true; + } + + /// + /// Retrieves an enumerated value setting. + /// + /// Setting name. + /// Enum type that the value is part of. + /// Setting default value. + /// The value found, or the default value if no setting with the specified + /// name exists, or the stored value is not a member of the specified enumerated + /// type. + public int GetEnum(string name, Type enumType, int defaultValue) { + if (!mSettings.TryGetValue(name, out string valueStr)) { + return defaultValue; + } + try { + object o = Enum.Parse(enumType, valueStr); + return (int)o; + } catch (ArgumentException ae) { + Debug.WriteLine("Failed to parse " + valueStr + " (enum " + enumType + "): " + + ae.Message); + return defaultValue; + } + } + + /// + /// Sets an enumerated setting. + /// + /// Setting name. + /// Enum type. + /// Setting value (integer enum index). + public void SetEnum(string name, Type enumType, int value) { + mSettings[name] = Enum.GetName(enumType, value); + Dirty = true; + } + + /// + /// Retrieves a string setting. The default value will be returned if the key + /// is not found, or if the value is null. + /// + /// Setting name. + /// Setting default value. + /// The value found, or defaultValue if not value is found. + public string GetString(string name, string defaultValue) { + if (!mSettings.TryGetValue(name, out string valueStr) || valueStr == null) { + return defaultValue; + } + return valueStr; + } + + /// + /// Sets a string setting. + /// + /// Setting name. + /// Setting value. + public void SetString(string name, string value) { + if (value == null) { + mSettings.Remove(name); + } else { + mSettings[name] = value; + } + Dirty = true; + } + + /// + /// Serializes settings dictionary into a string, for saving settings to a file. + /// + /// Serialized settings. + public string Serialize() { + StringBuilder sb = new StringBuilder(1024); + sb.Append(MAGIC); // augment with version string, which will be stripped + sb.Append("\r\n"); // will be ignored by deserializer; might get converted to \n + + JavaScriptSerializer ser = new JavaScriptSerializer(); + string cereal = ser.Serialize(mSettings); + + // add some linefeeds to make it easier for humans + cereal = CommonUtil.TextUtil.NonQuoteReplace(cereal, ",\"", ",\r\n\""); + sb.Append(cereal); + + // Stick a linefeed at the end. + sb.Append("\r\n"); + return sb.ToString(); + } + + /// + /// Deserializes settings from a string, for loading settings from a file. + /// + /// Serialized settings. + /// Deserialized settings, or null if deserialization failed. + public static AppSettings Deserialize(string cereal) { + if (!cereal.StartsWith(MAGIC)) { + return null; + } + + // Skip past header. + cereal = cereal.Substring(MAGIC.Length); + + AppSettings settings = new AppSettings(); + JavaScriptSerializer ser = new JavaScriptSerializer(); + try { + settings.mSettings = ser.Deserialize>(cereal); + return settings; + } catch (Exception ex) { + Debug.WriteLine("Settings deserialization failed: " + ex.Message); + return null; + } + } + + #endregion Implementation + } +} diff --git a/SourceGenWPF/AutoLabel.cs b/SourceGenWPF/AutoLabel.cs new file mode 100644 index 0000000..e250485 --- /dev/null +++ b/SourceGenWPF/AutoLabel.cs @@ -0,0 +1,164 @@ +/* + * 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.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SourceGenWPF { + /// + /// Functions for generation of "auto" labels. + /// + public static class AutoLabel { + /// + /// Auto-label style enumeration. Values were chosen to map directly to a combo box. + /// + public enum Style { + Unknown = -1, + Simple = 0, + Annotated = 1, + FullyAnnotated = 2 + } + + /// + /// Generates a unique address symbol. Does not add the symbol to the table. + /// + /// This does not follow any Formatter rules -- labels are always entirely upper-case. + /// + /// Address that label will be applied to. + /// Symbol table, for uniqueness check. + /// Prefix to use; must start with a letter. + /// Newly-created, unique symbol. + public static Symbol GenerateUniqueForAddress(int addr, SymbolTable symbols, + string prefix) { + // $1234 == L1234, $05/1234 == L51234. + string label = prefix + addr.ToString("X4"); // always upper-case + if (symbols.TryGetValue(label, out Symbol unused)) { + const int MAX_RENAME = 999; + string baseLabel = label; + StringBuilder sb = new StringBuilder(baseLabel.Length + 8); + int index = -1; + + do { + // This is expected to be unlikely and infrequent, so a simple linear + // probe for uniqueness is fine. Labels are based on the address, not + // the offset, so even without user-created labels there's still an + // opportunity for duplication. + index++; + sb.Clear(); + sb.Append(baseLabel); + sb.Append('_'); + sb.Append(index); + label = sb.ToString(); + } while (index <= MAX_RENAME && symbols.TryGetValue(label, out unused)); + if (index > MAX_RENAME) { + // I give up + throw new Exception("Too many identical symbols: " + label); + } + } + Symbol sym = new Symbol(label, addr, Symbol.Source.Auto, + Symbol.Type.LocalOrGlobalAddr); + return sym; + } + + /// + /// Source reference type. + /// + /// The enum is in priority order, i.e. the lowest-valued item "wins" in situations + /// where only one value is used. + /// + [Flags] + private enum RefTypes { + None = 0, + SubCall = 1 << 0, + Branch = 1 << 1, + DataRef = 1 << 2, + Write = 1 << 3, + Read = 1 << 4, + } + private static readonly char[] TAGS = { 'S', 'B', 'D', 'W', 'R' }; + + /// + /// Generates an auto-label with a prefix string based on the XrefSet. + /// + /// Address that label will be applied to. + /// Symbol table, for uniqueness check. + /// Cross-references for this location. + /// Newly-created, unique symbol. + public static Symbol GenerateAnnotatedLabel(int addr, SymbolTable symbols, + XrefSet xset, Style style) { + Debug.Assert(xset != null); + Debug.Assert(style != Style.Simple); + + RefTypes rtypes = RefTypes.None; + foreach (XrefSet.Xref xr in xset) { + switch (xr.Type) { + case XrefSet.XrefType.SubCallOp: + rtypes |= RefTypes.SubCall; + break; + case XrefSet.XrefType.BranchOp: + rtypes |= RefTypes.Branch; + break; + case XrefSet.XrefType.RefFromData: + rtypes |= RefTypes.DataRef; + break; + case XrefSet.XrefType.MemAccessOp: + switch (xr.AccType) { + case Asm65.OpDef.MemoryEffect.Read: + rtypes |= RefTypes.Read; + break; + case Asm65.OpDef.MemoryEffect.Write: + rtypes |= RefTypes.Write; + break; + case Asm65.OpDef.MemoryEffect.ReadModifyWrite: + rtypes |= RefTypes.Read; + rtypes |= RefTypes.Write; + break; + case Asm65.OpDef.MemoryEffect.None: + case Asm65.OpDef.MemoryEffect.Unknown: + break; + default: + Debug.Assert(false); + break; + } + break; + default: + Debug.Assert(false); + break; + } + } + + if (rtypes == RefTypes.None) { + // unexpected + Debug.Assert(false); + return GenerateUniqueForAddress(addr, symbols, "X_"); + } + + StringBuilder sb = new StringBuilder(8); + for (int i = 0; i < TAGS.Length; i++) { + if (((int) rtypes & (1 << i)) != 0) { + sb.Append(TAGS[i]); + + if (style == Style.Annotated) { + break; + } + } + } + sb.Append('_'); + return GenerateUniqueForAddress(addr, symbols, sb.ToString()); + } + } +} diff --git a/SourceGenWPF/ChangeSet.cs b/SourceGenWPF/ChangeSet.cs new file mode 100644 index 0000000..1a924c7 --- /dev/null +++ b/SourceGenWPF/ChangeSet.cs @@ -0,0 +1,109 @@ +/* + * 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.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace SourceGenWPF { + /// + /// Holds information about a set of changes. + /// + /// Does not have hooks into other data structures. This just holds the information + /// about the changes. + /// + public class ChangeSet : IEnumerable { + private List mChanges; + + /// + /// Constructs an empty ChangeSet with the specified initial capacity. + /// + /// Initial number of elements that the set can contain. + public ChangeSet(int capacity) { + mChanges = new List(capacity); + } + + /// + /// Constructs a ChangeSet with a single change. + /// + public ChangeSet(UndoableChange ac) { + mChanges = new List(1); + mChanges.Add(ac); + } + + /// + /// The number of changes in the set. + /// + public int Count { get { return mChanges.Count; } } + + /// + /// Returns the Nth change in the set. + /// + /// Change index. + public UndoableChange this[int key] { + get { + return mChanges[key]; + } + } + + /// + /// Adds a change to the change set. + /// + /// Change to add. + public void Add(UndoableChange change) { + Debug.Assert(change != null); + mChanges.Add(change); + } + + /// + /// Adds a change to the change set if the object is non-null. + /// + /// Change to add, or null. + public void AddNonNull(UndoableChange change) { + if (change != null) { + Add(change); + } + } + + /// + /// Trims unused capacity from the set. + /// + public void TrimExcess() { + mChanges.TrimExcess(); + } + + // IEnumerable, so we can use foreach syntax when going forward + public IEnumerator GetEnumerator() { + return mChanges.GetEnumerator(); + } + + // IEnumerable: generic version + IEnumerator IEnumerable.GetEnumerator() { + return mChanges.GetEnumerator(); + } + + // TODO(maybe): reverse-order enumerator? + + public override string ToString() { + string str = "[CS: count=" + mChanges.Count; + if (mChanges.Count > 0) { + str += " {0:" + mChanges[0] + "}"; + } + str += "]"; + return str; + } + } +} diff --git a/SourceGenWPF/CodeAnalysis.cs b/SourceGenWPF/CodeAnalysis.cs new file mode 100644 index 0000000..a04901f --- /dev/null +++ b/SourceGenWPF/CodeAnalysis.cs @@ -0,0 +1,1070 @@ +/* + * 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.Collections.Generic; +using System.Diagnostics; + +using Asm65; +using CommonUtil; +using PluginCommon; +using SourceGenWPF.Sandbox; + +namespace SourceGenWPF { + /// + /// Instruction analyzer. + /// + /// All data held in this object is transient, and will be discarded when analysis + /// completes. All user-defined values should be held elsewhere and provided as inputs + /// to the analyzer. Any change that merits re-analysis should be handled by creating a + /// new instance of this object. + /// + /// See the comments at the top of UndoableChange for a list of things that can + /// mandate code re-analysis. + /// + public class CodeAnalysis { + /// + /// Type hints are specified by the user. The identify a region as being code + /// or data. The code analyzer will stop at data-hinted regions, and will + /// process any code-hinted regions during the dead-code pass. + /// + /// The hints are not used directly by the data analyzer, but the effects they + /// have on the Anattrib array are. + /// + public enum TypeHint : sbyte { + // No hint. Default value populated in new arrays. + NoHint = 0, + + // Byte is an instruction. If the code analyzer doesn't find this + // naturally, it will be scanned. + Code, + + // Byte is inline data. Execution continues "through" the byte. + InlineData, + + // Byte is data. Execution halts. + Data + } + + /// + /// Class for handling callbacks from extension scripts. + /// + private class ScriptSupport : MarshalByRefObject, PluginCommon.IApplication { + private CodeAnalysis mOuter; + + public ScriptSupport(CodeAnalysis ca) { + mOuter = ca; + } + + /// + /// Call this when analysis is complete, to ensure that over-active scripts + /// can't keep doing things. (This is not part of IApplication.) + /// + public void Shutdown() { + mOuter = null; + } + + public void DebugLog(string msg) { + mOuter.mDebugLog.LogI("PLUGIN: " + msg); + } + + public bool SetOperandFormat(int offset, DataSubType subType, string label) { + return mOuter.SetOperandFormat(offset, subType, label); + } + + public bool SetInlineDataFormat(int offset, int length, DataType type, + DataSubType subType, string label) { + return mOuter.SetInlineDataFormat(offset, length, type, subType, label); + } + } + + /// + /// Extension script manager. + /// + private ScriptManager mScriptManager; + + /// + /// Local object that implements the IApplication interface for plugins. + /// + private ScriptSupport mScriptSupport; + + /// + /// List of interesting plugins. If we have plugins that don't do code inlining we + /// can ignore them. (I'm using an array instead of a List<IPlugin> as a + /// micro-optimization; see https://stackoverflow.com/a/454923/294248 .) + /// + private IPlugin[] mScriptArray; + + /// + /// CPU to use when analyzing data. + /// + private CpuDef mCpuDef; + + /// + /// Map of offsets to addresses. + /// + private AddressMap mAddrMap; + + /// + /// Reference to 65xx data. + /// + private byte[] mFileData; + + /// + /// Attributes, one per byte in input file. + /// + private Anattrib[] mAnattribs; + + /// + /// Reference to type hint array, one hint per byte. + /// + private TypeHint[] mTypeHints; + + /// + /// Reference to status flag override array, one entry per byte. + /// + private StatusFlags[] mStatusFlagOverrides; + + /// + /// Initial status flags to use at entry points. + /// + private StatusFlags mEntryFlags; + + /// + /// Debug trace log. + /// + private DebugLog mDebugLog = new DebugLog(DebugLog.Priority.Silent); + + + /// + /// Constructor. + /// + /// 65xx code stream. + /// CPU definition to use when interpreting code. + /// Anattrib array. Expected to be newly allocated, all + /// entries set to default values. + /// Map of offsets to addresses. + /// Type hints, one per byte. + /// Status flag overrides for instruction-start + /// bytes. + /// Status flags to use at code entry points. + /// Extension script manager. + /// Object that receives debug log messages. + public CodeAnalysis(byte[] data, CpuDef cpuDef, Anattrib[] anattribs, + AddressMap addrMap, TypeHint[] hints, StatusFlags[] statusFlagOverrides, + StatusFlags entryFlags, ScriptManager scriptMan, DebugLog debugLog) { + mFileData = data; + mCpuDef = cpuDef; + mAnattribs = anattribs; + mAddrMap = addrMap; + mTypeHints = hints; + mStatusFlagOverrides = statusFlagOverrides; + mEntryFlags = entryFlags; + mScriptManager = scriptMan; + mDebugLog = debugLog; + + mScriptSupport = new ScriptSupport(this); + } + + // Internal log functions. If we're concerned about performance overhead due to + // call-site string concatenation, we can #ifdef these to nothing in release builds, + // which should allow the compiler to elide the concat. +#if false + private void LogV(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Verbose)) { + mDebugLog.LogV("+" + offset.ToString("x6") + " " + msg); + } + } +#else + private void LogV(int offset, string msg) { } +#endif +#if true + private void LogD(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Debug)) { + mDebugLog.LogD("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogI(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Info)) { + mDebugLog.LogI("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogW(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Warning)) { + mDebugLog.LogW("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogE(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Error)) { + mDebugLog.LogE("+" + offset.ToString("x6") + " " + msg); + } + } +#else + private void LogD(int offset, string msg) { } + private void LogI(int offset, string msg) { } + private void LogW(int offset, string msg) { } + private void LogE(int offset, string msg) { } +#endif + + /// + /// Analyze a blob of code and data, annotating all code areas. + /// + /// Also identifies data embedded in code, e.g. parameter blocks following a JSR, + /// with the help of extension scripts. + /// + /// Failing here can leave us in a strange state, so prefer to work around unexpected + /// inputs rather than bailing entirely. + /// + public void Analyze() { + List scanOffsets = new List(); + + mDebugLog.LogI("Analyzing code: " + mFileData.Length + " bytes, CPU=" + mCpuDef.Name); + + PrepareScripts(); + + SetAddresses(); + + // Set the "is data" and "is inline data" flags on anything that the user has + // flagged as being such. This tells us to stop processing or skip over bytes + // as we work. We don't need to flag code hints explicitly for analysis, but + // we want to be able to display the flags in the info window. + // + // The data recognizers may spot additional inline data offsets as we work. This + // can cause a race if it mis-identifies code that is also a branch target; + // whichever marks the code first will win. + UnpackTypeHints(); + + // Find starting place, based on type hints. + // We only set the "visited" flag on the instruction start, so if the user + // puts a code hint in the middle of an instruction, we will find it and + // treat it as an entry point. (This is useful for embedded instructions + // that are branched to by code we aren't able to detect.) + int searchStart = FindFirstUnvisitedInstruction(0); + while (searchStart >= 0) { + mAnattribs[searchStart].IsEntryPoint = true; + mAnattribs[searchStart].StatusFlags = mEntryFlags; + mAnattribs[searchStart].ApplyStatusFlags(mStatusFlagOverrides[searchStart]); + + int offset = searchStart; + while (true) { + bool embedded = (mAnattribs[offset].IsInstruction && + !mAnattribs[offset].IsVisited); + LogI(offset, "Scan chunk (vis=" + mAnattribs[offset].IsVisited + + " chg=" + mAnattribs[offset].IsChanged + + (embedded ? " embedded " : "") + ")"); + + AnalyzeSegment(offset, scanOffsets); + + // Did anything new get added? + if (scanOffsets.Count == 0) { + break; + } + + // Pop one off the end. + int lastItem = scanOffsets.Count - 1; + offset = scanOffsets[lastItem]; + scanOffsets.RemoveAt(lastItem); + } + + searchStart = FindFirstUnvisitedInstruction(searchStart); + } + + mScriptSupport.Shutdown(); + + MarkUnexecutedEmbeddedCode(); + } + + /// + /// Prepare a list of relevant extension scripts. + /// + private void PrepareScripts() { + if (mScriptManager == null) { + // Currently happens for regression tests with no external files. + mScriptArray = new IPlugin[0]; + return; + } + + // Include all scripts. + mScriptArray = mScriptManager.GetAllInstances().ToArray(); + + // Prep them. + mScriptManager.PrepareScripts(mScriptSupport); + } + + /// + /// Sets the address for every byte in the input. + /// + private void SetAddresses() { + // The AddressMap will have at least one entry, will start at offset 0, and + // will exactly span the file. + foreach (AddressMap.AddressMapEntry ent in mAddrMap) { + int addr = ent.Addr; + for (int i = ent.Offset; i < ent.Offset + ent.Length; i++) { + mAnattribs[i].Address = addr++; + } + } + } + + /// + /// Sets the "is xxxxx" flags on type-hinted entries, so that the code analyzer + /// can find them easily. + /// + private void UnpackTypeHints() { + Debug.Assert(mTypeHints.Length == mAnattribs.Length); + int offset = 0; + foreach (TypeHint hint in mTypeHints) { + switch (hint) { + case TypeHint.Code: + // Set the IsInstruction flag to prevent inline data from being + // placed here. + OpDef op = mCpuDef.GetOpDef(mFileData[offset]); + if (op == OpDef.OpInvalid) { + LogI(offset, "Ignoring code hint on illegal opcode"); + } else { + mAnattribs[offset].IsHinted = true; + mAnattribs[offset].IsInstruction = true; + } + break; + case TypeHint.Data: + // Tells the code analyzer to stop. Does not define a data analyzer + // "uncategorized data" boundary. + mAnattribs[offset].IsHinted = true; + mAnattribs[offset].IsData = true; + break; + case TypeHint.InlineData: + // Tells the code analyzer to walk across these. + mAnattribs[offset].IsHinted = true; + mAnattribs[offset].IsInlineData = true; + break; + case TypeHint.NoHint: + break; + default: + Debug.Assert(false); + break; + } + offset++; + } + } + + /// + /// Finds the first offset that is hinted as code but hasn't yet been visited. + /// + /// This might be in the middle of an already-visited instruction. + /// + /// Offset at which to start the search. + /// Offset found. + private int FindFirstUnvisitedInstruction(int start) { + for (int i = start; i < mAnattribs.Length; i++) { + if (mAnattribs[i].IsHinted && mTypeHints[i] == TypeHint.Code && + !mAnattribs[i].IsVisited) { + LogD(i, "Unvisited code hint"); + if (mAnattribs[i].IsData || mAnattribs[i].IsInlineData) { + // Maybe the user put a code hint on something that was + // later recognized as inline data? Shouldn't have been allowed. + LogW(i, "Weird: code hint on data/inline"); + continue; + } + return i; + } + } + return -1; + } + + /// + /// Finds bits of code that are part of embedded instructions but not actually + /// executed, and marks them as inline data. + /// + private void MarkUnexecutedEmbeddedCode() { + // The problem arises when you have a line like 4C 60 EA, with a branch to the + // middle byte. The formatter will print "JMP $EA60", then "