Skip to content

Commit 47c32b4

Browse files
committed
Add support for Disassembly Source for GDB
This PR adds Location and Line to Disassembly Response.
1 parent 7393b72 commit 47c32b4

File tree

4 files changed

+250
-1
lines changed

4 files changed

+250
-1
lines changed

src/MIDebugEngine/AD7.Impl/AD7Disassembly.cs

+36
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ private DisassemblyData FetchBadInstruction(enum_DISASSEMBLY_STREAM_FIELDS dwFie
8989
dis.dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_OPCODE;
9090
dis.bstrOpcode = "??";
9191
}
92+
93+
if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL) != 0)
94+
{
95+
dis.dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL;
96+
dis.bstrDocumentUrl = string.Empty;
97+
}
98+
99+
if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION) != 0)
100+
{
101+
dis.dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION;
102+
dis.posBeg = new TEXT_POSITION();
103+
dis.posEnd = new TEXT_POSITION();
104+
}
105+
92106
return dis;
93107
}
94108

@@ -162,6 +176,28 @@ public int Read(uint dwInstructions, enum_DISASSEMBLY_STREAM_FIELDS dwFields, ou
162176
}
163177
}
164178

179+
if (!string.IsNullOrWhiteSpace(instruction.File))
180+
{
181+
if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL) != 0)
182+
{
183+
prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_DOCUMENTURL;
184+
prgDisassembly[iOp].bstrDocumentUrl = instruction.File;
185+
}
186+
187+
if ((dwFields & enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION) != 0)
188+
{
189+
prgDisassembly[iOp].dwFields |= enum_DISASSEMBLY_STREAM_FIELDS.DSF_POSITION;
190+
prgDisassembly[iOp].posBeg = new TEXT_POSITION()
191+
{
192+
dwLine = instruction.Line
193+
};
194+
prgDisassembly[iOp].posEnd = new TEXT_POSITION()
195+
{
196+
dwLine = instruction.Line
197+
};
198+
}
199+
}
200+
165201
iOp++;
166202
};
167203

src/MIDebugEngine/Engine.Impl/Disassembly.cs

+137-1
Original file line numberDiff line numberDiff line change
@@ -305,17 +305,153 @@ private void DeleteRangeFromCache(DisassemblyBlock block)
305305
}
306306
}
307307

308+
private class DisasmAddressRange
309+
{
310+
public ulong StartAddress;
311+
public ulong EndAddress;
312+
Dictionary<ulong, DisasmInstruction> AddressToInstruction;
313+
314+
public DisasmAddressRange(DisasmInstruction instruction)
315+
{
316+
StartAddress = instruction.Addr;
317+
EndAddress = instruction.Addr;
318+
AddressToInstruction = new Dictionary<ulong, DisasmInstruction>()
319+
{
320+
{instruction.Addr, instruction}
321+
};
322+
}
323+
324+
public void UpdateEndAddress(DisasmInstruction instruction)
325+
{
326+
EndAddress = instruction.Addr;
327+
AddressToInstruction.Add(instruction.Addr, instruction);
328+
}
329+
330+
public void MapSourceToInstructions(DebuggedProcess process, TupleValue[] src_and_asm_lines)
331+
{
332+
/* Example response
333+
* [
334+
* {
335+
* line="15",
336+
* file="main.cpp",
337+
* fullname="/home/cpp/main.cpp",
338+
* line_asm_insn=[
339+
* {
340+
* address="0x0000000008001485",
341+
* func-name="main(int, char**)",
342+
* offset="316",
343+
* opcodes="83 bd 4c ff ff ff 00",
344+
* inst="cmpl $0x0,-0xb4(%rbp)"
345+
* },
346+
* {
347+
* address="0x000000000800148c",
348+
* func-name="main(int, char**)",
349+
* offset="323",
350+
* opcodes="75 07",
351+
* inst="jne 0x8001495 <main(int, char**)+332>"
352+
* }
353+
* ]
354+
* }
355+
* ]
356+
*/
357+
foreach (TupleValue src_and_asm_line in src_and_asm_lines)
358+
{
359+
uint line = src_and_asm_line.FindUint("line");
360+
string file = process.GetMappedFileFromTuple(src_and_asm_line);
361+
ValueListValue line_asm_instructions = src_and_asm_line.Find<ValueListValue>("line_asm_insn");
362+
foreach (ResultValue line_asm_insn in line_asm_instructions.Content)
363+
{
364+
ulong address = line_asm_insn.FindAddr("address");
365+
AddressToInstruction[address].File = file;
366+
AddressToInstruction[address].Line = line;
367+
}
368+
}
369+
}
370+
}
371+
308372
// this is inefficient so we try and grab everything in one gulp
309373
internal static async Task<DisasmInstruction[]> Disassemble(DebuggedProcess process, ulong startAddr, ulong endAddr)
310374
{
375+
// Due to GDB not returning source information when requesting outside of the range of user code.
376+
// We first get disassembly with opcodes, then map each Symbol to an address range and attempt to retrieve source information per Symbol.
377+
378+
// Mode 2 - disassembly with raw opcodes
311379
string cmd = "-data-disassemble -s " + EngineUtils.AsAddr(startAddr, process.Is64BitArch) + " -e " + EngineUtils.AsAddr(endAddr, process.Is64BitArch) + " -- 2";
312380
Results results = await process.CmdAsync(cmd, ResultClass.None);
313381
if (results.ResultClass != ResultClass.done)
314382
{
315383
return null;
316384
}
317385

318-
return DecodeDisassemblyInstructions(results.Find<ValueListValue>("asm_insns").AsArray<TupleValue>());
386+
DisasmInstruction[] instructions = DecodeDisassemblyInstructions(results.Find<ValueListValue>("asm_insns").AsArray<TupleValue>());
387+
388+
if (instructions != null && instructions.Length != 0)
389+
{
390+
// Map 'Symbol' (Function Name) to Address Range
391+
Dictionary<string, DisasmAddressRange> funcToAddressRange = new Dictionary<string, DisasmAddressRange>();
392+
for (int i = 0; i < instructions.Length; i++)
393+
{
394+
string functionName = instructions[i].Symbol;
395+
if (funcToAddressRange.ContainsKey(functionName))
396+
{
397+
// Update the end address with this current instruction.
398+
funcToAddressRange[functionName].UpdateEndAddress(instructions[i]);
399+
}
400+
else
401+
{
402+
// Insert new function to keep track of.
403+
funcToAddressRange.Add(functionName, new DisasmAddressRange(instructions[i]));
404+
}
405+
406+
// endAddr will not show up in instructions, so make the last instruction we get to get the range til the end.
407+
if (i == instructions.Length - 1)
408+
{
409+
funcToAddressRange[functionName].EndAddress = endAddr;
410+
}
411+
}
412+
413+
foreach (string functionName in funcToAddressRange.Keys)
414+
{
415+
DisasmAddressRange range = funcToAddressRange[functionName];
416+
417+
// Mode 5 - mixed source and disassembly with raw opcodes
418+
cmd = "-data-disassemble -s " + EngineUtils.AsAddr(range.StartAddress, process.Is64BitArch) + " -e " + EngineUtils.AsAddr(range.EndAddress, process.Is64BitArch) + " -- 5";
419+
results = await process.CmdAsync(cmd, ResultClass.None);
420+
if (results.ResultClass != ResultClass.done)
421+
{
422+
return null;
423+
}
424+
try
425+
{
426+
/* Example response
427+
* asm_insns=[
428+
* src_and_asm_line={
429+
* line="15",
430+
* file="main.cpp",
431+
* fullname="/home/cpp/main.cpp",
432+
* line_asm_insn=[ ... ]
433+
* }
434+
* ]
435+
*/
436+
ResultListValue asm_insns = results.Find<ResultListValue>("asm_insns");
437+
if (asm_insns != null)
438+
{
439+
TupleValue[] values = asm_insns.FindAll<TupleValue>("src_and_asm_line");
440+
if (values != null)
441+
{
442+
range.MapSourceToInstructions(process, values);
443+
}
444+
}
445+
}
446+
catch
447+
{
448+
// asm_insns can be ValueListValue if it does not contain source information.
449+
}
450+
}
451+
}
452+
453+
454+
return instructions;
319455
}
320456

321457
// this is inefficient so we try and grab everything in one gulp

src/OpenDebugAD7/AD7DebugSession.cs

+10
Original file line numberDiff line numberDiff line change
@@ -2076,6 +2076,16 @@ protected override void HandleDisassembleRequestAsync(IRequestResponder<Disassem
20762076
Instruction = data.bstrOpcode,
20772077
Symbol = data.bstrSymbol
20782078
};
2079+
2080+
if (!string.IsNullOrWhiteSpace(data.bstrDocumentUrl))
2081+
{
2082+
instruction.Location = new Source()
2083+
{
2084+
Path = data.bstrDocumentUrl
2085+
};
2086+
instruction.Line = (int)data.posBeg.dwLine;
2087+
}
2088+
20792089
response.Instructions.Add(instruction);
20802090
}
20812091
responder.SetResponse(response);

test/CppTests/Tests/MemoryTests.cs

+67
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ public void InstructionBreakpointsBasic(ITestSettings settings)
8989
// Validate that we got two instructions.
9090
Assert.Equal(2, instructions.Count());
9191

92+
// Test Source Information for Disasembly
93+
IDisassemblyInstruction dismInstr = instructions.First();
94+
Assert.Equal(33, dismInstr.Line);
95+
Assert.NotNull(dismInstr.Location);
96+
Assert.Contains(SinkHelper.Main, dismInstr.Location.path);
97+
9298
// Get the next instruction's address
9399
string nextIPAddress = instructions.Last().Address;
94100
Assert.False(string.IsNullOrEmpty(nextIPAddress));
@@ -122,6 +128,67 @@ public void InstructionBreakpointsBasic(ITestSettings settings)
122128
}
123129
}
124130

131+
[Theory]
132+
[DependsOnTest(nameof(CompileKitchenSinkForBreakpointTests))]
133+
[RequiresTestSettings]
134+
public void DisassemblySourceBasic(ITestSettings settings)
135+
{
136+
this.TestPurpose("Tests basic operation of instruction breakpoints");
137+
this.WriteSettings(settings);
138+
139+
IDebuggee debuggee = SinkHelper.Open(this, settings.CompilerSettings, DebuggeeMonikers.KitchenSink.Breakpoint);
140+
141+
using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings))
142+
{
143+
this.Comment("Configure launch");
144+
runner.Launch(settings.DebuggerSettings, debuggee, "-fCalling");
145+
146+
SourceBreakpoints mainBreakpoints = debuggee.Breakpoints(SinkHelper.Main, 33);
147+
148+
this.Comment("Set initial breakpoints");
149+
runner.SetBreakpoints(mainBreakpoints);
150+
151+
this.Comment("Launch and run until first breakpoint");
152+
runner.Expects.HitBreakpointEvent(SinkHelper.Main, 33)
153+
.AfterConfigurationDone();
154+
155+
string ip = string.Empty;
156+
157+
this.Comment("Inspect the stack and try evaluation.");
158+
using (IThreadInspector inspector = runner.GetThreadInspector())
159+
{
160+
this.Comment("Get the stack trace");
161+
IFrameInspector mainFrame = inspector.Stack.First();
162+
inspector.AssertStackFrameNames(true, "main.*");
163+
164+
this.WriteLine("Main frame: {0}", mainFrame);
165+
ip = mainFrame?.InstructionPointerReference;
166+
}
167+
168+
Assert.False(string.IsNullOrEmpty(ip));
169+
170+
// Send Disassemble Request to get the current instruction
171+
this.WriteLine("Disassemble to get current and next instruction.");
172+
IEnumerable<IDisassemblyInstruction> instructions = runner.Disassemble(ip, 1);
173+
174+
// Validate that we got one instructions.
175+
Assert.Single(instructions);
176+
177+
// Test Source Information for Disasembly
178+
IDisassemblyInstruction dismInstr = instructions.First();
179+
Assert.Equal(33, dismInstr.Line);
180+
Assert.NotNull(dismInstr.Location);
181+
Assert.Contains(SinkHelper.Main, dismInstr.Location.path);
182+
183+
this.Comment("Continue until end");
184+
runner.Expects.ExitedEvent()
185+
.TerminatedEvent()
186+
.AfterContinue();
187+
188+
runner.DisconnectAndVerify();
189+
}
190+
}
191+
125192
#endregion
126193
}
127194
}

0 commit comments

Comments
 (0)