diff --git a/CommonUtil/AddressMap.cs b/CommonUtil/AddressMap.cs index ca39344..fb96db9 100644 --- a/CommonUtil/AddressMap.cs +++ b/CommonUtil/AddressMap.cs @@ -33,6 +33,8 @@ namespace CommonUtil { /// script extensions. /// public class AddressMap : IEnumerable { + public const int NO_ENTRY_ADDR = -1; // address value indicating no entry + /// /// Code starting at the specified offset will have the specified address. /// @@ -45,6 +47,10 @@ namespace CommonUtil { /// Entries are mutable, but must only be altered by AddressMap. Don't retain /// instances of this across other activity. /// + /// + /// TODO: make this immutable. That should allow us to eliminate the copy constructor, + /// since we won't need to make copies of things. + /// [Serializable] public class AddressMapEntry { public int Offset { get; set; } @@ -56,6 +62,13 @@ namespace CommonUtil { Addr = addr; Length = len; } + + // Copy constructor. + public AddressMapEntry(AddressMapEntry src) { + Offset = src.Offset; + Addr = src.Addr; + Length = src.Length; + } } /// @@ -84,11 +97,11 @@ namespace CommonUtil { /// /// List of AddressMapEntry. public AddressMap(List entries) { - // TODO(someday): validate list contents mTotalLength = entries[entries.Count - 1].Offset + entries[entries.Count - 1].Length; foreach (AddressMapEntry ent in entries) { - mAddrList.Add(ent); + mAddrList.Add(new AddressMapEntry(ent)); } + DebugValidate(); } /// @@ -98,7 +111,7 @@ namespace CommonUtil { public List GetEntryList() { List newList = new List(mAddrList.Count); foreach (AddressMapEntry ent in mAddrList) { - newList.Add(ent); + newList.Add(new AddressMapEntry(ent)); } return newList; } @@ -127,7 +140,8 @@ namespace CommonUtil { /// /// 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. + /// offset, or NO_ENTRY_ADDR if there is no address map entry there. The offset must + /// match exactly. /// public int Get(int offset) { foreach (AddressMapEntry ad in mAddrList) { @@ -135,7 +149,7 @@ namespace CommonUtil { return ad.Addr; } } - return -1; + return NO_ENTRY_ADDR; } /// @@ -159,7 +173,7 @@ namespace CommonUtil { /// 24-bit address. public void Set(int offset, int addr) { Debug.Assert(offset >= 0); - if (addr == -1) { + if (addr == NO_ENTRY_ADDR) { if (offset != 0) { // ignore attempts to remove entry at offset zero Remove(offset); } diff --git a/SourceGen/DataAnalysis.cs b/SourceGen/DataAnalysis.cs index 3c72cc3..affec10 100644 --- a/SourceGen/DataAnalysis.cs +++ b/SourceGen/DataAnalysis.cs @@ -573,6 +573,10 @@ namespace SourceGen { offset++; // Check to see if the address has changed from the previous entry. + // TODO(BUG): this test is insufficient -- they might have a .ORG that + // doesn't change the address. It's currently harmless because the + // .ORG is a no-op and gets swallowed up by the asm generator, but it + // looks wrong and could break things. if (offset < mAnattribs.Length && mAnattribs[offset-1].Address + 1 != mAnattribs[offset].Address) { // Must be an ORG here. Scan previous region. diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 240d99e..3787c56 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -1168,8 +1168,7 @@ namespace SourceGen { Symbol sym = kvp.Value; int expectedAddr = AddrMap.OffsetToAddress(offset); if (sym.Value != expectedAddr) { - Symbol newSym = new Symbol(sym.Label, expectedAddr, sym.SymbolSource, - sym.SymbolType, sym.LabelAnno); + Symbol newSym = sym.UpdateValue(expectedAddr); Debug.WriteLine("Updating label value: " + sym + " --> " + newSym); changes[offset] = newSym; sym = newSym; diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 31799d8..a4f6629 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -1653,54 +1653,130 @@ namespace SourceGen { } public bool CanEditAddress() { - if (SelectionAnalysis.mNumItemsSelected != 1) { + // First line must be code, data, or an ORG directive. + int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); + if (selIndex < 0) { return false; } - EntityCounts counts = SelectionAnalysis.mEntityCounts; - // Line must be code, data, or an ORG directive. - return (counts.mDataLines > 0 || counts.mCodeLines > 0) || - (SelectionAnalysis.mLineType == LineListGen.Line.Type.OrgDirective); + LineListGen.Line selLine = CodeLineList[selIndex]; + if (selLine.LineType != LineListGen.Line.Type.Code && + selLine.LineType != LineListGen.Line.Type.Data && + selLine.LineType != LineListGen.Line.Type.OrgDirective) { + return false; + } + + // If multiple lines are selected, there must not be an address change between them. + int lastIndex = mMainWin.CodeListView_GetLastSelectedIndex(); + int firstOffset = CodeLineList[selIndex].FileOffset; + int lastOffset = CodeLineList[lastIndex].FileOffset; + if (firstOffset == lastOffset) { + // Single-item selection, we're fine. + return true; + } + + int nextOffset = lastOffset + CodeLineList[lastIndex].OffsetSpan; + + foreach (AddressMap.AddressMapEntry ent in mProject.AddrMap) { + // It's okay to have an existing entry at firstOffset or nextOffset. + if (ent.Offset > firstOffset && ent.Offset < nextOffset) { + Debug.WriteLine("Found mid-selection AddressMap entry at +" + + ent.Offset.ToString("x6")); + return false; + } + } + + return true; } public void EditAddress() { int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); - int offset = CodeLineList[selIndex].FileOffset; - Anattrib attr = mProject.GetAnattrib(offset); + int lastIndex = mMainWin.CodeListView_GetLastSelectedIndex(); + int firstOffset = CodeLineList[selIndex].FileOffset; + int lastOffset = CodeLineList[lastIndex].FileOffset; + int nextOffset = lastOffset + CodeLineList[lastIndex].OffsetSpan; + int nextAddr; - // Compute load address, i.e. where the byte would have been placed if the entire - // file were loaded at the address of the first address map entry. We assume - // offsets wrap at the bank boundary. - int firstAddr = mProject.AddrMap.OffsetToAddress(0); - int loadAddr = ((firstAddr + offset) & 0xffff) | (firstAddr & 0xff0000); - EditAddress dlg = new EditAddress(mMainWin, attr.Address, loadAddr, - mProject.CpuDef.MaxAddressValue, mOutputFormatter); + if (firstOffset == lastOffset || nextOffset == mProject.FileDataLength) { + // Single item (which may not be a single *line*) is selected, or the + // last selected item is the end of the file. + nextOffset = -1; + nextAddr = AddressMap.NO_ENTRY_ADDR; + } else { + // Compute "nextAddr". If there's an existing entry at nextOffset, we use + // that. If not, we use the "load address", which is determined by the very + // first address. + // + // I tried this by just removing the selected entry and seeing what the address + // would be without it, useful for relocations inside relocations. This worked + // poorly when relocations were chained, i.e. two consecutive blocks were + // relocated to different places. The end address of the second block gets + // set based on the first address of the first block, which doesn't seem useful. +#if false + nextAddr = mProject.AddrMap.Get(nextOffset); + if (nextAddr == AddressMap.NO_ENTRY_ADDR) { + AddressMap cloneMap = new AddressMap(mProject.AddrMap.GetEntryList()); + if (firstOffset != 0) { + cloneMap.Remove(firstOffset); + } + nextAddr = cloneMap.OffsetToAddress(nextOffset); + } +#else + int fileStartAddr = mProject.AddrMap.OffsetToAddress(0); + nextAddr = ((fileStartAddr + nextOffset) & 0xffff) | (fileStartAddr & 0xff0000); +#endif + } + + EditAddress dlg = new EditAddress(mMainWin, firstOffset, nextOffset, nextAddr, + mProject, mOutputFormatter); if (dlg.ShowDialog() != true) { return; } - if (offset == 0 && dlg.Address < 0) { + if (firstOffset == 0 && dlg.NewAddress < 0) { // Not allowed. The AddressMap will just put it back, which confuses // the undo operation. Debug.WriteLine("EditAddress: not allowed to remove address at offset +000000"); - } else if (true || attr.Address != dlg.Address) { - // NOTE: we used to prevent creation of an apparently redundant address change, - // but it's really helpful to put one on code that isn't moving before you - // start moving other stuff around. - Debug.WriteLine("EditAddress: changing addr at offset +" + offset.ToString("x6") + - " to " + dlg.Address); + return; + } - AddressMap addrMap = mProject.AddrMap; - // Get the previous address map entry for this exact offset, if one - // exists. This may be different from the value used as the default - // (attr.Address), which is the address assigned to the offset, in - // the case where no previous mapping existed. - int prevAddress = addrMap.Get(offset); - UndoableChange uc = UndoableChange.CreateAddressChange(offset, - prevAddress, dlg.Address); - ChangeSet cs = new ChangeSet(uc); + ChangeSet cs = new ChangeSet(1); + + if (mProject.AddrMap.Get(firstOffset) != dlg.NewAddress) { + // Added / removed / changed existing entry. + // + // We allow creation of an apparently redundant address override, because + // sometimes it's helpful to add one to "anchor" an area before relocating + // something that appears earlier in the file. + int prevAddress = mProject.AddrMap.Get(firstOffset); + UndoableChange uc = UndoableChange.CreateAddressChange(firstOffset, + prevAddress, dlg.NewAddress); + cs.Add(uc); + Debug.WriteLine("EditAddress: changing addr at offset +" + + firstOffset.ToString("x6") + " to $" + dlg.NewAddress.ToString("x4")); + } + + // We want to create an entry for the chunk that follows the selected area. + // We don't modify the trailing address if an entry already exists. + // (Note the "can edit" code prevented us from being called if there's an + // address map entry in the middle of the selected area.) + // + // If they're removing an existing entry, don't add a new entry at the end. + if (nextAddr >= 0 && dlg.NewAddress != AddressMap.NO_ENTRY_ADDR && + mProject.AddrMap.Get(nextOffset) == AddressMap.NO_ENTRY_ADDR) { + // We don't screen for redundant entries here. That should only happen if + // they select a range and then don't change the address. Maybe it's useful? + int prevAddress = mProject.AddrMap.Get(nextOffset); + UndoableChange uc = UndoableChange.CreateAddressChange(nextOffset, + prevAddress, nextAddr); + cs.Add(uc); + Debug.WriteLine("EditAddress: setting trailing addr at offset +" + + nextOffset.ToString("x6") + " to $" + nextAddr.ToString("x4")); + } + + if (cs.Count > 0) { ApplyUndoableChanges(cs); } else { - Debug.WriteLine("EditAddress: no change"); + Debug.WriteLine("EditAddress: no changes"); } } @@ -3110,7 +3186,6 @@ namespace SourceGen { int lastOffset = Math.Max(firstOffset, CodeLineList[lastIndex].FileOffset + CodeLineList[lastIndex].OffsetSpan - 1); mHexDumpDialog.ShowOffsetRange(firstOffset, lastOffset); - } } diff --git a/SourceGen/RuntimeData/Help/editors.html b/SourceGen/RuntimeData/Help/editors.html index ac39e2f..b6b7db9 100644 --- a/SourceGen/RuntimeData/Help/editors.html +++ b/SourceGen/RuntimeData/Help/editors.html @@ -17,6 +17,16 @@

Edit Address

This adds a target address directive (".ORG") to the current offset. If you leave the text field blank, the directive will be removed.

+

The text entry field is initialized to the address of the +first selected line. The "load address", i.e. the place where the +code or data will live when the file is first loaded into memory, +is shown for reference.

+

If multiple lines were selected, some additional information will be +shown, and an address directive will be added after the last selected +line. This directive will set the address to the "load address". +This is useful for "relocating" a block of code or data in the middle of +the file. You're not allowed to do this when the selected range of +lines spans another address directive.

Addresses are always interpreted as hexadecimal. You can prefix it with a '$', but that's not required. 24-bit addresses may be written with a bank separator, e.g. "12/3456" @@ -25,9 +35,6 @@ would resolve to address $123456.

There will always be an address directive at the start of the file. Attempts to remove it will be ignored.

-

If the byte at the current offset is not at the address where it was -initially loaded, the "load address" will be shown for reference.

-

Edit Status Flag Override

The state of the processor status flags are tracked for every diff --git a/SourceGen/RuntimeData/Help/mainwin.html b/SourceGen/RuntimeData/Help/mainwin.html index 9fbaf9d..f097a5a 100644 --- a/SourceGen/RuntimeData/Help/mainwin.html +++ b/SourceGen/RuntimeData/Help/mainwin.html @@ -161,8 +161,11 @@ the Actions menu item in the menu bar. The set of options that are enabled will depend on what you have selected in the main window.

  • Set Address. Sets the - target address at that offset. Enabled when a single instruction or - data line is selected.
  • + target address at that offset. When multiple lines are selected, + the target addresses at the start and end of the range is set. + Enabled when the first line selected is code, data, or an address + override, and the full selected range does not overlap with another + address override.
  • Override Status Flags. Changes the status flags at that offset. Enabled when a single instruction line is selected.
  • diff --git a/SourceGen/Symbol.cs b/SourceGen/Symbol.cs index 4f1a6f0..e6cbd92 100644 --- a/SourceGen/Symbol.cs +++ b/SourceGen/Symbol.cs @@ -228,6 +228,21 @@ namespace SourceGen { Label = label + UNIQUE_TAG_CHAR + uniqueTag.ToString("x6"); } + /// + /// Creates a new Symbol where everything is identical to the argument except the value. + /// + public Symbol UpdateValue(int newValue) { + Symbol newSym = new Symbol(); + newSym.Label = Label; + newSym.Value = newValue; + newSym.SymbolType = SymbolType; + newSym.SymbolSource = SymbolSource; + newSym.LabelAnno = LabelAnno; + // generated field, not dependent on Value + newSym.SourceTypeString = SourceTypeString; + return newSym; + } + /// /// Generates a displayable form of the label. This will have the non-unique label /// prefix and annotation suffix, and will have the non-unique tag removed. diff --git a/SourceGen/WpfGui/EditAddress.xaml b/SourceGen/WpfGui/EditAddress.xaml index 6b72d07..978981d 100644 --- a/SourceGen/WpfGui/EditAddress.xaml +++ b/SourceGen/WpfGui/EditAddress.xaml @@ -27,34 +27,56 @@ limitations under the License. ContentRendered="Window_ContentRendered"> - - + + + + + + + + - + + IsInactiveSelectionHighlightEnabled="True"> + - - - + + + + + + + + + + + + + + + - public partial class EditAddress : Window, INotifyPropertyChanged { /// - /// Address typed by user. Only valid after the dialog returns OK. Will be set to -1 - /// if the user is attempting to delete the address. + /// Address typed by user. Only valid after the dialog returns OK. Will be set to + /// AddressMap.NO_ENTRY_ADDR if the user is attempting to delete the address. /// - public int Address { get; private set; } + public int NewAddress { get; private set; } + + /// + /// Offset being edited. + /// + private int mFirstOffset; + + /// + /// Offset after the end of the selection, or -1 if only one line is selected. + /// + private int mNextOffset; + + /// + /// Address after the end of the selection, or -1 if only one line is selected. + /// + private int mNextAddress; /// /// Maximum allowed address value. @@ -48,10 +63,30 @@ namespace SourceGen.WpfGui { /// private Formatter mFormatter; + public string FirstOffsetStr { + get { return mFormatter.FormatOffset24(mFirstOffset); } + } + public string NextOffsetStr { + get { return mFormatter.FormatOffset24(mNextOffset); } + } + public string NextAddressStr { + get { return '$' + mFormatter.FormatAddress(mNextAddress, mNextAddress > 0xffff); } + } + public string BytesSelectedStr { + get { + int count = mNextOffset - mFirstOffset; + return count.ToString() + " (" + mFormatter.FormatHexValue(count, 2) + ")"; + } + } + /// - /// Bound two-way property. + /// Address input TextBox. /// - public string AddressText { get; set; } + public string AddressText { + get { return mAddressText; } + set { mAddressText = value; OnPropertyChanged(); UpdateControls(); } + } + private string mAddressText; /// /// Set to true when input is valid. Controls whether the OK button is enabled. @@ -62,11 +97,12 @@ namespace SourceGen.WpfGui { } private bool mIsValid; - public Visibility LoadAddressVis { - get { return mLoadAddressVis; } - set { mLoadAddressVis = value; OnPropertyChanged(); } + public Visibility NextAddressVis { + get { return mNextAddressVis; } + set { mNextAddressVis = value; OnPropertyChanged(); } } - public Visibility mLoadAddressVis = Visibility.Collapsed; + public Visibility mNextAddressVis = Visibility.Collapsed; + public string LoadAddressText { get { return mLoadAddressText; } set { mLoadAddressText = value; OnPropertyChanged(); } @@ -80,25 +116,45 @@ namespace SourceGen.WpfGui { } - public EditAddress(Window owner, int initialAddr, int loadAddr, int maxAddressValue, - Formatter formatter) { - // Set the property before initializing the window -- we don't have a property - // change notifier. - Address = -2; - mMaxAddressValue = maxAddressValue; - mBaseAddr = loadAddr; - mFormatter = formatter; - - AddressText = Asm65.Address.AddressToString(initialAddr, false); - - if (initialAddr != loadAddr) { - LoadAddressVis = Visibility.Visible; - LoadAddressText = mFormatter.FormatAddress(loadAddr, loadAddr > 0xffff); - } - + /// + /// Constructor. + /// + /// Parent window. + /// Offset at top of selection. + /// Offset past bottom of selection, or -1 if only one + /// line is selected. + /// Project reference. + /// Text formatter object. + public EditAddress(Window owner, int firstOffset, int nextOffset, int nextAddr, + DisasmProject project, Formatter formatter) { InitializeComponent(); Owner = owner; DataContext = this; + + mFirstOffset = firstOffset; + mNextOffset = nextOffset; + mNextAddress = nextAddr; + mFormatter = formatter; + mMaxAddressValue = project.CpuDef.MaxAddressValue; + + // Compute load address, i.e. where the byte would have been placed if the entire + // file were loaded at the address of the first address map entry. We assume + // offsets wrap at the bank boundary. + int fileStartAddr = project.AddrMap.OffsetToAddress(0); + mBaseAddr = ((fileStartAddr + firstOffset) & 0xffff) | (fileStartAddr & 0xff0000); + + int firstAddr = project.GetAnattrib(firstOffset).Address; + Debug.Assert(project.AddrMap.OffsetToAddress(firstOffset) == firstAddr); + + AddressText = Asm65.Address.AddressToString(firstAddr, false); + + LoadAddressText = '$' + mFormatter.FormatAddress(mBaseAddr, mBaseAddr > 0xffff); + + if (nextOffset >= 0) { + NextAddressVis = Visibility.Visible; + } + + NewAddress = -2; } private void Window_ContentRendered(object sender, EventArgs e) { @@ -108,10 +164,11 @@ namespace SourceGen.WpfGui { private void OkButton_Click(object sender, RoutedEventArgs e) { if (AddressText.Length == 0) { - Address = -1; + NewAddress = CommonUtil.AddressMap.NO_ENTRY_ADDR; } else { - Asm65.Address.ParseAddress(AddressText, mMaxAddressValue, out int addr); - Address = addr; + bool ok = Asm65.Address.ParseAddress(AddressText, mMaxAddressValue, out int addr); + Debug.Assert(ok); + NewAddress = addr; } DialogResult = true; } @@ -123,12 +180,9 @@ namespace SourceGen.WpfGui { /// Must have UpdateSourceTrigger=PropertyChanged set for this to work. The default /// for TextBox is LostFocus. /// - private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { - if (IsLoaded) { - string text = AddressText; - IsValid = (text.Length == 0) || - Asm65.Address.ParseAddress(text, mMaxAddressValue, out int unused); - } + private void UpdateControls() { + IsValid = (AddressText.Length == 0) || + Asm65.Address.ParseAddress(AddressText, mMaxAddressValue, out int unused); } }