diff --git a/src/Gui/TextViewing/AbstractDisassemblyTextModel.cs b/src/Gui/TextViewing/AbstractDisassemblyTextModel.cs index 0c54860d0a..7683a2687e 100644 --- a/src/Gui/TextViewing/AbstractDisassemblyTextModel.cs +++ b/src/Gui/TextViewing/AbstractDisassemblyTextModel.cs @@ -88,7 +88,7 @@ public LineSpan[] GetLineSpans(int count) if (arch is not null) { var addr = Align(arch, addrStart + offset, arch.CodeMemoryGranularity); - if (program.SegmentMap.TryFindSegment(addr, out ImageSegment seg) && + if (program.SegmentMap.TryFindSegment(addr, out ImageSegment? seg) && seg.MemoryArea != null && seg.MemoryArea.IsValidAddress(addr)) { @@ -124,7 +124,7 @@ private IProcessorArchitecture GetArchitectureForAddress(Address addr) { if (this.arch is not null) return this.arch; - IProcessorArchitecture arch = null; + IProcessorArchitecture? arch = null; // Try to find a basic block at this address and use its architecture. if (program.ImageMap.TryFindItem(addr, out var item) && item is ImageMapBlock imb && diff --git a/src/UserInterfaces/WindowsForms/Controls/MixedCodeDataModel.SpanGenerator.cs b/src/Gui/TextViewing/AbstractMixedCodeDataModel.SpanGenerator.cs similarity index 80% rename from src/UserInterfaces/WindowsForms/Controls/MixedCodeDataModel.SpanGenerator.cs rename to src/Gui/TextViewing/AbstractMixedCodeDataModel.SpanGenerator.cs index e789bd6d7d..b6868f4a29 100644 --- a/src/UserInterfaces/WindowsForms/Controls/MixedCodeDataModel.SpanGenerator.cs +++ b/src/Gui/TextViewing/AbstractMixedCodeDataModel.SpanGenerator.cs @@ -32,9 +32,9 @@ using System.Drawing; using System.Text; -namespace Reko.UserInterfaces.WindowsForms.Controls +namespace Reko.Gui.TextViewing { - public partial class MixedCodeDataModel + public abstract partial class AbstractMixedCodeDataModel { private bool TryReadComment(out LineSpan line) { @@ -44,7 +44,7 @@ private bool TryReadComment(out LineSpan line) line = new LineSpan( curPos, curPos.Address, - new MemoryTextSpan( + CreateMemoryTextSpan( $"; {commentLines[curPos.Offset]}", UiStyles.CodeComment)); curPos = Pos(curPos.Address, curPos.Offset + 1); @@ -130,9 +130,10 @@ private SpanGenerator CreateSpanifier( ModelPosition pos) { SpanGenerator sp; - if (item is ImageMapBlock b && b.Block.Procedure != null) + if (item is ImageMapBlock b && b.Block?.Procedure != null) { sp = new AsmSpanifyer( + this, program, b.Block.Procedure.Architecture, instructions[b], @@ -141,7 +142,7 @@ private SpanGenerator CreateSpanifier( } else { - sp = new MemSpanifyer(program, mem, item, pos); + sp = new MemSpanifyer(this,program, mem, item, pos); } return sp; } @@ -165,6 +166,7 @@ public void DecorateLastLine(LineSpan line) private class AsmSpanifyer : SpanGenerator { + private readonly AbstractMixedCodeDataModel model; private readonly Program program; private readonly IProcessorArchitecture arch; private readonly MachineInstruction[] instrs; @@ -173,12 +175,14 @@ private class AsmSpanifyer : SpanGenerator private readonly Address addrSelected; public AsmSpanifyer( + AbstractMixedCodeDataModel model, Program program, IProcessorArchitecture arch, MachineInstruction[] instrs, ModelPosition pos, Address addrSelected) { + this.model = model; this.instrs = instrs; this.arch = arch; var addr = pos.Address; @@ -196,7 +200,8 @@ public override (ModelPosition, LineSpan)? GenerateSpan() ++offset; var options = new MachineInstructionRendererOptions( flags: MachineInstructionRendererFlags.ResolvePcRelativeAddress); - var asmLine = DisassemblyTextModel.RenderAsmLine( + + var asmLine = model.RenderAssemblerLine( position, program, arch, @@ -213,26 +218,31 @@ public override (ModelPosition, LineSpan)? GenerateSpan() } } + protected abstract LineSpan RenderAssemblerLine(object position, Program program, IProcessorArchitecture arch, MachineInstruction instr, MachineInstructionRendererOptions options); + private class MemSpanifyer : SpanGenerator, IMemoryFormatterOutput { + private readonly AbstractMixedCodeDataModel model; private readonly Program program; private readonly MemoryArea mem; private readonly ImageMapItem item; - private readonly List line; + private readonly List line; private readonly StringBuilder sbText; private ModelPosition position; public MemSpanifyer( + AbstractMixedCodeDataModel model, Program program, MemoryArea mem, ImageMapItem item, ModelPosition pos) { + this.model = model; this.program = program; this.mem = mem; this.item = item; this.position = pos; - this.line = new List(); + this.line = new List(); this.sbText = new StringBuilder(); } @@ -261,19 +271,19 @@ public void BeginLine() public void RenderAddress(Address addr) { - line.Add(new AddressSpan(addr.ToString(), addr, UiStyles.MemoryWindow)); + line.Add(model.CreateAddressSpan(addr.ToString(), addr, UiStyles.MemoryWindow)); } public void RenderUnit(Address addr, string sUnit) { - line.Add(new MemoryTextSpan(" ", UiStyles.MemoryWindow)); - line.Add(new MemoryTextSpan(addr, sUnit, UiStyles.MemoryWindow)); + line.Add(model.CreateMemoryTextSpan(" ", UiStyles.MemoryWindow)); + line.Add(model.CreateMemoryTextSpan(addr, sUnit, UiStyles.MemoryWindow)); } public void RenderFillerSpan(int nChunks, int nCellsPerChunk) { var nCells = (1 + nCellsPerChunk) * nChunks; - line.Add(new MemoryTextSpan(new string(' ', nCells), UiStyles.MemoryWindow)); + line.Add(model.CreateMemoryTextSpan(new string(' ', nCells), UiStyles.MemoryWindow)); } public void RenderUnitAsText(Address addr, string sUnit) @@ -288,7 +298,7 @@ public void RenderTextFillerSpan(int padding) public void EndLine(Constant[] bytes) { - line.Add(new MemoryTextSpan(sbText.ToString(), UiStyles.MemoryWindow)); + line.Add(model.CreateMemoryTextSpan(sbText.ToString(), UiStyles.MemoryWindow)); } } @@ -301,63 +311,5 @@ public static int FindIndexOfInstructionAddress(MachineInstruction[] instrs, Add i => i.Contains(addr)); } - /// - /// An segment of memory - /// - public class MemoryTextSpan : TextSpan - { - private readonly string text; - - public Address Address { get; private set; } - - public MemoryTextSpan(string text, string style) - { - this.text = text; - base.Style = style; - } - - public MemoryTextSpan(Address address, string text, string style) : this(text, style) - { - this.Tag = this; - this.Address = address; - } - - public override string GetText() - { - return text; - } - - public override SizeF GetSize(string text, Font font, Graphics g) - { - SizeF sz = base.GetSize(text, font, g); - return sz; - } - } - - /// - /// An inert text span is not clickable nor has a context menu. - /// - public class InertTextSpan : TextSpan - { - private readonly string text; - - public InertTextSpan(string text, string style) - { - this.text = text; - base.Style = style; - } - - public override string GetText() - { - return text; - } - - public override SizeF GetSize(string text, Font font, Graphics g) - { - SizeF sz = base.GetSize(text, font, g); - return sz; - } - } - } } diff --git a/src/Gui/TextViewing/AbstractMixedCodeDataModel.cs b/src/Gui/TextViewing/AbstractMixedCodeDataModel.cs new file mode 100644 index 0000000000..8789b7370b --- /dev/null +++ b/src/Gui/TextViewing/AbstractMixedCodeDataModel.cs @@ -0,0 +1,577 @@ +#region License +/* + * Copyright (C) 1999-2023 John Källén. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#endregion + +using Reko.Core; +using Reko.Core.Collections; +using Reko.Core.Machine; +using Reko.Gui.Services; +using Reko.Gui.TextViewing; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; + +namespace Reko.Gui.TextViewing +{ + /// + /// Provides a text model that mixes code and data. + /// + public abstract partial class AbstractMixedCodeDataModel : ITextViewModel + { + const int BytesPerLine = 16; + + private readonly Program program; + private readonly ImageMap imageMap; + private readonly ISelectedAddressService selSvc; + private ModelPosition curPos; + private readonly ModelPosition endPos; + private readonly Dictionary instructions; + private readonly IDictionary comments; + + public AbstractMixedCodeDataModel( + Program program, + ImageMap imageMap, + ISelectedAddressService selSvc) + { + this.program = program; + this.imageMap = imageMap; + this.selSvc = selSvc; + + var firstSeg = program.SegmentMap.Segments.Values.FirstOrDefault(); + if (firstSeg is null) + { + this.curPos = Pos(imageMap.BaseAddress); + this.StartPosition = Pos(imageMap.BaseAddress); + this.endPos = Pos(imageMap.BaseAddress); + this.instructions = default!; + this.LineCount = 0; + } + else + { + var lastSeg = program.SegmentMap.Segments.Values.Last(); + this.curPos = Pos(firstSeg.Address); + this.StartPosition = Pos(firstSeg.Address); + this.endPos = Pos(lastSeg.EndAddress); + this.instructions = this.CollectInstructions(); + this.LineCount = CountLines(); + } + this.comments = program.User.Annotations.ToSortedList( + a => a.Address, + a => Lines(a.Text)); + } + + public AbstractMixedCodeDataModel(AbstractMixedCodeDataModel that) + { + this.program = that.program; + this.imageMap = that.imageMap; + this.curPos = that.curPos; + this.StartPosition = that.StartPosition; + this.endPos = that.endPos; + this.instructions = that.instructions; + this.LineCount = that.LineCount; + this.comments = that.comments; + this.selSvc = that.selSvc; + } + + public object CurrentPosition { get { return curPos; } } + public object StartPosition { get; private set; } + public object EndPosition { get { return endPos; } } + + public int LineCount { get; private set; } + + public abstract AbstractMixedCodeDataModel Clone(); + + protected abstract ITextSpan CreateAddressSpan(string formattedAddress, Address addr, string style); + protected abstract ITextSpan CreateMemoryTextSpan(string text, string style); + protected abstract ITextSpan CreateMemoryTextSpan(Address addr, string text, string style); + + private int CountLines() + { + int sum = 0; + foreach (var item in imageMap.Items.Values) + { + sum += CountBlockLines(item); + } + return sum; + } + + private int CountBlockLines(ImageMapItem item) + { + if (item is ImageMapBlock bi) + { + return CountDisassembledLines(bi); + } + else + { + return CountMemoryLines(item); + } + } + + /// + /// Count the number of lines a memory area subtends. + /// + /// + /// We align memory spans on 16-byte boundaries (//$REVIEW for now, + /// this should be user-adjustable) so if we have a memory span + /// straddling such a boundary, we have to account for it. E.g. the + /// span [01FC-0201] should be rendered: + /// + /// 01FC 0C 0D 0F .... + /// 0200 00 01 .. + /// + /// and therefore requires 2 lines even though the number of bytes is + /// less than 16. + /// + /// + /// + private int CountMemoryLines(ImageMapItem item) + { + if (item.Size == 0) + return 0; //$TODO: this shouldn't ever happen! + var linStart = item.Address.ToLinear(); + var linEnd = linStart + item.Size; + linStart = Align(linStart, BytesPerLine); + linEnd = Align(linEnd + (BytesPerLine - 1), BytesPerLine); + return (int)(linEnd - linStart) / BytesPerLine; + } + + private int CountDisassembledLines(ImageMapBlock bi) + { + return instructions[bi].Length; + //if (!instructions.TryGetValue(bi, out var instrs)) + // return 0; + //return instrs.Length; + } + + private static ulong Align(ulong ul, uint alignment) + { + return alignment * (ul / alignment); + } + + private static Address Align(Address addr, uint alignment) + { + var lin = addr.ToLinear(); + var linAl = Align(lin, alignment); + return addr - (int)(lin - linAl); + } + + /// + /// Preemptively collect the machine code instructions + /// in all image map blocks. + /// + private Dictionary CollectInstructions() + { + var instructions = new Dictionary(); + foreach (var bi in imageMap.Items.Values.OfType() + .ToList()) + { + var instrs = new List(); + if (bi.Size > 0 && bi.Block!.Procedure != null) + { + var addrStart = bi.Address; + var addrEnd = bi.Address + bi.Size; + var arch = bi.Block.Procedure.Architecture; + var dasm = program.CreateDisassembler(arch, addrStart).GetEnumerator(); + while (dasm.MoveNext() && dasm.Current.Address < addrEnd) + { + instrs.Add(dasm.Current); + } + } + instructions.Add(bi, instrs.ToArray()); + } + + return instructions; + } + + public int ComparePositions(object a, object b) + { + return ((ModelPosition)a).CompareTo((ModelPosition)b); + } + + /// + /// Returns the (approximate) position. This doesn't have to be + /// 100% precise, but it shouldn't be insanely wrong either. + /// + /// + public (int, int) GetPositionAsFraction() + { + long numer = 0; + foreach (var item in imageMap.Items.Values) + { + if (item.Address <= curPos.Address) + { + if (item.IsInRange(curPos.Address)) + { + numer += GetLineOffset(item, curPos.Address); + break; + } + numer += CountBlockLines(item); + } + } + long denom = LineCount; + while (denom >= 0x80000000) + { + numer >>= 1; + denom >>= 1; + } + return ((int)numer, (int)denom); + } + + private int GetLineOffset(ImageMapItem item, Address addr) + { + if (item is ImageMapBlock bi) + { + int i = 0; + while (i < instructions[bi].Length) + { + if (instructions[bi][i].Address >= addr) + { + break; + } + ++i; + } + return i; + } + else + { + return (int)((Align(addr.ToLinear(), BytesPerLine) - + Align(item.Address.ToLinear(), BytesPerLine)) / + BytesPerLine); + } + } + + public int MoveToLine(object position, int offset) + { + if (position is null) + throw new ArgumentNullException(nameof(position)); + curPos = SanitizePosition((ModelPosition)position); + if (offset == 0) + return 0; + int moved = 0; + if (offset > 0) + { + + if (!imageMap.TryFindItem(curPos.Address, out var item)) + return moved; + int iItem = imageMap.Items.IndexOfKey(item.Address); + for (;;) + { + Debug.Assert(item != null); + if (item is ImageMapBlock bi) + { + var instrs = instructions[bi]; + int i = FindIndexOfInstructionAddress( + instrs, curPos.Address); + Debug.Assert(i >= 0, "TryFindItem said this item contains the address."); + int iNew = i + offset; + if (0 <= iNew && iNew < instrs.Length) + { + moved += offset; + curPos = Pos(instrs[iNew].Address); + return moved; + } + // Fell off the end. + + if (offset > 0) + { + moved += instrs.Length - i; + offset -= instrs.Length - i; + } + else + { + moved -= i; + offset += i; + } + } + else + { + // Determine current line # within memory block + + int i = FindIndexOfMemoryAddress(item, curPos.Address); + int iEnd = CountBlockLines(item); + Debug.Assert(i >= 0, "Should have been inside item"); + int iNew = i + offset; + if (0 <= iNew && iNew < iEnd) + { + moved += offset; + curPos = Pos(GetAddressOfLine(item, iNew)); + return moved; + } + // Fall of the end + if (offset > 0) + { + moved += iEnd - i; + offset -= iEnd - i; + } + else + { + moved -= i; + offset += i; + } + } + + // Since we fall off the current map item, + // move to next image map item. + ++iItem; + if (iItem >= imageMap.Items.Count) + { + // At the end of image, no need for SanitizeAddress + curPos = (ModelPosition)this.EndPosition; + return moved; + } + else + { + // At the start of an item, no need for SanitizeAddress + item = imageMap.Items.Values[iItem]; + curPos = Pos(item.Address); + } + } + } + throw new NotImplementedException("Moving backwards not implemented yet."); + } + + /// + /// Given an address, attempts to make sure that it points to a valid + /// position in the address space or to the EOF + /// + /// + /// + private ModelPosition SanitizePosition(ModelPosition position) + { + if (imageMap.TryFindItem(position.Address, out var item)) + { + if (item.IsInRange(position.Address)) + { + // Safely inside an item. + return position; + } + // If we're positioned at the end of the item + // advance to the start of the next item if possible. + int iItem = imageMap.Items.Keys.IndexOf(item.Address) + 1; + if (iItem >= imageMap.Items.Count) + { + return this.endPos; + } + return Pos(imageMap.Items.Keys[iItem]); + } + // We're outside the range of all items, so peg the position + // at either the beginning or the end. + if (position.Address < imageMap.BaseAddress) + { + return Pos(imageMap.BaseAddress); + } + return endPos; + } + + private Address GetAddressOfLine(ImageMapItem item, int i) + { + if (i == 0) + return item.Address; + else + return Align(item.Address + i * BytesPerLine, BytesPerLine); + } + + /// + /// Find the index of the address within the item. + /// + /// + /// + /// + private int FindIndexOfMemoryAddress(ImageMapItem item, Address addr) + { + var addrStart = Align(item.Address, BytesPerLine); + long idx = (addr - addrStart) / BytesPerLine; + return (int) idx; + } + + public void SetPositionAsFraction(int numer, int denom) + { + if (denom <= 0) + throw new ArgumentOutOfRangeException("denom", "Denominator must be larger than 0."); +#if SIMPLE + // This is PTomin's simpler implementation of SetPositionAsFraction + // Notice that just like the original implementation, it is O(n) where + // n is the number of items in the image map. Consider measuring + // performance on really large image maps (1,000,000 items or more) + // to see if the brittle code in the #else branch makes any difference + // and if not, keep the SIMPLE implementation. + + long total = LineCount; + long iPos = (numer * total) / denom; + + MoveToLine(StartPosition, (int)iPos); +#else + if (numer <= 0) + { + curPos = (ModelPosition)StartPosition; + return; + } + else if (numer >= denom) + { + curPos = (ModelPosition)EndPosition; + return; + } + + var targetLine = (int)(((long)numer * LineCount) / denom); + int curLine = 0; + foreach (var item in imageMap.Items.Values) + { + int size; + if (item is ImageMapBlock bi) + { + size = CountDisassembledLines(bi); + if (curLine + size > targetLine) + { + this.curPos = Pos(instructions[bi][targetLine - curLine].Address); + return; + } + } + else + { + size = CountMemoryLines(item); + if (curLine + size > targetLine) + { + this.curPos = Pos(GetAddressOfLine(item, targetLine - curLine)); + return; + } + } + curLine += size; + } + curPos = (ModelPosition)EndPosition; +#endif + } + + public int CountLines(object startPos, object endPos) + { + var oldPos = CurrentPosition; + + MoveToLine(startPos, 0); + + int numLines = 0; + while (ComparePositions(CurrentPosition, endPos) < 0) + MoveToLine(startPos, ++numLines); + + MoveToLine(oldPos, 0); + + return numLines; + } + + private static ModelPosition Pos(Address addr, int offset = 0) + { + return new ModelPosition(addr, offset); + } + + public static object Position(Address addr, int offset) + { + return Pos(addr, offset); + } + + public static Address PositionAddress(object position) + { + return ((ModelPosition)position).Address; + } + + private static string[] Lines(string s) + { + return s.Split( + new string[] { Environment.NewLine }, + StringSplitOptions.None); + } + + private class ModelPosition : IComparable + { + public readonly Address Address; + public readonly int Offset; + + public ModelPosition(Address addr, int offset) + { + this.Address = addr; + this.Offset = offset; + } + + public int CompareTo(ModelPosition? that) + { + if (that is null) + return 1; + var cmp = this.Address.CompareTo(that.Address); + if (cmp != 0) + return cmp; + return this.Offset.CompareTo(that.Offset); + } + + public override string ToString() + { + return $"{Address}({Offset})"; + } + } + + public class DataItemNode + { + public Address StartAddress { get; internal set; } + public Address EndAddress { get; internal set; } + public Procedure? Proc { get; private set; } + public int NumLines { get; internal set; } + public TextModelNode ModelNode { get; set; } + public DataItemNode(Procedure? proc, int numLines) { this.Proc = proc; this.NumLines = numLines; } + } + + public Collection GetDataItemNodes() + { + var nodes = new Collection(); + Procedure? curProc; + DataItemNode? curNode = null; + + foreach (var item in imageMap.Items.Values) + { + int numLines; + var startAddr = item.Address; + var endAddr = item.Address + item.Size; + if (item is ImageMapBlock bi) + { + numLines = CountDisassembledLines(bi); + curProc = bi.Block?.Procedure; + } + else + { + numLines = CountMemoryLines(item); + curProc = null; + } + + if (curNode == null || curNode.Proc != curProc || curProc == null) + { + curNode = new DataItemNode(curProc, numLines) + { + StartAddress = startAddr, + EndAddress = endAddr + }; + nodes.Add(curNode); + } + else + { + curNode.NumLines += numLines; + curNode.EndAddress = endAddr; + } + } + + return nodes; + } + } +} diff --git a/src/UserInterfaces/WindowsForms/Controls/MixedCodeDataModel.cs b/src/UserInterfaces/WindowsForms/Controls/MixedCodeDataModel.cs index 34609b5387..6c54953738 100644 --- a/src/UserInterfaces/WindowsForms/Controls/MixedCodeDataModel.cs +++ b/src/UserInterfaces/WindowsForms/Controls/MixedCodeDataModel.cs @@ -19,552 +19,110 @@ #endregion using Reko.Core; -using Reko.Core.Collections; using Reko.Core.Machine; -using Reko.Gui; using Reko.Gui.Services; using Reko.Gui.TextViewing; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; +using System.Drawing; namespace Reko.UserInterfaces.WindowsForms.Controls { - /// - /// Provides a text model that mixes code and data. - /// - public partial class MixedCodeDataModel : ITextViewModel + public class MixedCodeDataModel : AbstractMixedCodeDataModel { - const int BytesPerLine = 16; - - private readonly Program program; - private readonly ImageMap imageMap; - private readonly ISelectedAddressService selSvc; - private ModelPosition curPos; - private readonly ModelPosition endPos; - private readonly Dictionary instructions; - private readonly IDictionary comments; - - public MixedCodeDataModel(Program program, ImageMap imageMap, ISelectedAddressService selSvc) + public MixedCodeDataModel( + Program program, + ImageMap imageMap, + ISelectedAddressService selSvc) + : base(program,imageMap , selSvc) { - this.program = program; - this.imageMap = imageMap; - this.selSvc = selSvc; - - var firstSeg = program.SegmentMap.Segments.Values.FirstOrDefault(); - if (firstSeg is null) - { - this.curPos = Pos(imageMap.BaseAddress); - this.StartPosition = Pos(imageMap.BaseAddress); - this.endPos = Pos(imageMap.BaseAddress); - this.LineCount = 0; - } - else - { - var lastSeg = program.SegmentMap.Segments.Values.Last(); - this.curPos = Pos(firstSeg.Address); - this.StartPosition = Pos(firstSeg.Address); - this.endPos = Pos(lastSeg.EndAddress); - this.instructions = this.CollectInstructions(); - this.LineCount = CountLines(); - } - this.comments = program.User.Annotations.ToSortedList( - a => a.Address, - a => Lines(a.Text)); } public MixedCodeDataModel(MixedCodeDataModel that) + : base(that) { - this.program = that.program; - this.imageMap = that.imageMap; - this.curPos = that.curPos; - this.StartPosition = that.StartPosition; - this.endPos = that.endPos; - this.instructions = that.instructions; - this.LineCount = that.LineCount; - this.comments = that.comments; } - public object CurrentPosition { get { return curPos; } } - public object StartPosition { get; private set; } - public object EndPosition { get { return endPos; } } - - public int LineCount { get; private set; } - - public MixedCodeDataModel Clone() + public override AbstractMixedCodeDataModel Clone() { return new MixedCodeDataModel(this); } - private int CountLines() + protected override LineSpan RenderAssemblerLine(object position, Program program, IProcessorArchitecture arch, MachineInstruction instr, MachineInstructionRendererOptions options) { - int sum = 0; - foreach (var item in imageMap.Items.Values) - { - sum += CountBlockLines(item); - } - return sum; + return DisassemblyTextModel.RenderAsmLine(position, program, arch, instr, options); } - private int CountBlockLines(ImageMapItem item) + protected override ITextSpan CreateAddressSpan(string formattedAddress, Address addr, string style) { - if (item is ImageMapBlock bi) - { - return CountDisassembledLines(bi); - } - else - { - return CountMemoryLines(item); - } - } - - /// - /// Count the number of lines a memory area subtends. - /// - /// - /// We align memory spans on 16-byte boundaries (//$REVIEW for now, - /// this should be user-adjustable) so if we have a memory span - /// straddling such a boundary, we have to account for it. E.g. the - /// span [01FC-0201] should be rendered: - /// - /// 01FC 0C 0D 0F .... - /// 0200 00 01 .. - /// - /// and therefore requires 2 lines even though the number of bytes is - /// less than 16. - /// - /// - /// - private int CountMemoryLines(ImageMapItem item) - { - if (item.Size == 0) - return 0; //$TODO: this shouldn't ever happen! - var linStart = item.Address.ToLinear(); - var linEnd = linStart + item.Size; - linStart = Align(linStart, BytesPerLine); - linEnd = Align(linEnd + (BytesPerLine - 1), BytesPerLine); - return (int)(linEnd - linStart) / BytesPerLine; + return new AddressSpan(formattedAddress, addr, style); } - private int CountDisassembledLines(ImageMapBlock bi) + protected override ITextSpan CreateMemoryTextSpan(string text, string style) { - return instructions[bi].Length; - //if (!instructions.TryGetValue(bi, out var instrs)) - // return 0; - //return instrs.Length; + return new MemoryTextSpan(text, style); } - private static ulong Align(ulong ul, uint alignment) + protected override ITextSpan CreateMemoryTextSpan(Address addr, string text, string style) { - return alignment * (ul / alignment); + return new MemoryTextSpan(addr, text, style); } - private static Address Align(Address addr, uint alignment) - { - var lin = addr.ToLinear(); - var linAl = Align(lin, alignment); - return addr - (int)(lin - linAl); - } /// - /// Preemptively collect the machine code instructions - /// in all image map blocks. + /// An segment of memory /// - private Dictionary CollectInstructions() + public class MemoryTextSpan : TextSpan { - var instructions = new Dictionary(); - foreach (var bi in imageMap.Items.Values.OfType() - .ToList()) - { - var instrs = new List(); - if (bi.Size > 0 && bi.Block.Procedure != null) - { - var addrStart = bi.Address; - var addrEnd = bi.Address + bi.Size; - var arch = bi.Block.Procedure.Architecture; - var dasm = program.CreateDisassembler(arch, addrStart).GetEnumerator(); - while (dasm.MoveNext() && dasm.Current.Address < addrEnd) - { - instrs.Add(dasm.Current); - } - } - instructions.Add(bi, instrs.ToArray()); - } + private readonly string text; - return instructions; - } + public Address Address { get; private set; } - public int ComparePositions(object a, object b) - { - return ((ModelPosition)a).CompareTo((ModelPosition)b); - } - - /// - /// Returns the (approximate) position. This doesn't have to be - /// 100% precise, but it shouldn't be insanely wrong either. - /// - /// - public (int, int) GetPositionAsFraction() - { - long numer = 0; - foreach (var item in imageMap.Items.Values) + public MemoryTextSpan(string text, string style) { - if (item.Address <= curPos.Address) - { - if (item.IsInRange(curPos.Address)) - { - numer += GetLineOffset(item, curPos.Address); - break; - } - numer += CountBlockLines(item); - } + this.text = text; + base.Style = style; } - long denom = LineCount; - while (denom >= 0x80000000) - { - numer >>= 1; - denom >>= 1; - } - return ((int)numer, (int)denom); - } - private int GetLineOffset(ImageMapItem item, Address addr) - { - if (item is ImageMapBlock bi) - { - int i = 0; - while (i < instructions[bi].Length) - { - if (instructions[bi][i].Address >= addr) - { - break; - } - ++i; - } - return i; - } - else + public MemoryTextSpan(Address address, string text, string style) : this(text, style) { - return (int)((Align(addr.ToLinear(), BytesPerLine) - - Align(item.Address.ToLinear(), BytesPerLine)) / - BytesPerLine); + this.Tag = this; + this.Address = address; } - } - public int MoveToLine(object position, int offset) - { - if (position is null) - throw new ArgumentNullException(nameof(position)); - curPos = SanitizePosition((ModelPosition)position); - if (offset == 0) - return 0; - int moved = 0; - if (offset > 0) + public override string GetText() { - - if (!imageMap.TryFindItem(curPos.Address, out var item)) - return moved; - int iItem = imageMap.Items.IndexOfKey(item.Address); - for (;;) - { - Debug.Assert(item != null); - if (item is ImageMapBlock bi) - { - var instrs = instructions[bi]; - int i = FindIndexOfInstructionAddress( - instrs, curPos.Address); - Debug.Assert(i >= 0, "TryFindItem said this item contains the address."); - int iNew = i + offset; - if (0 <= iNew && iNew < instrs.Length) - { - moved += offset; - curPos = Pos(instrs[iNew].Address); - return moved; - } - // Fell off the end. - - if (offset > 0) - { - moved += instrs.Length - i; - offset -= instrs.Length - i; - } - else - { - moved -= i; - offset += i; - } - } - else - { - // Determine current line # within memory block - - int i = FindIndexOfMemoryAddress(item, curPos.Address); - int iEnd = CountBlockLines(item); - Debug.Assert(i >= 0, "Should have been inside item"); - int iNew = i + offset; - if (0 <= iNew && iNew < iEnd) - { - moved += offset; - curPos = Pos(GetAddressOfLine(item, iNew)); - return moved; - } - // Fall of the end - if (offset > 0) - { - moved += iEnd - i; - offset -= iEnd - i; - } - else - { - moved -= i; - offset += i; - } - } - - // Since we fall off the current map item, - // move to next image map item. - ++iItem; - if (iItem >= imageMap.Items.Count) - { - // At the end of image, no need for SanitizeAddress - curPos = (ModelPosition)this.EndPosition; - return moved; - } - else - { - // At the start of an item, no need for SanitizeAddress - item = imageMap.Items.Values[iItem]; - curPos = Pos(item.Address); - } - } + return text; } - throw new NotImplementedException("Moving backwards not implemented yet."); - } - /// - /// Given an address, attempts to make sure that it points to a valid - /// position in the address space or to the EOF - /// - /// - /// - private ModelPosition SanitizePosition(ModelPosition position) - { - if (imageMap.TryFindItem(position.Address, out var item)) - { - if (item.IsInRange(position.Address)) - { - // Safely inside an item. - return position; - } - // If we're positioned at the end of the item - // advance to the start of the next item if possible. - int iItem = imageMap.Items.Keys.IndexOf(item.Address) + 1; - if (iItem >= imageMap.Items.Count) - { - return this.endPos; - } - return Pos(imageMap.Items.Keys[iItem]); - } - // We're outside the range of all items, so peg the position - // at either the beginning or the end. - if (position.Address < imageMap.BaseAddress) + public override SizeF GetSize(string text, Font font, Graphics g) { - return Pos(imageMap.BaseAddress); + SizeF sz = base.GetSize(text, font, g); + return sz; } - return endPos; - } - - private Address GetAddressOfLine(ImageMapItem item, int i) - { - if (i == 0) - return item.Address; - else - return Align(item.Address + i * BytesPerLine, BytesPerLine); } /// - /// Find the index of the address within the item. + /// An inert text span is not clickable nor has a context menu. /// - /// - /// - /// - private int FindIndexOfMemoryAddress(ImageMapItem item, Address addr) - { - var addrStart = Align(item.Address, BytesPerLine); - long idx = (addr - addrStart) / BytesPerLine; - return (int) idx; - } - - public void SetPositionAsFraction(int numer, int denom) - { - if (denom <= 0) - throw new ArgumentOutOfRangeException("denom", "Denominator must be larger than 0."); -#if SIMPLE - // This is PTomin's simpler implementation of SetPositionAsFraction - // Notice that just like the original implementation, it is O(n) where - // n is the number of items in the image map. Consider measuring - // performance on really large image maps (1,000,000 items or more) - // to see if the brittle code in the #else branch makes any difference - // and if not, keep the SIMPLE implementation. - - long total = LineCount; - long iPos = (numer * total) / denom; - - MoveToLine(StartPosition, (int)iPos); -#else - if (numer <= 0) - { - curPos = (ModelPosition)StartPosition; - return; - } - else if (numer >= denom) - { - curPos = (ModelPosition)EndPosition; - return; - } - - var targetLine = (int)(((long)numer * LineCount) / denom); - int curLine = 0; - foreach (var item in imageMap.Items.Values) - { - int size; - if (item is ImageMapBlock bi) - { - size = CountDisassembledLines(bi); - if (curLine + size > targetLine) - { - this.curPos = Pos(instructions[bi][targetLine - curLine].Address); - return; - } - } - else - { - size = CountMemoryLines(item); - if (curLine + size > targetLine) - { - this.curPos = Pos(GetAddressOfLine(item, targetLine - curLine)); - return; - } - } - curLine += size; - } - curPos = (ModelPosition)EndPosition; -#endif - } - - public int CountLines(object startPos, object endPos) - { - var oldPos = CurrentPosition; - - MoveToLine(startPos, 0); - - int numLines = 0; - while (ComparePositions(CurrentPosition, endPos) < 0) - MoveToLine(startPos, ++numLines); - - MoveToLine(oldPos, 0); - - return numLines; - } - - private static ModelPosition Pos(Address addr, int offset = 0) - { - return new ModelPosition(addr, offset); - } - - public static object Position(Address addr, int offset) - { - return Pos(addr, offset); - } - - public static Address PositionAddress(object position) - { - return ((ModelPosition)position).Address; - } - - private static string[] Lines(string s) - { - return s.Split( - new string[] { Environment.NewLine }, - StringSplitOptions.None); - } - - private class ModelPosition : IComparable + public class InertTextSpan : TextSpan { - public readonly Address Address; - public readonly int Offset; - - public ModelPosition(Address addr, int offset) - { - this.Address = addr; - this.Offset = offset; - } + private readonly string text; - public int CompareTo(ModelPosition that) + public InertTextSpan(string text, string style) { - var cmp = this.Address.CompareTo(that.Address); - if (cmp != 0) - return cmp; - return this.Offset.CompareTo(that.Offset); + this.text = text; + base.Style = style; } - public override string ToString() + public override string GetText() { - return $"{Address}({Offset})"; + return text; } - } - - public class DataItemNode - { - public Address StartAddress { get; internal set; } - public Address EndAddress { get; internal set; } - public Procedure Proc { get; private set; } - public int NumLines { get; internal set; } - public TextModelNode ModelNode { get; internal set; } - public DataItemNode(Procedure proc, int numLines) { this.Proc = proc; this.NumLines = numLines; } - } - - public Collection GetDataItemNodes() - { - var nodes = new Collection(); - Procedure curProc; - DataItemNode curNode = null; - foreach (var item in imageMap.Items.Values) + public override SizeF GetSize(string text, Font font, Graphics g) { - int numLines; - var startAddr = item.Address; - var endAddr = item.Address + item.Size; - if (item is ImageMapBlock bi) - { - numLines = CountDisassembledLines(bi); - curProc = bi.Block.Procedure; - } - else - { - numLines = CountMemoryLines(item); - curProc = null; - } - - if (curNode == null || curNode.Proc != curProc || curProc == null) - { - curNode = new DataItemNode(curProc, numLines) - { - StartAddress = startAddr, - EndAddress = endAddr - }; - nodes.Add(curNode); - } - else - { - curNode.NumLines += numLines; - curNode.EndAddress = endAddr; - } + SizeF sz = base.GetSize(text, font, g); + return sz; } - - return nodes; } } } diff --git a/src/UserInterfaces/WindowsForms/Controls/ProcedureCodeModel.cs b/src/UserInterfaces/WindowsForms/Controls/ProcedureCodeModel.cs index e53bd00282..96267b0eaa 100644 --- a/src/UserInterfaces/WindowsForms/Controls/ProcedureCodeModel.cs +++ b/src/UserInterfaces/WindowsForms/Controls/ProcedureCodeModel.cs @@ -25,7 +25,7 @@ namespace Reko.UserInterfaces.WindowsForms.Controls { /// - /// Provides a text model that use to show code of procedure. + /// Provides a text model that use to show code of a procedure. /// public class ProcedureCodeModel : AbstractProcedureCodeModel {