diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -266,8 +266,8 @@ stopOnEntry=False, disableASLR=True, disableSTDIO=False, shellExpandArguments=False, trace=False, initCommands=None, preRunCommands=None, - stopCommands=None, exitCommands=None,sourcePath= None, - debuggerRoot=None, launchCommands=None): + stopCommands=None, exitCommands=None,sourcePath=None, + debuggerRoot=None, launchCommands=None, sourceMap=None): '''Sending launch request to vscode ''' @@ -298,7 +298,8 @@ exitCommands=exitCommands, sourcePath=sourcePath, debuggerRoot=debuggerRoot, - launchCommands=launchCommands) + launchCommands=launchCommands, + sourceMap=sourceMap) if not (response and response['success']): self.assertTrue(response['success'], 'launch failed (%s)' % (response['message'])) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -570,7 +570,7 @@ disableSTDIO=False, shellExpandArguments=False, trace=False, initCommands=None, preRunCommands=None, stopCommands=None, exitCommands=None, sourcePath=None, - debuggerRoot=None, launchCommands=None): + debuggerRoot=None, launchCommands=None, sourceMap=None): args_dict = { 'program': program } @@ -605,6 +605,8 @@ args_dict['debuggerRoot'] = debuggerRoot if launchCommands: args_dict['launchCommands'] = launchCommands + if sourceMap: + args_dict['sourceMap'] = sourceMap command_dict = { 'command': 'launch', 'type': 'request', diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint/Makefile b/lldb/test/API/tools/lldb-vscode/breakpoint/Makefile --- a/lldb/test/API/tools/lldb-vscode/breakpoint/Makefile +++ b/lldb/test/API/tools/lldb-vscode/breakpoint/Makefile @@ -1,3 +1,16 @@ -CXX_SOURCES := main.cpp - include Makefile.rules + +# We copy the source files to move them to test relocation +other-copy.c: other.c + cp -f $< $@ + +main-copy.cpp: main.cpp + cp -f $< $@ + +libother.so: other-copy.c + $(CC) $(CFLAGS) -fpic -o other.o -c other-copy.c + $(CC) $(CFLAGS) -shared -o libother.so other.o + +a.out: main-copy.cpp libother.so + echo $(CXXFLAGS) > ./flags + $(CXX) $(CXXFLAGS) -o a.out -Wl,-rpath "-Wl,$(shell pwd)" main-copy.cpp diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py b/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py --- a/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py +++ b/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py @@ -5,6 +5,7 @@ import unittest2 import vscode +import shutil from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil @@ -18,6 +19,76 @@ @skipIfWindows @skipIfRemote + def test_source_map(self): + self.build_and_create_debug_adaptor() + + main_basename = 'main-copy.cpp' + main_path = self.getBuildArtifact(main_basename) + other_basename = 'other-copy.c' + other_path = self.getBuildArtifact(other_basename) + + source_folder = os.path.dirname(main_path) + + new_main_folder = os.path.join(os.path.dirname(source_folder), 'moved_main') + new_other_folder = os.path.join(os.path.dirname(source_folder), 'moved_other') + + new_main_path = os.path.join(new_main_folder, main_basename) + new_other_path = os.path.join(new_other_folder, other_basename) + + # clean up new locations + shutil.rmtree(new_main_folder) + shutil.rmtree(new_other_folder) + + # move the sources + os.mkdir(new_main_folder) + os.mkdir(new_other_folder) + shutil.move(main_path, new_main_path) + shutil.move(other_path, new_other_path) + + main_line = line_number('main.cpp', 'break 12') + other_line = line_number('other.c', 'break other') + + program = self.getBuildArtifact("a.out") + source_map = [ + [source_folder, new_main_folder], + [source_folder, new_other_folder], + ] + self.launch(program, sourceMap=source_map) + + # breakpoint in main.cpp + response = self.vscode.request_setBreakpoints(new_main_path, [main_line]) + breakpoints = response['body']['breakpoints'] + self.assertEquals(len(breakpoints), 1) + breakpoint = breakpoints[0] + self.assertEqual(breakpoint['line'], main_line) + self.assertTrue(breakpoint['verified']) + self.assertEqual(main_basename, breakpoint['source']['name']) + self.assertEqual(new_main_path, breakpoint['source']['path']) + + # 2nd breakpoint, which is from a dynamically loaded library + response = self.vscode.request_setBreakpoints(new_other_path, [other_line]) + breakpoints = response['body']['breakpoints'] + breakpoint = breakpoints[0] + self.assertEqual(breakpoint['line'], other_line) + self.assertFalse(breakpoint['verified']) + self.assertEqual(other_basename, breakpoint['source']['name']) + self.assertEqual(new_other_path, breakpoint['source']['path']) + other_breakpoint_id = breakpoint['id'] + + self.vscode.request_continue() + self.verify_breakpoint_hit([other_breakpoint_id]) + + # 2nd breakpoint again, which should be valid at this point + response = self.vscode.request_setBreakpoints(new_other_path, [other_line]) + breakpoints = response['body']['breakpoints'] + breakpoint = breakpoints[0] + self.assertEqual(breakpoint['line'], other_line) + self.assertTrue(breakpoint['verified']) + self.assertEqual(other_basename, breakpoint['source']['name']) + self.assertEqual(new_other_path, breakpoint['source']['path']) + + @skipIfWindows + @skipIfRemote def test_set_and_clear(self): '''Tests setting and clearing source file and line breakpoints. This packet is a bit tricky on the debug adaptor side since there @@ -31,8 +102,8 @@ and makes sure things happen correctly. It doesn't test hitting breakpoints and the functionality of each breakpoint, like 'conditions' and 'hitCondition' settings.''' - source_basename = 'main.cpp' - source_path = os.path.join(os.getcwd(), source_basename) + source_basename = 'main-copy.cpp' + source_path = self.getBuildArtifact(source_basename) first_line = line_number('main.cpp', 'break 12') second_line = line_number('main.cpp', 'break 13') third_line = line_number('main.cpp', 'break 14') @@ -155,8 +226,8 @@ def test_functionality(self): '''Tests hitting breakpoints and the functionality of a single breakpoint, like 'conditions' and 'hitCondition' settings.''' - source_basename = 'main.cpp' - source_path = os.path.join(os.getcwd(), source_basename) + source_basename = 'main-copy.cpp' + source_path = self.getBuildArtifact(source_basename) loop_line = line_number('main.cpp', '// break loop') program = self.getBuildArtifact("a.out") diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp b/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp --- a/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp +++ b/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp @@ -1,5 +1,6 @@ -#include +#include #include +#include int twelve(int i) { return 12 + i; // break 12 @@ -15,6 +16,10 @@ } } int main(int argc, char const *argv[]) { + void *handle = dlopen("libother.so", RTLD_NOW); + int (*foo)(int) = (int (*)(int))dlsym(handle, "foo"); + foo(12); + for (int i=0; i<10; ++i) { int x = twelve(i) + thirteen(i) + a::fourteen(i); // break loop } diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint/other.c b/lldb/test/API/tools/lldb-vscode/breakpoint/other.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-vscode/breakpoint/other.c @@ -0,0 +1,5 @@ +extern int foo(int x) { + int y = x + 42; // break other + int z = y + 42; + return z; +} diff --git a/lldb/tools/lldb-vscode/JSONUtils.h b/lldb/tools/lldb-vscode/JSONUtils.h --- a/lldb/tools/lldb-vscode/JSONUtils.h +++ b/lldb/tools/lldb-vscode/JSONUtils.h @@ -184,28 +184,58 @@ void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object, llvm::StringRef key); -/// Converts \a bp to a JSON value and appends all locations to the +/// Converts \a bp to a JSON value and appends the first valid location to the /// \a breakpoints array. /// /// \param[in] bp -/// A LLDB breakpoint object which will get all locations extracted -/// and converted into a JSON objects in the \a breakpoints array +/// A LLDB breakpoint object which will get the first valid location +/// extracted and converted into a JSON object in the \a breakpoints array /// /// \param[in] breakpoints /// A JSON array that will get a llvm::json::Value for \a bp /// appended to it. -void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints); +/// +/// \param[in] request_path +/// An optional source path to use when creating the "Source" object of this +/// breakpoint. If not specified, the "Source" object is created from the +/// breakpoint's address' LineEntry. It is useful to ensure the same source +/// paths provided by the setBreakpoints request are returned to the IDE. +/// +/// \param[in] request_line +/// An optional line to use when creating the "Breakpoint" object to append. +/// It is used if the breakpoint has no valid locations. +/// It is useful to ensure the same line +/// provided by the setBreakpoints request are returned to the IDE as a +/// fallback. +void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints, + llvm::Optional request_path = {}, + llvm::Optional request_line = {}); /// Converts breakpoint location to a Visual Studio Code "Breakpoint" -/// JSON object and appends it to the \a breakpoints array. /// /// \param[in] bp /// A LLDB breakpoint object to convert into a JSON value /// +/// \param[in] request_path +/// An optional source path to use when creating the "Source" object of this +/// breakpoint. If not specified, the "Source" object is created from the +/// breakpoint's address' LineEntry. It is useful to ensure the same source +/// paths provided by the setBreakpoints request are returned to the IDE. +/// +/// \param[in] request_line +/// An optional line to use when creating the resulting "Breakpoint" object. +/// It is used if the breakpoint has no valid locations. +/// It is useful to ensure the same line +/// provided by the setBreakpoints request are returned to the IDE as a +/// fallback. +/// /// \return /// A "Breakpoint" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp); +llvm::json::Value +CreateBreakpoint(lldb::SBBreakpoint &bp, + llvm::Optional request_path = {}, + llvm::Optional request_line = {}); /// Create a "Event" JSON object using \a event_name as the event name /// @@ -263,6 +293,16 @@ /// definition outlined by Microsoft. llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry); +/// Create a "Source" object for a given source path. +/// +/// \param[in] source_path +/// The path to the source to use when creating the "Source" object. +/// +/// \return +/// A "Source" JSON object that follows the formal JSON +/// definition outlined by Microsoft. +llvm::json::Value CreateSource(llvm::StringRef source_path); + /// Create a "Source" object for a given frame. /// /// When there is no source file information for a stack frame, we will diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp --- a/lldb/tools/lldb-vscode/JSONUtils.cpp +++ b/lldb/tools/lldb-vscode/JSONUtils.cpp @@ -8,7 +8,10 @@ #include +#include "llvm/ADT/Optional.h" + #include "llvm/Support/FormatAdapters.h" +#include "llvm/Support/Path.h" #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBBreakpointLocation.h" @@ -281,7 +284,9 @@ // }, // "required": [ "verified" ] // } -llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp) { +llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp, + llvm::Optional request_path, + llvm::Optional request_line) { // Each breakpoint location is treated as a separate breakpoint for VS code. // They don't have the notion of a single breakpoint with multiple locations. llvm::json::Object object; @@ -300,7 +305,7 @@ // that is at least loaded in the current process. lldb::SBBreakpointLocation bp_loc; const auto num_locs = bp.GetNumLocations(); - for (size_t i=0; i request_path, + llvm::Optional request_line) { + breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line)); } // "Event": { @@ -481,6 +495,14 @@ return llvm::json::Value(std::move(object)); } +llvm::json::Value CreateSource(llvm::StringRef source_path) { + llvm::json::Object source; + llvm::StringRef name = llvm::sys::path::filename(source_path); + EmplaceSafeString(source, "name", name); + EmplaceSafeString(source, "path", source_path); + return llvm::json::Value(std::move(source)); +} + llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) { disasm_line = 0; auto line_entry = frame.GetLineEntry(); diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -418,7 +418,16 @@ bp.MatchesName(BreakpointBase::GetBreakpointLabel())) { auto bp_event = CreateEventObject("breakpoint"); llvm::json::Object body; - body.try_emplace("breakpoint", CreateBreakpoint(bp)); + // As VSCode already knows the path of this breakpoint, we don't + // need to send it back as part of a "changed" event. This + // prevent us from sending to VSCode paths that should be source + // mapped. Note that CreateBreakpoint doesn't apply source mapping. + // Besides, the current implementation of VSCode ignores the + // "source" element of breakpoint events. + llvm::json::Value source_bp = CreateBreakpoint(bp); + source_bp.getAsObject()->erase("source"); + + body.try_emplace("breakpoint", source_bp); body.try_emplace("reason", "changed"); bp_event.try_emplace("body", std::move(body)); g_vsc.SendJSON(llvm::json::Value(std::move(bp_event))); @@ -1364,13 +1373,13 @@ llvm::sys::fs::set_current_path(debuggerRoot.data()); } - SetSourceMapFromArguments(*arguments); - // Run any initialize LLDB commands the user specified in the launch.json. // This is run before target is created, so commands can't do anything with // the targets - preRunCommands are run with the target. g_vsc.RunInitCommands(); + SetSourceMapFromArguments(*arguments); + lldb::SBError status; g_vsc.SetTarget(g_vsc.CreateTargetFromArguments(*arguments, status)); if (status.Fail()) { @@ -1748,13 +1757,14 @@ const auto &existing_bp = existing_source_bps->second.find(src_bp.line); if (existing_bp != existing_source_bps->second.end()) { existing_bp->second.UpdateBreakpoint(src_bp); - AppendBreakpoint(existing_bp->second.bp, response_breakpoints); + AppendBreakpoint(existing_bp->second.bp, response_breakpoints, path, + src_bp.line); continue; } } // At this point the breakpoint is new src_bp.SetBreakpoint(path.data()); - AppendBreakpoint(src_bp.bp, response_breakpoints); + AppendBreakpoint(src_bp.bp, response_breakpoints, path, src_bp.line); g_vsc.source_breakpoints[path][src_bp.line] = std::move(src_bp); } }