Skip to content

Commit

Permalink
Add support for Disassembly Source for GDB
Browse files Browse the repository at this point in the history
This PR adds Location and Line to Disassembly Response.
  • Loading branch information
WardenGnaw committed Nov 9, 2021
1 parent 7393b72 commit 47c32b4
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 1 deletion.
36 changes: 36 additions & 0 deletions src/MIDebugEngine/AD7.Impl/AD7Disassembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ private DisassemblyData FetchBadInstruction(enum_DISASSEMBLY_STREAM_FIELDS dwFie
dis.dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_OPCODE;
dis.bstrOpcode = "??";
}

if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL) != 0)
{
dis.dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL;
dis.bstrDocumentUrl = string.Empty;
}

if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION) != 0)
{
dis.dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION;
dis.posBeg = new TEXT_POSITION();
dis.posEnd = new TEXT_POSITION();
}

return dis;
}

Expand Down Expand Up @@ -162,6 +176,28 @@ public int Read(uint dwInstructions, enum_DISASSEMBLY_STREAM_FIELDS dwFields, ou
}
}

if (!string.IsNullOrWhiteSpace(instruction.File))
{
if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL) != 0)
{
prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL;
prgDisassembly[iOp].bstrDocumentUrl = instruction.File;
}

if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION) != 0)
{
prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION;
prgDisassembly[iOp].posBeg = new TEXT_POSITION()
{
dwLine = instruction.Line
};
prgDisassembly[iOp].posEnd = new TEXT_POSITION()
{
dwLine = instruction.Line
};
}
}

iOp++;
};

Expand Down
138 changes: 137 additions & 1 deletion src/MIDebugEngine/Engine.Impl/Disassembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,17 +305,153 @@ private void DeleteRangeFromCache(DisassemblyBlock block)
}
}

private class DisasmAddressRange
{
public ulong StartAddress;
public ulong EndAddress;
Dictionary<ulong, DisasmInstruction> AddressToInstruction;

public DisasmAddressRange(DisasmInstruction instruction)
{
StartAddress = instruction.Addr;
EndAddress = instruction.Addr;
AddressToInstruction = new Dictionary<ulong, DisasmInstruction>()
{
{instruction.Addr, instruction}
};
}

public void UpdateEndAddress(DisasmInstruction instruction)
{
EndAddress = instruction.Addr;
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 <main(int, char**)+332>"
* }
* ]
* }
* ]
*/
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<ValueListValue>("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<DisasmInstruction[]> 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)
{
return null;
}

return DecodeDisassemblyInstructions(results.Find<ValueListValue>("asm_insns").AsArray<TupleValue>());
DisasmInstruction[] instructions = DecodeDisassemblyInstructions(results.Find<ValueListValue>("asm_insns").AsArray<TupleValue>());

if (instructions != null && instructions.Length != 0)
{
// Map 'Symbol' (Function Name) to Address Range
Dictionary<string, DisasmAddressRange> funcToAddressRange = new Dictionary<string, DisasmAddressRange>();
for (int i = 0; i < instructions.Length; i++)
{
string functionName = instructions[i].Symbol;
if (funcToAddressRange.ContainsKey(functionName))
{
// Update the end address with this current instruction.
funcToAddressRange[functionName].UpdateEndAddress(instructions[i]);
}
else
{
// Insert new function to keep track of.
funcToAddressRange.Add(functionName, new DisasmAddressRange(instructions[i]));
}

// endAddr will not show up in instructions, so make the last instruction we get to get the range til the end.
if (i == instructions.Length - 1)
{
funcToAddressRange[functionName].EndAddress = endAddr;
}
}

foreach (string functionName in funcToAddressRange.Keys)
{
DisasmAddressRange range = funcToAddressRange[functionName];

// Mode 5 - mixed source and disassembly with raw opcodes
cmd = "-data-disassemble -s " + EngineUtils.AsAddr(range.StartAddress, process.Is64BitArch) + " -e " + EngineUtils.AsAddr(range.EndAddress, process.Is64BitArch) + " -- 5";
results = await process.CmdAsync(cmd, ResultClass.None);
if (results.ResultClass != ResultClass.done)
{
return null;
}
try
{
/* 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.Find<ResultListValue>("asm_insns");
if (asm_insns != null)
{
TupleValue[] values = asm_insns.FindAll<TupleValue>("src_and_asm_line");
if (values != null)
{
range.MapSourceToInstructions(process, values);
}
}
}
catch
{
// asm_insns can be ValueListValue if it does not contain source information.
}
}
}


return instructions;
}

// this is inefficient so we try and grab everything in one gulp
Expand Down
10 changes: 10 additions & 0 deletions src/OpenDebugAD7/AD7DebugSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2076,6 +2076,16 @@ protected override void HandleDisassembleRequestAsync(IRequestResponder<Disassem
Instruction = data.bstrOpcode,
Symbol = data.bstrSymbol
};

if (!string.IsNullOrWhiteSpace(data.bstrDocumentUrl))
{
instruction.Location = new Source()
{
Path = data.bstrDocumentUrl
};
instruction.Line = (int)data.posBeg.dwLine;
}

response.Instructions.Add(instruction);
}
responder.SetResponse(response);
Expand Down
67 changes: 67 additions & 0 deletions test/CppTests/Tests/MemoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ public void InstructionBreakpointsBasic(ITestSettings settings)
// Validate that we got two instructions.
Assert.Equal(2, instructions.Count());

// 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);

// Get the next instruction's address
string nextIPAddress = instructions.Last().Address;
Assert.False(string.IsNullOrEmpty(nextIPAddress));
Expand Down Expand Up @@ -122,6 +128,67 @@ public void InstructionBreakpointsBasic(ITestSettings settings)
}
}

[Theory]
[DependsOnTest(nameof(CompileKitchenSinkForBreakpointTests))]
[RequiresTestSettings]
public void DisassemblySourceBasic(ITestSettings settings)
{
this.TestPurpose("Tests basic operation of instruction breakpoints");
this.WriteSettings(settings);

IDebuggee debuggee = SinkHelper.Open(this, settings.CompilerSettings, DebuggeeMonikers.KitchenSink.Breakpoint);

using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings))
{
this.Comment("Configure launch");
runner.Launch(settings.DebuggerSettings, debuggee, "-fCalling");

SourceBreakpoints mainBreakpoints = debuggee.Breakpoints(SinkHelper.Main, 33);

this.Comment("Set initial breakpoints");
runner.SetBreakpoints(mainBreakpoints);

this.Comment("Launch and run until first breakpoint");
runner.Expects.HitBreakpointEvent(SinkHelper.Main, 33)
.AfterConfigurationDone();

string ip = string.Empty;

this.Comment("Inspect the stack and try evaluation.");
using (IThreadInspector inspector = runner.GetThreadInspector())
{
this.Comment("Get the stack trace");
IFrameInspector mainFrame = inspector.Stack.First();
inspector.AssertStackFrameNames(true, "main.*");

this.WriteLine("Main frame: {0}", mainFrame);
ip = mainFrame?.InstructionPointerReference;
}

Assert.False(string.IsNullOrEmpty(ip));

// Send Disassemble Request to get the current instruction
this.WriteLine("Disassemble to get current and next instruction.");
IEnumerable<IDisassemblyInstruction> 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
}
}

0 comments on commit 47c32b4

Please sign in to comment.