diff --git a/src/MIDebugEngine/AD7.Impl/AD7Disassembly.cs b/src/MIDebugEngine/AD7.Impl/AD7Disassembly.cs index b699a49df..cef41e69e 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Disassembly.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Disassembly.cs @@ -14,6 +14,10 @@ internal class AD7DisassemblyStream : IDebugDisassemblyStream2 private AD7Engine _engine; private ulong _addr; private enum_DISASSEMBLY_STREAM_SCOPE _scope; + private string _pLastDocument; + private uint _dwLastSourceLine; + private bool _lastSourceInfoStale; + private bool _skipNextInstruction; internal AD7DisassemblyStream(AD7Engine engine, enum_DISASSEMBLY_STREAM_SCOPE scope, IDebugCodeContext2 pCodeContext) { @@ -21,6 +25,9 @@ internal AD7DisassemblyStream(AD7Engine engine, enum_DISASSEMBLY_STREAM_SCOPE sc _scope = scope; AD7MemoryAddress addr = pCodeContext as AD7MemoryAddress; _addr = addr.Address; + _pLastDocument = null; + _dwLastSourceLine = 0; + _lastSourceInfoStale = true; } #region IDebugDisassemblyStream2 Members @@ -89,11 +96,22 @@ private DisassemblyData FetchBadInstruction(enum_DISASSEMBLY_STREAM_FIELDS dwFie dis.dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_OPCODE; dis.bstrOpcode = "??"; } + return dis; } public int Read(uint dwInstructions, enum_DISASSEMBLY_STREAM_FIELDS dwFields, out uint pdwInstructionsRead, DisassemblyData[] prgDisassembly) { + if (_lastSourceInfoStale && !_skipNextInstruction) + { + SeekBack(1); + } + + if (_skipNextInstruction) + { + dwInstructions += 1; + } + uint iOp = 0; IEnumerable instructions = null; @@ -106,8 +124,17 @@ public int Read(uint dwInstructions, enum_DISASSEMBLY_STREAM_FIELDS dwFields, ou // bad address range, return '??' for (iOp = 0; iOp < dwInstructions; _addr++, ++iOp) { + if (_skipNextInstruction) + { + _addr++; + dwInstructions--; + _skipNextInstruction = false; + } prgDisassembly[iOp] = FetchBadInstruction(dwFields); } + _lastSourceInfoStale = false; + _dwLastSourceLine = 0; + _pLastDocument = null; pdwInstructionsRead = iOp; return Constants.S_OK; } @@ -118,52 +145,146 @@ public int Read(uint dwInstructions, enum_DISASSEMBLY_STREAM_FIELDS dwFields, ou prgDisassembly[iOp] = FetchBadInstruction(dwFields); } - foreach (DisasmInstruction instruction in instructions) + using (IEnumerator instructionEnumerator = instructions.GetEnumerator()) { - if (iOp >= dwInstructions) + int idx = 0; + if (_skipNextInstruction) { - break; - } - _addr = instruction.Addr; + _skipNextInstruction = false; - if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_ADDRESS) != 0) - { - prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_ADDRESS; - prgDisassembly[iOp].bstrAddress = instruction.AddressString; - } + instructionEnumerator.MoveNext(); + DisasmInstruction instruction = instructionEnumerator.Current; - if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODELOCATIONID) != 0) - { - prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODELOCATIONID; - prgDisassembly[iOp].uCodeLocationId = instruction.Addr; + _dwLastSourceLine = instruction.Line != 0 ? instruction.Line - 1 : 0; + _pLastDocument = instruction.File; + + dwInstructions--; + idx++; } - if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_SYMBOL) != 0) + for (; idx < dwInstructions + 1 && iOp < dwInstructions; idx++) { - if (instruction.Offset == 0) + instructionEnumerator.MoveNext(); + DisasmInstruction instruction = instructionEnumerator.Current; + + _addr = instruction.Addr; + + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_ADDRESS) != 0) { - prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_SYMBOL; - prgDisassembly[iOp].bstrSymbol = instruction.Symbol ?? string.Empty; + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_ADDRESS; + prgDisassembly[iOp].bstrAddress = instruction.AddressString; } - } - if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_OPCODE) != 0) - { - prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_OPCODE; - prgDisassembly[iOp].bstrOpcode = instruction.Opcode; - } + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODELOCATIONID) != 0) + { + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODELOCATIONID; + prgDisassembly[iOp].uCodeLocationId = instruction.Addr; + } - if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODEBYTES) != 0) - { - if (!string.IsNullOrWhiteSpace(instruction.CodeBytes)) + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_SYMBOL) != 0) { - prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODEBYTES; - prgDisassembly[iOp].bstrCodeBytes = instruction.CodeBytes; + if (instruction.Offset == 0) + { + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_SYMBOL; + prgDisassembly[iOp].bstrSymbol = instruction.Symbol ?? string.Empty; + } } - } - iOp++; - }; + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_OPCODE) != 0) + { + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_OPCODE; + prgDisassembly[iOp].bstrOpcode = instruction.Opcode; + } + + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODEBYTES) != 0) + { + if (!string.IsNullOrWhiteSpace(instruction.CodeBytes)) + { + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_CODEBYTES; + prgDisassembly[iOp].bstrCodeBytes = instruction.CodeBytes; + } + } + + + string currentFile = instruction.File; + uint currentLine = instruction.Line - 1; + bool isNewDocument = string.IsNullOrEmpty(_pLastDocument) || !_pLastDocument.Equals(currentFile, StringComparison.Ordinal); + bool isNewLine = currentLine != _dwLastSourceLine; + + /* GDB will return a lines of sources not in sequential order. + * We ignore lines with lower line numbers and have it captured in a different group of source. + * + * Example MI Response: + * src_and_asm_line={line="22",file="main.cpp",fullname="/home/waan/cpp/main.cpp",line_asm_insn=[ + * {address="0x00007fc4b52b54c5",func-name="main(int, char**)",offset="124",opcodes="48 8d 95 40 ff ff ff",inst="lea -0xc0(%rbp),%rdx" } + * ] + * },src_and_asm_line={line="21",file="main.cpp",fullname="/home/waan/cpp/main.cpp",line_asm_insn=[ + * {address="0x00007fc4b52b54df",func-name="main(int, char**)",offset="150",opcodes="c7 85 48 ff ff ff 02 00 00 00",inst="movl $0x2,-0xb8(%rbp)"} + * ] + * },src_and_asm_line={line="22",file="main.cpp",fullname="/home/waan/cpp/main.cpp",line_asm_insn=[ + * {address="0x00007fc4b52b54e9",func-name="main(int, char**)",offset="160",opcodes="48 8d 85 48 ff ff ff",inst="lea -0xb8(%rbp),%rax"} + * } + */ + + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION) != 0 && currentLine != 0 && currentLine >= _dwLastSourceLine) + { + // If we have a new line and the current line is greater than the previously seen source line. + // Try to grab the last seen source line + 1 and show a group of source code. + // Else, just show the single line. + uint startLine = isNewLine && currentLine > _dwLastSourceLine ? _dwLastSourceLine + 1 : currentLine; + + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION; + prgDisassembly[iOp].posBeg = new TEXT_POSITION() + { + dwLine = startLine, + dwColumn = 0 + }; + prgDisassembly[iOp].posEnd = new TEXT_POSITION() + { + dwLine = currentLine, + dwColumn = 0 + }; + + // Update last seen source line. + _dwLastSourceLine = currentLine; + } + + // Show source if we have line and file information and if there is a new line. + if (currentLine != 0 && currentFile != null && isNewLine) + { + prgDisassembly[iOp].dwFlags |= enum_DISASSEMBLY_FLAGS.DF_HASSOURCE; + } + + if (!string.IsNullOrWhiteSpace(currentFile)) + { + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL) != 0) + { + // idx 0 is the previous instruction. The first requested instruction + // is at idx 1. + if (isNewDocument || idx == 1) + { + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL; + prgDisassembly[iOp].bstrDocumentUrl = string.Concat("file://", currentFile); + } + } + } + + if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_BYTEOFFSET) != 0) + { + prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_BYTEOFFSET; + // For the disassembly window, offset should be set to 0 to show source and non-zero to hide it. + prgDisassembly[iOp].dwByteOffset = isNewLine ? 0 : uint.MaxValue; + } + + if (isNewDocument) + { + prgDisassembly[iOp].dwFlags |= enum_DISASSEMBLY_FLAGS.DF_DOCUMENTCHANGE; + _pLastDocument = instruction.File; + } + + iOp++; + } + } if (iOp < dwInstructions) { @@ -208,6 +329,11 @@ private int SeekForward(long iInstructions) private int SeekBack(long iInstructions) { + if (_lastSourceInfoStale) + { + iInstructions += 1; + _skipNextInstruction = true; + } _engine.DebuggedProcess.WorkerThread.RunOperation(async () => { _addr = await _engine.DebuggedProcess.Disassembly.SeekBack(_addr, (int)iInstructions); @@ -217,6 +343,10 @@ private int SeekBack(long iInstructions) public int Seek(enum_SEEK_START dwSeekStart, IDebugCodeContext2 pCodeContext, ulong uCodeLocationId, long iInstructions) { + _pLastDocument = null; + _lastSourceInfoStale = true; + _dwLastSourceLine = 0; + if (dwSeekStart == enum_SEEK_START.SEEK_START_CODECONTEXT) { AD7MemoryAddress addr = pCodeContext as AD7MemoryAddress; diff --git a/src/MIDebugEngine/Engine.Impl/Disassembly.cs b/src/MIDebugEngine/Engine.Impl/Disassembly.cs index eb2d07993..5d5215fb9 100644 --- a/src/MIDebugEngine/Engine.Impl/Disassembly.cs +++ b/src/MIDebugEngine/Engine.Impl/Disassembly.cs @@ -305,9 +305,79 @@ private void DeleteRangeFromCache(DisassemblyBlock block) } } + private class DisasmAddressRange + { + public readonly string Symbol; + public ulong StartAddress; + public ulong EndAddress; + private readonly Dictionary _AddressToInstruction; + + public DisasmAddressRange(DisasmInstruction instruction) + { + Symbol = instruction.Symbol; + StartAddress = instruction.Addr; + EndAddress = instruction.Addr + 1; + _AddressToInstruction = new Dictionary() + { + {instruction.Addr, instruction} + }; + } + + public void UpdateEndAddress(DisasmInstruction instruction) + { + EndAddress = instruction.Addr + 1; + _AddressToInstruction.Add(instruction.Addr, instruction); + } + + public void MapSourceToInstructions(DebuggedProcess process, TupleValue[] src_and_asm_lines) + { + /* Example response + * [ + * { + * line="15", + * file="main.cpp", + * fullname="/home/cpp/main.cpp", + * line_asm_insn=[ + * { + * address="0x0000000008001485", + * func-name="main(int, char**)", + * offset="316", + * opcodes="83 bd 4c ff ff ff 00", + * inst="cmpl $0x0,-0xb4(%rbp)" + * }, + * { + * address="0x000000000800148c", + * func-name="main(int, char**)", + * offset="323", + * opcodes="75 07", + * inst="jne 0x8001495 " + * } + * ] + * } + * ] + */ + foreach (TupleValue src_and_asm_line in src_and_asm_lines) + { + uint line = src_and_asm_line.FindUint("line"); + string file = process.GetMappedFileFromTuple(src_and_asm_line); + ValueListValue line_asm_instructions = src_and_asm_line.Find("line_asm_insn"); + foreach (ResultValue line_asm_insn in line_asm_instructions.Content) + { + ulong address = line_asm_insn.FindAddr("address"); + _AddressToInstruction[address].File = file; + _AddressToInstruction[address].Line = line; + } + } + } + } + // this is inefficient so we try and grab everything in one gulp internal static async Task Disassemble(DebuggedProcess process, ulong startAddr, ulong endAddr) { + // Due to GDB not returning source information when requesting outside of the range of user code. + // We first get disassembly with opcodes, then map each Symbol to an address range and attempt to retrieve source information per Symbol. + + // Mode 2 - disassembly with raw opcodes string cmd = "-data-disassemble -s " + EngineUtils.AsAddr(startAddr, process.Is64BitArch) + " -e " + EngineUtils.AsAddr(endAddr, process.Is64BitArch) + " -- 2"; Results results = await process.CmdAsync(cmd, ResultClass.None); if (results.ResultClass != ResultClass.done) @@ -315,7 +385,65 @@ internal static async Task Disassemble(DebuggedProcess proc return null; } - return DecodeDisassemblyInstructions(results.Find("asm_insns").AsArray()); + DisasmInstruction[] instructions = DecodeDisassemblyInstructions(results.Find("asm_insns").AsArray()); + + if (instructions != null && instructions.Length != 0) + { + IList ranges = new List(); + // Map 'Symbol' (Function Name) to Address Range + DisasmAddressRange currentRange = new DisasmAddressRange(instructions[0]); + for (int i = 1; i < instructions.Length; i++) + { + if (currentRange.Symbol == instructions[i].Symbol) + { + currentRange.UpdateEndAddress(instructions[i]); + } + else + { + ranges.Add(currentRange); + + // Start new range + currentRange = new DisasmAddressRange(instructions[i]); + } + } + + // Add the last range + ranges.Add(currentRange); + + foreach (DisasmAddressRange dismAddressRange in ranges) + { + // Mode 5 - mixed source and disassembly with raw opcodes + cmd = "-data-disassemble -s " + EngineUtils.AsAddr(dismAddressRange.StartAddress, process.Is64BitArch) + " -e " + EngineUtils.AsAddr(dismAddressRange.EndAddress, process.Is64BitArch) + " -- 5"; + results = await process.CmdAsync(cmd, ResultClass.None); + if (results.ResultClass != ResultClass.done) + { + return null; + } + + /* Example response + * asm_insns=[ + * src_and_asm_line={ + * line="15", + * file="main.cpp", + * fullname="/home/cpp/main.cpp", + * line_asm_insn=[ ... ] + * } + * ] + */ + ResultListValue asm_insns = results.TryFind("asm_insns"); + if (asm_insns != null) + { + TupleValue[] values = asm_insns.FindAll("src_and_asm_line"); + if (values != null) + { + dismAddressRange.MapSourceToInstructions(process, values); + } + } + } + } + + + return instructions; } // this is inefficient so we try and grab everything in one gulp diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index f0b08d439..4934e9dca 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -2064,18 +2064,32 @@ protected override void HandleDisassembleRequestAsync(IRequestResponder instructions = runner.Disassemble(ip, 1); + + // Validate that we got one instructions. + Assert.Single(instructions); + + // Test Source Information for Disasembly + IDisassemblyInstruction dismInstr = instructions.First(); + Assert.Equal(33, dismInstr.Line); + Assert.NotNull(dismInstr.Location); + Assert.Contains(SinkHelper.Main, dismInstr.Location.path); + + this.Comment("Continue until end"); + runner.Expects.ExitedEvent() + .TerminatedEvent() + .AfterContinue(); + + runner.DisconnectAndVerify(); + } + } + #endregion } }