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 @@ -179,6 +179,9 @@ def get_console(self, timeout=0.0): return self.vscode.get_output('console', timeout=timeout) + def collect_console(self, duration): + return self.vscode.collect_output('console', duration=duration) + def get_local_as_int(self, name, threadId=None): value = self.vscode.get_local_variable_value(name, threadId=threadId) if value.startswith('0x'): @@ -239,14 +242,16 @@ def attach(self, program=None, pid=None, waitFor=None, trace=None, initCommands=None, preRunCommands=None, stopCommands=None, - exitCommands=None, attachCommands=None, coreFile=None): + exitCommands=None, attachCommands=None, terminateCommands=None, + coreFile=None): '''Build the default Makefile target, create the VSCode debug adaptor, and attach to the process. ''' # Make sure we disconnect and terminate the VSCode debug adaptor even # if we throw an exception during the test case. def cleanup(): - self.vscode.request_disconnect(terminateDebuggee=True) + if self.vscode.debugging: + self.vscode.request_disconnect(terminateDebuggee=True) self.vscode.terminate() # Execute the cleanup function during test case tear down. @@ -257,7 +262,8 @@ program=program, pid=pid, waitFor=waitFor, trace=trace, initCommands=initCommands, preRunCommands=preRunCommands, stopCommands=stopCommands, exitCommands=exitCommands, - attachCommands=attachCommands, coreFile=coreFile) + attachCommands=attachCommands, terminateCommands=terminateCommands, + coreFile=coreFile) if not (response and response['success']): self.assertTrue(response['success'], 'attach failed (%s)' % (response['message'])) @@ -266,15 +272,17 @@ stopOnEntry=False, disableASLR=True, disableSTDIO=False, shellExpandArguments=False, trace=False, initCommands=None, preRunCommands=None, - stopCommands=None, exitCommands=None,sourcePath=None, - debuggerRoot=None, launchCommands=None, sourceMap=None): + stopCommands=None, exitCommands=None, terminateCommands=None, + sourcePath=None, debuggerRoot=None, launchCommands=None, + sourceMap=None): '''Sending launch request to vscode ''' # Make sure we disconnect and terminate the VSCode debug adapter, # if we throw an exception during the test case def cleanup(): - self.vscode.request_disconnect(terminateDebuggee=True) + if self.vscode.debugging: + self.vscode.request_disconnect(terminateDebuggee=True) self.vscode.terminate() # Execute the cleanup function during test case tear down. @@ -296,6 +304,7 @@ preRunCommands=preRunCommands, stopCommands=stopCommands, exitCommands=exitCommands, + terminateCommands=terminateCommands, sourcePath=sourcePath, debuggerRoot=debuggerRoot, launchCommands=launchCommands, @@ -309,7 +318,8 @@ disableSTDIO=False, shellExpandArguments=False, trace=False, initCommands=None, preRunCommands=None, stopCommands=None, exitCommands=None, - sourcePath=None, debuggerRoot=None): + terminateCommands=None, sourcePath=None, + debuggerRoot=None): '''Build the default Makefile target, create the VSCode debug adaptor, and launch the process. ''' @@ -319,4 +329,4 @@ self.launch(program, args, cwd, env, stopOnEntry, disableASLR, disableSTDIO, shellExpandArguments, trace, initCommands, preRunCommands, stopCommands, exitCommands, - sourcePath, debuggerRoot) + terminateCommands, sourcePath, debuggerRoot) 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 @@ -10,6 +10,7 @@ import subprocess import sys import threading +import time def dump_memory(base_addr, data, num_per_line, outfile): @@ -120,6 +121,7 @@ self.configuration_done_sent = False self.frame_scopes = {} self.init_commands = init_commands + self.debugging = False @classmethod def encode_content(cls, s): @@ -148,6 +150,15 @@ self.output_condition.release() return output + def collect_output(self, category, duration, clear=True): + end_time = time.time() + duration + collected_output = "" + while end_time > time.time(): + output = self.get_output(category, timeout=0.25, clear=clear) + if output: + collected_output += output + return collected_output if collected_output else None + def enqueue_recv_packet(self, packet): self.recv_condition.acquire() self.recv_packets.append(packet) @@ -208,9 +219,13 @@ self.breakpoint_events.append(packet) # no need to add 'breakpoint' event packets to our packets list return keepGoing - elif packet_type == 'response': - if packet['command'] == 'disconnect': + elif event == 'initialized': + self.debugging = True + elif event == 'terminated': + self.debugging = False keepGoing = False + elif event == 'exited': + self.debugging = False self.enqueue_recv_packet(packet) return keepGoing @@ -450,7 +465,8 @@ def request_attach(self, program=None, pid=None, waitFor=None, trace=None, initCommands=None, preRunCommands=None, stopCommands=None, exitCommands=None, - attachCommands=None, coreFile=None): + attachCommands=None, terminateCommands=None, + coreFile=None): args_dict = {} if pid is not None: args_dict['pid'] = pid @@ -469,6 +485,8 @@ args_dict['stopCommands'] = stopCommands if exitCommands: args_dict['exitCommands'] = exitCommands + if terminateCommands: + args_dict['terminateCommands'] = terminateCommands if attachCommands: args_dict['attachCommands'] = attachCommands if coreFile: @@ -571,7 +589,8 @@ stopOnEntry=False, disableASLR=True, disableSTDIO=False, shellExpandArguments=False, trace=False, initCommands=None, preRunCommands=None, - stopCommands=None, exitCommands=None, sourcePath=None, + stopCommands=None, exitCommands=None, + terminateCommands=None ,sourcePath=None, debuggerRoot=None, launchCommands=None, sourceMap=None): args_dict = { 'program': program @@ -601,6 +620,8 @@ args_dict['stopCommands'] = stopCommands if exitCommands: args_dict['exitCommands'] = exitCommands + if terminateCommands: + args_dict['terminateCommands'] = terminateCommands if sourcePath: args_dict['sourcePath'] = sourcePath if debuggerRoot: @@ -905,7 +926,8 @@ initCommands=options.initCmds, preRunCommands=options.preRunCmds, stopCommands=options.stopCmds, - exitCommands=options.exitCmds) + exitCommands=options.exitCmds, + terminateCommands=options.terminateCmds) else: response = dbg.request_launch(options.program, args=args, @@ -916,7 +938,8 @@ initCommands=options.initCmds, preRunCommands=options.preRunCmds, stopCommands=options.stopCmds, - exitCommands=options.exitCmds) + exitCommands=options.exitCmds, + terminateCommands=options.terminateCmds) if response['success']: if options.sourceBreakpoints: @@ -1090,6 +1113,15 @@ 'exits. Can be specified more than once.')) parser.add_option( + '--terminateCommand', + type='string', + action='append', + dest='terminateCmds', + default=[], + help=('Specify a LLDB command that will be executed when the debugging ' + 'session is terminated. Can be specified more than once.')) + + parser.add_option( '--env', type='string', action='append', diff --git a/lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py b/lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py --- a/lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py +++ b/lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py @@ -121,8 +121,8 @@ def test_commands(self): ''' Tests the "initCommands", "preRunCommands", "stopCommands", - "exitCommands", and "attachCommands" that can be passed during - attach. + "exitCommands", "terminateCommands" and "attachCommands" + that can be passed during attach. "initCommands" are a list of LLDB commands that get executed before the targt is created. @@ -136,6 +136,8 @@ must have a valid process in the selected target in LLDB after they are done executing. This allows custom commands to create any kind of debug session. + "terminateCommands" are a list of LLDB commands that get executed when + the debugger session terminates. ''' self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") @@ -150,13 +152,14 @@ preRunCommands = ['image list a.out', 'image dump sections a.out'] stopCommands = ['frame variable', 'bt'] exitCommands = ['expr 2+3', 'expr 3+4'] + terminateCommands = ['expr 4+2'] self.attach(program=program, attachCommands=attachCommands, initCommands=initCommands, preRunCommands=preRunCommands, stopCommands=stopCommands, - exitCommands=exitCommands) - + exitCommands=exitCommands, + terminateCommands=terminateCommands) # Get output from the console. This should contain both the # "initCommands" and the "preRunCommands". output = self.get_console() @@ -187,5 +190,35 @@ self.continue_to_exit() # Get output from the console. This should contain both the # "exitCommands" that were run after the second breakpoint was hit - output = self.get_console(timeout=1.0) + # and the "terminateCommands" due to the debugging session ending + output = self.collect_console(duration=1.0) self.verify_commands('exitCommands', output, exitCommands) + self.verify_commands('terminateCommands', output, terminateCommands) + + @skipIfWindows + @skipIfDarwin + @skipIfNetBSD # Hangs on NetBSD as well + def test_terminate_commands(self): + ''' + Tests that the "terminateCommands", that can be passed during + attach, are run when the debugger is disconnected. + ''' + self.build_and_create_debug_adaptor() + program = self.getBuildArtifact("a.out") + # Here we just create a target and launch the process as a way to test + # if we are able to use attach commands to create any kind of a target + # and use it for debugging + attachCommands = [ + 'target create -d "%s"' % (program), + 'process launch' + ] + terminateCommands = ['expr 4+2'] + self.attach(program=program, + attachCommands=attachCommands, + terminateCommands=terminateCommands) + self.get_console() + # Once it's disconnected the console should contain the + # "terminateCommands" + self.vscode.request_disconnect(terminateDebuggee=True) + output = self.collect_console(duration=1.0) + self.verify_commands('terminateCommands', output, terminateCommands) diff --git a/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py b/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py --- a/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py +++ b/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py @@ -294,8 +294,9 @@ @skipIfRemote def test_commands(self): ''' - Tests the "initCommands", "preRunCommands", "stopCommands" and - "exitCommands" that can be passed during launch. + Tests the "initCommands", "preRunCommands", "stopCommands", + "terminateCommands" and "exitCommands" that can be passed during + launch. "initCommands" are a list of LLDB commands that get executed before the targt is created. @@ -305,17 +306,21 @@ time the program stops. "exitCommands" are a list of LLDB commands that get executed when the process exits + "terminateCommands" are a list of LLDB commands that get executed when + the debugger session terminates. ''' program = self.getBuildArtifact("a.out") initCommands = ['target list', 'platform list'] preRunCommands = ['image list a.out', 'image dump sections a.out'] stopCommands = ['frame variable', 'bt'] exitCommands = ['expr 2+3', 'expr 3+4'] + terminateCommands = ['expr 4+2'] self.build_and_launch(program, initCommands=initCommands, preRunCommands=preRunCommands, stopCommands=stopCommands, - exitCommands=exitCommands) + exitCommands=exitCommands, + terminateCommands=terminateCommands) # Get output from the console. This should contain both the # "initCommands" and the "preRunCommands". @@ -354,8 +359,10 @@ self.continue_to_exit() # Get output from the console. This should contain both the # "exitCommands" that were run after the second breakpoint was hit - output = self.get_console(timeout=1.0) + # and the "terminateCommands" due to the debugging session ending + output = self.collect_console(duration=1.0) self.verify_commands('exitCommands', output, exitCommands) + self.verify_commands('terminateCommands', output, terminateCommands) @skipIfWindows @skipIfRemote @@ -420,3 +427,29 @@ # "exitCommands" that were run after the second breakpoint was hit output = self.get_console(timeout=1.0) self.verify_commands('exitCommands', output, exitCommands) + + @skipIfWindows + @skipIfNetBSD # Hangs on NetBSD as well + def test_terminate_commands(self): + ''' + Tests that the "terminateCommands", that can be passed during + launch, are run when the debugger is disconnected. + ''' + self.build_and_create_debug_adaptor() + program = self.getBuildArtifact("a.out") + # Here we just create a target and launch the process as a way to test + # if we are able to use attach commands to create any kind of a target + # and use it for debugging + attachCommands = [ + 'target create -d "%s"' % (program), + 'process launch' + ] + terminateCommands = ['expr 4+2'] + self.launch(program=program, + terminateCommands=terminateCommands) + self.get_console() + # Once it's disconnected the console should contain the + # "terminateCommands" + self.vscode.request_disconnect(terminateDebuggee=True) + output = self.collect_console(duration=1.0) + self.verify_commands('terminateCommands', output, terminateCommands) diff --git a/lldb/tools/lldb-vscode/README.md b/lldb/tools/lldb-vscode/README.md --- a/lldb/tools/lldb-vscode/README.md +++ b/lldb/tools/lldb-vscode/README.md @@ -16,14 +16,14 @@ The `lldb-vscode` tool creates a command line tool that implements the [Visual Studio Code Debug API](https://code.visualstudio.com/docs/extensionAPI/api-debugging). -It can be installed as an extension for the Visual Studio Code and Nuclide IDE. +It can be installed as an extension for the Visual Studio Code and Nuclide IDE. The protocol is easy to run remotely and also can allow other tools and IDEs to -get a full featured debugger with a well defined protocol. +get a full featured debugger with a well defined protocol. # Installation for Visual Studio Code Installing the plug-in involves creating a directory in the `~/.vscode/extensions` folder and copying the package.json file that is in the same directory as this -documentation into it, and copying to symlinking a lldb-vscode binary into +documentation into it, and copying to symlinking a lldb-vscode binary into the `bin` directory inside the plug-in directory. If you want to make a stand alone plug-in that you can send to others on unix systems: @@ -86,6 +86,7 @@ |**preRunCommands** |[string]| | LLDB commands executed just before launching after the LLDB target has been created. Commands and command output will be sent to the debugger console when they are executed. |**stopCommands** |[string]| | LLDB commands executed just after each stop. Commands and command output will be sent to the debugger console when they are executed. |**exitCommands** |[string]| | LLDB commands executed when the program exits. Commands and command output will be sent to the debugger console when they are executed. +|**terminateCommands** |[string]| | LLDB commands executed when the debugging session ends. Commands and command output will be sent to the debugger console when they are executed. |**sourceMap** |[string[2]]| | Specify an array of path re-mappings. Each element in the array must be a two element array containing a source and destination pathname. |**debuggerRoot** | string| |Specify a working directory to use when launching lldb-vscode. If the debug information in your executable contains relative paths, this option can be used so that `lldb-vscode` can find source files and object files that have relative paths. @@ -112,6 +113,7 @@ |**preRunCommands** |[string]| | LLDB commands executed just before launching after the LLDB target has been created. Commands and command output will be sent to the debugger console when they are executed. |**stopCommands** |[string]| | LLDB commands executed just after each stop. Commands and command output will be sent to the debugger console when they are executed. |**exitCommands** |[string]| | LLDB commands executed when the program exits. Commands and command output will be sent to the debugger console when they are executed. +|**terminateCommands** |[string]| | LLDB commands executed when the debugging session ends. Commands and command output will be sent to the debugger console when they are executed. |**attachCommands** |[string]| | LLDB commands that will be executed after **preRunCommands** which take place of the code that normally does the attach. The commands can create a new target and attach or launch it however desired. This allows custom launch and attach configurations. Core files can use `target create --core /path/to/core` to attach to core files. diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -86,6 +86,7 @@ std::vector pre_run_commands; std::vector exit_commands; std::vector stop_commands; + std::vector terminate_commands; lldb::tid_t focus_tid; bool sent_terminated_event; bool stop_at_entry; @@ -132,6 +133,7 @@ void RunPreRunCommands(); void RunStopCommands(); void RunExitCommands(); + void RunTerminateCommands(); /// Create a new SBTarget object from the given request arguments. /// \param[in] arguments diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp --- a/lldb/tools/lldb-vscode/VSCode.cpp +++ b/lldb/tools/lldb-vscode/VSCode.cpp @@ -309,6 +309,10 @@ RunLLDBCommands("Running exitCommands:", exit_commands); } +void VSCode::RunTerminateCommands() { + RunLLDBCommands("Running terminateCommands:", terminate_commands); +} + lldb::SBTarget VSCode::CreateTargetFromArguments( const llvm::json::Object &arguments, lldb::SBError &error) { 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 @@ -174,6 +174,7 @@ void SendTerminatedEvent() { if (!g_vsc.sent_terminated_event) { g_vsc.sent_terminated_event = true; + g_vsc.RunTerminateCommands(); // Send a "terminated" event llvm::json::Object event(CreateEventObject("terminated")); g_vsc.SendJSON(llvm::json::Value(std::move(event))); @@ -529,6 +530,7 @@ g_vsc.pre_run_commands = GetStrings(arguments, "preRunCommands"); g_vsc.stop_commands = GetStrings(arguments, "stopCommands"); g_vsc.exit_commands = GetStrings(arguments, "exitCommands"); + g_vsc.terminate_commands = GetStrings(arguments, "terminateCommands"); auto attachCommands = GetStrings(arguments, "attachCommands"); llvm::StringRef core_file = GetString(arguments, "coreFile"); g_vsc.stop_at_entry = @@ -772,7 +774,6 @@ bool terminateDebuggee = GetBoolean(arguments, "terminateDebuggee", false); lldb::SBProcess process = g_vsc.target.GetProcess(); auto state = process.GetState(); - switch (state) { case lldb::eStateInvalid: case lldb::eStateUnloaded: @@ -1365,6 +1366,7 @@ g_vsc.pre_run_commands = GetStrings(arguments, "preRunCommands"); g_vsc.stop_commands = GetStrings(arguments, "stopCommands"); g_vsc.exit_commands = GetStrings(arguments, "exitCommands"); + g_vsc.terminate_commands = GetStrings(arguments, "terminateCommands"); auto launchCommands = GetStrings(arguments, "launchCommands"); g_vsc.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false); const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot"); diff --git a/lldb/tools/lldb-vscode/package.json b/lldb/tools/lldb-vscode/package.json --- a/lldb/tools/lldb-vscode/package.json +++ b/lldb/tools/lldb-vscode/package.json @@ -152,8 +152,13 @@ }, "exitCommands": { "type": "array", - "description": "Commands executed at the end of debugging session.", + "description": "Commands executed when the program exits.", "default": [] + }, + "terminateCommands": { + "type": "array", + "description": "Commands executed at the end of debugging session.", + "default": [] } } },