OSDN Git Service

Add terminateCommands to lldb-vscode protocol
authorAntónio Afonso <antonio.afonso@gmail.com>
Tue, 19 May 2020 23:01:43 +0000 (16:01 -0700)
committerAntónio Afonso <aadsm@fb.com>
Tue, 19 May 2020 23:18:05 +0000 (16:18 -0700)
Summary: Adding this in line with "stopCommands" and "exitCommands" so that we can run commands at the end of the debugging session.

Reviewers: clayborg, wallace, labath

Reviewed By: clayborg, labath

Subscribers: lldb-commits

Tags: #lldb

Differential Revision: https://reviews.llvm.org/D79726

lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py
lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py
lldb/tools/lldb-vscode/README.md
lldb/tools/lldb-vscode/VSCode.cpp
lldb/tools/lldb-vscode/VSCode.h
lldb/tools/lldb-vscode/lldb-vscode.cpp
lldb/tools/lldb-vscode/package.json

index 790628d..15eb77c 100644 (file)
@@ -179,6 +179,9 @@ class VSCodeTestCaseBase(TestBase):
     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 @@ class VSCodeTestCaseBase(TestBase):
 
     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 @@ class VSCodeTestCaseBase(TestBase):
             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 @@ class VSCodeTestCaseBase(TestBase):
                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 @@ class VSCodeTestCaseBase(TestBase):
             preRunCommands=preRunCommands,
             stopCommands=stopCommands,
             exitCommands=exitCommands,
+            terminateCommands=terminateCommands,
             sourcePath=sourcePath,
             debuggerRoot=debuggerRoot,
             launchCommands=launchCommands,
@@ -309,7 +318,8 @@ class VSCodeTestCaseBase(TestBase):
                          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 @@ class VSCodeTestCaseBase(TestBase):
         self.launch(program, args, cwd, env, stopOnEntry, disableASLR,
                     disableSTDIO, shellExpandArguments, trace,
                     initCommands, preRunCommands, stopCommands, exitCommands,
-                    sourcePath, debuggerRoot)
+                    terminateCommands, sourcePath, debuggerRoot)
index 643a313..0b7b67d 100644 (file)
@@ -10,6 +10,7 @@ import string
 import subprocess
 import sys
 import threading
+import time
 
 
 def dump_memory(base_addr, data, num_per_line, outfile):
@@ -120,6 +121,7 @@ class DebugCommunication(object):
         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 @@ class DebugCommunication(object):
         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 @@ class DebugCommunication(object):
                 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 @@ class DebugCommunication(object):
     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 @@ class DebugCommunication(object):
             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 @@ class DebugCommunication(object):
                        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 @@ class DebugCommunication(object):
             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 @@ def run_vscode(dbg, args, options):
                                       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 @@ def run_vscode(dbg, args, options):
                                       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 @@ def main():
               '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',
index bd66f98..49766c8 100644 (file)
@@ -121,8 +121,8 @@ class TestVSCode_attach(lldbvscode_testcase.VSCodeTestCaseBase):
     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 @@ class TestVSCode_attach(lldbvscode_testcase.VSCodeTestCaseBase):
             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 @@ class TestVSCode_attach(lldbvscode_testcase.VSCodeTestCaseBase):
         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 @@ class TestVSCode_attach(lldbvscode_testcase.VSCodeTestCaseBase):
         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)
index f7f314d..acc9103 100644 (file)
@@ -294,8 +294,9 @@ class TestVSCode_launch(lldbvscode_testcase.VSCodeTestCaseBase):
     @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 @@ class TestVSCode_launch(lldbvscode_testcase.VSCodeTestCaseBase):
             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 @@ class TestVSCode_launch(lldbvscode_testcase.VSCodeTestCaseBase):
         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 @@ class TestVSCode_launch(lldbvscode_testcase.VSCodeTestCaseBase):
         # "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)
index 20c7131..70ca245 100644 (file)
 
 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 @@ file that defines how your program will be run. The JSON configuration file can
 |**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 @@ The JSON configuration file can contain the following `lldb-vscode` specific lau
 |**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.
 
 
index 36bc8ec..2f5d6bf 100644 (file)
@@ -309,6 +309,10 @@ void VSCode::RunExitCommands() {
   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) {
index 5298d75..77e88ad 100644 (file)
@@ -86,6 +86,7 @@ struct VSCode {
   std::vector<std::string> pre_run_commands;
   std::vector<std::string> exit_commands;
   std::vector<std::string> stop_commands;
+  std::vector<std::string> terminate_commands;
   lldb::tid_t focus_tid;
   bool sent_terminated_event;
   bool stop_at_entry;
@@ -132,6 +133,7 @@ struct VSCode {
   void RunPreRunCommands();
   void RunStopCommands();
   void RunExitCommands();
+  void RunTerminateCommands();
 
   /// Create a new SBTarget object from the given request arguments.
   /// \param[in] arguments
index 764eded..03521ff 100644 (file)
@@ -174,6 +174,7 @@ void SendThreadExitedEvent(lldb::tid_t tid) {
 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 @@ void request_attach(const llvm::json::Object &request) {
   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 @@ void request_disconnect(const llvm::json::Object &request) {
   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 @@ void request_launch(const llvm::json::Object &request) {
   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");
index 1df16d0..e2697bf 100644 (file)
                                                        },
                                                        "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": []
                                                        }
                                                }
                                        },