Skip to content

Commit ca71dc1

Browse files
ashgtiDavidGoldman
authored andcommitted
[lldb-vscode] Adding support for the "disassemble" request.
Instead of creating psuedo source files for each stack frame this change adopts the new DAP “disassemble” request, allowing clients to inspect assembly instructions of files with debug info in addition to files without debug info. [[ https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble | spec ]] See attached screenshot of the disassembly view. {F28473848} Reviewed By: wallace Differential Revision: https://reviews.llvm.org/D156493
1 parent bae14bc commit ca71dc1

File tree

13 files changed

+354
-193
lines changed

13 files changed

+354
-193
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,19 @@ def continue_to_exit(self, exitCode=0):
257257
"exitCode == %i" % (exitCode),
258258
)
259259

260+
def disassemble(self, threadId=None, frameIndex=None):
261+
stackFrames = self.get_stackFrames(
262+
threadId=threadId, startFrame=frameIndex, levels=1
263+
)
264+
self.assertIsNotNone(stackFrames)
265+
memoryReference = stackFrames[0]["instructionPointerReference"]
266+
self.assertIsNotNone(memoryReference)
267+
268+
if memoryReference not in self.vscode.disassembled_instructions:
269+
self.vscode.request_disassemble(memoryReference=memoryReference)
270+
271+
return self.vscode.disassembled_instructions[memoryReference]
272+
260273
def attach(
261274
self,
262275
program=None,

lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def __init__(self, recv, send, init_commands, log_file=None):
135135
self.configuration_done_sent = False
136136
self.frame_scopes = {}
137137
self.init_commands = init_commands
138+
self.disassembled_instructions = {}
138139

139140
@classmethod
140141
def encode_content(cls, s):
@@ -427,7 +428,7 @@ def get_thread_id(self, threadIndex=0):
427428

428429
def get_stackFrame(self, frameIndex=0, threadId=None):
429430
"""Get a single "StackFrame" object from a "stackTrace" request and
430-
return the "StackFrame as a python dictionary, or None on failure
431+
return the "StackFrame" as a python dictionary, or None on failure
431432
"""
432433
if threadId is None:
433434
threadId = self.get_thread_id()
@@ -647,6 +648,22 @@ def request_disconnect(self, terminateDebuggee=None):
647648
"arguments": args_dict,
648649
}
649650
return self.send_recv(command_dict)
651+
652+
def request_disassemble(self, memoryReference, offset=-50, instructionCount=200, resolveSymbols=True):
653+
args_dict = {
654+
"memoryReference": memoryReference,
655+
"offset": offset,
656+
"instructionCount": instructionCount,
657+
"resolveSymbols": resolveSymbols
658+
}
659+
command_dict = {
660+
"command": "disassemble",
661+
"type": "request",
662+
"arguments": args_dict,
663+
}
664+
instructions = self.send_recv(command_dict)["body"]["instructions"]
665+
for inst in instructions:
666+
self.disassembled_instructions[inst["address"]] = inst
650667

651668
def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None):
652669
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)

lldb/test/API/tools/lldb-vscode/coreFile/TestVSCode_coreFile.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,25 @@ def test_core_file(self):
2525

2626
expected_frames = [
2727
{
28-
"column": 0,
2928
"id": 524288,
3029
"line": 4,
3130
"name": "bar",
3231
"source": {"name": "main.c", "path": "/home/labath/test/main.c"},
32+
"instructionPointerReference": "0x40011C",
3333
},
3434
{
35-
"column": 0,
3635
"id": 524289,
3736
"line": 10,
3837
"name": "foo",
3938
"source": {"name": "main.c", "path": "/home/labath/test/main.c"},
39+
"instructionPointerReference": "0x400142",
4040
},
4141
{
42-
"column": 0,
4342
"id": 524290,
4443
"line": 16,
4544
"name": "_start",
4645
"source": {"name": "main.c", "path": "/home/labath/test/main.c"},
46+
"instructionPointerReference": "0x40015F",
4747
},
4848
]
4949

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Test lldb-vscode disassemble request
3+
"""
4+
5+
6+
import vscode
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test import lldbutil
10+
import lldbvscode_testcase
11+
import os
12+
13+
14+
class TestVSCode_disassemble(lldbvscode_testcase.VSCodeTestCaseBase):
15+
@skipIfWindows
16+
@skipIfRemote
17+
def test_disassemble(self):
18+
"""
19+
Tests the 'disassemble' request.
20+
"""
21+
program = self.getBuildArtifact("a.out")
22+
self.build_and_launch(program)
23+
source = "main.c"
24+
self.source_path = os.path.join(os.getcwd(), source)
25+
self.set_source_breakpoints(
26+
source,
27+
[
28+
line_number(source, "// breakpoint 1"),
29+
],
30+
)
31+
self.continue_to_next_stop()
32+
33+
pc_assembly = self.disassemble(frameIndex=0)
34+
self.assertTrue("location" in pc_assembly, "Source location missing.")
35+
self.assertTrue("instruction" in pc_assembly, "Assembly instruction missing.")
36+
37+
# The calling frame (qsort) is coming from a system library, as a result
38+
# we should not have a source location.
39+
qsort_assembly = self.disassemble(frameIndex=1)
40+
self.assertFalse("location" in qsort_assembly, "Source location not expected.")
41+
self.assertTrue("instruction" in pc_assembly, "Assembly instruction missing.")
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <limits.h>
4+
5+
int compare_ints(const void* a, const void* b)
6+
{
7+
int arg1 = *(const int*)a;
8+
int arg2 = *(const int*)b;
9+
10+
// breakpoint 1
11+
12+
if (arg1 < arg2) return -1;
13+
if (arg1 > arg2) return 1;
14+
return 0;
15+
}
16+
17+
int main(void)
18+
{
19+
int ints[] = { -2, 99, 0, -743, 2, INT_MIN, 4 };
20+
int size = sizeof ints / sizeof *ints;
21+
22+
qsort(ints, size, sizeof(int), compare_ints);
23+
24+
for (int i = 0; i < size; i++) {
25+
printf("%d ", ints[i]);
26+
}
27+
28+
printf("\n");
29+
return 0;
30+
}

lldb/tools/lldb-vscode/JSONUtils.cpp

Lines changed: 36 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,9 @@ llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp,
342342
object.try_emplace("source", CreateSource(*request_path));
343343

344344
if (bp_addr.IsValid()) {
345+
std::string formatted_addr =
346+
"0x" + llvm::utohexstr(bp_addr.GetLoadAddress(g_vsc.target));
347+
object.try_emplace("instructionReference", formatted_addr);
345348
auto line_entry = bp_addr.GetLineEntry();
346349
const auto line = line_entry.GetLine();
347350
if (line != UINT32_MAX)
@@ -600,8 +603,8 @@ llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) {
600603
if (name)
601604
EmplaceSafeString(object, "name", name);
602605
char path[PATH_MAX] = "";
603-
file.GetPath(path, sizeof(path));
604-
if (path[0]) {
606+
if (file.GetPath(path, sizeof(path)) &&
607+
lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) {
605608
EmplaceSafeString(object, "path", std::string(path));
606609
}
607610
}
@@ -616,97 +619,14 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) {
616619
return llvm::json::Value(std::move(source));
617620
}
618621

619-
llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) {
620-
disasm_line = 0;
622+
std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) {
621623
auto line_entry = frame.GetLineEntry();
622624
// A line entry of 0 indicates the line is compiler generated i.e. no source
623-
// file so don't return early with the line entry.
625+
// file is associated with the frame.
624626
if (line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0)
625627
return CreateSource(line_entry);
626628

627-
llvm::json::Object object;
628-
const auto pc = frame.GetPC();
629-
630-
lldb::SBInstructionList insts;
631-
lldb::SBFunction function = frame.GetFunction();
632-
lldb::addr_t low_pc = LLDB_INVALID_ADDRESS;
633-
if (function.IsValid()) {
634-
low_pc = function.GetStartAddress().GetLoadAddress(g_vsc.target);
635-
auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
636-
if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
637-
// We have this disassembly cached already, return the existing
638-
// sourceReference
639-
object.try_emplace("sourceReference", addr_srcref->second);
640-
disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
641-
} else {
642-
insts = function.GetInstructions(g_vsc.target);
643-
}
644-
} else {
645-
lldb::SBSymbol symbol = frame.GetSymbol();
646-
if (symbol.IsValid()) {
647-
low_pc = symbol.GetStartAddress().GetLoadAddress(g_vsc.target);
648-
auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
649-
if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
650-
// We have this disassembly cached already, return the existing
651-
// sourceReference
652-
object.try_emplace("sourceReference", addr_srcref->second);
653-
disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
654-
} else {
655-
insts = symbol.GetInstructions(g_vsc.target);
656-
}
657-
}
658-
}
659-
const auto num_insts = insts.GetSize();
660-
if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) {
661-
if (line_entry.GetLine() == 0) {
662-
EmplaceSafeString(object, "name", "<compiler-generated>");
663-
} else {
664-
EmplaceSafeString(object, "name", frame.GetDisplayFunctionName());
665-
}
666-
SourceReference source;
667-
llvm::raw_string_ostream src_strm(source.content);
668-
std::string line;
669-
for (size_t i = 0; i < num_insts; ++i) {
670-
lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
671-
const auto inst_addr = inst.GetAddress().GetLoadAddress(g_vsc.target);
672-
const char *m = inst.GetMnemonic(g_vsc.target);
673-
const char *o = inst.GetOperands(g_vsc.target);
674-
const char *c = inst.GetComment(g_vsc.target);
675-
if (pc == inst_addr)
676-
disasm_line = i + 1;
677-
const auto inst_offset = inst_addr - low_pc;
678-
int spaces = 0;
679-
if (inst_offset < 10)
680-
spaces = 3;
681-
else if (inst_offset < 100)
682-
spaces = 2;
683-
else if (inst_offset < 1000)
684-
spaces = 1;
685-
line.clear();
686-
llvm::raw_string_ostream line_strm(line);
687-
line_strm << llvm::formatv("{0:X+}: <{1}> {2} {3,12} {4}", inst_addr,
688-
inst_offset, llvm::fmt_repeat(' ', spaces), m,
689-
o);
690-
691-
// If there is a comment append it starting at column 60 or after one
692-
// space past the last char
693-
const uint32_t comment_row = std::max(line_strm.str().size(), (size_t)60);
694-
if (c && c[0]) {
695-
if (line.size() < comment_row)
696-
line_strm.indent(comment_row - line_strm.str().size());
697-
line_strm << " # " << c;
698-
}
699-
src_strm << line_strm.str() << "\n";
700-
source.addr_to_line[inst_addr] = i + 1;
701-
}
702-
// Flush the source stream
703-
src_strm.str();
704-
auto sourceReference = VSCode::GetNextSourceReference();
705-
g_vsc.source_map[sourceReference] = std::move(source);
706-
g_vsc.addr_to_source_ref[low_pc] = sourceReference;
707-
object.try_emplace("sourceReference", sourceReference);
708-
}
709-
return llvm::json::Value(std::move(object));
629+
return {};
710630
}
711631

712632
// "StackFrame": {
@@ -748,6 +668,12 @@ llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) {
748668
// "description": "An optional end column of the range covered by the
749669
// stack frame."
750670
// },
671+
// "instructionPointerReference": {
672+
// "type": "string",
673+
// "description": "A memory reference for the current instruction
674+
// pointer
675+
// in this frame."
676+
// },
751677
// "moduleId": {
752678
// "type": ["integer", "string"],
753679
// "description": "The module associated with this frame, if any."
@@ -770,30 +696,37 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
770696
int64_t frame_id = MakeVSCodeFrameID(frame);
771697
object.try_emplace("id", frame_id);
772698

773-
std::string frame_name;
774-
const char *func_name = frame.GetFunctionName();
775-
if (func_name)
776-
frame_name = func_name;
777-
else
699+
std::string frame_name = frame.GetDisplayFunctionName();
700+
if (frame_name.empty())
778701
frame_name = "<unknown>";
779702
bool is_optimized = frame.GetFunction().GetIsOptimized();
780703
if (is_optimized)
781704
frame_name += " [opt]";
782705
EmplaceSafeString(object, "name", frame_name);
783706

784-
int64_t disasm_line = 0;
785-
object.try_emplace("source", CreateSource(frame, disasm_line));
707+
auto source = CreateSource(frame);
786708

787-
auto line_entry = frame.GetLineEntry();
788-
if (disasm_line > 0) {
789-
object.try_emplace("line", disasm_line);
790-
} else {
709+
if (source) {
710+
object.try_emplace("source", *source);
711+
auto line_entry = frame.GetLineEntry();
791712
auto line = line_entry.GetLine();
792-
if (line == UINT32_MAX)
793-
line = 0;
794-
object.try_emplace("line", line);
713+
if (line && line != LLDB_INVALID_LINE_NUMBER)
714+
object.try_emplace("line", line);
715+
auto column = line_entry.GetColumn();
716+
if (column && column != LLDB_INVALID_COLUMN_NUMBER)
717+
object.try_emplace("column", column);
718+
} else {
719+
object.try_emplace("line", 0);
720+
object.try_emplace("column", 0);
721+
object.try_emplace("presentationHint", "subtle");
722+
}
723+
724+
const auto pc = frame.GetPC();
725+
if (pc != LLDB_INVALID_ADDRESS) {
726+
std::string formatted_addr = "0x" + llvm::utohexstr(pc);
727+
object.try_emplace("instructionPointerReference", formatted_addr);
795728
}
796-
object.try_emplace("column", line_entry.GetColumn());
729+
797730
return llvm::json::Value(std::move(object));
798731
}
799732

lldb/tools/lldb-vscode/JSONUtils.h

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -324,31 +324,6 @@ llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry);
324324
/// definition outlined by Microsoft.
325325
llvm::json::Value CreateSource(llvm::StringRef source_path);
326326

327-
/// Create a "Source" object for a given frame.
328-
///
329-
/// When there is no source file information for a stack frame, we will
330-
/// create disassembly for a function and store a permanent
331-
/// "sourceReference" that contains the textual disassembly for a
332-
/// function along with address to line information. The "Source" object
333-
/// that is created will contain a "sourceReference" that the VSCode
334-
/// protocol can later fetch as text in order to display disassembly.
335-
/// The PC will be extracted from the frame and the disassembly line
336-
/// within the source referred to by "sourceReference" will be filled
337-
/// in.
338-
///
339-
/// \param[in] frame
340-
/// The LLDB stack frame to use when populating out the "Source"
341-
/// object.
342-
///
343-
/// \param[out] disasm_line
344-
/// The line within the "sourceReference" file that the PC from
345-
/// \a frame matches.
346-
///
347-
/// \return
348-
/// A "Source" JSON object with that follows the formal JSON
349-
/// definition outlined by Microsoft.
350-
llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line);
351-
352327
/// Create a "StackFrame" object for a LLDB frame object.
353328
///
354329
/// This function will fill in the following keys in the returned

0 commit comments

Comments
 (0)