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,19 @@ -CXX_SOURCES := main.cpp +CXX_SOURCES := main-copy.cpp +LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)" +USE_LIBDL :=1 + +a.out: libother include Makefile.rules + +# We copy the source files to move them to test source mapping +other-copy.c: other.c + cp -f $< $@ + +main-copy.cpp: main.cpp + cp -f $< $@ + +# The following shared library will be used to test breakpoints under dynamic loading +libother: other-copy.c + $(MAKE) -f $(MAKEFILE_RULES) \ + DYLIB_ONLY=YES DYLIB_C_SOURCES=other-copy.c DYLIB_NAME=other 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 @@ -16,6 +17,76 @@ mydir = TestBase.compute_mydir(__file__) + def setUp(self): + lldbvscode_testcase.VSCodeTestCaseBase.setUp(self) + + self.main_basename = 'main-copy.cpp' + self.main_path = self.getBuildArtifact(self.main_basename) + + @skipIfWindows + @skipIfRemote + def test_source_map(self): + self.build_and_create_debug_adaptor() + + other_basename = 'other-copy.c' + other_path = self.getBuildArtifact(other_basename) + + source_folder = os.path.dirname(self.main_path) + + new_main_folder = os.path.join(source_folder, 'moved_main') + new_other_folder = os.path.join(source_folder, 'moved_other') + + new_main_path = os.path.join(new_main_folder, self.main_basename) + new_other_path = os.path.join(new_other_folder, other_basename) + + # move the sources + os.mkdir(new_main_folder) + os.mkdir(new_other_folder) + shutil.move(self.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(self.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): @@ -31,8 +102,6 @@ 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) first_line = line_number('main.cpp', 'break 12') second_line = line_number('main.cpp', 'break 13') third_line = line_number('main.cpp', 'break 14') @@ -45,7 +114,7 @@ self.build_and_launch(program) # Set 3 breakpoints and verify that they got set correctly - response = self.vscode.request_setBreakpoints(source_path, lines) + response = self.vscode.request_setBreakpoints(self.main_path, lines) line_to_id = {} if response: breakpoints = response['body']['breakpoints'] @@ -70,7 +139,7 @@ lines.remove(second_line) # Set 2 breakpoints and verify that the previous breakpoints that were # set above are still set. - response = self.vscode.request_setBreakpoints(source_path, lines) + response = self.vscode.request_setBreakpoints(self.main_path, lines) if response: breakpoints = response['body']['breakpoints'] self.assertEquals(len(breakpoints), len(lines), @@ -108,7 +177,7 @@ # Now clear all breakpoints for the source file by passing down an # empty lines array lines = [] - response = self.vscode.request_setBreakpoints(source_path, lines) + response = self.vscode.request_setBreakpoints(self.main_path, lines) if response: breakpoints = response['body']['breakpoints'] self.assertEquals(len(breakpoints), len(lines), @@ -124,7 +193,7 @@ # Now set a breakpoint again in the same source file and verify it # was added. lines = [second_line] - response = self.vscode.request_setBreakpoints(source_path, lines) + response = self.vscode.request_setBreakpoints(self.main_path, lines) if response: breakpoints = response['body']['breakpoints'] self.assertEquals(len(breakpoints), len(lines), @@ -155,15 +224,13 @@ 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) loop_line = line_number('main.cpp', '// break loop') program = self.getBuildArtifact("a.out") self.build_and_launch(program) # Set a breakpoint at the loop line with no condition and no # hitCondition - breakpoint_ids = self.set_source_breakpoints(source_path, [loop_line]) + breakpoint_ids = self.set_source_breakpoints(self.main_path, [loop_line]) self.assertEquals(len(breakpoint_ids), 1, "expect one breakpoint") self.vscode.request_continue() @@ -175,7 +242,7 @@ self.assertEquals(i, 0, 'i != 0 after hitting breakpoint') # Update the condition on our breakpoint - new_breakpoint_ids = self.set_source_breakpoints(source_path, + new_breakpoint_ids = self.set_source_breakpoints(self.main_path, [loop_line], condition="i==4") self.assertEquals(breakpoint_ids, new_breakpoint_ids, @@ -187,7 +254,7 @@ self.assertEquals(i, 4, 'i != 4 showing conditional works') - new_breakpoint_ids = self.set_source_breakpoints(source_path, + new_breakpoint_ids = self.set_source_breakpoints(self.main_path, [loop_line], hitCondition="2") 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,25 @@ } } int main(int argc, char const *argv[]) { +#if defined(__APPLE__) + const char *libother_name = "libother.dylib"; +#else + const char *libother_name = "libother.so"; +#endif + + void *handle = dlopen(libother_name, RTLD_NOW); + if (handle == nullptr) { + fprintf(stderr, "%s\n", dlerror()); + exit(1); + } + + int (*foo)(int) = (int (*)(int))dlsym(handle, "foo"); + if (foo == nullptr) { + fprintf(stderr, "%s\n", dlerror()); + exit(2); + } + 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::None, + llvm::Optional request_line = llvm::None); /// 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::None, + llvm::Optional request_line = llvm::None); /// 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()) { @@ -1766,13 +1775,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); } }