Skip to content

Commit

Permalink
Add a new tool named "lldb-vscode" that implements the Visual Studio …
Browse files Browse the repository at this point in the history
…Code Debug Adaptor Protocol

This patch adds a new lldb-vscode tool that speaks the Microsoft Visual Studio Code debug adaptor protocol. It has full unit tests that test all packets.

This tool can be easily packaged up into a native extension and used with Visual Studio Code, and it can also be used by Nuclide

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

llvm-svn: 339911
  • Loading branch information
Greg Clayton committed Aug 16, 2018
1 parent cecc9f5 commit 2f5cf85
Show file tree
Hide file tree
Showing 48 changed files with 8,761 additions and 6 deletions.
358 changes: 352 additions & 6 deletions lldb/lldb.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions lldb/packages/Python/lldbsuite/test/dotest.py
Expand Up @@ -646,6 +646,7 @@ def setupSysPath():

pluginPath = os.path.join(scriptPath, 'plugins')
toolsLLDBMIPath = os.path.join(scriptPath, 'tools', 'lldb-mi')
toolsLLDBVSCode = os.path.join(scriptPath, 'tools', 'lldb-vscode')
toolsLLDBServerPath = os.path.join(scriptPath, 'tools', 'lldb-server')

# Insert script dir, plugin dir, lldb-mi dir and lldb-server dir to the
Expand All @@ -654,6 +655,9 @@ def setupSysPath():
# Adding test/tools/lldb-mi to the path makes it easy
sys.path.insert(0, toolsLLDBMIPath)
# to "import lldbmi_testcase" from the MI tests
# Adding test/tools/lldb-vscode to the path makes it easy to
# "import lldb_vscode_testcase" from the VSCode tests
sys.path.insert(0, toolsLLDBVSCode)
# Adding test/tools/lldb-server to the path makes it easy
sys.path.insert(0, toolsLLDBServerPath)
# to "import lldbgdbserverutils" from the lldb-server tests
Expand Down Expand Up @@ -723,6 +727,15 @@ def setupSysPath():
"The 'lldb-mi' executable cannot be located. The lldb-mi tests can not be run as a result.")
configuration.skipCategories.append("lldb-mi")

lldbVSCodeExec = os.path.join(lldbDir, "lldb-vscode")
if is_exe(lldbVSCodeExec):
os.environ["LLDBVSCODE_EXEC"] = lldbVSCodeExec
else:
if not configuration.shouldSkipBecauseOfCategories(["lldb-vscode"]):
print(
"The 'lldb-vscode' executable cannot be located. The lldb-vscode tests can not be run as a result.")
configuration.skipCategories.append("lldb-vscode")

lldbPythonDir = None # The directory that contains 'lldb/__init__.py'
if not configuration.lldbFrameworkPath and os.path.exists(os.path.join(lldbLibDir, "LLDB.framework")):
configuration.lldbFrameworkPath = os.path.join(lldbLibDir, "LLDB.framework")
Expand Down
5 changes: 5 additions & 0 deletions lldb/packages/Python/lldbsuite/test/lldbtest.py
Expand Up @@ -740,6 +740,11 @@ def setUp(self):
else:
self.lldbMiExec = None

if "LLDBVSCODE_EXEC" in os.environ:
self.lldbVSCodeExec = os.environ["LLDBVSCODE_EXEC"]
else:
self.lldbVSCodeExec = None

# If we spawn an lldb process for test (via pexpect), do not load the
# init file unless told otherwise.
if "NO_LLDBINIT" in os.environ and "NO" == os.environ["NO_LLDBINIT"]:
Expand Down
@@ -0,0 +1 @@
lldb-vscode
@@ -0,0 +1,5 @@
LEVEL = ../../../make

C_SOURCES := main.c

include $(LEVEL)/Makefile.rules
@@ -0,0 +1,176 @@
"""
Test lldb-vscode setBreakpoints request
"""

from __future__ import print_function

import unittest2
import vscode
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbvscode_testcase
import shutil
import subprocess
import tempfile
import threading
import time


def spawn_and_wait(program, delay):
if delay:
time.sleep(delay)
process = subprocess.Popen([program],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
process.wait()


class TestVSCode_attach(lldbvscode_testcase.VSCodeTestCaseBase):

mydir = TestBase.compute_mydir(__file__)

def set_and_hit_breakpoint(self, continueToExit=True):
source = 'main.c'
breakpoint1_line = line_number(source, '// breakpoint 1')
lines = [breakpoint1_line]
# Set breakoint in the thread function so we can step the threads
breakpoint_ids = self.set_source_breakpoints(source, lines)
self.assertTrue(len(breakpoint_ids) == len(lines),
"expect correct number of breakpoints")
self.continue_to_breakpoints(breakpoint_ids)
if continueToExit:
self.continue_to_exit()


@skipIfWindows
@no_debug_info_test
def test_by_pid(self):
'''
Tests attaching to a process by process ID.
'''
self.build_and_create_debug_adaptor()
program = self.getBuildArtifact("a.out")
self.process = subprocess.Popen([program],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
self.attach(pid=self.process.pid)
self.set_and_hit_breakpoint(continueToExit=True)

@skipIfWindows
@no_debug_info_test
def test_by_name(self):
'''
Tests attaching to a process by process name.
'''
self.build_and_create_debug_adaptor()
orig_program = self.getBuildArtifact("a.out")
# Since we are going to attach by process name, we need a unique
# process name that has minimal chance to match a process that is
# already running. To do this we use tempfile.mktemp() to give us a
# full path to a location where we can copy our executable. We then
# run this copy to ensure we don't get the error "more that one
# process matches 'a.out'".
program = tempfile.mktemp()
shutil.copyfile(orig_program, program)
shutil.copymode(orig_program, program)
self.process = subprocess.Popen([program],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# Wait for a bit to ensure the process is launched
time.sleep(1)
self.attach(program=program)
self.set_and_hit_breakpoint(continueToExit=True)

@skipUnlessDarwin
@no_debug_info_test
def test_by_name_waitFor(self):
'''
Tests attaching to a process by process name and waiting for the
next instance of a process to be launched, ingoring all current
ones.
'''
self.build_and_create_debug_adaptor()
program = self.getBuildArtifact("a.out")
self.spawn_thread = threading.Thread(target=spawn_and_wait,
args=(program, 1.0,))
self.spawn_thread.start()
self.attach(program=program, waitFor=True)
self.set_and_hit_breakpoint(continueToExit=True)

@skipIfWindows
@no_debug_info_test
def test_commands(self):
'''
Tests the "initCommands", "preRunCommands", "stopCommands",
"exitCommands", and "attachCommands" that can be passed during
attach.
"initCommands" are a list of LLDB commands that get executed
before the targt is created.
"preRunCommands" are a list of LLDB commands that get executed
after the target has been created and before the launch.
"stopCommands" are a list of LLDB commands that get executed each
time the program stops.
"exitCommands" are a list of LLDB commands that get executed when
the process exits
"attachCommands" are a list of LLDB commands that get executed and
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.
'''
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 -- arg1'
]
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']
self.attach(program=program,
attachCommands=attachCommands,
initCommands=initCommands,
preRunCommands=preRunCommands,
stopCommands=stopCommands,
exitCommands=exitCommands)

# Get output from the console. This should contain both the
# "initCommands" and the "preRunCommands".
output = self.get_console()
# Verify all "initCommands" were found in console output
self.verify_commands('initCommands', output, initCommands)
# Verify all "preRunCommands" were found in console output
self.verify_commands('preRunCommands', output, preRunCommands)

functions = ['main']
breakpoint_ids = self.set_function_breakpoints(functions)
self.assertTrue(len(breakpoint_ids) == len(functions),
"expect one breakpoint")
self.continue_to_breakpoints(breakpoint_ids)
output = self.get_console(timeout=1.0)
self.verify_commands('stopCommands', output, stopCommands)

# Continue after launch and hit the "pause()" call and stop the target.
# Get output from the console. This should contain both the
# "stopCommands" that were run after we stop.
self.vscode.request_continue()
time.sleep(0.5)
self.vscode.request_pause()
self.vscode.wait_for_stopped()
output = self.get_console(timeout=1.0)
self.verify_commands('stopCommands', output, stopCommands)

# Continue until the program exits
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)
self.verify_commands('exitCommands', output, exitCommands)
@@ -0,0 +1,8 @@
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[]) {
printf("pid = %i\n", getpid());
sleep(5);
return 0; // breakpoint 1
}
@@ -0,0 +1,5 @@
LEVEL = ../../../make

CXX_SOURCES := main.cpp

include $(LEVEL)/Makefile.rules

0 comments on commit 2f5cf85

Please sign in to comment.