1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-12 08:29:29 +00:00

Allow setting the start/end address for a block

If you have a single line selected, Set Address adds a .ORG directive
that changes the addresses of all following data, until the next .ORG
directive is reached.  Sometimes code will relocate part of itself,
and it's useful to be able to set the address at the end of the block
to what it would have been before the .ORG change.

If you have multiple lines selected, we now add the second .ORG to
the offset that follows the last selected line.

Also, fixed a bug in the Symbol value updater that wasn't handling
non-unique labels correctly.
This commit is contained in:
Andy McFadden 2019-12-25 17:15:37 -08:00
parent 3acf83ead3
commit 091955b9c2
9 changed files with 283 additions and 90 deletions

View File

@ -33,6 +33,8 @@ namespace CommonUtil {
/// script extensions.
/// </remarks>
public class AddressMap : IEnumerable<AddressMap.AddressMapEntry> {
public const int NO_ENTRY_ADDR = -1; // address value indicating no entry
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// TODO: make this immutable. That should allow us to eliminate the copy constructor,
/// since we won't need to make copies of things.
/// </remarks>
[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;
}
}
/// <summary>
@ -84,11 +97,11 @@ namespace CommonUtil {
/// </summary>
/// <param name="entries">List of AddressMapEntry.</param>
public AddressMap(List<AddressMapEntry> 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();
}
/// <summary>
@ -98,7 +111,7 @@ namespace CommonUtil {
public List<AddressMapEntry> GetEntryList() {
List<AddressMapEntry> newList = new List<AddressMapEntry>(mAddrList.Count);
foreach (AddressMapEntry ent in mAddrList) {
newList.Add(ent);
newList.Add(new AddressMapEntry(ent));
}
return newList;
}
@ -127,7 +140,8 @@ namespace CommonUtil {
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
@ -159,7 +173,7 @@ namespace CommonUtil {
/// <param name="addr">24-bit address.</param>
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);
}

View File

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

View File

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

View File

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

View File

@ -17,6 +17,16 @@
<h2><a name="address">Edit Address</a></h2>
<p>This adds a target address directive (".ORG") to the current offset.
If you leave the text field blank, the directive will be removed.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>There will always be an address directive at the start of the file.
Attempts to remove it will be ignored.</p>
<p>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.</p>
<h2><a name="flags">Edit Status Flag Override</a></h2>
<p>The state of the processor status flags are tracked for every

View File

@ -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.</p>
<ul>
<li><a href="editors.html#address">Set Address</a>. Sets the
target address at that offset. Enabled when a single instruction or
data line is selected.</li>
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.</li>
<li><a href="editors.html#flags">Override Status Flags</a>. Changes
the status flags at that offset. Enabled when a single instruction
line is selected.</li>

View File

@ -228,6 +228,21 @@ namespace SourceGen {
Label = label + UNIQUE_TAG_CHAR + uniqueTag.ToString("x6");
}
/// <summary>
/// Creates a new Symbol where everything is identical to the argument except the value.
/// </summary>
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;
}
/// <summary>
/// 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.

View File

@ -27,34 +27,56 @@ limitations under the License.
ContentRendered="Window_ContentRendered">
<StackPanel Margin="8">
<TextBlock Text="Enter 16-bit or 24-bit address in hexadecimal, e.g. $1000 or 00/be00."/>
<TextBlock Text="Leave the field blank to remove the address override."/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Editing address at offset: "/>
<TextBlock Margin="0,2,0,0" Text="{Binding FirstOffsetStr}"
FontFamily="{StaticResource GeneralMonoFont}"/>
<TextBlock Margin="16,0,0,0" Text="(load address: "/>
<TextBlock Margin="0,2,0,0" Text="{Binding LoadAddressText, FallbackValue=$1234}"
FontFamily="{StaticResource GeneralMonoFont}"/>
<TextBlock Text=")"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<TextBlock Text="Address:"/>
<TextBlock Text="Address (hex):"/>
<TextBox Name="addrTextBox" Width="100" Margin="4,1,0,0"
FontFamily="{StaticResource GeneralMonoFont}"
Text="{Binding Path=AddressText, UpdateSourceTrigger=PropertyChanged}"
IsInactiveSelectionHighlightEnabled="True"
TextChanged="TextBox_TextChanged">
IsInactiveSelectionHighlightEnabled="True">
<TextBox.Resources>
<!-- default non-focus highlight color is nearly invisible -->
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
Color="LightBlue"/>
</TextBox.Resources>
</TextBox>
</StackPanel>
<TextBlock Margin="16,0,0,0" Text="(load address: " Visibility="{Binding LoadAddressVis}"/>
<TextBlock Text="{Binding LoadAddressText, FallbackValue=$1234}" Visibility="{Binding LoadAddressVis}"/>
<TextBlock Text=")" Visibility="{Binding LoadAddressVis}"/>
<TextBlock Text="• Enter 16-bit or 24-bit address, e.g. $1000 or 01/be00." Margin="0,4,0,0"/>
<TextBlock Text="• Leave the field blank to remove the address override."/>
<StackPanel Margin="0,8,0,0" Visibility="{Binding NextAddressVis}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bytes spanned: "/>
<TextBlock Text="{Binding BytesSelectedStr, FallbackValue=123 ($123)}" Margin="0,2,0,0"
FontFamily="{StaticResource GeneralMonoFont}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Offset after last selected item ("/>
<TextBlock Text="{Binding NextOffsetStr, FallbackValue=+001234}" Margin="0,2,0,0"
FontFamily="{StaticResource GeneralMonoFont}"/>
<TextBlock Text=") will resume at address "/>
<TextBlock Text="{Binding NextAddressStr, FallbackValue=$abcd}" Margin="0,2,0,0"
FontFamily="{StaticResource GeneralMonoFont}"/>
<TextBlock Text="."/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<Button Name="okButton" Content="OK" IsDefault="True" Width="70"
<Button Content="OK" IsDefault="True" Width="70"
IsEnabled="{Binding IsValid}" Click="OkButton_Click"/>
<Button Name="cancelButton" Content="Cancel" IsCancel="True"
Width="70" Margin="4,0,0,0"/>
<Button Content="Cancel" IsCancel="True" Width="70" Margin="4,0,0,0"/>
</StackPanel>
</StackPanel>
</Window>

View File

@ -28,10 +28,25 @@ namespace SourceGen.WpfGui {
/// </summary>
public partial class EditAddress : Window, INotifyPropertyChanged {
/// <summary>
/// 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.
/// </summary>
public int Address { get; private set; }
public int NewAddress { get; private set; }
/// <summary>
/// Offset being edited.
/// </summary>
private int mFirstOffset;
/// <summary>
/// Offset after the end of the selection, or -1 if only one line is selected.
/// </summary>
private int mNextOffset;
/// <summary>
/// Address after the end of the selection, or -1 if only one line is selected.
/// </summary>
private int mNextAddress;
/// <summary>
/// Maximum allowed address value.
@ -48,10 +63,30 @@ namespace SourceGen.WpfGui {
/// </summary>
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) + ")";
}
}
/// <summary>
/// Bound two-way property.
/// Address input TextBox.
/// </summary>
public string AddressText { get; set; }
public string AddressText {
get { return mAddressText; }
set { mAddressText = value; OnPropertyChanged(); UpdateControls(); }
}
private string mAddressText;
/// <summary>
/// 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);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Parent window.</param>
/// <param name="firstOffset">Offset at top of selection.</param>
/// <param name="nextOffset">Offset past bottom of selection, or -1 if only one
/// line is selected.</param>
/// <param name="project">Project reference.</param>
/// <param name="formatter">Text formatter object.</param>
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.
/// </remarks>
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);
}
}