diff options
author | Greg Clayton <gclayton@apple.com> | 2018-08-16 17:59:38 +0000 |
---|---|---|
committer | Greg Clayton <gclayton@apple.com> | 2018-08-16 17:59:38 +0000 |
commit | 2f5cf8511a3f0ad97db2acf51c6c9ea0ddc9da92 (patch) | |
tree | 43ad29bf650e6570830784ff0aa4cfc2ccab5807 /lldb | |
parent | cecc9f5828615c07192a31c7ba8cfda5d26f3cfc (diff) | |
download | bcm5719-llvm-2f5cf8511a3f0ad97db2acf51c6c9ea0ddc9da92.tar.gz bcm5719-llvm-2f5cf8511a3f0ad97db2acf51c6c9ea0ddc9da92.zip |
Add a new tool named "lldb-vscode" that implements the Visual Studio 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
Diffstat (limited to 'lldb')
48 files changed, 8761 insertions, 6 deletions
diff --git a/lldb/lldb.xcodeproj/project.pbxproj b/lldb/lldb.xcodeproj/project.pbxproj index 66d7c618ed6..1a0ea8f8538 100644 --- a/lldb/lldb.xcodeproj/project.pbxproj +++ b/lldb/lldb.xcodeproj/project.pbxproj @@ -119,6 +119,7 @@ 268900D013353E6F00698AC0 /* Block.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7F1310F1B8EC00F91463 /* Block.cpp */; }; 49DEF1251CD7C6DF006A7C7D /* BlockPointer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 49DEF11F1CD7BD90006A7C7D /* BlockPointer.cpp */; }; 2689FFEF13353DB600698AC0 /* Breakpoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E0A10F1B83100F91463 /* Breakpoint.cpp */; }; + 2660387E211CA98200329572 /* BreakpointBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2660387D211CA98200329572 /* BreakpointBase.cpp */; }; 2689FFF113353DB600698AC0 /* BreakpointID.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E0B10F1B83100F91463 /* BreakpointID.cpp */; }; 2689FFF313353DB600698AC0 /* BreakpointIDList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E0C10F1B83100F91463 /* BreakpointIDList.cpp */; }; 23E2E5321D903832006F38BB /* BreakpointIDTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23E2E52D1D90382B006F38BB /* BreakpointIDTest.cpp */; }; @@ -290,6 +291,7 @@ 9A22A163135E30370024DDC3 /* EmulationStateARM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A22A15F135E30370024DDC3 /* EmulationStateARM.cpp */; }; 6B74D89B200696BB0074051B /* Environment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 22DC561920064C9600A7E9E8 /* Environment.cpp */; }; 2689003D13353E0400698AC0 /* Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E7910F1B85900F91463 /* Event.cpp */; }; + 2660387A211CA90F00329572 /* ExceptionBreakpoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26603874211CA90E00329572 /* ExceptionBreakpoint.cpp */; }; 268900EB13353E6F00698AC0 /* ExecutionContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7F3510F1B90C00F91463 /* ExecutionContext.cpp */; }; 4C88BC2A1BA3722B00AA0964 /* Expression.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C88BC291BA3722B00AA0964 /* Expression.cpp */; }; 49A1CAC51430E8DE00306AC9 /* ExpressionSourceCode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 49A1CAC31430E8BD00306AC9 /* ExpressionSourceCode.cpp */; }; @@ -313,6 +315,7 @@ AF23B4DB19009C66003E2A58 /* FreeBSDSignals.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF23B4D919009C66003E2A58 /* FreeBSDSignals.cpp */; }; 268900D913353E6F00698AC0 /* FuncUnwinders.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 961FABB81235DE1600F93A47 /* FuncUnwinders.cpp */; }; 268900D813353E6F00698AC0 /* Function.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7F1810F1B8EC00F91463 /* Function.cpp */; }; + 2660387C211CA90F00329572 /* FunctionBreakpoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26603877211CA90E00329572 /* FunctionBreakpoint.cpp */; }; 4C2479BD1BA39295009C9A7B /* FunctionCaller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C0083321B9A5DE200D5CF24 /* FunctionCaller.cpp */; }; 2374D7531D4BB2FF005C9575 /* GDBRemoteClientBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2374D74E1D4BB299005C9575 /* GDBRemoteClientBase.cpp */; }; 23CB153A1D66DA9300EDDDE1 /* GDBRemoteClientBaseTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2370A37D1D66C587000E7BE6 /* GDBRemoteClientBaseTest.cpp */; }; @@ -369,6 +372,7 @@ 26BC179A18C7F2B300D2196D /* JITLoaderList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC179818C7F2B300D2196D /* JITLoaderList.cpp */; }; 942829561A89614C00521B30 /* JSON.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942829551A89614C00521B30 /* JSON.cpp */; }; 8C3BD9A01EF5D1FF0016C343 /* JSONTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C3BD99F1EF5D1B50016C343 /* JSONTest.cpp */; }; + 2660387B211CA90F00329572 /* JSONUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26603875211CA90E00329572 /* JSONUtils.cpp */; }; 6D0F61431C80AAAE00A4ECEE /* JavaASTContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D0F61411C80AAAA00A4ECEE /* JavaASTContext.cpp */; }; 6D0F61591C80AB3500A4ECEE /* JavaFormatterFunctions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D0F61511C80AB3000A4ECEE /* JavaFormatterFunctions.cpp */; }; 6D0F615A1C80AB3900A4ECEE /* JavaLanguage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D0F61531C80AB3000A4ECEE /* JavaLanguage.cpp */; }; @@ -376,9 +380,11 @@ 6D0F614F1C80AB0C00A4ECEE /* JavaLanguageRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D0F614B1C80AB0400A4ECEE /* JavaLanguageRuntime.h */; }; 2668035C11601108008E1FE4 /* LLDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26680207115FD0ED008E1FE4 /* LLDB.framework */; }; 2669424D1A6DC32B0063BE93 /* LLDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26680207115FD0ED008E1FE4 /* LLDB.framework */; }; + 26792623211CA42300EE1D10 /* LLDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26680207115FD0ED008E1FE4 /* LLDB.framework */; }; 26B42C4D1187ABA50079C8C8 /* LLDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 26B42C4C1187ABA50079C8C8 /* LLDB.h */; settings = {ATTRIBUTES = (Public, ); }; }; 943BDEFE1AA7B2F800789CE8 /* LLDBAssert.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 943BDEFD1AA7B2F800789CE8 /* LLDBAssert.cpp */; }; 6D762BEE1B1605D2006C929D /* LLDBServerUtilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D762BEC1B1605CD006C929D /* LLDBServerUtilities.cpp */; }; + 26F7619B211CBBB30044F6EA /* LLDBUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26F76199211CBBB30044F6EA /* LLDBUtils.cpp */; }; 2660AAB914622483003A9694 /* LLDBWrapPython.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26A4EEB511682AAC007A372A /* LLDBWrapPython.cpp */; settings = {COMPILER_FLAGS = "-Dregister="; }; }; AEB0E4591BD6E9F800B24093 /* LLVMUserExpression.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEB0E4581BD6E9F800B24093 /* LLVMUserExpression.cpp */; }; 94B638531B8F8E6C004FE1E4 /* Language.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94B638521B8F8E6C004FE1E4 /* Language.cpp */; }; @@ -487,9 +493,6 @@ 8C3BD9961EF45DA50016C343 /* MainThreadCheckerRuntime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C3BD9951EF45D9B0016C343 /* MainThreadCheckerRuntime.cpp */; }; 2689004313353E0400698AC0 /* Mangled.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E8010F1B85900F91463 /* Mangled.cpp */; }; 4F29D3CF21010FA3003B549A /* MangledTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4F29D3CD21010F84003B549A /* MangledTest.cpp */; }; - 4FBC04EF211A06820015A814 /* RichManglingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 4FBC04EE211A06820015A814 /* RichManglingContext.h */; }; - 4FBC04ED211A06200015A814 /* RichManglingContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4FBC04EC211A06200015A814 /* RichManglingContext.cpp */; }; - 4FBC04F5211A13770015A814 /* RichManglingContextTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4FBC04F3211A0F0F0015A814 /* RichManglingContextTest.cpp */; }; 4CD44CFC20B37C440003557C /* ManualDWARFIndex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CD44CF920B37C440003557C /* ManualDWARFIndex.cpp */; }; 49DCF702170E70120092F75E /* Materializer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 49DCF700170E70120092F75E /* Materializer.cpp */; }; 2690B3711381D5C300ECFBAE /* Memory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2690B3701381D5C300ECFBAE /* Memory.cpp */; }; @@ -708,6 +711,9 @@ 23D065901D4A7BEE0008EDE6 /* RenderScriptRuntime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23D065841D4A7BDA0008EDE6 /* RenderScriptRuntime.cpp */; }; 9485545A1DCBAE3B00345FF5 /* RenderScriptScriptGroup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 948554591DCBAE3B00345FF5 /* RenderScriptScriptGroup.cpp */; }; 23D065911D4A7BEE0008EDE6 /* RenderScriptx86ABIFixups.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23D065861D4A7BDA0008EDE6 /* RenderScriptx86ABIFixups.cpp */; }; + 4FBC04ED211A06200015A814 /* RichManglingContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4FBC04EC211A06200015A814 /* RichManglingContext.cpp */; }; + 4FBC04EF211A06820015A814 /* RichManglingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 4FBC04EE211A06820015A814 /* RichManglingContext.h */; }; + 4FBC04F5211A13770015A814 /* RichManglingContextTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4FBC04F3211A0F0F0015A814 /* RichManglingContextTest.cpp */; }; 26DE204511618ADA00A093E2 /* SBAddress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26DE204411618ADA00A093E2 /* SBAddress.cpp */; }; 26DE204311618ACA00A093E2 /* SBAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = 26DE204211618ACA00A093E2 /* SBAddress.h */; settings = {ATTRIBUTES = (Public, ); }; }; 254FBBA51A91670E00BD6378 /* SBAttachInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 254FBBA41A91670E00BD6378 /* SBAttachInfo.cpp */; }; @@ -858,6 +864,7 @@ 23CB15451D66DA9300EDDDE1 /* SocketAddressTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2321F9391BDD332400BA9A93 /* SocketAddressTest.cpp */; }; 23CB153B1D66DA9300EDDDE1 /* SocketTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2321F93A1BDD332400BA9A93 /* SocketTest.cpp */; }; 232CB61D191E00CD00EF39FC /* SoftwareBreakpoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 232CB613191E00CC00EF39FC /* SoftwareBreakpoint.cpp */; }; + 26603879211CA90F00329572 /* SourceBreakpoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26603870211CA90D00329572 /* SourceBreakpoint.cpp */; }; 2689004C13353E0400698AC0 /* SourceManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E8F10F1B85900F91463 /* SourceManager.cpp */; }; 268900F313353E6F00698AC0 /* StackFrame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7F3810F1B90C00F91463 /* StackFrame.cpp */; }; 268900F413353E6F00698AC0 /* StackFrameList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7F3910F1B90C00F91463 /* StackFrameList.cpp */; }; @@ -1002,6 +1009,7 @@ 2654A6901E552ED500DA1013 /* VASprintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2654A68F1E552ED500DA1013 /* VASprintf.cpp */; }; 9A2057031F3A605200F6C293 /* VASprintfTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A3D43C41F3150D200EB767C /* VASprintfTest.cpp */; }; AFC2DCF01E6E2FD200283714 /* VMRange.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AFC2DCEF1E6E2FD200283714 /* VMRange.cpp */; }; + 26603878211CA90F00329572 /* VSCode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2660386E211CA90D00329572 /* VSCode.cpp */; }; 2689005613353E0400698AC0 /* Value.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E9910F1B85900F91463 /* Value.cpp */; }; 2689005713353E0400698AC0 /* ValueObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E9A10F1B85900F91463 /* ValueObject.cpp */; }; 94094C6B163B6F840083A547 /* ValueObjectCast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94094C69163B6CD90083A547 /* ValueObjectCast.cpp */; }; @@ -1050,6 +1058,7 @@ 2656BBC41AE073A800441749 /* libncurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2670F8111862B44A006B332C /* libncurses.dylib */; }; 2670F8121862B44A006B332C /* libncurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2670F8111862B44A006B332C /* libncurses.dylib */; }; 26780C611867C33D00234593 /* libncurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2670F8111862B44A006B332C /* libncurses.dylib */; }; + 26792622211CA41E00EE1D10 /* libncurses.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 26792621211CA41E00EE1D10 /* libncurses.tbd */; }; 26680233115FD1A7008E1FE4 /* libobjc.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 26F5C37410F3F61B009D5894 /* libobjc.dylib */; }; 23CB154A1D66DA9300EDDDE1 /* libpanel.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2326CF4E1BDD687800A5CEAC /* libpanel.dylib */; }; 260157C61885F51C00F875CF /* libpanel.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 260157C41885F4FF00F875CF /* libpanel.dylib */; }; @@ -1074,6 +1083,7 @@ E769331E1A94D18100C73337 /* lldb-server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E769331D1A94D18100C73337 /* lldb-server.cpp */; }; 26680214115FD12C008E1FE4 /* lldb-types.h in Headers */ = {isa = PBXBuildFile; fileRef = 26BC7C2910F1B3BC00F91463 /* lldb-types.h */; settings = {ATTRIBUTES = (Public, ); }; }; 94145431175E63B500284436 /* lldb-versioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 94145430175D7FDE00284436 /* lldb-versioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2679261E211CA3F200EE1D10 /* lldb-vscode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26792619211CA3E100EE1D10 /* lldb-vscode.cpp */; }; AF90106515AB7D3600FF120D /* lldb.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = AF90106315AB7C5700FF120D /* lldb.1 */; }; 2689FFDA13353D9D00698AC0 /* lldb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E7410F1B85900F91463 /* lldb.cpp */; }; AF415AE71D949E4400FCE0D4 /* x86AssemblyInspectionEngine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF415AE51D949E4400FCE0D4 /* x86AssemblyInspectionEngine.cpp */; }; @@ -1123,6 +1133,13 @@ remoteGlobalIDString = 26680206115FD0ED008E1FE4; remoteInfo = LLDB; }; + 2679261F211CA40700EE1D10 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 26680206115FD0ED008E1FE4; + remoteInfo = LLDB; + }; 2687EACA1508115000DD8C2E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -1264,6 +1281,15 @@ name = "Copy Files"; runOnlyForDeploymentPostprocessing = 1; }; + 2679260A211CA3AC00EE1D10 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 940B04E31A89875C0045D5F7 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1427,6 +1453,8 @@ 49DEF1201CD7BD90006A7C7D /* BlockPointer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BlockPointer.h; path = Language/CPlusPlus/BlockPointer.h; sourceTree = "<group>"; }; 26BC7E0A10F1B83100F91463 /* Breakpoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Breakpoint.cpp; path = source/Breakpoint/Breakpoint.cpp; sourceTree = "<group>"; }; 26BC7CEE10F1B71400F91463 /* Breakpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpoint.h; path = include/lldb/Breakpoint/Breakpoint.h; sourceTree = "<group>"; }; + 2660387D211CA98200329572 /* BreakpointBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BreakpointBase.cpp; path = "tools/lldb-vscode/BreakpointBase.cpp"; sourceTree = "<group>"; }; + 26603872211CA90D00329572 /* BreakpointBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BreakpointBase.h; path = "tools/lldb-vscode/BreakpointBase.h"; sourceTree = "<group>"; }; 26BC7E0B10F1B83100F91463 /* BreakpointID.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BreakpointID.cpp; path = source/Breakpoint/BreakpointID.cpp; sourceTree = "<group>"; }; 26BC7CEF10F1B71400F91463 /* BreakpointID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BreakpointID.h; path = include/lldb/Breakpoint/BreakpointID.h; sourceTree = "<group>"; }; 26BC7E0C10F1B83100F91463 /* BreakpointIDList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BreakpointIDList.cpp; path = source/Breakpoint/BreakpointIDList.cpp; sourceTree = "<group>"; }; @@ -1497,6 +1525,7 @@ 23E2E5191D9036F2006F38BB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = "<group>"; }; 23E2E5361D9048FB006F38BB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = "<group>"; }; 2669415B1A6DC2AB0063BE93 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CMakeLists.txt; path = "tools/lldb-mi/CMakeLists.txt"; sourceTree = SOURCE_ROOT; }; + 26792617211CA3E100EE1D10 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CMakeLists.txt; path = "tools/lldb-vscode/CMakeLists.txt"; sourceTree = "<group>"; }; 9A1890311F47D5D400394BCA /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CMakeLists.txt; path = TestingSupport/CMakeLists.txt; sourceTree = "<group>"; }; 4CB443BC1249920C00C13DC2 /* CPPLanguageRuntime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPPLanguageRuntime.cpp; path = source/Target/CPPLanguageRuntime.cpp; sourceTree = "<group>"; }; 4CB443BB1249920C00C13DC2 /* CPPLanguageRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CPPLanguageRuntime.h; path = include/lldb/Target/CPPLanguageRuntime.h; sourceTree = "<group>"; }; @@ -1796,6 +1825,8 @@ 6B74D89C2006972D0074051B /* Environment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Environment.h; path = include/lldb/Utility/Environment.h; sourceTree = "<group>"; }; 26BC7E7910F1B85900F91463 /* Event.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Event.cpp; path = source/Core/Event.cpp; sourceTree = "<group>"; }; 26BC7D6110F1B77400F91463 /* Event.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Event.h; path = include/lldb/Core/Event.h; sourceTree = "<group>"; }; + 26603874211CA90E00329572 /* ExceptionBreakpoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ExceptionBreakpoint.cpp; path = "tools/lldb-vscode/ExceptionBreakpoint.cpp"; sourceTree = "<group>"; }; + 26603873211CA90E00329572 /* ExceptionBreakpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ExceptionBreakpoint.h; path = "tools/lldb-vscode/ExceptionBreakpoint.h"; sourceTree = "<group>"; }; 26BC7F3510F1B90C00F91463 /* ExecutionContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ExecutionContext.cpp; path = source/Target/ExecutionContext.cpp; sourceTree = "<group>"; }; 26BC7DF210F1B81A00F91463 /* ExecutionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ExecutionContext.h; path = include/lldb/Target/ExecutionContext.h; sourceTree = "<group>"; }; 26DAFD9711529BC7005A394E /* ExecutionContextScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ExecutionContextScope.h; path = include/lldb/Target/ExecutionContextScope.h; sourceTree = "<group>"; }; @@ -1847,6 +1878,8 @@ 269FF07D12494F7D00225026 /* FuncUnwinders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FuncUnwinders.h; path = include/lldb/Symbol/FuncUnwinders.h; sourceTree = "<group>"; }; 26BC7F1810F1B8EC00F91463 /* Function.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Function.cpp; path = source/Symbol/Function.cpp; sourceTree = "<group>"; }; 26BC7C5A10F1B6E900F91463 /* Function.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Function.h; path = include/lldb/Symbol/Function.h; sourceTree = "<group>"; }; + 26603877211CA90E00329572 /* FunctionBreakpoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FunctionBreakpoint.cpp; path = "tools/lldb-vscode/FunctionBreakpoint.cpp"; sourceTree = "<group>"; }; + 26603871211CA90D00329572 /* FunctionBreakpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FunctionBreakpoint.h; path = "tools/lldb-vscode/FunctionBreakpoint.h"; sourceTree = "<group>"; }; 4C0083321B9A5DE200D5CF24 /* FunctionCaller.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FunctionCaller.cpp; path = source/Expression/FunctionCaller.cpp; sourceTree = "<group>"; }; 4C00832D1B9A58A700D5CF24 /* FunctionCaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FunctionCaller.h; path = include/lldb/Expression/FunctionCaller.h; sourceTree = "<group>"; }; 2374D74E1D4BB299005C9575 /* GDBRemoteClientBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GDBRemoteClientBase.cpp; sourceTree = "<group>"; }; @@ -1977,6 +2010,8 @@ 942829551A89614C00521B30 /* JSON.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JSON.cpp; path = source/Utility/JSON.cpp; sourceTree = "<group>"; }; 942829541A89614000521B30 /* JSON.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JSON.h; path = include/lldb/Utility/JSON.h; sourceTree = "<group>"; }; 8C3BD99F1EF5D1B50016C343 /* JSONTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSONTest.cpp; sourceTree = "<group>"; }; + 26603875211CA90E00329572 /* JSONUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JSONUtils.cpp; path = "tools/lldb-vscode/JSONUtils.cpp"; sourceTree = "<group>"; }; + 26603876211CA90E00329572 /* JSONUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSONUtils.h; path = "tools/lldb-vscode/JSONUtils.h"; sourceTree = "<group>"; }; 6D0F61411C80AAAA00A4ECEE /* JavaASTContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JavaASTContext.cpp; path = source/Symbol/JavaASTContext.cpp; sourceTree = "<group>"; }; 6D0F613D1C80AA8900A4ECEE /* JavaASTContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JavaASTContext.h; path = include/lldb/Symbol/JavaASTContext.h; sourceTree = "<group>"; }; 6D0F61511C80AB3000A4ECEE /* JavaFormatterFunctions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JavaFormatterFunctions.cpp; path = Language/Java/JavaFormatterFunctions.cpp; sourceTree = "<group>"; }; @@ -1993,6 +2028,8 @@ 6D762BEC1B1605CD006C929D /* LLDBServerUtilities.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = LLDBServerUtilities.cpp; path = "tools/lldb-server/LLDBServerUtilities.cpp"; sourceTree = "<group>"; }; 6D762BED1B1605CD006C929D /* LLDBServerUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = LLDBServerUtilities.h; path = "tools/lldb-server/LLDBServerUtilities.h"; sourceTree = "<group>"; }; 2361029A1CF38A2B00B8E0B9 /* LLDBStandalone.cmake */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LLDBStandalone.cmake; sourceTree = "<group>"; }; + 26F76199211CBBB30044F6EA /* LLDBUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LLDBUtils.cpp; path = "tools/lldb-vscode/LLDBUtils.cpp"; sourceTree = "<group>"; }; + 26F7619A211CBBB30044F6EA /* LLDBUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LLDBUtils.h; path = "tools/lldb-vscode/LLDBUtils.h"; sourceTree = "<group>"; }; 26A4EEB511682AAC007A372A /* LLDBWrapPython.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = LLDBWrapPython.cpp; sourceTree = BUILT_PRODUCTS_DIR; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; AEB0E4581BD6E9F800B24093 /* LLVMUserExpression.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LLVMUserExpression.cpp; path = source/Expression/LLVMUserExpression.cpp; sourceTree = "<group>"; }; AEB0E45A1BD6EA1400B24093 /* LLVMUserExpression.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = LLVMUserExpression.h; path = include/lldb/Expression/LLVMUserExpression.h; sourceTree = "<group>"; }; @@ -2199,9 +2236,6 @@ 26BC7E8010F1B85900F91463 /* Mangled.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mangled.cpp; path = source/Core/Mangled.cpp; sourceTree = "<group>"; }; 26BC7D6910F1B77400F91463 /* Mangled.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Mangled.h; path = include/lldb/Core/Mangled.h; sourceTree = "<group>"; }; 4F29D3CD21010F84003B549A /* MangledTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MangledTest.cpp; sourceTree = "<group>"; }; - 4FBC04EE211A06820015A814 /* RichManglingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RichManglingContext.h; path = include/lldb/Core/RichManglingContext.h; sourceTree = "<group>"; }; - 4FBC04EC211A06200015A814 /* RichManglingContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RichManglingContext.cpp; path = source/Core/RichManglingContext.cpp; sourceTree = "<group>"; }; - 4FBC04F3211A0F0F0015A814 /* RichManglingContextTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RichManglingContextTest.cpp; sourceTree = "<group>"; }; 4CD44CF920B37C440003557C /* ManualDWARFIndex.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ManualDWARFIndex.cpp; sourceTree = "<group>"; }; 4CD44D0020B37C580003557C /* ManualDWARFIndex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ManualDWARFIndex.h; sourceTree = "<group>"; }; 2682100C143A59AE004BCF2D /* MappedHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MappedHash.h; path = include/lldb/Core/MappedHash.h; sourceTree = "<group>"; }; @@ -2638,6 +2672,9 @@ 948554581DCBAE3200345FF5 /* RenderScriptScriptGroup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RenderScriptScriptGroup.h; sourceTree = "<group>"; }; 23D065861D4A7BDA0008EDE6 /* RenderScriptx86ABIFixups.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderScriptx86ABIFixups.cpp; sourceTree = "<group>"; }; 23D065871D4A7BDA0008EDE6 /* RenderScriptx86ABIFixups.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderScriptx86ABIFixups.h; sourceTree = "<group>"; }; + 4FBC04EC211A06200015A814 /* RichManglingContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RichManglingContext.cpp; path = source/Core/RichManglingContext.cpp; sourceTree = "<group>"; }; + 4FBC04EE211A06820015A814 /* RichManglingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RichManglingContext.h; path = include/lldb/Core/RichManglingContext.h; sourceTree = "<group>"; }; + 4FBC04F3211A0F0F0015A814 /* RichManglingContextTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RichManglingContextTest.cpp; sourceTree = "<group>"; }; 26DE204411618ADA00A093E2 /* SBAddress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SBAddress.cpp; path = source/API/SBAddress.cpp; sourceTree = "<group>"; }; 26DE204211618ACA00A093E2 /* SBAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBAddress.h; path = include/lldb/API/SBAddress.h; sourceTree = "<group>"; }; 2611FEEF142D83060017FEA3 /* SBAddress.i */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c.preprocessed; path = SBAddress.i; sourceTree = "<group>"; }; @@ -2867,8 +2904,11 @@ 2321F93A1BDD332400BA9A93 /* SocketTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SocketTest.cpp; sourceTree = "<group>"; }; 232CB613191E00CC00EF39FC /* SoftwareBreakpoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SoftwareBreakpoint.cpp; path = source/Host/common/SoftwareBreakpoint.cpp; sourceTree = "<group>"; }; 267A47F21B14115A0021A5BC /* SoftwareBreakpoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SoftwareBreakpoint.h; path = include/lldb/Host/common/SoftwareBreakpoint.h; sourceTree = "<group>"; }; + 26603870211CA90D00329572 /* SourceBreakpoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SourceBreakpoint.cpp; path = "tools/lldb-vscode/SourceBreakpoint.cpp"; sourceTree = "<group>"; }; + 2660386D211CA90C00329572 /* SourceBreakpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SourceBreakpoint.h; path = "tools/lldb-vscode/SourceBreakpoint.h"; sourceTree = "<group>"; }; 26BC7E8F10F1B85900F91463 /* SourceManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SourceManager.cpp; path = source/Core/SourceManager.cpp; sourceTree = "<group>"; }; 26BC7D7610F1B77400F91463 /* SourceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SourceManager.h; path = include/lldb/Core/SourceManager.h; sourceTree = "<group>"; }; + 26F76198211CB8870044F6EA /* SourceReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SourceReference.h; path = "tools/lldb-vscode/SourceReference.h"; sourceTree = "<group>"; }; 26BC7F3810F1B90C00F91463 /* StackFrame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StackFrame.cpp; path = source/Target/StackFrame.cpp; sourceTree = "<group>"; }; 26BC7DF510F1B81A00F91463 /* StackFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StackFrame.h; path = include/lldb/Target/StackFrame.h; sourceTree = "<group>"; }; 26BC7F3910F1B90C00F91463 /* StackFrameList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StackFrameList.cpp; path = source/Target/StackFrameList.cpp; sourceTree = "<group>"; }; @@ -3135,6 +3175,9 @@ 9A3D43C41F3150D200EB767C /* VASprintfTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VASprintfTest.cpp; sourceTree = "<group>"; }; AFC2DCEF1E6E2FD200283714 /* VMRange.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VMRange.cpp; path = source/Utility/VMRange.cpp; sourceTree = "<group>"; }; AFC2DCF11E6E2FDA00283714 /* VMRange.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = VMRange.h; path = include/lldb/Utility/VMRange.h; sourceTree = "<group>"; }; + 2660386E211CA90D00329572 /* VSCode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VSCode.cpp; path = "tools/lldb-vscode/VSCode.cpp"; sourceTree = "<group>"; }; + 2660386F211CA90D00329572 /* VSCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VSCode.h; path = "tools/lldb-vscode/VSCode.h"; sourceTree = "<group>"; }; + 26F7619C211CDD700044F6EA /* VSCodeForward.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VSCodeForward.h; path = "tools/lldb-vscode/VSCodeForward.h"; sourceTree = "<group>"; }; 26BC7E9910F1B85900F91463 /* Value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Value.cpp; path = source/Core/Value.cpp; sourceTree = "<group>"; }; 26BC7D8110F1B77400F91463 /* Value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Value.h; path = include/lldb/Core/Value.h; sourceTree = "<group>"; }; 26BC7E9A10F1B85900F91463 /* ValueObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ValueObject.cpp; path = source/Core/ValueObject.cpp; sourceTree = "<group>"; }; @@ -3217,6 +3260,7 @@ 2326CF471BDD67C100A5CEAC /* libncurses.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libncurses.dylib; path = ../../../../../../usr/lib/libncurses.dylib; sourceTree = "<group>"; }; 239481851C59EBDD00DF7168 /* libncurses.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libncurses.dylib; path = ../../../../../usr/lib/libncurses.dylib; sourceTree = "<group>"; }; 2670F8111862B44A006B332C /* libncurses.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libncurses.dylib; path = /usr/lib/libncurses.dylib; sourceTree = "<absolute>"; }; + 26792621211CA41E00EE1D10 /* libncurses.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libncurses.tbd; path = usr/lib/libncurses.tbd; sourceTree = SDKROOT; }; 26F5C37410F3F61B009D5894 /* libobjc.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libobjc.dylib; path = /usr/lib/libobjc.dylib; sourceTree = "<absolute>"; }; 2326CF4E1BDD687800A5CEAC /* libpanel.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpanel.dylib; path = ../../../../../../usr/lib/libpanel.dylib; sourceTree = "<group>"; }; 260157C41885F4FF00F875CF /* libpanel.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpanel.dylib; path = /usr/lib/libpanel.dylib; sourceTree = "<absolute>"; }; @@ -3250,11 +3294,15 @@ E769331D1A94D18100C73337 /* lldb-server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "lldb-server.cpp"; path = "tools/lldb-server/lldb-server.cpp"; sourceTree = "<group>"; }; 26BC7C2910F1B3BC00F91463 /* lldb-types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "lldb-types.h"; path = "include/lldb/lldb-types.h"; sourceTree = "<group>"; }; 94145430175D7FDE00284436 /* lldb-versioning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "lldb-versioning.h"; path = "include/lldb/lldb-versioning.h"; sourceTree = "<group>"; }; + 2679260C211CA3AC00EE1D10 /* lldb-vscode */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "lldb-vscode"; sourceTree = BUILT_PRODUCTS_DIR; }; + 26792618211CA3E100EE1D10 /* lldb-vscode-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "lldb-vscode-Info.plist"; path = "tools/lldb-vscode/lldb-vscode-Info.plist"; sourceTree = "<group>"; }; + 26792619211CA3E100EE1D10 /* lldb-vscode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "lldb-vscode.cpp"; path = "tools/lldb-vscode/lldb-vscode.cpp"; sourceTree = "<group>"; }; 23173F8B192BA93F005C708F /* lldb-x86-register-enums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "lldb-x86-register-enums.h"; path = "Utility/lldb-x86-register-enums.h"; sourceTree = "<group>"; }; AF90106315AB7C5700FF120D /* lldb.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = lldb.1; path = docs/lldb.1; sourceTree = "<group>"; }; 26BC7E7410F1B85900F91463 /* lldb.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lldb.cpp; path = source/lldb.cpp; sourceTree = "<group>"; }; 2669605E1199F4230075C61A /* lldb.swig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = lldb.swig; sourceTree = "<group>"; }; 94E367CC140C4EC4001C7A5A /* modify-python-lldb.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = "modify-python-lldb.py"; sourceTree = "<group>"; }; + 26792616211CA3E100EE1D10 /* package.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = package.json; path = "tools/lldb-vscode/package.json"; sourceTree = "<group>"; }; 9A48A3A7124AAA5A00922451 /* python-extensions.swig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "python-extensions.swig"; sourceTree = "<group>"; }; 944DC3481774C99000D7D884 /* python-swigsafecast.swig */ = {isa = PBXFileReference; lastKnownFileType = text; path = "python-swigsafecast.swig"; sourceTree = "<group>"; }; 94E367CE140C4EEA001C7A5A /* python-typemaps.swig */ = {isa = PBXFileReference; lastKnownFileType = text; path = "python-typemaps.swig"; sourceTree = "<group>"; }; @@ -3322,6 +3370,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 26792609211CA3AC00EE1D10 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 26792623211CA42300EE1D10 /* LLDB.framework in Frameworks */, + 26792622211CA41E00EE1D10 /* libncurses.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2689FFC713353D7A00698AC0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -3437,6 +3494,7 @@ 942829C01A89835300521B30 /* lldb-argdumper */, 239504D41BDD451400963CEA /* lldb-gtest */, 23CB15561D66DA9300EDDDE1 /* lldb-gtest */, + 2679260C211CA3AC00EE1D10 /* lldb-vscode */, ); name = Products; sourceTree = "<group>"; @@ -3672,6 +3730,7 @@ 23CB14E21D66CA2200EDDDE1 /* Frameworks */ = { isa = PBXGroup; children = ( + 26792621211CA41E00EE1D10 /* libncurses.tbd */, 23CB14E31D66CA2200EDDDE1 /* libxml2.2.dylib */, ); name = Frameworks; @@ -4469,6 +4528,33 @@ path = OperatingSystem; sourceTree = "<group>"; }; + 26792615211CA3C100EE1D10 /* lldb-vscode */ = { + isa = PBXGroup; + children = ( + 26792617211CA3E100EE1D10 /* CMakeLists.txt */, + 26792618211CA3E100EE1D10 /* lldb-vscode-Info.plist */, + 26792619211CA3E100EE1D10 /* lldb-vscode.cpp */, + 26792616211CA3E100EE1D10 /* package.json */, + 2660387D211CA98200329572 /* BreakpointBase.cpp */, + 26603872211CA90D00329572 /* BreakpointBase.h */, + 26603874211CA90E00329572 /* ExceptionBreakpoint.cpp */, + 26603873211CA90E00329572 /* ExceptionBreakpoint.h */, + 26603877211CA90E00329572 /* FunctionBreakpoint.cpp */, + 26603871211CA90D00329572 /* FunctionBreakpoint.h */, + 26603875211CA90E00329572 /* JSONUtils.cpp */, + 26603876211CA90E00329572 /* JSONUtils.h */, + 26F76199211CBBB30044F6EA /* LLDBUtils.cpp */, + 26F7619A211CBBB30044F6EA /* LLDBUtils.h */, + 26603870211CA90D00329572 /* SourceBreakpoint.cpp */, + 2660386D211CA90C00329572 /* SourceBreakpoint.h */, + 26F76198211CB8870044F6EA /* SourceReference.h */, + 2660386E211CA90D00329572 /* VSCode.cpp */, + 2660386F211CA90D00329572 /* VSCode.h */, + 26F7619C211CDD700044F6EA /* VSCodeForward.h */, + ); + name = "lldb-vscode"; + sourceTree = "<group>"; + }; 267F68461CC02DED0086832B /* SysV-s390x */ = { isa = PBXGroup; children = ( @@ -5844,6 +5930,7 @@ isa = PBXGroup; children = ( E769331B1A94D10E00C73337 /* lldb-server */, + 26792615211CA3C100EE1D10 /* lldb-vscode */, 942829BA1A89830900521B30 /* argdumper */, 26579F55126A255E0007C5CB /* darwin-debug */, 265E9BE0115C2B8500D0DCCB /* debugserver */, @@ -7098,6 +7185,24 @@ productReference = 26680207115FD0ED008E1FE4 /* LLDB.framework */; productType = "com.apple.product-type.framework"; }; + 2679260B211CA3AC00EE1D10 /* lldb-vscode */ = { + isa = PBXNativeTarget; + buildConfigurationList = 26792614211CA3AD00EE1D10 /* Build configuration list for PBXNativeTarget "lldb-vscode" */; + buildPhases = ( + 26792608211CA3AC00EE1D10 /* Sources */, + 26792609211CA3AC00EE1D10 /* Frameworks */, + 2679260A211CA3AC00EE1D10 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 26792620211CA40700EE1D10 /* PBXTargetDependency */, + ); + name = "lldb-vscode"; + productName = "lldb-vscode"; + productReference = 2679260C211CA3AC00EE1D10 /* lldb-vscode */; + productType = "com.apple.product-type.tool"; + }; 2689FFC913353D7A00698AC0 /* lldb-core */ = { isa = PBXNativeTarget; buildConfigurationList = 2689FFD813353D7A00698AC0 /* Build configuration list for PBXNativeTarget "lldb-core" */; @@ -7202,6 +7307,9 @@ 239504D31BDD451400963CEA = { CreatedOnToolsVersion = 7.1; }; + 2679260B211CA3AC00EE1D10 = { + CreatedOnToolsVersion = 9.4.1; + }; 2690CD161A6DC0D000E717C8 = { CreatedOnToolsVersion = 6.3; }; @@ -7242,6 +7350,7 @@ 2687EAC51508110B00DD8C2E /* install-headers */, 2690CD161A6DC0D000E717C8 /* lldb-mi */, 942829BF1A89835300521B30 /* lldb-argdumper */, + 2679260B211CA3AC00EE1D10 /* lldb-vscode */, ); }; /* End PBXProject section */ @@ -7588,6 +7697,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 26792608211CA3AC00EE1D10 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2679261E211CA3F200EE1D10 /* lldb-vscode.cpp in Sources */, + 2660387E211CA98200329572 /* BreakpointBase.cpp in Sources */, + 26603879211CA90F00329572 /* SourceBreakpoint.cpp in Sources */, + 2660387A211CA90F00329572 /* ExceptionBreakpoint.cpp in Sources */, + 2660387B211CA90F00329572 /* JSONUtils.cpp in Sources */, + 26F7619B211CBBB30044F6EA /* LLDBUtils.cpp in Sources */, + 2660387C211CA90F00329572 /* FunctionBreakpoint.cpp in Sources */, + 26603878211CA90F00329572 /* VSCode.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2689FFC613353D7A00698AC0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -8384,6 +8508,11 @@ target = 26680206115FD0ED008E1FE4 /* LLDB */; targetProxy = 266803611160110D008E1FE4 /* PBXContainerItemProxy */; }; + 26792620211CA40700EE1D10 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 26680206115FD0ED008E1FE4 /* LLDB */; + targetProxy = 2679261F211CA40700EE1D10 /* PBXContainerItemProxy */; + }; 2687EACB1508115000DD8C2E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 2687EAC51508110B00DD8C2E /* install-headers */; @@ -9273,6 +9402,212 @@ }; name = Release; }; + 26792610211CA3AD00EE1D10 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CURRENT_PROJECT_VERSION = 360.99.0; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/tools/lldb-vscode/lldb-vscode-Info.plist", + "-Wl,-rpath,@loader_path/", + "-filelist", + "$(LLVM_BUILD_DIR)/archives.txt", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/include $(SRCROOT)/source $(LLVM_SOURCE_DIR)/include $(LLVM_SOURCE_DIR)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/lib/Target/ARM"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 26792611211CA3AD00EE1D10 /* DebugClang */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CURRENT_PROJECT_VERSION = 360.99.0; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/tools/lldb-vscode/lldb-vscode-Info.plist", + "-Wl,-rpath,@loader_path/", + "-filelist", + "$(LLVM_BUILD_DIR)/archives.txt", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/include $(SRCROOT)/source $(LLVM_SOURCE_DIR)/include $(LLVM_SOURCE_DIR)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/lib/Target/ARM"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = DebugClang; + }; + 26792612211CA3AD00EE1D10 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CURRENT_PROJECT_VERSION = 360.99.0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/tools/lldb-vscode/lldb-vscode-Info.plist", + "-Wl,-rpath,@loader_path/", + "-filelist", + "$(LLVM_BUILD_DIR)/archives.txt", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/include $(SRCROOT)/source $(LLVM_SOURCE_DIR)/include $(LLVM_SOURCE_DIR)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/lib/Target/ARM"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 26792613211CA3AD00EE1D10 /* BuildAndIntegration */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 360.99.0; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MTL_ENABLE_DEBUG_INFO = NO; + "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/tools/lldb-vscode/lldb-vscode-Info.plist", + "-Wl,-rpath,@loader_path/../../../System/Library/PrivateFrameworks", + "-filelist", + "$(LLVM_BUILD_DIR)/archives.txt", + ); + "OTHER_LDFLAGS[sdk=macosx*]" = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/tools/lldb-vscode/lldb-vscode-Info.plist", + "-Wl,-rpath,@loader_path/../../Library/PrivateFrameworks/", + "-Wl,-rpath,@loader_path/../../../SharedFrameworks", + "-Wl,-rpath,@loader_path/../../System/Library/PrivateFrameworks", + "-Wl,-rpath,@loader_path/../../Library/PrivateFrameworks", + "-filelist", + "$(LLVM_BUILD_DIR)/archives.txt", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + STRIP_INSTALLED_PRODUCT = YES; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/include $(SRCROOT)/source $(LLVM_SOURCE_DIR)/include $(LLVM_SOURCE_DIR)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/tools/clang/include $(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)/lib/Target/ARM"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = BuildAndIntegration; + }; 2687EAC71508110B00DD8C2E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -10966,6 +11301,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = BuildAndIntegration; }; + 26792614211CA3AD00EE1D10 /* Build configuration list for PBXNativeTarget "lldb-vscode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 26792610211CA3AD00EE1D10 /* Debug */, + 26792611211CA3AD00EE1D10 /* DebugClang */, + 26792612211CA3AD00EE1D10 /* Release */, + 26792613211CA3AD00EE1D10 /* BuildAndIntegration */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = BuildAndIntegration; + }; 2687EAC61508110B00DD8C2E /* Build configuration list for PBXLegacyTarget "install-headers" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/lldb/packages/Python/lldbsuite/test/dotest.py b/lldb/packages/Python/lldbsuite/test/dotest.py index d28c5ef069b..83616d1214b 100644 --- a/lldb/packages/Python/lldbsuite/test/dotest.py +++ b/lldb/packages/Python/lldbsuite/test/dotest.py @@ -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 @@ -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 @@ -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") diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index b287a0feea1..ed9862c6192 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -740,6 +740,11 @@ class Base(unittest2.TestCase): 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"]: diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/.categories b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/.categories new file mode 100644 index 00000000000..ce2cfd048e3 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/.categories @@ -0,0 +1 @@ +lldb-vscode diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile new file mode 100644 index 00000000000..b09a579159d --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py new file mode 100644 index 00000000000..939025dfd1c --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py @@ -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) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c new file mode 100644 index 00000000000..a078d42203e --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c @@ -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 +} diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile new file mode 100644 index 00000000000..314f1cb2f07 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py new file mode 100644 index 00000000000..4a54ec4f6d7 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py @@ -0,0 +1,209 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_setBreakpoints(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIfWindows + @no_debug_info_test + 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 + is no "clearBreakpoints" packet. Source file and line breakpoints + are set by sending a "setBreakpoints" packet with a source file + specified and zero or more source lines. If breakpoints have been + set in the source file before, any exising breakpoints must remain + set, and any new breakpoints must be created, and any breakpoints + that were in previous requests and are not in the current request + must be removed. This function tests this setting and clearing + 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') + lines = [first_line, second_line, third_line] + + # Visual Studio Code Debug Adaptors have no way to specify the file + # without launching or attaching to a process, so we must start a + # process in order to be able to set breakpoints. + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Set 3 breakoints and verify that they got set correctly + response = self.vscode.request_setBreakpoints(source_path, lines) + line_to_id = {} + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(lines), + "expect %u source breakpoints" % (len(lines))) + for breakpoint in breakpoints: + line = breakpoint['line'] + # Store the "id" of the breakpoint that was set for later + line_to_id[line] = breakpoint['id'] + self.assertTrue(line in lines, "line expected in lines array") + self.assertTrue(breakpoint['verified'], + "expect breakpoint verified") + + # There is no breakpoint delete packet, clients just send another + # setBreakpoints packet with the same source file with fewer lines. + # Below we remove the second line entry and call the setBreakpoints + # function again. We want to verify that any breakpoints that were set + # before still have the same "id". This means we didn't clear the + # breakpoint and set it again at the same location. We also need to + # verify that the second line location was actually removed. + lines.remove(second_line) + # Set 2 breakoints and verify that the previous breakoints that were + # set above are still set. + response = self.vscode.request_setBreakpoints(source_path, lines) + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(lines), + "expect %u source breakpoints" % (len(lines))) + for breakpoint in breakpoints: + line = breakpoint['line'] + # Verify the same breakpoints are still set within LLDB by + # making sure the breakpoint ID didn't change + self.assertTrue(line_to_id[line] == breakpoint['id'], + "verify previous breakpoints stayed the same") + self.assertTrue(line in lines, "line expected in lines array") + self.assertTrue(breakpoint['verified'], + "expect breakpoint still verified") + + # Now get the full list of breakpoints set in the target and verify + # we have only 2 breakpoints set. The response above could have told + # us about 2 breakpoints, but we want to make sure we don't have the + # third one still set in the target + response = self.vscode.request_testGetTargetBreakpoints() + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(lines), + "expect %u source breakpoints" % (len(lines))) + for breakpoint in breakpoints: + line = breakpoint['line'] + # Verify the same breakpoints are still set within LLDB by + # making sure the breakpoint ID didn't change + self.assertTrue(line_to_id[line] == breakpoint['id'], + "verify previous breakpoints stayed the same") + self.assertTrue(line in lines, "line expected in lines array") + self.assertTrue(breakpoint['verified'], + "expect breakpoint still verified") + + # Now clear all breakpoints for the source file by passing down an + # empty lines array + lines = [] + response = self.vscode.request_setBreakpoints(source_path, lines) + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(lines), + "expect %u source breakpoints" % (len(lines))) + + # Verify with the target that all breakpoints have been cleared + response = self.vscode.request_testGetTargetBreakpoints() + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(lines), + "expect %u source breakpoints" % (len(lines))) + + # 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) + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(lines), + "expect %u source breakpoints" % (len(lines))) + for breakpoint in breakpoints: + line = breakpoint['line'] + self.assertTrue(line in lines, "line expected in lines array") + self.assertTrue(breakpoint['verified'], + "expect breakpoint still verified") + + # Now get the full list of breakpoints set in the target and verify + # we have only 2 breakpoints set. The response above could have told + # us about 2 breakpoints, but we want to make sure we don't have the + # third one still set in the target + response = self.vscode.request_testGetTargetBreakpoints() + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(lines), + "expect %u source breakpoints" % (len(lines))) + for breakpoint in breakpoints: + line = breakpoint['line'] + self.assertTrue(line in lines, "line expected in lines array") + self.assertTrue(breakpoint['verified'], + "expect breakpoint still verified") + + @skipIfWindows + @no_debug_info_test + 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]) + self.assertTrue(len(breakpoint_ids) == 1, "expect one breakpoint") + self.vscode.request_continue() + + # Verify we hit the breakpoint we just set + self.verify_breakpoint_hit(breakpoint_ids) + + # Make sure i is zero at first breakpoint + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 0, 'i != 0 after hitting breakpoint') + + # Update the condition on our breakpoint + new_breakpoint_ids = self.set_source_breakpoints(source_path, + [loop_line], + condition="i==4") + self.assertTrue(breakpoint_ids == new_breakpoint_ids, + "existing breakpoint should have its condition " + "updated") + + self.continue_to_breakpoints(breakpoint_ids) + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 4, + 'i != 4 showing conditional works') + + new_breakpoint_ids = self.set_source_breakpoints(source_path, + [loop_line], + hitCondition="2") + + self.assertTrue(breakpoint_ids == new_breakpoint_ids, + "existing breakpoint should have its condition " + "updated") + + # Continue with a hitContidtion of 2 and expect it to skip 1 value + self.continue_to_breakpoints(breakpoint_ids) + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 6, + 'i != 6 showing hitCondition works') + + # continue after hitting our hitCondition and make sure it only goes + # up by 1 + self.continue_to_breakpoints(breakpoint_ids) + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 7, + 'i != 7 showing post hitCondition hits every time') diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py new file mode 100644 index 00000000000..17c87de0880 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py @@ -0,0 +1,50 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_setExceptionBreakpoints( + lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIfWindows + @no_debug_info_test + def test_functionality(self): + '''Tests setting and clearing exception breakpoints. + This packet is a bit tricky on the debug adaptor side since there + is no "clear exception breakpoints" packet. Exception breakpoints + are set by sending a "setExceptionBreakpoints" packet with zero or + more exception filters. If exception breakpoints have been set + before, any exising breakpoints must remain set, and any new + breakpoints must be created, and any breakpoints that were in + previous requests and are not in the current request must be + removed. This exception tests this setting and clearing and makes + sure things happen correctly. It doesn't test hitting breakpoints + and the functionality of each breakpoint, like 'conditions' and + x'hitCondition' settings. + ''' + # Visual Studio Code Debug Adaptors have no way to specify the file + # without launching or attaching to a process, so we must start a + # process in order to be able to set breakpoints. + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + filters = ['cpp_throw', 'cpp_catch'] + response = self.vscode.request_setExceptionBreakpoints(filters) + if response: + self.assertTrue(response['success']) + + self.continue_to_exception_breakpoint('C++ Throw') + self.continue_to_exception_breakpoint('C++ Catch') diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py new file mode 100644 index 00000000000..8f07cb07dcf --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py @@ -0,0 +1,164 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os + + +class TestVSCode_setFunctionBreakpoints( + lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIfWindows + @no_debug_info_test + def test_set_and_clear(self): + '''Tests setting and clearing function breakpoints. + This packet is a bit tricky on the debug adaptor side since there + is no "clearFunction Breakpoints" packet. Function breakpoints + are set by sending a "setFunctionBreakpoints" packet with zero or + more function names. If function breakpoints have been set before, + any exising breakpoints must remain set, and any new breakpoints + must be created, and any breakpoints that were in previous requests + and are not in the current request must be removed. This function + tests this setting and clearing and makes sure things happen + correctly. It doesn't test hitting breakpoints and the functionality + of each breakpoint, like 'conditions' and 'hitCondition' settings. + ''' + # Visual Studio Code Debug Adaptors have no way to specify the file + # without launching or attaching to a process, so we must start a + # process in order to be able to set breakpoints. + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + bp_id_12 = None + functions = ['twelve'] + # Set a function breakpoint at 'twelve' + response = self.vscode.request_setFunctionBreakpoints(functions) + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(functions), + "expect %u source breakpoints" % (len(functions))) + for breakpoint in breakpoints: + bp_id_12 = breakpoint['id'] + self.assertTrue(breakpoint['verified'], + "expect breakpoint verified") + + # Add an extra name and make sure we have two breakpoints after this + functions.append('thirteen') + response = self.vscode.request_setFunctionBreakpoints(functions) + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(functions), + "expect %u source breakpoints" % (len(functions))) + for breakpoint in breakpoints: + self.assertTrue(breakpoint['verified'], + "expect breakpoint verified") + + # There is no breakpoint delete packet, clients just send another + # setFunctionBreakpoints packet with the different function names. + functions.remove('thirteen') + response = self.vscode.request_setFunctionBreakpoints(functions) + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(functions), + "expect %u source breakpoints" % (len(functions))) + for breakpoint in breakpoints: + bp_id = breakpoint['id'] + self.assertTrue(bp_id == bp_id_12, + 'verify "twelve" breakpoint ID is same') + self.assertTrue(breakpoint['verified'], + "expect breakpoint still verified") + + # Now get the full list of breakpoints set in the target and verify + # we have only 1 breakpoints set. The response above could have told + # us about 1 breakpoints, but we want to make sure we don't have the + # second one still set in the target + response = self.vscode.request_testGetTargetBreakpoints() + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(functions), + "expect %u source breakpoints" % (len(functions))) + for breakpoint in breakpoints: + bp_id = breakpoint['id'] + self.assertTrue(bp_id == bp_id_12, + 'verify "twelve" breakpoint ID is same') + self.assertTrue(breakpoint['verified'], + "expect breakpoint still verified") + + # Now clear all breakpoints for the source file by passing down an + # empty lines array + functions = [] + response = self.vscode.request_setFunctionBreakpoints(functions) + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(functions), + "expect %u source breakpoints" % (len(functions))) + + # Verify with the target that all breakpoints have been cleared + response = self.vscode.request_testGetTargetBreakpoints() + if response: + breakpoints = response['body']['breakpoints'] + self.assertTrue(len(breakpoints) == len(functions), + "expect %u source breakpoints" % (len(functions))) + + @skipIfWindows + @no_debug_info_test + def test_functionality(self): + '''Tests hitting breakpoints and the functionality of a single + breakpoint, like 'conditions' and 'hitCondition' settings.''' + + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + # Set a breakpoint on "twelve" with no condition and no hitCondition + functions = ['twelve'] + breakpoint_ids = self.set_function_breakpoints(functions) + + self.assertTrue(len(breakpoint_ids) == len(functions), + "expect one breakpoint") + + # Verify we hit the breakpoint we just set + self.continue_to_breakpoints(breakpoint_ids) + + # Make sure i is zero at first breakpoint + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 0, 'i != 0 after hitting breakpoint') + + # Update the condition on our breakpoint + new_breakpoint_ids = self.set_function_breakpoints(functions, + condition="i==4") + self.assertTrue(breakpoint_ids == new_breakpoint_ids, + "existing breakpoint should have its condition " + "updated") + + self.continue_to_breakpoints(breakpoint_ids) + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 4, + 'i != 4 showing conditional works') + new_breakpoint_ids = self.set_function_breakpoints(functions, + hitCondition="2") + + self.assertTrue(breakpoint_ids == new_breakpoint_ids, + "existing breakpoint should have its condition " + "updated") + + # Continue with a hitContidtion of 2 and expect it to skip 1 value + self.continue_to_breakpoints(breakpoint_ids) + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 6, + 'i != 6 showing hitCondition works') + + # continue after hitting our hitCondition and make sure it only goes + # up by 1 + self.continue_to_breakpoints(breakpoint_ids) + i = int(self.vscode.get_local_variable_value('i')) + self.assertTrue(i == 7, + 'i != 7 showing post hitCondition hits every time') diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp new file mode 100644 index 00000000000..e859b04e3a9 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp @@ -0,0 +1,27 @@ +#include <stdio.h> +#include <stdexcept> + +int twelve(int i) { + return 12 + i; // break 12 +} + +int thirteen(int i) { + return 13 + i; // break 13 +} + +namespace a { + int fourteen(int i) { + return 14 + i; // break 14 + } +} +int main(int argc, char const *argv[]) { + for (int i=0; i<10; ++i) { + int x = twelve(i) + thirteen(i) + a::fourteen(i); // break loop + } + try { + throw std::invalid_argument( "throwing exception for testing" ); + } catch (...) { + puts("caught exception..."); + } + return 0; +} diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile new file mode 100644 index 00000000000..b09a579159d --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py new file mode 100644 index 00000000000..edaee299416 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py @@ -0,0 +1,331 @@ +""" +Test lldb-vscode setBreakpoints request +""" + +from __future__ import print_function + +import pprint +import unittest2 +import vscode +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbvscode_testcase +import os +import time + + +class TestVSCode_launch(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIfWindows + @no_debug_info_test + def test_default(self): + ''' + Tests the default launch of a simple program. No arguments, + environment, or anything else is specified. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + self.continue_to_exit() + # Now get the STDOUT and verify our program argument is correct + output = self.get_stdout() + self.assertTrue(output and len(output) > 0, + "expect program output") + lines = output.splitlines() + self.assertTrue(program in lines[0], + "make sure program path is in first argument") + + @skipIfWindows + @no_debug_info_test + def test_stopOnEntry(self): + ''' + Tests the default launch of a simple program that stops at the + entry point instead of continuing. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program, stopOnEntry=True) + self.set_function_breakpoints(['main']) + stopped_events = self.continue_to_next_stop() + for stopped_event in stopped_events: + if 'body' in stopped_event: + body = stopped_event['body'] + if 'reason' in body: + reason = body['reason'] + self.assertTrue( + reason != 'breakpoint', + 'verify stop isn\'t "main" breakpoint') + + @skipIfWindows + @no_debug_info_test + def test_cwd(self): + ''' + Tests the default launch of a simple program with a current working + directory. + ''' + program = self.getBuildArtifact("a.out") + program_parent_dir = os.path.split(os.path.split(program)[0])[0] + self.build_and_launch(program, + cwd=program_parent_dir) + self.continue_to_exit() + # Now get the STDOUT and verify our program argument is correct + output = self.get_stdout() + self.assertTrue(output and len(output) > 0, + "expect program output") + lines = output.splitlines() + found = False + for line in lines: + if line.startswith('cwd = \"'): + quote_path = '"%s"' % (program_parent_dir) + found = True + self.assertTrue(quote_path in line, + "working directory '%s' not in '%s'" % ( + program_parent_dir, line)) + self.assertTrue(found, "verified program working directory") + + @skipIfWindows + @no_debug_info_test + def test_debuggerRoot(self): + ''' + Tests the "debuggerRoot" will change the working directory of + the lldb-vscode debug adaptor. + ''' + program = self.getBuildArtifact("a.out") + program_parent_dir = os.path.split(os.path.split(program)[0])[0] + commands = ['platform shell echo cwd = $PWD'] + self.build_and_launch(program, + debuggerRoot=program_parent_dir, + initCommands=commands) + output = self.get_console() + self.assertTrue(output and len(output) > 0, + "expect console output") + lines = output.splitlines() + prefix = 'cwd = ' + found = False + for line in lines: + if line.startswith(prefix): + found = True + self.assertTrue(program_parent_dir == line[len(prefix):], + "lldb-vscode working dir '%s' == '%s'" % ( + program_parent_dir, line[6:])) + self.assertTrue(found, "verified lldb-vscode working directory") + self.continue_to_exit() + + @skipIfWindows + @no_debug_info_test + def test_sourcePath(self): + ''' + Tests the "sourcePath" will set the target.source-map. + ''' + program = self.getBuildArtifact("a.out") + program_dir = os.path.split(program)[0] + self.build_and_launch(program, + sourcePath=program_dir) + output = self.get_console() + self.assertTrue(output and len(output) > 0, + "expect console output") + lines = output.splitlines() + prefix = '(lldb) settings set target.source-map "." ' + found = False + for line in lines: + if line.startswith(prefix): + found = True + quoted_path = '"%s"' % (program_dir) + self.assertTrue(quoted_path == line[len(prefix):], + "lldb-vscode working dir %s == %s" % ( + quoted_path, line[6:])) + self.assertTrue(found, 'found "sourcePath" in console output') + self.continue_to_exit() + + @skipIfWindows + @no_debug_info_test + def test_disableSTDIO(self): + ''' + Tests the default launch of a simple program with STDIO disabled. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program, + disableSTDIO=True) + self.continue_to_exit() + # Now get the STDOUT and verify our program argument is correct + output = self.get_stdout() + self.assertTrue(output is None or len(output) == 0, + "expect no program output") + + @skipUnlessDarwin + @skipIfDarwinEmbedded + @no_debug_info_test + def test_shellExpandArguments_enabled(self): + ''' + Tests the default launch of a simple program with shell expansion + enabled. + ''' + program = self.getBuildArtifact("a.out") + program_dir = os.path.split(program)[0] + glob = os.path.join(program_dir, '*.out') + self.build_and_launch(program, args=[glob], shellExpandArguments=True) + self.continue_to_exit() + # Now get the STDOUT and verify our program argument is correct + output = self.get_stdout() + self.assertTrue(output and len(output) > 0, + "expect no program output") + lines = output.splitlines() + for line in lines: + quote_path = '"%s"' % (program) + if line.startswith("arg[1] ="): + self.assertTrue(quote_path in line, + 'verify "%s" expanded to "%s"' % ( + glob, program)) + + @skipIfWindows + @no_debug_info_test + def test_shellExpandArguments_disabled(self): + ''' + Tests the default launch of a simple program with shell expansion + disabled. + ''' + program = self.getBuildArtifact("a.out") + program_dir = os.path.split(program)[0] + glob = os.path.join(program_dir, '*.out') + self.build_and_launch(program, + args=[glob], + shellExpandArguments=False) + self.continue_to_exit() + # Now get the STDOUT and verify our program argument is correct + output = self.get_stdout() + self.assertTrue(output and len(output) > 0, + "expect no program output") + lines = output.splitlines() + for line in lines: + quote_path = '"%s"' % (glob) + if line.startswith("arg[1] ="): + self.assertTrue(quote_path in line, + 'verify "%s" stayed to "%s"' % ( + glob, glob)) + + @skipIfWindows + @no_debug_info_test + def test_args(self): + ''' + Tests launch of a simple program with arguments + ''' + program = self.getBuildArtifact("a.out") + args = ["one", "with space", "'with single quotes'", + '"with double quotes"'] + self.build_and_launch(program, + args=args) + self.continue_to_exit() + + # Now get the STDOUT and verify our arguments got passed correctly + output = self.get_stdout() + self.assertTrue(output and len(output) > 0, + "expect program output") + lines = output.splitlines() + # Skip the first argument that contains the program name + lines.pop(0) + # Make sure arguments we specified are correct + for (i, arg) in enumerate(args): + quoted_arg = '"%s"' % (arg) + self.assertTrue(quoted_arg in lines[i], + 'arg[%i] "%s" not in "%s"' % (i+1, quoted_arg, lines[i])) + + @skipIfWindows + @no_debug_info_test + def test_environment(self): + ''' + Tests launch of a simple program with environment variables + ''' + program = self.getBuildArtifact("a.out") + env = ["NO_VALUE", "WITH_VALUE=BAR", "EMPTY_VALUE=", + "SPACE=Hello World"] + self.build_and_launch(program, + env=env) + self.continue_to_exit() + + # Now get the STDOUT and verify our arguments got passed correctly + output = self.get_stdout() + self.assertTrue(output and len(output) > 0, + "expect program output") + lines = output.splitlines() + # Skip the all arguments so we have only environment vars left + while len(lines) and lines[0].startswith("arg["): + lines.pop(0) + # Make sure each environment variable in "env" is actually set in the + # program environment that was printed to STDOUT + for var in env: + found = False + for program_var in lines: + if var in program_var: + found = True + break + self.assertTrue(found, + '"%s" must exist in program environment (%s)' % ( + var, lines)) + + @skipIfWindows + @no_debug_info_test + def test_commands(self): + ''' + Tests the "initCommands", "preRunCommands", "stopCommands" and + "exitCommands" that can be passed during launch. + + "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 + ''' + 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'] + self.build_and_launch(program, + 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) + + source = 'main.c' + first_line = line_number(source, '// breakpoint 1') + second_line = line_number(source, '// breakpoint 2') + lines = [first_line, second_line] + + # Set 2 breakoints so we can verify that "stopCommands" get run as the + # breakpoints get hit + breakpoint_ids = self.set_source_breakpoints(source, lines) + self.assertTrue(len(breakpoint_ids) == len(lines), + "expect correct number of breakpoints") + + # Continue after launch and hit the first breakpoint. + # Get output from the console. This should contain both the + # "stopCommands" that were run after the first breakpoint was hit + self.continue_to_breakpoints(breakpoint_ids) + output = self.get_console(timeout=1.0) + self.verify_commands('stopCommands', output, stopCommands) + + # Continue again and hit the second breakpoint. + # Get output from the console. This should contain both the + # "stopCommands" that were run after the second breakpoint was hit + self.continue_to_breakpoints(breakpoint_ids) + 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) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c new file mode 100644 index 00000000000..aed2af9828f --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c @@ -0,0 +1,15 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int main(int argc, char const *argv[], char const *envp[]) { + for (int i=0; i<argc; ++i) + printf("arg[%i] = \"%s\"\n", i, argv[i]); + for (int i=0; envp[i]; ++i) + printf("env[%i] = \"%s\"\n", i, envp[i]); + char *cwd = getcwd(NULL, 0); + printf("cwd = \"%s\"\n", cwd); // breakpoint 1 + free(cwd); + cwd = NULL; + return 0; // breakpoint 2 +} 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 new file mode 100644 index 00000000000..c66c6bb7af6 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -0,0 +1,288 @@ +from __future__ import print_function + +from lldbsuite.test.lldbtest import * +import os +import vscode + + +class VSCodeTestCaseBase(TestBase): + + def create_debug_adaptor(self): + '''Create the Visual Studio Code debug adaptor''' + self.assertTrue(os.path.exists(self.lldbVSCodeExec), + 'lldb-vscode must exist') + self.vscode = vscode.DebugAdaptor(executable=self.lldbVSCodeExec) + + def build_and_create_debug_adaptor(self): + self.build() + self.create_debug_adaptor() + + def set_source_breakpoints(self, source_path, lines, condition=None, + hitCondition=None): + '''Sets source breakpoints and returns an array of strings containing + the breakpoint location IDs ("1.1", "1.2") for each breakpoint + that was set. + ''' + response = self.vscode.request_setBreakpoints( + source_path, lines, condition=condition, hitCondition=hitCondition) + if response is None: + return [] + breakpoints = response['body']['breakpoints'] + breakpoint_ids = [] + for breakpoint in breakpoints: + response_id = breakpoint['id'] + bp_id = response_id >> 32 + bp_loc_id = response_id & 0xffffffff + breakpoint_ids.append('%i.%i' % (bp_id, bp_loc_id)) + return breakpoint_ids + + def set_function_breakpoints(self, functions, condition=None, + hitCondition=None): + '''Sets breakpoints by function name given an array of function names + and returns an array of strings containing the breakpoint location + IDs ("1.1", "1.2") for each breakpoint that was set. + ''' + response = self.vscode.request_setFunctionBreakpoints( + functions, condition=condition, hitCondition=hitCondition) + if response is None: + return [] + breakpoints = response['body']['breakpoints'] + breakpoint_ids = [] + for breakpoint in breakpoints: + response_id = breakpoint['id'] + bp_id = response_id >> 32 + bp_loc_id = response_id & 0xffffffff + breakpoint_ids.append('%i.%i' % (bp_id, bp_loc_id)) + return breakpoint_ids + + def verify_breakpoint_hit(self, breakpoint_ids): + '''Wait for the process we are debugging to stop, and verify we hit + any breakpoint location in the "breakpoint_ids" array. + "breakpoint_ids" should be a list of breakpoint location ID strings + (["1.1", "2.1"]). The return value from + self.set_source_breakpoints() can be passed to this function''' + stopped_events = self.vscode.wait_for_stopped() + for stopped_event in stopped_events: + if 'body' in stopped_event: + body = stopped_event['body'] + if 'reason' not in body: + continue + if body['reason'] != 'breakpoint': + continue + if 'description' not in body: + continue + # Description is "breakpoint 1.1", so look for any location id + # ("1.1") in the description field as verification that one of + # the breakpoint locations was hit + description = body['description'] + for breakpoint_id in breakpoint_ids: + if breakpoint_id in description: + return True + return False + + def verify_exception_breakpoint_hit(self, filter_label): + '''Wait for the process we are debugging to stop, and verify the stop + reason is 'exception' and that the description matches + 'filter_label' + ''' + stopped_events = self.vscode.wait_for_stopped() + for stopped_event in stopped_events: + if 'body' in stopped_event: + body = stopped_event['body'] + if 'reason' not in body: + continue + if body['reason'] != 'exception': + continue + if 'description' not in body: + continue + description = body['description'] + if filter_label == description: + return True + return False + + def verify_commands(self, flavor, output, commands): + self.assertTrue(output and len(output) > 0, "expect console output") + lines = output.splitlines() + prefix = '(lldb) ' + for cmd in commands: + found = False + for line in lines: + if line.startswith(prefix) and cmd in line: + found = True + break + self.assertTrue(found, + "verify '%s' found in console output for '%s'" % ( + cmd, flavor)) + + def get_dict_value(self, d, key_path): + '''Verify each key in the key_path array is in contained in each + dictionary within "d". Assert if any key isn't in the + corresponding dictionary. This is handy for grabbing values from VS + Code response dictionary like getting + response['body']['stackFrames'] + ''' + value = d + for key in key_path: + if key in value: + value = value[key] + else: + self.assertTrue(key in value, + 'key "%s" from key_path "%s" not in "%s"' % ( + key, key_path, d)) + return value + + def get_stackFrames(self, threadId=None, startFrame=None, levels=None, + dump=False): + response = self.vscode.request_stackTrace(threadId=threadId, + startFrame=startFrame, + levels=levels, + dump=dump) + if response: + return self.get_dict_value(response, ['body', 'stackFrames']) + return None + + def get_source_and_line(self, threadId=None, frameIndex=0): + stackFrames = self.get_stackFrames(threadId=threadId, + startFrame=frameIndex, + levels=1) + if stackFrames is not None: + stackFrame = stackFrames[0] + ['source', 'path'] + if 'source' in stackFrame: + source = stackFrame['source'] + if 'path' in source: + if 'line' in stackFrame: + return (source['path'], stackFrame['line']) + return ('', 0) + + def get_stdout(self, timeout=0.0): + return self.vscode.get_output('stdout', timeout=timeout) + + def get_console(self, timeout=0.0): + return self.vscode.get_output('console', timeout=timeout) + + def get_local_as_int(self, name, threadId=None): + value = self.vscode.get_local_variable_value(name, threadId=threadId) + if value.startswith('0x'): + return int(value, 16) + elif value.startswith('0'): + return int(value, 8) + else: + return int(value) + + def set_local(self, name, value, id=None): + '''Set a top level local variable only.''' + return self.vscode.request_setVariable(1, name, str(value), id=id) + + def set_global(self, name, value, id=None): + '''Set a top level global variable only.''' + return self.vscode.request_setVariable(2, name, str(value), id=id) + + def stepIn(self, threadId=None, waitForStop=True): + self.vscode.request_stepIn(threadId=threadId) + if waitForStop: + return self.vscode.wait_for_stopped() + return None + + def stepOver(self, threadId=None, waitForStop=True): + self.vscode.request_next(threadId=threadId) + if waitForStop: + return self.vscode.wait_for_stopped() + return None + + def stepOut(self, threadId=None, waitForStop=True): + self.vscode.request_stepOut(threadId=threadId) + if waitForStop: + return self.vscode.wait_for_stopped() + return None + + def continue_to_next_stop(self): + self.vscode.request_continue() + return self.vscode.wait_for_stopped() + + def continue_to_breakpoints(self, breakpoint_ids): + self.vscode.request_continue() + self.verify_breakpoint_hit(breakpoint_ids) + + def continue_to_exception_breakpoint(self, filter_label): + self.vscode.request_continue() + self.assertTrue(self.verify_exception_breakpoint_hit(filter_label), + 'verify we got "%s"' % (filter_label)) + + def continue_to_exit(self, exitCode=0): + self.vscode.request_continue() + stopped_events = self.vscode.wait_for_stopped() + self.assertTrue(len(stopped_events) == 1, + "expecting single 'exited' event") + self.assertTrue(stopped_events[0]['event'] == 'exited', + 'make sure program ran to completion') + self.assertTrue(stopped_events[0]['body']['exitCode'] == exitCode, + 'exitCode == %i' % (exitCode)) + + def attach(self, program=None, pid=None, waitFor=None, trace=None, + initCommands=None, preRunCommands=None, stopCommands=None, + exitCommands=None, attachCommands=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) + self.vscode.terminate() + + # Execute the cleanup function during test case tear down. + self.addTearDownHook(cleanup) + # Initialize and launch the program + self.vscode.request_initialize() + response = self.vscode.request_attach( + program=program, pid=pid, waitFor=waitFor, trace=trace, + initCommands=initCommands, preRunCommands=preRunCommands, + stopCommands=stopCommands, exitCommands=exitCommands, + attachCommands=attachCommands) + if not (response and response['success']): + self.assertTrue(response['success'], + 'attach failed (%s)' % (response['message'])) + + def build_and_launch(self, program, args=None, cwd=None, env=None, + stopOnEntry=False, disableASLR=True, + disableSTDIO=False, shellExpandArguments=False, + trace=False, initCommands=None, preRunCommands=None, + stopCommands=None, exitCommands=None, + sourcePath=None, debuggerRoot=None): + '''Build the default Makefile target, create the VSCode debug adaptor, + and launch the process. + ''' + self.build_and_create_debug_adaptor() + self.assertTrue(os.path.exists(program), 'executable must exist') + + # 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) + self.vscode.terminate() + + # Execute the cleanup function during test case tear down. + self.addTearDownHook(cleanup) + + # Initialize and launch the program + self.vscode.request_initialize() + response = self.vscode.request_launch( + program, + args=args, + cwd=cwd, + env=env, + stopOnEntry=stopOnEntry, + disableASLR=disableASLR, + disableSTDIO=disableSTDIO, + shellExpandArguments=shellExpandArguments, + trace=trace, + initCommands=initCommands, + preRunCommands=preRunCommands, + stopCommands=stopCommands, + exitCommands=exitCommands, + sourcePath=sourcePath, + debuggerRoot=debuggerRoot) + 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/stackTrace/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile new file mode 100644 index 00000000000..b09a579159d --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py new file mode 100644 index 00000000000..4aca14fc827 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py @@ -0,0 +1,159 @@ +""" +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 os + + +class TestVSCode_stackTrace(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + name_key_path = ['name'] + source_key_path = ['source', 'path'] + line_key_path = ['line'] + + def verify_stackFrames(self, start_idx, stackFrames): + frame_idx = start_idx + for stackFrame in stackFrames: + # Don't care about frame above main + if frame_idx > 20: + return + self.verify_stackFrame(frame_idx, stackFrame) + frame_idx += 1 + + def verify_stackFrame(self, frame_idx, stackFrame): + frame_name = self.get_dict_value(stackFrame, self.name_key_path) + frame_source = self.get_dict_value(stackFrame, self.source_key_path) + frame_line = self.get_dict_value(stackFrame, self.line_key_path) + if frame_idx == 0: + expected_line = self.recurse_end + expected_name = 'recurse' + elif frame_idx < 20: + expected_line = self.recurse_call + expected_name = 'recurse' + else: + expected_line = self.recurse_invocation + expected_name = 'main' + self.assertTrue(frame_name == expected_name, + 'frame #%i name "%s" == "%s"' % ( + frame_idx, frame_name, expected_name)) + self.assertTrue(frame_source == self.source_path, + 'frame #%i source "%s" == "%s"' % ( + frame_idx, frame_source, self.source_path)) + self.assertTrue(frame_line == expected_line, + 'frame #%i line %i == %i' % (frame_idx, frame_line, + expected_line)) + + @skipIfWindows + @no_debug_info_test + def test_stackTrace(self): + ''' + Tests the 'stackTrace' packet and all its variants. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = 'main.c' + self.source_path = os.path.join(os.getcwd(), source) + self.recurse_end = line_number(source, 'recurse end') + self.recurse_call = line_number(source, 'recurse call') + self.recurse_invocation = line_number(source, 'recurse invocation') + + lines = [self.recurse_end] + + # Set breakoint at a point of deepest recuusion + 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) + startFrame = 0 + # Verify we get all stack frames with no arguments + stackFrames = self.get_stackFrames() + frameCount = len(stackFrames) + self.assertTrue(frameCount >= 20, + 'verify we get at least 20 frames for all frames') + self.verify_stackFrames(startFrame, stackFrames) + + # Verify all stack frames by specifying startFrame = 0 and levels not + # specified + stackFrames = self.get_stackFrames(startFrame=startFrame) + self.assertTrue(frameCount == len(stackFrames), + ('verify same number of frames with startFrame=%i') % ( + startFrame)) + self.verify_stackFrames(startFrame, stackFrames) + + # Verify all stack frames by specifying startFrame = 0 and levels = 0 + levels = 0 + stackFrames = self.get_stackFrames(startFrame=startFrame, + levels=levels) + self.assertTrue(frameCount == len(stackFrames), + ('verify same number of frames with startFrame=%i and' + ' levels=%i') % (startFrame, levels)) + self.verify_stackFrames(startFrame, stackFrames) + + # Get only the first stack frame by sepcifying startFrame = 0 and + # levels = 1 + levels = 1 + stackFrames = self.get_stackFrames(startFrame=startFrame, + levels=levels) + self.assertTrue(levels == len(stackFrames), + ('verify one frame with startFrame=%i and' + ' levels=%i') % (startFrame, levels)) + self.verify_stackFrames(startFrame, stackFrames) + + # Get only the first 3 stack frames by sepcifying startFrame = 0 and + # levels = 3 + levels = 3 + stackFrames = self.get_stackFrames(startFrame=startFrame, + levels=levels) + self.assertTrue(levels == len(stackFrames), + ('verify %i frames with startFrame=%i and' + ' levels=%i') % (levels, startFrame, levels)) + self.verify_stackFrames(startFrame, stackFrames) + + # Get only the first 15 stack frames by sepcifying startFrame = 5 and + # levels = 16 + startFrame = 5 + levels = 16 + stackFrames = self.get_stackFrames(startFrame=startFrame, + levels=levels) + self.assertTrue(levels == len(stackFrames), + ('verify %i frames with startFrame=%i and' + ' levels=%i') % (levels, startFrame, levels)) + self.verify_stackFrames(startFrame, stackFrames) + + # Verify we cap things correctly when we ask for too many frames + startFrame = 5 + levels = 1000 + stackFrames = self.get_stackFrames(startFrame=startFrame, + levels=levels) + self.assertTrue(len(stackFrames) == frameCount - startFrame, + ('verify less than 1000 frames with startFrame=%i and' + ' levels=%i') % (startFrame, levels)) + self.verify_stackFrames(startFrame, stackFrames) + + # Verify level=0 works with non-zerp start frame + startFrame = 5 + levels = 0 + stackFrames = self.get_stackFrames(startFrame=startFrame, + levels=levels) + self.assertTrue(len(stackFrames) == frameCount - startFrame, + ('verify less than 1000 frames with startFrame=%i and' + ' levels=%i') % (startFrame, levels)) + self.verify_stackFrames(startFrame, stackFrames) + + # Verify we get not frames when startFrame is too high + startFrame = 1000 + levels = 1 + stackFrames = self.get_stackFrames(startFrame=startFrame, + levels=levels) + self.assertTrue(0 == len(stackFrames), + 'verify zero frames with startFrame out of bounds') diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c new file mode 100644 index 00000000000..85b41c49281 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c @@ -0,0 +1,13 @@ +#include <stdio.h> +#include <unistd.h> + +int recurse(int x) { + if (x <= 1) + return 1; // recurse end + return recurse(x-1) + x; // recurse call +} + +int main(int argc, char const *argv[]) { + recurse(20); // recurse invocation + return 0; +} diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile new file mode 100644 index 00000000000..314f1cb2f07 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py new file mode 100644 index 00000000000..3a9e18b189e --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py @@ -0,0 +1,78 @@ +""" +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 os + + +class TestVSCode_step(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIfWindows + @no_debug_info_test + def test_step(self): + ''' + Tests the stepping in/out/over in threads. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = 'main.cpp' + # source_path = os.path.join(os.getcwd(), source) + 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) + threads = self.vscode.get_threads() + for thread in threads: + if 'reason' in thread: + reason = thread['reason'] + if reason == 'breakpoint': + # We have a thread that is stopped at our breakpoint. + # Get the value of "x" and get the source file and line. + # These will help us determine if we are stepping + # correctly. If we step a thread correctly we will verify + # the correct falue for x as it progresses through the + # program. + tid = thread['id'] + x1 = self.get_local_as_int('x', threadId=tid) + (src1, line1) = self.get_source_and_line(threadId=tid) + + # Now step into the "recurse()" function call again and + # verify, using the new value of "x" and the source file + # and line if we stepped correctly + self.stepIn(threadId=tid, waitForStop=True) + x2 = self.get_local_as_int('x', threadId=tid) + (src2, line2) = self.get_source_and_line(threadId=tid) + self.assertTrue(x1 == x2 + 1, 'verify step in variable') + self.assertTrue(line2 < line1, 'verify step in line') + self.assertTrue(src1 == src2, 'verify step in source') + + # Now step out and verify + self.stepOut(threadId=tid, waitForStop=True) + x3 = self.get_local_as_int('x', threadId=tid) + (src3, line3) = self.get_source_and_line(threadId=tid) + self.assertTrue(x1 == x3, 'verify step out variable') + self.assertTrue(line3 >= line1, 'verify step out line') + self.assertTrue(src1 == src3, 'verify step in source') + + # Step over and verify + self.stepOver(threadId=tid, waitForStop=True) + x4 = self.get_local_as_int('x', threadId=tid) + (src4, line4) = self.get_source_and_line(threadId=tid) + self.assertTrue(x4 == x3, 'verify step over variable') + self.assertTrue(line4 > line3, 'verify step over line') + self.assertTrue(src1 == src4, 'verify step over source') + # only step one thread that is at the breakpoint and stop + break diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp new file mode 100644 index 00000000000..2fd06311387 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp @@ -0,0 +1,16 @@ +#include <thread> + +int function(int x) { + if ((x % 2) == 0) + return function(x-1) + x; // breakpoint 1 + else + return x; +} + +int main(int argc, char const *argv[]) { + std::thread thread1(function, 2); + std::thread thread2(function, 4); + thread1.join(); + thread2.join(); + return 0; +} diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile new file mode 100644 index 00000000000..314f1cb2f07 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py new file mode 100644 index 00000000000..12c231a2632 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py @@ -0,0 +1,224 @@ +""" +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 os + + +def make_buffer_verify_dict(start_idx, count, offset=0): + verify_dict = {} + for i in range(start_idx, start_idx + count): + verify_dict['[%i]' % (i)] = {'type': 'int', 'value': str(i+offset)} + return verify_dict + + +class TestVSCode_variables(lldbvscode_testcase.VSCodeTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + def verify_values(self, verify_dict, actual, varref_dict=None): + if 'equals' in verify_dict: + verify = verify_dict['equals'] + for key in verify: + verify_value = verify[key] + actual_value = actual[key] + self.assertTrue(verify_value == actual_value, + '"%s" keys don\'t match (%s != %s)' % ( + key, actual_value, verify_value)) + if 'startswith' in verify_dict: + verify = verify_dict['startswith'] + for key in verify: + verify_value = verify[key] + actual_value = actual[key] + startswith = actual_value.startswith(verify_value) + self.assertTrue(startswith, + ('"%s" value "%s" doesn\'t start with' + ' "%s")') % ( + key, actual_value, + verify_value)) + hasVariablesReference = 'variablesReference' in actual + varRef = None + if hasVariablesReference: + # Remember variable references in case we want to test further + # by using the evaluate name. + varRef = actual['variablesReference'] + if varRef != 0 and varref_dict is not None: + varref_dict[actual['evaluateName']] = varRef + if ('hasVariablesReference' in verify_dict and + verify_dict['hasVariablesReference']): + self.assertTrue(hasVariablesReference, + "verify variable reference") + if 'children' in verify_dict: + self.assertTrue(hasVariablesReference and varRef is not None and + varRef != 0, + ("children verify values specified for " + "variable without children")) + + response = self.vscode.request_variables(varRef) + self.verify_variables(verify_dict['children'], + response['body']['variables'], + varref_dict) + + def verify_variables(self, verify_dict, variables, varref_dict=None): + for variable in variables: + name = variable['name'] + self.assertTrue(name in verify_dict, + 'variable "%s" in verify dictionary' % (name)) + self.verify_values(verify_dict[name], variable, varref_dict) + + @skipIfWindows + @no_debug_info_test + def test_scopes_variables_setVariable_evaluate(self): + ''' + Tests the "scopes", "variables", "setVariable", and "evaluate" + packets. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = 'main.cpp' + 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) + locals = self.vscode.get_local_variables() + globals = self.vscode.get_global_variables() + buffer_children = make_buffer_verify_dict(0, 32) + verify_locals = { + 'argc': { + 'equals': {'type': 'int', 'value': '1'} + }, + 'argv': { + 'equals': {'type': 'const char **'}, + 'startswith': {'value': '0x'}, + 'hasVariablesReference': True + }, + 'pt': { + 'equals': {'type': 'PointType'}, + 'hasVariablesReference': True, + 'children': { + 'x': {'equals': {'type': 'int', 'value': '11'}}, + 'y': {'equals': {'type': 'int', 'value': '22'}}, + 'buffer': {'children': buffer_children} + } + } + } + verify_globals = { + 's_local': { + 'equals': {'type': 'float', 'value': '2.25'} + }, + '::g_global': { + 'equals': {'type': 'int', 'value': '123'} + }, + 's_global': { + 'equals': {'type': 'int', 'value': '234'} + }, + } + varref_dict = {} + self.verify_variables(verify_locals, locals, varref_dict) + self.verify_variables(verify_globals, globals, varref_dict) + # pprint.PrettyPrinter(indent=4).pprint(varref_dict) + # We need to test the functionality of the "variables" request as it + # has optional parameters like "start" and "count" to limit the number + # of variables that are fetched + varRef = varref_dict['pt.buffer'] + response = self.vscode.request_variables(varRef) + self.verify_variables(buffer_children, response['body']['variables']) + # Verify setting start=0 in the arguments still gets all children + response = self.vscode.request_variables(varRef, start=0) + self.verify_variables(buffer_children, response['body']['variables']) + # Verify setting count=0 in the arguments still gets all children. + # If count is zero, it means to get all children. + response = self.vscode.request_variables(varRef, count=0) + self.verify_variables(buffer_children, response['body']['variables']) + # Verify setting count to a value that is too large in the arguments + # still gets all children, and no more + response = self.vscode.request_variables(varRef, count=1000) + self.verify_variables(buffer_children, response['body']['variables']) + # Verify setting the start index and count gets only the children we + # want + response = self.vscode.request_variables(varRef, start=5, count=5) + self.verify_variables(make_buffer_verify_dict(5, 5), + response['body']['variables']) + # Verify setting the start index to a value that is out of range + # results in an empty list + response = self.vscode.request_variables(varRef, start=32, count=1) + self.assertTrue(len(response['body']['variables']) == 0, + 'verify we get no variable back for invalid start') + + # Test evaluate + expressions = { + 'pt.x': { + 'equals': {'result': '11', 'type': 'int'}, + 'hasVariablesReference': False + }, + 'pt.buffer[2]': { + 'equals': {'result': '2', 'type': 'int'}, + 'hasVariablesReference': False + }, + 'pt': { + 'equals': {'type': 'PointType'}, + 'startswith': {'result': 'PointType @ 0x'}, + 'hasVariablesReference': True + }, + 'pt.buffer': { + 'equals': {'type': 'int [32]'}, + 'startswith': {'result': 'int [32] @ 0x'}, + 'hasVariablesReference': True + }, + 'argv': { + 'equals': {'type': 'const char **'}, + 'startswith': {'result': '0x'}, + 'hasVariablesReference': True + }, + 'argv[0]': { + 'equals': {'type': 'const char *'}, + 'startswith': {'result': '0x'}, + 'hasVariablesReference': True + }, + '2+3': { + 'equals': {'result': '5', 'type': 'int'}, + 'hasVariablesReference': False + }, + } + for expression in expressions: + response = self.vscode.request_evaluate(expression) + self.verify_values(expressions[expression], response['body']) + + # Test setting variables + self.set_local('argc', 123) + argc = self.get_local_as_int('argc') + self.assertTrue(argc == 123, + 'verify argc was set to 123 (123 != %i)' % (argc)) + + self.set_local('argv', 0x1234) + argv = self.get_local_as_int('argv') + self.assertTrue(argv == 0x1234, + 'verify argv was set to 0x1234 (0x1234 != %#x)' % ( + argv)) + + # Set a variable value whose name is synthetic, like a variable index + # and verify the value by reading it + self.vscode.request_setVariable(varRef, "[0]", 100) + response = self.vscode.request_variables(varRef, start=0, count=1) + self.verify_variables(make_buffer_verify_dict(0, 1, 100), + response['body']['variables']) + + # Set a variable value whose name is a real child value, like "pt.x" + # and verify the value by reading it + varRef = varref_dict['pt'] + self.vscode.request_setVariable(varRef, "x", 111) + response = self.vscode.request_variables(varRef, start=0, count=1) + value = response['body']['variables'][0]['value'] + self.assertTrue(value == '111', + 'verify pt.x got set to 111 (111 != %s)' % (value)) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp new file mode 100644 index 00000000000..0223bd0a75e --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp @@ -0,0 +1,18 @@ + +#define BUFFER_SIZE 32 +struct PointType { + int x; + int y; + int buffer[BUFFER_SIZE]; +}; + +int g_global = 123; +static int s_global = 234; + +int main(int argc, char const *argv[]) { + static float s_local = 2.25; + PointType pt = { 11,22, {0}}; + for (int i=0; i<BUFFER_SIZE; ++i) + pt.buffer[i] = i; + return s_global - g_global - pt.y; // breakpoint 1 +} diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py new file mode 100644 index 00000000000..7e959d71d8f --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -0,0 +1,1084 @@ +#!/usr/bin/env python + +import binascii +import json +import optparse +import os +import pprint +import socket +import string +import subprocess +import sys +import threading + + +def dump_memory(base_addr, data, num_per_line, outfile): + + data_len = len(data) + hex_string = binascii.hexlify(data) + addr = base_addr + ascii_str = '' + i = 0 + while i < data_len: + outfile.write('0x%8.8x: ' % (addr + i)) + bytes_left = data_len - i + if bytes_left >= num_per_line: + curr_data_len = num_per_line + else: + curr_data_len = bytes_left + hex_start_idx = i * 2 + hex_end_idx = hex_start_idx + curr_data_len * 2 + curr_hex_str = hex_string[hex_start_idx:hex_end_idx] + # 'curr_hex_str' now contains the hex byte string for the + # current line with no spaces between bytes + t = iter(curr_hex_str) + # Print hex bytes separated by space + outfile.write(' '.join(a + b for a, b in zip(t, t))) + # Print two spaces + outfile.write(' ') + # Calculate ASCII string for bytes into 'ascii_str' + ascii_str = '' + for j in range(i, i + curr_data_len): + ch = data[j] + if ch in string.printable and ch not in string.whitespace: + ascii_str += '%c' % (ch) + else: + ascii_str += '.' + # Print ASCII representation and newline + outfile.write(ascii_str) + i = i + curr_data_len + outfile.write('\n') + + +def read_packet(f, verbose=False, trace_file=None): + '''Decode a JSON packet that starts with the content length and is + followed by the JSON bytes from a file 'f' + ''' + line = f.readline() + if len(line) == 0: + return None + + # Watch for line that starts with the prefix + prefix = 'Content-Length: ' + if line.startswith(prefix): + # Decode length of JSON bytes + if verbose: + print('content: "%s"' % (line)) + length = int(line[len(prefix):]) + if verbose: + print('length: "%u"' % (length)) + # Skip empty line + line = f.readline() + if verbose: + print('empty: "%s"' % (line)) + # Read JSON bytes + json_str = f.read(length) + if verbose: + print('json: "%s"' % (json_str)) + if trace_file: + trace_file.write('from adaptor:\n%s\n' % (json_str)) + # Decode the JSON bytes into a python dictionary + return json.loads(json_str) + + return None + + +def packet_type_is(packet, packet_type): + return 'type' in packet and packet['type'] == packet_type + + +def read_packet_thread(vs_comm): + done = False + while not done: + packet = read_packet(vs_comm.recv, trace_file=vs_comm.trace_file) + if packet: + done = not vs_comm.handle_recv_packet(packet) + else: + done = True + + +class DebugCommunication(object): + + def __init__(self, recv, send): + self.trace_file = None + self.send = send + self.recv = recv + self.recv_packets = [] + self.recv_condition = threading.Condition() + self.recv_thread = threading.Thread(target=read_packet_thread, + args=(self,)) + self.process_event_body = None + self.exit_status = None + self.initialize_body = None + self.thread_stop_reasons = {} + self.sequence = 1 + self.threads = None + self.recv_thread.start() + self.output_condition = threading.Condition() + self.output = {} + self.configuration_done_sent = False + self.frame_scopes = {} + + @classmethod + def encode_content(cls, s): + return "Content-Length: %u\r\n\r\n%s" % (len(s), s) + + @classmethod + def validate_response(cls, command, response): + if command['command'] != response['command']: + raise ValueError('command mismatch in response') + if command['seq'] != response['request_seq']: + raise ValueError('seq mismatch in response') + + def get_output(self, category, timeout=0.0, clear=True): + self.output_condition.acquire() + output = None + if category in self.output: + output = self.output[category] + if clear: + del self.output[category] + elif timeout != 0.0: + self.output_condition.wait(timeout) + if category in self.output: + output = self.output[category] + if clear: + del self.output[category] + self.output_condition.release() + return output + + def handle_recv_packet(self, packet): + '''Called by the read thread that is waiting for all incoming packets + to store the incoming packet in "self.recv_packets" in a thread safe + way. This function will then signal the "self.recv_condition" to + indicate a new packet is available. Returns True if the caller + should keep calling this function for more packets. + ''' + # Check the packet to see if is an event packet + keepGoing = True + packet_type = packet['type'] + if packet_type == 'event': + event = packet['event'] + body = None + if 'body' in packet: + body = packet['body'] + # Handle the event packet and cache information from these packets + # as they come in + if event == 'output': + # Store any output we receive so clients can retrieve it later. + category = body['category'] + output = body['output'] + self.output_condition.acquire() + if category in self.output: + self.output[category] += output + else: + self.output[category] = output + self.output_condition.notify() + self.output_condition.release() + # no need to add 'output' packets to our packets list + return keepGoing + elif event == 'process': + # When a new process is attached or launched, remember the + # details that are available in the body of the event + self.process_event_body = body + elif event == 'stopped': + # Each thread that stops with a reason will send a + # 'stopped' event. We need to remember the thread stop + # reasons since the 'threads' command doesn't return + # that information. + self._process_stopped() + tid = body['threadId'] + self.thread_stop_reasons[tid] = body + elif packet_type == 'response': + if packet['command'] == 'disconnect': + keepGoing = False + self.recv_condition.acquire() + self.recv_packets.append(packet) + self.recv_condition.notify() + self.recv_condition.release() + return keepGoing + + def send_packet(self, command_dict, set_sequence=True): + '''Take the "command_dict" python dictionary and encode it as a JSON + string and send the contents as a packet to the VSCode debug + adaptor''' + # Set the sequence ID for this command automatically + if set_sequence: + command_dict['seq'] = self.sequence + self.sequence += 1 + # Encode our command dictionary as a JSON string + json_str = json.dumps(command_dict, separators=(',', ':')) + if self.trace_file: + self.trace_file.write('to adaptor:\n%s\n' % (json_str)) + length = len(json_str) + if length > 0: + # Send the encoded JSON packet and flush the 'send' file + self.send.write(self.encode_content(json_str)) + self.send.flush() + + def recv_packet(self, filter_type=None, filter_event=None, timeout=None): + '''Get a JSON packet from the VSCode debug adaptor. This function + assumes a thread that reads packets is running and will deliver + any received packets by calling handle_recv_packet(...). This + function will wait for the packet to arrive and return it when + it does.''' + while True: + self.recv_condition.acquire() + packet = None + while True: + for (i, curr_packet) in enumerate(self.recv_packets): + packet_type = curr_packet['type'] + if filter_type is None or packet_type in filter_type: + if (filter_event is None or + (packet_type == 'event' and + curr_packet['event'] in filter_event)): + packet = self.recv_packets.pop(i) + break + if packet: + break + # Sleep until packet is received + len_before = len(self.recv_packets) + self.recv_condition.wait(timeout) + len_after = len(self.recv_packets) + if len_before == len_after: + return None # Timed out + self.recv_condition.release() + return packet + + return None + + def send_recv(self, command): + '''Send a command python dictionary as JSON and receive the JSON + response. Validates that the response is the correct sequence and + command in the reply. Any events that are received are added to the + events list in this object''' + self.send_packet(command) + done = False + while not done: + response = self.recv_packet(filter_type='response') + if response is None: + desc = 'no response for "%s"' % (command['command']) + raise ValueError(desc) + self.validate_response(command, response) + return response + return None + + def wait_for_event(self, filter=None, timeout=None): + while True: + return self.recv_packet(filter_type='event', filter_event=filter, + timeout=timeout) + return None + + def wait_for_stopped(self, timeout=None): + stopped_events = [] + stopped_event = self.wait_for_event(filter=['stopped', 'exited'], + timeout=timeout) + exited = False + while stopped_event: + stopped_events.append(stopped_event) + # If we exited, then we are done + if stopped_event['event'] == 'exited': + self.exit_status = stopped_event['body']['exitCode'] + exited = True + break + # Otherwise we stopped and there might be one or more 'stopped' + # events for each thread that stopped with a reason, so keep + # checking for more 'stopped' events and return all of them + stopped_event = self.wait_for_event(filter='stopped', timeout=0.25) + if exited: + self.threads = [] + return stopped_events + + def wait_for_exited(self): + event_dict = self.wait_for_event('exited') + if event_dict is None: + raise ValueError("didn't get stopped event") + return event_dict + + def get_initialize_value(self, key): + '''Get a value for the given key if it there is a key/value pair in + the "initialize" request response body. + ''' + if self.initialize_body and key in self.initialize_body: + return self.initialize_body[key] + return None + + def get_threads(self): + if self.threads is None: + self.request_threads() + return self.threads + + def get_thread_id(self, threadIndex=0): + '''Utility function to get the first thread ID in the thread list. + If the thread list is empty, then fetch the threads. + ''' + if self.threads is None: + self.request_threads() + if self.threads and threadIndex < len(self.threads): + return self.threads[threadIndex]['id'] + return None + + def get_stackFrame(self, frameIndex=0, threadId=None): + '''Get a single "StackFrame" object from a "stackTrace" request and + return the "StackFrame as a python dictionary, or None on failure + ''' + if threadId is None: + threadId = self.get_thread_id() + if threadId is None: + print('invalid threadId') + return None + response = self.request_stackTrace(threadId, startFrame=frameIndex, + levels=1) + if response: + return response['body']['stackFrames'][0] + print('invalid response') + return None + + def get_scope_variables(self, scope_name, frameIndex=0, threadId=None): + stackFrame = self.get_stackFrame(frameIndex=frameIndex, + threadId=threadId) + if stackFrame is None: + return [] + frameId = stackFrame['id'] + if frameId in self.frame_scopes: + frame_scopes = self.frame_scopes[frameId] + else: + scopes_response = self.request_scopes(frameId) + frame_scopes = scopes_response['body']['scopes'] + self.frame_scopes[frameId] = frame_scopes + for scope in frame_scopes: + if scope['name'] == scope_name: + varRef = scope['variablesReference'] + variables_response = self.request_variables(varRef) + if variables_response: + if 'body' in variables_response: + body = variables_response['body'] + if 'variables' in body: + vars = body['variables'] + return vars + return [] + + def get_global_variables(self, frameIndex=0, threadId=None): + return self.get_scope_variables('Globals', frameIndex=frameIndex, + threadId=threadId) + + def get_local_variables(self, frameIndex=0, threadId=None): + return self.get_scope_variables('Locals', frameIndex=frameIndex, + threadId=threadId) + + def get_local_variable(self, name, frameIndex=0, threadId=None): + locals = self.get_local_variables(frameIndex=frameIndex, + threadId=threadId) + for local in locals: + if 'name' in local and local['name'] == name: + return local + return None + + def get_local_variable_value(self, name, frameIndex=0, threadId=None): + variable = self.get_local_variable(name, frameIndex=frameIndex, + threadId=threadId) + if variable and 'value' in variable: + return variable['value'] + return None + + def replay_packets(self, replay_file_path): + f = open(replay_file_path, 'r') + mode = 'invalid' + set_sequence = False + command_dict = None + while mode != 'eof': + if mode == 'invalid': + line = f.readline() + if line.startswith('to adapter:'): + mode = 'send' + elif line.startswith('from adapter:'): + mode = 'recv' + elif mode == 'send': + command_dict = read_packet(f) + # Skip the end of line that follows the JSON + f.readline() + if command_dict is None: + raise ValueError('decode packet failed from replay file') + print('Sending:') + pprint.PrettyPrinter(indent=2).pprint(command_dict) + # raw_input('Press ENTER to send:') + self.send_packet(command_dict, set_sequence) + mode = 'invalid' + elif mode == 'recv': + print('Replay response:') + replay_response = read_packet(f) + # Skip the end of line that follows the JSON + f.readline() + pprint.PrettyPrinter(indent=2).pprint(replay_response) + actual_response = self.recv_packet() + if actual_response: + type = actual_response['type'] + print('Actual response:') + if type == 'response': + self.validate_response(command_dict, actual_response) + pprint.PrettyPrinter(indent=2).pprint(actual_response) + else: + print("error: didn't get a valid response") + mode = 'invalid' + + def request_attach(self, program=None, pid=None, waitFor=None, trace=None, + initCommands=None, preRunCommands=None, + stopCommands=None, exitCommands=None, + attachCommands=None): + args_dict = {} + if pid is not None: + args_dict['pid'] = pid + if program is not None: + args_dict['program'] = program + if waitFor is not None: + args_dict['waitFor'] = waitFor + if trace: + args_dict['trace'] = trace + if initCommands: + args_dict['initCommands'] = initCommands + if preRunCommands: + args_dict['preRunCommands'] = preRunCommands + if stopCommands: + args_dict['stopCommands'] = stopCommands + if exitCommands: + args_dict['exitCommands'] = exitCommands + if attachCommands: + args_dict['attachCommands'] = attachCommands + command_dict = { + 'command': 'attach', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_configurationDone(self): + command_dict = { + 'command': 'configurationDone', + 'type': 'request', + 'arguments': {} + } + response = self.send_recv(command_dict) + if response: + self.configuration_done_sent = True + return response + + def _process_stopped(self): + self.threads = None + self.frame_scopes = {} + + def request_continue(self, threadId=None): + if self.exit_status is not None: + raise ValueError('request_continue called after process exited') + # If we have launched or attached, then the first continue is done by + # sending the 'configurationDone' request + if not self.configuration_done_sent: + return self.request_configurationDone() + args_dict = {} + if threadId is None: + threadId = self.get_thread_id() + args_dict['threadId'] = threadId + command_dict = { + 'command': 'continue', + 'type': 'request', + 'arguments': args_dict + } + response = self.send_recv(command_dict) + recv_packets = [] + self.recv_condition.acquire() + for event in self.recv_packets: + if event['event'] != 'stopped': + recv_packets.append(event) + self.recv_packets = recv_packets + self.recv_condition.release() + return response + + def request_disconnect(self, terminateDebuggee=None): + args_dict = {} + if terminateDebuggee is not None: + if terminateDebuggee: + args_dict['terminateDebuggee'] = True + else: + args_dict['terminateDebuggee'] = False + command_dict = { + 'command': 'disconnect', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_evaluate(self, expression, frameIndex=0, threadId=None): + stackFrame = self.get_stackFrame(frameIndex=frameIndex, + threadId=threadId) + if stackFrame is None: + return [] + args_dict = { + 'expression': expression, + 'frameId': stackFrame['id'], + } + command_dict = { + 'command': 'evaluate', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_initialize(self): + command_dict = { + 'command': 'initialize', + 'type': 'request', + 'arguments': { + 'adapterID': 'lldb-native', + 'clientID': 'vscode', + 'columnsStartAt1': True, + 'linesStartAt1': True, + 'locale': 'en-us', + 'pathFormat': 'path', + 'supportsRunInTerminalRequest': True, + 'supportsVariablePaging': True, + 'supportsVariableType': True + } + } + response = self.send_recv(command_dict) + if response: + if 'body' in response: + self.initialize_body = response['body'] + return response + + def request_launch(self, program, args=None, cwd=None, env=None, + stopOnEntry=False, disableASLR=True, + disableSTDIO=False, shellExpandArguments=False, + trace=False, initCommands=None, preRunCommands=None, + stopCommands=None, exitCommands=None, sourcePath=None, + debuggerRoot=None): + args_dict = { + 'program': program + } + if args: + args_dict['args'] = args + if cwd: + args_dict['cwd'] = cwd + if env: + args_dict['env'] = env + if stopOnEntry: + args_dict['stopOnEntry'] = stopOnEntry + if disableASLR: + args_dict['disableASLR'] = disableASLR + if disableSTDIO: + args_dict['disableSTDIO'] = disableSTDIO + if shellExpandArguments: + args_dict['shellExpandArguments'] = shellExpandArguments + if trace: + args_dict['trace'] = trace + if initCommands: + args_dict['initCommands'] = initCommands + if preRunCommands: + args_dict['preRunCommands'] = preRunCommands + if stopCommands: + args_dict['stopCommands'] = stopCommands + if exitCommands: + args_dict['exitCommands'] = exitCommands + if sourcePath: + args_dict['sourcePath'] = sourcePath + if debuggerRoot: + args_dict['debuggerRoot'] = debuggerRoot + command_dict = { + 'command': 'launch', + 'type': 'request', + 'arguments': args_dict + } + response = self.send_recv(command_dict) + + # Wait for a 'process' and 'initialized' event in any order + self.wait_for_event(filter=['process', 'initialized']) + self.wait_for_event(filter=['process', 'initialized']) + return response + + def request_next(self, threadId): + if self.exit_status is not None: + raise ValueError('request_continue called after process exited') + args_dict = {'threadId': threadId} + command_dict = { + 'command': 'next', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_stepIn(self, threadId): + if self.exit_status is not None: + raise ValueError('request_continue called after process exited') + args_dict = {'threadId': threadId} + command_dict = { + 'command': 'stepIn', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_stepOut(self, threadId): + if self.exit_status is not None: + raise ValueError('request_continue called after process exited') + args_dict = {'threadId': threadId} + command_dict = { + 'command': 'stepOut', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_pause(self, threadId=None): + if self.exit_status is not None: + raise ValueError('request_continue called after process exited') + if threadId is None: + threadId = self.get_thread_id() + args_dict = {'threadId': threadId} + command_dict = { + 'command': 'pause', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_scopes(self, frameId): + args_dict = {'frameId': frameId} + command_dict = { + 'command': 'scopes', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_setBreakpoints(self, file_path, line_array, condition=None, + hitCondition=None): + (dir, base) = os.path.split(file_path) + breakpoints = [] + for line in line_array: + bp = {'line': line} + if condition is not None: + bp['condition'] = condition + if hitCondition is not None: + bp['hitCondition'] = hitCondition + breakpoints.append(bp) + source_dict = { + 'name': base, + 'path': file_path + } + args_dict = { + 'source': source_dict, + 'breakpoints': breakpoints, + 'lines': '%s' % (line_array), + 'sourceModified': False, + } + command_dict = { + 'command': 'setBreakpoints', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_setExceptionBreakpoints(self, filters): + args_dict = {'filters': filters} + command_dict = { + 'command': 'setExceptionBreakpoints', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_setFunctionBreakpoints(self, names, condition=None, + hitCondition=None): + breakpoints = [] + for name in names: + bp = {'name': name} + if condition is not None: + bp['condition'] = condition + if hitCondition is not None: + bp['hitCondition'] = hitCondition + breakpoints.append(bp) + args_dict = {'breakpoints': breakpoints} + command_dict = { + 'command': 'setFunctionBreakpoints', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_stackTrace(self, threadId=None, startFrame=None, levels=None, + dump=False): + if threadId is None: + threadId = self.get_thread_id() + args_dict = {'threadId': threadId} + if startFrame is not None: + args_dict['startFrame'] = startFrame + if levels is not None: + args_dict['levels'] = levels + command_dict = { + 'command': 'stackTrace', + 'type': 'request', + 'arguments': args_dict + } + response = self.send_recv(command_dict) + if dump: + for (idx, frame) in enumerate(response['body']['stackFrames']): + name = frame['name'] + if 'line' in frame and 'source' in frame: + source = frame['source'] + if 'sourceReference' not in source: + if 'name' in source: + source_name = source['name'] + line = frame['line'] + print("[%3u] %s @ %s:%u" % (idx, name, source_name, + line)) + continue + print("[%3u] %s" % (idx, name)) + return response + + def request_threads(self): + '''Request a list of all threads and combine any information from any + "stopped" events since those contain more information about why a + thread actually stopped. Returns an array of thread dictionaries + with information about all threads''' + command_dict = { + 'command': 'threads', + 'type': 'request', + 'arguments': {} + } + response = self.send_recv(command_dict) + body = response['body'] + # Fill in "self.threads" correctly so that clients that call + # self.get_threads() or self.get_thread_id(...) can get information + # on threads when the process is stopped. + if 'threads' in body: + self.threads = body['threads'] + for thread in self.threads: + # Copy the thread dictionary so we can add key/value pairs to + # it without affecfting the original info from the "threads" + # command. + tid = thread['id'] + if tid in self.thread_stop_reasons: + thread_stop_info = self.thread_stop_reasons[tid] + copy_keys = ['reason', 'description', 'text'] + for key in copy_keys: + if key in thread_stop_info: + thread[key] = thread_stop_info[key] + else: + self.threads = None + return response + + def request_variables(self, variablesReference, start=None, count=None): + args_dict = {'variablesReference': variablesReference} + if start is not None: + args_dict['start'] = start + if count is not None: + args_dict['count'] = count + command_dict = { + 'command': 'variables', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_setVariable(self, containingVarRef, name, value, id=None): + args_dict = { + 'variablesReference': containingVarRef, + 'name': name, + 'value': str(value) + } + if id is not None: + args_dict['id'] = id + command_dict = { + 'command': 'setVariable', + 'type': 'request', + 'arguments': args_dict + } + return self.send_recv(command_dict) + + def request_testGetTargetBreakpoints(self): + '''A request packet used in the LLDB test suite to get all currently + set breakpoint infos for all breakpoints currently set in the + target. + ''' + command_dict = { + 'command': '_testGetTargetBreakpoints', + 'type': 'request', + 'arguments': {} + } + return self.send_recv(command_dict) + + def terminate(self): + self.send.close() + # self.recv.close() + + +class DebugAdaptor(DebugCommunication): + def __init__(self, executable=None, port=None): + self.process = None + if executable is not None: + self.process = subprocess.Popen([executable], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + DebugCommunication.__init__(self, self.process.stdout, + self.process.stdin) + elif port is not None: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(('127.0.0.1', port)) + DebugCommunication.__init__(self, s.makefile('r'), s.makefile('w')) + + def get_pid(self): + if self.process: + return self.process.pid + return -1 + + def terminate(self): + super(DebugAdaptor, self).terminate() + if self.process is not None: + self.process.terminate() + self.process.wait() + self.process = None + + +def attach_options_specified(options): + if options.pid is not None: + return True + if options.waitFor: + return True + if options.attach: + return True + if options.attachCmds: + return True + return False + + +def run_vscode(dbg, args, options): + dbg.request_initialize() + if attach_options_specified(options): + response = dbg.request_attach(program=options.program, + pid=options.pid, + waitFor=options.waitFor, + attachCommands=options.attachCmds, + initCommands=options.initCmds, + preRunCommands=options.preRunCmds, + stopCommands=options.stopCmds, + exitCommands=options.exitCmds) + else: + response = dbg.request_launch(options.program, + args=args, + env=options.envs, + cwd=options.workingDir, + debuggerRoot=options.debuggerRoot, + sourcePath=options.sourcePath, + initCommands=options.initCmds, + preRunCommands=options.preRunCmds, + stopCommands=options.stopCmds, + exitCommands=options.exitCmds) + + if response['success']: + if options.sourceBreakpoints: + source_to_lines = {} + for file_line in options.sourceBreakpoints: + (path, line) = file_line.split(':') + if len(path) == 0 or len(line) == 0: + print('error: invalid source with line "%s"' % + (file_line)) + + else: + if path in source_to_lines: + source_to_lines[path].append(int(line)) + else: + source_to_lines[path] = [int(line)] + for source in source_to_lines: + dbg.request_setBreakpoints(source, source_to_lines[source]) + if options.funcBreakpoints: + dbg.request_setFunctionBreakpoints(options.funcBreakpoints) + dbg.request_configurationDone() + dbg.wait_for_stopped() + else: + if 'message' in response: + print(response['message']) + dbg.request_disconnect(terminateDebuggee=True) + + +def main(): + parser = optparse.OptionParser( + description=('A testing framework for the Visual Studio Code Debug ' + 'Adaptor protocol')) + + parser.add_option( + '--vscode', + type='string', + dest='vscode_path', + help=('The path to the a command line program that implements the ' + 'Visual Studio Code Debug Adaptor protocol.'), + default=None) + + parser.add_option( + '--program', + type='string', + dest='program', + help='The path to the program to debug.', + default=None) + + parser.add_option( + '--workingDir', + type='string', + dest='workingDir', + default=None, + help='Set the working directory for the process we launch.') + + parser.add_option( + '--sourcePath', + type='string', + dest='sourcePath', + default=None, + help=('Set the relative source root for any debug info that has ' + 'relative paths in it.')) + + parser.add_option( + '--debuggerRoot', + type='string', + dest='debuggerRoot', + default=None, + help=('Set the working directory for lldb-vscode for any object files ' + 'with relative paths in the Mach-o debug map.')) + + parser.add_option( + '-r', '--replay', + type='string', + dest='replay', + help=('Specify a file containing a packet log to replay with the ' + 'current Visual Studio Code Debug Adaptor executable.'), + default=None) + + parser.add_option( + '-g', '--debug', + action='store_true', + dest='debug', + default=False, + help='Pause waiting for a debugger to attach to the debug adaptor') + + parser.add_option( + '--port', + type='int', + dest='port', + help="Attach a socket to a port instead of using STDIN for VSCode", + default=None) + + parser.add_option( + '--pid', + type='int', + dest='pid', + help="The process ID to attach to", + default=None) + + parser.add_option( + '--attach', + action='store_true', + dest='attach', + default=False, + help=('Specify this option to attach to a process by name. The ' + 'process name is the basanme of the executable specified with ' + 'the --program option.')) + + parser.add_option( + '-f', '--function-bp', + type='string', + action='append', + dest='funcBreakpoints', + help=('Specify the name of a function to break at. ' + 'Can be specified more than once.'), + default=[]) + + parser.add_option( + '-s', '--source-bp', + type='string', + action='append', + dest='sourceBreakpoints', + default=[], + help=('Specify source breakpoints to set in the format of ' + '<source>:<line>. ' + 'Can be specified more than once.')) + + parser.add_option( + '--attachCommand', + type='string', + action='append', + dest='attachCmds', + default=[], + help=('Specify a LLDB command that will attach to a process. ' + 'Can be specified more than once.')) + + parser.add_option( + '--initCommand', + type='string', + action='append', + dest='initCmds', + default=[], + help=('Specify a LLDB command that will be executed before the target ' + 'is created. Can be specified more than once.')) + + parser.add_option( + '--preRunCommand', + type='string', + action='append', + dest='preRunCmds', + default=[], + help=('Specify a LLDB command that will be executed after the target ' + 'has been created. Can be specified more than once.')) + + parser.add_option( + '--stopCommand', + type='string', + action='append', + dest='stopCmds', + default=[], + help=('Specify a LLDB command that will be executed each time the' + 'process stops. Can be specified more than once.')) + + parser.add_option( + '--exitCommand', + type='string', + action='append', + dest='exitCmds', + default=[], + help=('Specify a LLDB command that will be executed when the process ' + 'exits. Can be specified more than once.')) + + parser.add_option( + '--env', + type='string', + action='append', + dest='envs', + default=[], + help=('Specify environment variables to pass to the launched ' + 'process.')) + + parser.add_option( + '--waitFor', + action='store_true', + dest='waitFor', + default=False, + help=('Wait for the next process to be launched whose name matches ' + 'the basename of the program specified with the --program ' + 'option')) + + (options, args) = parser.parse_args(sys.argv[1:]) + + if options.vscode_path is None and options.port is None: + print('error: must either specify a path to a Visual Studio Code ' + 'Debug Adaptor vscode executable path using the --vscode ' + 'option, or a port to attach to for an existing lldb-vscode ' + 'using the --port option') + return + dbg = DebugAdaptor(executable=options.vscode_path, port=options.port) + if options.debug: + raw_input('Waiting for debugger to attach pid "%i"' % ( + dbg.get_pid())) + if options.replay: + dbg.replay_packets(options.replay) + else: + run_vscode(dbg, args, options) + dbg.terminate() + + +if __name__ == '__main__': + main() diff --git a/lldb/tools/CMakeLists.txt b/lldb/tools/CMakeLists.txt index 75c9c15c487..6afcb4c37f2 100644 --- a/lldb/tools/CMakeLists.txt +++ b/lldb/tools/CMakeLists.txt @@ -5,6 +5,7 @@ endif() add_subdirectory(argdumper) add_subdirectory(driver) add_subdirectory(lldb-mi) +add_subdirectory(lldb-vscode) if (LLDB_CAN_USE_LLDB_SERVER) add_subdirectory(lldb-server) endif() diff --git a/lldb/tools/lldb-vscode/BreakpointBase.cpp b/lldb/tools/lldb-vscode/BreakpointBase.cpp new file mode 100644 index 00000000000..adb7abd1a3a --- /dev/null +++ b/lldb/tools/lldb-vscode/BreakpointBase.cpp @@ -0,0 +1,37 @@ +//===-- BreakpointBase.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BreakpointBase.h" +#include "llvm/ADT/StringExtras.h" + +using namespace lldb_vscode; + +BreakpointBase::BreakpointBase(const llvm::json::Object &obj) + : condition(GetString(obj, "condition")), + hitCondition(GetString(obj, "hitCondition")), + logMessage(GetString(obj, "logMessage")) {} + +void BreakpointBase::SetCondition() { bp.SetCondition(condition.c_str()); } + +void BreakpointBase::SetHitCondition() { + uint64_t hitCount = 0; + if (llvm::to_integer(hitCondition, hitCount)) + bp.SetIgnoreCount(hitCount - 1); +} + +void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) { + if (condition != request_bp.condition) { + condition = request_bp.condition; + SetCondition(); + } + if (hitCondition != request_bp.hitCondition) { + hitCondition = request_bp.hitCondition; + SetHitCondition(); + } +} diff --git a/lldb/tools/lldb-vscode/BreakpointBase.h b/lldb/tools/lldb-vscode/BreakpointBase.h new file mode 100644 index 00000000000..e27ffe3bd39 --- /dev/null +++ b/lldb/tools/lldb-vscode/BreakpointBase.h @@ -0,0 +1,44 @@ +//===-- BreakpointBase.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_BREAKPOINTBASE_H_ +#define LLDBVSCODE_BREAKPOINTBASE_H_ + +#include "JSONUtils.h" +#include "lldb/API/SBBreakpoint.h" +#include "llvm/Support/JSON.h" +#include <string> + +namespace lldb_vscode { + +struct BreakpointBase { + + // An optional expression for conditional breakpoints. + std::string condition; + // An optional expression that controls how many hits of the breakpoint are + // ignored. The backend is expected to interpret the expression as needed + std::string hitCondition; + // If this attribute exists and is non-empty, the backend must not 'break' + // (stop) but log the message instead. Expressions within {} are + // interpolated. + std::string logMessage; + // The LLDB breakpoint associated wit this source breakpoint + lldb::SBBreakpoint bp; + + BreakpointBase() = default; + BreakpointBase(const llvm::json::Object &obj); + + void SetCondition(); + void SetHitCondition(); + void UpdateBreakpoint(const BreakpointBase &request_bp); +}; + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/CMakeLists.txt b/lldb/tools/lldb-vscode/CMakeLists.txt new file mode 100644 index 00000000000..f1570089a84 --- /dev/null +++ b/lldb/tools/lldb-vscode/CMakeLists.txt @@ -0,0 +1,30 @@ +if ( CMAKE_SYSTEM_NAME MATCHES "Windows" OR CMAKE_SYSTEM_NAME MATCHES "NetBSD" ) + add_definitions( -DIMPORT_LIBLLDB ) + list(APPEND extra_libs lldbHost) +endif () + +if (HAVE_LIBPTHREAD) + list(APPEND extra_libs pthread) +endif () + +# We need to include the llvm components we depend on manually, as liblldb does +# not re-export those. +set(LLVM_LINK_COMPONENTS Support) +add_lldb_tool(lldb-vscode + lldb-vscode.cpp + BreakpointBase.cpp + ExceptionBreakpoint.cpp + FunctionBreakpoint.cpp + JSONUtils.cpp + LLDBUtils.cpp + SourceBreakpoint.cpp + VSCode.cpp + + LINK_LIBS + liblldb + ${host_lib} + ${extra_libs} + + LINK_COMPONENTS + Support + ) diff --git a/lldb/tools/lldb-vscode/ExceptionBreakpoint.cpp b/lldb/tools/lldb-vscode/ExceptionBreakpoint.cpp new file mode 100644 index 00000000000..96bc0930e42 --- /dev/null +++ b/lldb/tools/lldb-vscode/ExceptionBreakpoint.cpp @@ -0,0 +1,32 @@ +//===-- ExceptionBreakpoint.cpp ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExceptionBreakpoint.h" +#include "VSCode.h" + +namespace lldb_vscode { + +void ExceptionBreakpoint::SetBreakpoint() { + if (bp.IsValid()) + return; + bool catch_value = filter.find("_catch") != std::string::npos; + bool throw_value = filter.find("_throw") != std::string::npos; + bp = g_vsc.target.BreakpointCreateForException(language, catch_value, + throw_value); +} + +void ExceptionBreakpoint::ClearBreakpoint() { + if (!bp.IsValid()) + return; + g_vsc.target.BreakpointDelete(bp.GetID()); + bp = lldb::SBBreakpoint(); +} + +} // namespace lldb_vscode + diff --git a/lldb/tools/lldb-vscode/ExceptionBreakpoint.h b/lldb/tools/lldb-vscode/ExceptionBreakpoint.h new file mode 100644 index 00000000000..f3e1e706809 --- /dev/null +++ b/lldb/tools/lldb-vscode/ExceptionBreakpoint.h @@ -0,0 +1,38 @@ +//===-- ExceptionBreakpoint.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_EXCEPTIONBREAKPOINT_H_ +#define LLDBVSCODE_EXCEPTIONBREAKPOINT_H_ + +#include <string> + +#include "lldb/API/SBBreakpoint.h" + +namespace lldb_vscode { + +struct ExceptionBreakpoint { + std::string filter; + std::string label; + lldb::LanguageType language; + bool default_value; + lldb::SBBreakpoint bp; + ExceptionBreakpoint(std::string f, std::string l, lldb::LanguageType lang) : + filter(std::move(f)), + label(std::move(l)), + language(lang), + default_value(false), + bp() {} + + void SetBreakpoint(); + void ClearBreakpoint(); +}; + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp b/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp new file mode 100644 index 00000000000..f83333dc989 --- /dev/null +++ b/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp @@ -0,0 +1,28 @@ +//===-- FunctionBreakpoint.cpp ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FunctionBreakpoint.h" +#include "VSCode.h" + +namespace lldb_vscode { + +FunctionBreakpoint::FunctionBreakpoint(const llvm::json::Object &obj) + : BreakpointBase(obj), functionName(GetString(obj, "name")) {} + +void FunctionBreakpoint::SetBreakpoint() { + if (functionName.empty()) + return; + bp = g_vsc.target.BreakpointCreateByName(functionName.c_str()); + if (!condition.empty()) + SetCondition(); + if (!hitCondition.empty()) + SetHitCondition(); +} + +} diff --git a/lldb/tools/lldb-vscode/FunctionBreakpoint.h b/lldb/tools/lldb-vscode/FunctionBreakpoint.h new file mode 100644 index 00000000000..ff4f34dba07 --- /dev/null +++ b/lldb/tools/lldb-vscode/FunctionBreakpoint.h @@ -0,0 +1,29 @@ +//===-- FunctionBreakpoint.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_FUNCTIONBREAKPOINT_H_ +#define LLDBVSCODE_FUNCTIONBREAKPOINT_H_ + +#include "BreakpointBase.h" + +namespace lldb_vscode { + +struct FunctionBreakpoint : public BreakpointBase { + std::string functionName; + + FunctionBreakpoint() = default; + FunctionBreakpoint(const llvm::json::Object &obj); + + // Set this breakpoint in LLDB as a new breakpoint + void SetBreakpoint(); +}; + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp new file mode 100644 index 00000000000..e50b9e2f968 --- /dev/null +++ b/lldb/tools/lldb-vscode/JSONUtils.cpp @@ -0,0 +1,882 @@ +//===-- JSONUtils.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/FormatAdapters.h" + +#include "lldb/API/SBBreakpoint.h" +#include "lldb/API/SBBreakpointLocation.h" +#include "lldb/API/SBValue.h" + +#include "ExceptionBreakpoint.h" +#include "JSONUtils.h" +#include "LLDBUtils.h" +#include "VSCode.h" + +namespace lldb_vscode { + +llvm::StringRef GetAsString(const llvm::json::Value &value) { + if (auto s = value.getAsString()) + return *s; + return llvm::StringRef(); +} + +// Gets a string from a JSON object using the key, or returns an empty string. +llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key) { + if (auto value = obj.getString(key)) + return GetAsString(*value); + return llvm::StringRef(); +} + +llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key) { + if (obj == nullptr) + return llvm::StringRef(); + return GetString(*obj, key); +} + +// Gets an unsigned integer from a JSON object using the key, or returns the +// specified fail value. +uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key, + uint64_t fail_value) { + if (auto value = obj.getInteger(key)) + return (uint64_t)*value; + return fail_value; +} + +uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key, + uint64_t fail_value) { + if (obj == nullptr) + return fail_value; + return GetUnsigned(*obj, key, fail_value); +} + +bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key, + bool fail_value) { + if (auto value = obj.getBoolean(key)) + return *value; + if (auto value = obj.getInteger(key)) + return *value != 0; + return fail_value; +} + +bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key, + bool fail_value) { + if (obj == nullptr) + return fail_value; + return GetBoolean(*obj, key, fail_value); +} + +int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key, + int64_t fail_value) { + if (auto value = obj.getInteger(key)) + return *value; + return fail_value; +} + +int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key, + int64_t fail_value) { + if (obj == nullptr) + return fail_value; + return GetSigned(*obj, key, fail_value); +} + +bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) { + if (obj.find(key) != obj.end()) + return true; + return false; +} + +std::vector<std::string> GetStrings(const llvm::json::Object *obj, + llvm::StringRef key) { + std::vector<std::string> strs; + auto json_array = obj->getArray(key); + if (!json_array) + return strs; + for (const auto &value : *json_array) { + switch (value.kind()) { + case llvm::json::Value::String: + strs.push_back(value.getAsString()->str()); + break; + case llvm::json::Value::Number: + case llvm::json::Value::Boolean: { + std::string s; + llvm::raw_string_ostream strm(s); + strm << value; + strs.push_back(strm.str()); + break; + } + case llvm::json::Value::Null: + case llvm::json::Value::Object: + case llvm::json::Value::Array: + break; + } + } + return strs; +} + +void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object, + llvm::StringRef key) { + + llvm::StringRef value = v.GetValue(); + llvm::StringRef summary = v.GetSummary(); + llvm::StringRef type_name = v.GetType().GetDisplayTypeName(); + + std::string result; + llvm::raw_string_ostream strm(result); + if (!value.empty()) { + strm << value; + if (!summary.empty()) + strm << ' ' << summary; + } else if (!summary.empty()) { + strm << ' ' << summary; + } else if (!type_name.empty()) { + strm << type_name; + lldb::addr_t address = v.GetLoadAddress(); + if (address != LLDB_INVALID_ADDRESS) + strm << " @ " << llvm::format_hex(address, 0); + } + strm.flush(); + object.try_emplace(key, result); +} + +void FillResponse(const llvm::json::Object &request, + llvm::json::Object &response) { + // Fill in all of the needed response fields to a "request" and set "success" + // to true by default. + response.try_emplace("type", "response"); + response.try_emplace("seq", (int64_t)0); + response.try_emplace("command", GetString(request, "command")); + const int64_t seq = GetSigned(request, "seq", 0); + response.try_emplace("request_seq", seq); + response.try_emplace("success", true); +} + +//---------------------------------------------------------------------- +// "Scope": { +// "type": "object", +// "description": "A Scope is a named container for variables. Optionally +// a scope can map to a source or a range within a source.", +// "properties": { +// "name": { +// "type": "string", +// "description": "Name of the scope such as 'Arguments', 'Locals'." +// }, +// "variablesReference": { +// "type": "integer", +// "description": "The variables of this scope can be retrieved by +// passing the value of variablesReference to the +// VariablesRequest." +// }, +// "namedVariables": { +// "type": "integer", +// "description": "The number of named variables in this scope. The +// client can use this optional information to present +// the variables in a paged UI and fetch them in chunks." +// }, +// "indexedVariables": { +// "type": "integer", +// "description": "The number of indexed variables in this scope. The +// client can use this optional information to present +// the variables in a paged UI and fetch them in chunks." +// }, +// "expensive": { +// "type": "boolean", +// "description": "If true, the number of variables in this scope is +// large or expensive to retrieve." +// }, +// "source": { +// "$ref": "#/definitions/Source", +// "description": "Optional source for this scope." +// }, +// "line": { +// "type": "integer", +// "description": "Optional start line of the range covered by this +// scope." +// }, +// "column": { +// "type": "integer", +// "description": "Optional start column of the range covered by this +// scope." +// }, +// "endLine": { +// "type": "integer", +// "description": "Optional end line of the range covered by this scope." +// }, +// "endColumn": { +// "type": "integer", +// "description": "Optional end column of the range covered by this +// scope." +// } +// }, +// "required": [ "name", "variablesReference", "expensive" ] +// } +//---------------------------------------------------------------------- +llvm::json::Value CreateScope(const llvm::StringRef name, + int64_t variablesReference, + int64_t namedVariables, bool expensive) { + llvm::json::Object object; + object.try_emplace("name", name.str()); + object.try_emplace("variablesReference", variablesReference); + object.try_emplace("expensive", expensive); + object.try_emplace("namedVariables", namedVariables); + return llvm::json::Value(std::move(object)); +} + +//---------------------------------------------------------------------- +// "Breakpoint": { +// "type": "object", +// "description": "Information about a Breakpoint created in setBreakpoints +// or setFunctionBreakpoints.", +// "properties": { +// "id": { +// "type": "integer", +// "description": "An optional unique identifier for the breakpoint." +// }, +// "verified": { +// "type": "boolean", +// "description": "If true breakpoint could be set (but not necessarily +// at the desired location)." +// }, +// "message": { +// "type": "string", +// "description": "An optional message about the state of the breakpoint. +// This is shown to the user and can be used to explain +// why a breakpoint could not be verified." +// }, +// "source": { +// "$ref": "#/definitions/Source", +// "description": "The source where the breakpoint is located." +// }, +// "line": { +// "type": "integer", +// "description": "The start line of the actual range covered by the +// breakpoint." +// }, +// "column": { +// "type": "integer", +// "description": "An optional start column of the actual range covered +// by the breakpoint." +// }, +// "endLine": { +// "type": "integer", +// "description": "An optional end line of the actual range covered by +// the breakpoint." +// }, +// "endColumn": { +// "type": "integer", +// "description": "An optional end column of the actual range covered by +// the breakpoint. If no end line is given, then the end +// column is assumed to be in the start line." +// } +// }, +// "required": [ "verified" ] +// } +//---------------------------------------------------------------------- +llvm::json::Value CreateBreakpoint(lldb::SBBreakpointLocation &bp_loc) { + // 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; + if (!bp_loc.IsValid()) + return llvm::json::Value(std::move(object)); + + object.try_emplace("verified", true); + const auto bp_id = bp_loc.GetBreakpoint().GetID(); + const auto vs_id = (int64_t)(((int64_t)bp_id << 32) | bp_loc.GetID()); + object.try_emplace("id", vs_id); + auto bp_addr = bp_loc.GetAddress(); + if (bp_addr.IsValid()) { + auto line_entry = bp_addr.GetLineEntry(); + const auto line = line_entry.GetLine(); + if (line != UINT32_MAX) + object.try_emplace("line", line); + object.try_emplace("source", CreateSource(line_entry)); + } + return llvm::json::Value(std::move(object)); +} + +void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints) { + if (!bp.IsValid()) + return; + const auto num_locations = bp.GetNumLocations(); + if (num_locations == 0) + return; + for (size_t i = 0; i < num_locations; ++i) { + auto bp_loc = bp.GetLocationAtIndex(i); + breakpoints.emplace_back(CreateBreakpoint(bp_loc)); + } +} + +//---------------------------------------------------------------------- +// "Event": { +// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { +// "type": "object", +// "description": "Server-initiated event.", +// "properties": { +// "type": { +// "type": "string", +// "enum": [ "event" ] +// }, +// "event": { +// "type": "string", +// "description": "Type of event." +// }, +// "body": { +// "type": [ "array", "boolean", "integer", "null", "number" , +// "object", "string" ], +// "description": "Event-specific information." +// } +// }, +// "required": [ "type", "event" ] +// }] +// }, +// "ProtocolMessage": { +// "type": "object", +// "description": "Base class of requests, responses, and events.", +// "properties": { +// "seq": { +// "type": "integer", +// "description": "Sequence number." +// }, +// "type": { +// "type": "string", +// "description": "Message type.", +// "_enum": [ "request", "response", "event" ] +// } +// }, +// "required": [ "seq", "type" ] +// } +//---------------------------------------------------------------------- +llvm::json::Object CreateEvent(const llvm::StringRef event_name) { + llvm::json::Object event; + event.try_emplace("seq", 0); + event.try_emplace("type", "event"); + event.try_emplace("event", event_name); + return event; +} + +//---------------------------------------------------------------------- +// "ExceptionBreakpointsFilter": { +// "type": "object", +// "description": "An ExceptionBreakpointsFilter is shown in the UI as an +// option for configuring how exceptions are dealt with.", +// "properties": { +// "filter": { +// "type": "string", +// "description": "The internal ID of the filter. This value is passed +// to the setExceptionBreakpoints request." +// }, +// "label": { +// "type": "string", +// "description": "The name of the filter. This will be shown in the UI." +// }, +// "default": { +// "type": "boolean", +// "description": "Initial value of the filter. If not specified a value +// 'false' is assumed." +// } +// }, +// "required": [ "filter", "label" ] +// } +//---------------------------------------------------------------------- +llvm::json::Value +CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) { + llvm::json::Object object; + object.try_emplace("filter", bp.filter); + object.try_emplace("label", bp.label); + object.try_emplace("default", bp.default_value); + return llvm::json::Value(std::move(object)); +} + +//---------------------------------------------------------------------- +// "Source": { +// "type": "object", +// "description": "A Source is a descriptor for source code. It is returned +// from the debug adapter as part of a StackFrame and it is +// used by clients when specifying breakpoints.", +// "properties": { +// "name": { +// "type": "string", +// "description": "The short name of the source. Every source returned +// from the debug adapter has a name. When sending a +// source to the debug adapter this name is optional." +// }, +// "path": { +// "type": "string", +// "description": "The path of the source to be shown in the UI. It is +// only used to locate and load the content of the +// source if no sourceReference is specified (or its +// value is 0)." +// }, +// "sourceReference": { +// "type": "number", +// "description": "If sourceReference > 0 the contents of the source must +// be retrieved through the SourceRequest (even if a path +// is specified). A sourceReference is only valid for a +// session, so it must not be used to persist a source." +// }, +// "presentationHint": { +// "type": "string", +// "description": "An optional hint for how to present the source in the +// UI. A value of 'deemphasize' can be used to indicate +// that the source is not available or that it is +// skipped on stepping.", +// "enum": [ "normal", "emphasize", "deemphasize" ] +// }, +// "origin": { +// "type": "string", +// "description": "The (optional) origin of this source: possible values +// 'internal module', 'inlined content from source map', +// etc." +// }, +// "sources": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/Source" +// }, +// "description": "An optional list of sources that are related to this +// source. These may be the source that generated this +// source." +// }, +// "adapterData": { +// "type":["array","boolean","integer","null","number","object","string"], +// "description": "Optional data that a debug adapter might want to loop +// through the client. The client should leave the data +// intact and persist it across sessions. The client +// should not interpret the data." +// }, +// "checksums": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/Checksum" +// }, +// "description": "The checksums associated with this file." +// } +// } +// } +//---------------------------------------------------------------------- +llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) { + llvm::json::Object object; + lldb::SBFileSpec file = line_entry.GetFileSpec(); + if (file.IsValid()) { + const char *name = file.GetFilename(); + if (name) + object.try_emplace("name", name); + char path[PATH_MAX] = ""; + file.GetPath(path, sizeof(path)); + if (path[0]) { + object.try_emplace("path", std::string(path)); + } + } + return llvm::json::Value(std::move(object)); +} + +llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) { + disasm_line = 0; + auto line_entry = frame.GetLineEntry(); + if (line_entry.GetFileSpec().IsValid()) + return CreateSource(line_entry); + + llvm::json::Object object; + const auto pc = frame.GetPC(); + + lldb::SBInstructionList insts; + lldb::SBFunction function = frame.GetFunction(); + lldb::addr_t low_pc = LLDB_INVALID_ADDRESS; + if (function.IsValid()) { + low_pc = function.GetStartAddress().GetLoadAddress(g_vsc.target); + auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc); + if (addr_srcref != g_vsc.addr_to_source_ref.end()) { + // We have this disassembly cached already, return the existing + // sourceReference + object.try_emplace("sourceReference", addr_srcref->second); + disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc); + } else { + insts = function.GetInstructions(g_vsc.target); + } + } else { + lldb::SBSymbol symbol = frame.GetSymbol(); + if (symbol.IsValid()) { + low_pc = symbol.GetStartAddress().GetLoadAddress(g_vsc.target); + auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc); + if (addr_srcref != g_vsc.addr_to_source_ref.end()) { + // We have this disassembly cached already, return the existing + // sourceReference + object.try_emplace("sourceReference", addr_srcref->second); + disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc); + } else { + insts = symbol.GetInstructions(g_vsc.target); + } + } + } + const auto num_insts = insts.GetSize(); + if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) { + object.try_emplace("name", frame.GetFunctionName()); + SourceReference source; + llvm::raw_string_ostream src_strm(source.content); + std::string line; + for (size_t i = 0; i < num_insts; ++i) { + lldb::SBInstruction inst = insts.GetInstructionAtIndex(i); + const auto inst_addr = inst.GetAddress().GetLoadAddress(g_vsc.target); + const char *m = inst.GetMnemonic(g_vsc.target); + const char *o = inst.GetOperands(g_vsc.target); + const char *c = inst.GetComment(g_vsc.target); + if (pc == inst_addr) + disasm_line = i + 1; + const auto inst_offset = inst_addr - low_pc; + int spaces = 0; + if (inst_offset < 10) + spaces = 3; + else if (inst_offset < 100) + spaces = 2; + else if (inst_offset < 1000) + spaces = 1; + line.clear(); + llvm::raw_string_ostream line_strm(line); + line_strm << llvm::formatv("{0:X+}: <{1}> {2} {3,12} {4}", inst_addr, + inst_offset, llvm::fmt_repeat(' ', spaces), + m, o); + const uint32_t comment_row = 60; + // If there is a comment append it starting at column 60 + if (c && c[0]) { + if (line.size() < comment_row) + line_strm.indent(comment_row - line_strm.str().size()); + line_strm << " # " << c; + } + src_strm << line_strm.str() << "\n"; + source.addr_to_line[inst_addr] = i + 1; + } + // Flush the source stream + src_strm.str(); + auto sourceReference = VSCode::GetNextSourceReference(); + g_vsc.source_map[sourceReference] = std::move(source); + g_vsc.addr_to_source_ref[low_pc] = sourceReference; + object.try_emplace("sourceReference", sourceReference); + } + return llvm::json::Value(std::move(object)); +} + +//---------------------------------------------------------------------- +// "StackFrame": { +// "type": "object", +// "description": "A Stackframe contains the source location.", +// "properties": { +// "id": { +// "type": "integer", +// "description": "An identifier for the stack frame. It must be unique +// across all threads. This id can be used to retrieve +// the scopes of the frame with the 'scopesRequest' or +// to restart the execution of a stackframe." +// }, +// "name": { +// "type": "string", +// "description": "The name of the stack frame, typically a method name." +// }, +// "source": { +// "$ref": "#/definitions/Source", +// "description": "The optional source of the frame." +// }, +// "line": { +// "type": "integer", +// "description": "The line within the file of the frame. If source is +// null or doesn't exist, line is 0 and must be ignored." +// }, +// "column": { +// "type": "integer", +// "description": "The column within the line. If source is null or +// doesn't exist, column is 0 and must be ignored." +// }, +// "endLine": { +// "type": "integer", +// "description": "An optional end line of the range covered by the +// stack frame." +// }, +// "endColumn": { +// "type": "integer", +// "description": "An optional end column of the range covered by the +// stack frame." +// }, +// "moduleId": { +// "type": ["integer", "string"], +// "description": "The module associated with this frame, if any." +// }, +// "presentationHint": { +// "type": "string", +// "enum": [ "normal", "label", "subtle" ], +// "description": "An optional hint for how to present this frame in +// the UI. A value of 'label' can be used to indicate +// that the frame is an artificial frame that is used +// as a visual label or separator. A value of 'subtle' +// can be used to change the appearance of a frame in +// a 'subtle' way." +// } +// }, +// "required": [ "id", "name", "line", "column" ] +// } +//---------------------------------------------------------------------- +llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { + llvm::json::Object object; + int64_t frame_id = MakeVSCodeFrameID(frame); + object.try_emplace("id", frame_id); + object.try_emplace("name", frame.GetFunctionName()); + int64_t disasm_line = 0; + object.try_emplace("source", CreateSource(frame, disasm_line)); + + auto line_entry = frame.GetLineEntry(); + if (disasm_line > 0) { + object.try_emplace("line", disasm_line); + } else { + auto line = line_entry.GetLine(); + if (line == UINT32_MAX) + line = 0; + object.try_emplace("line", line); + } + object.try_emplace("column", line_entry.GetColumn()); + return llvm::json::Value(std::move(object)); +} + +//---------------------------------------------------------------------- +// "Thread": { +// "type": "object", +// "description": "A Thread", +// "properties": { +// "id": { +// "type": "integer", +// "description": "Unique identifier for the thread." +// }, +// "name": { +// "type": "string", +// "description": "A name of the thread." +// } +// }, +// "required": [ "id", "name" ] +// } +//---------------------------------------------------------------------- +llvm::json::Value CreateThread(lldb::SBThread &thread) { + llvm::json::Object object; + object.try_emplace("id", (int64_t)thread.GetThreadID()); + char thread_str[64]; + snprintf(thread_str, sizeof(thread_str), "Thread #%u", thread.GetIndexID()); + const char *name = thread.GetName(); + if (name) { + std::string thread_with_name(thread_str); + thread_with_name += ' '; + thread_with_name += name; + object.try_emplace("name", thread_with_name); + } else { + object.try_emplace("name", std::string(thread_str)); + } + return llvm::json::Value(std::move(object)); +} + +//---------------------------------------------------------------------- +// "StoppedEvent": { +// "allOf": [ { "$ref": "#/definitions/Event" }, { +// "type": "object", +// "description": "Event message for 'stopped' event type. The event +// indicates that the execution of the debuggee has stopped +// due to some condition. This can be caused by a break +// point previously set, a stepping action has completed, +// by executing a debugger statement etc.", +// "properties": { +// "event": { +// "type": "string", +// "enum": [ "stopped" ] +// }, +// "body": { +// "type": "object", +// "properties": { +// "reason": { +// "type": "string", +// "description": "The reason for the event. For backward +// compatibility this string is shown in the UI if +// the 'description' attribute is missing (but it +// must not be translated).", +// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ] +// }, +// "description": { +// "type": "string", +// "description": "The full reason for the event, e.g. 'Paused +// on exception'. This string is shown in the UI +// as is." +// }, +// "threadId": { +// "type": "integer", +// "description": "The thread which was stopped." +// }, +// "text": { +// "type": "string", +// "description": "Additional information. E.g. if reason is +// 'exception', text contains the exception name. +// This string is shown in the UI." +// }, +// "allThreadsStopped": { +// "type": "boolean", +// "description": "If allThreadsStopped is true, a debug adapter +// can announce that all threads have stopped. +// The client should use this information to +// enable that all threads can be expanded to +// access their stacktraces. If the attribute +// is missing or false, only the thread with the +// given threadId can be expanded." +// } +// }, +// "required": [ "reason" ] +// } +// }, +// "required": [ "event", "body" ] +// }] +// } +//---------------------------------------------------------------------- +llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, + uint32_t stop_id) { + llvm::json::Object event(CreateEvent("stopped")); + llvm::json::Object body; + switch (thread.GetStopReason()) { + case lldb::eStopReasonTrace: + case lldb::eStopReasonPlanComplete: + body.try_emplace("reason", "step"); + break; + case lldb::eStopReasonBreakpoint: { + ExceptionBreakpoint *exc_bp = g_vsc.GetExceptionBPFromStopReason(thread); + if (exc_bp) { + body.try_emplace("reason", "exception"); + body.try_emplace("description", exc_bp->label); + } else { + body.try_emplace("reason", "breakpoint"); + } + } break; + case lldb::eStopReasonWatchpoint: + case lldb::eStopReasonInstrumentation: + body.try_emplace("reason", "breakpoint"); + break; + case lldb::eStopReasonSignal: + body.try_emplace("reason", "exception"); + break; + case lldb::eStopReasonException: + body.try_emplace("reason", "exception"); + break; + case lldb::eStopReasonExec: + body.try_emplace("reason", "entry"); + break; + case lldb::eStopReasonThreadExiting: + case lldb::eStopReasonInvalid: + case lldb::eStopReasonNone: + break; + } + if (stop_id == 0) + body.try_emplace("reason", "entry"); + const lldb::tid_t tid = thread.GetThreadID(); + body.try_emplace("threadId", (int64_t)tid); + // If no description has been set, then set it to the default thread stopped + // description. If we have breakpoints that get hit and shouldn't be reported + // as breakpoints, then they will set the description above. + if (ObjectContainsKey(body, "description")) { + char description[1024]; + if (thread.GetStopDescription(description, sizeof(description))) { + body.try_emplace("description", std::string(description)); + } + } + if (tid == g_vsc.focus_tid) { + body.try_emplace("threadCausedFocus", true); + } + body.try_emplace("preserveFocusHint", tid != g_vsc.focus_tid); + body.try_emplace("allThreadsStopped", true); + event.try_emplace("body", std::move(body)); + return llvm::json::Value(std::move(event)); +} + +//---------------------------------------------------------------------- +// "Variable": { +// "type": "object", +// "description": "A Variable is a name/value pair. Optionally a variable +// can have a 'type' that is shown if space permits or when +// hovering over the variable's name. An optional 'kind' is +// used to render additional properties of the variable, +// e.g. different icons can be used to indicate that a +// variable is public or private. If the value is +// structured (has children), a handle is provided to +// retrieve the children with the VariablesRequest. If +// the number of named or indexed children is large, the +// numbers should be returned via the optional +// 'namedVariables' and 'indexedVariables' attributes. The +// client can use this optional information to present the +// children in a paged UI and fetch them in chunks.", +// "properties": { +// "name": { +// "type": "string", +// "description": "The variable's name." +// }, +// "value": { +// "type": "string", +// "description": "The variable's value. This can be a multi-line text, +// e.g. for a function the body of a function." +// }, +// "type": { +// "type": "string", +// "description": "The type of the variable's value. Typically shown in +// the UI when hovering over the value." +// }, +// "presentationHint": { +// "$ref": "#/definitions/VariablePresentationHint", +// "description": "Properties of a variable that can be used to determine +// how to render the variable in the UI." +// }, +// "evaluateName": { +// "type": "string", +// "description": "Optional evaluatable name of this variable which can +// be passed to the 'EvaluateRequest' to fetch the +// variable's value." +// }, +// "variablesReference": { +// "type": "integer", +// "description": "If variablesReference is > 0, the variable is +// structured and its children can be retrieved by +// passing variablesReference to the VariablesRequest." +// }, +// "namedVariables": { +// "type": "integer", +// "description": "The number of named child variables. The client can +// use this optional information to present the children +// in a paged UI and fetch them in chunks." +// }, +// "indexedVariables": { +// "type": "integer", +// "description": "The number of indexed child variables. The client +// can use this optional information to present the +// children in a paged UI and fetch them in chunks." +// } +// }, +// "required": [ "name", "value", "variablesReference" ] +// } +//---------------------------------------------------------------------- +llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference, + int64_t varID, bool format_hex) { + llvm::json::Object object; + auto name = v.GetName(); + object.try_emplace("name", name ? name : "<null>"); + if (format_hex) + v.SetFormat(lldb::eFormatHex); + SetValueForKey(v, object, "value"); + auto type_cstr = v.GetType().GetDisplayTypeName(); + object.try_emplace("type", type_cstr ? type_cstr : NO_TYPENAME); + if (varID != INT64_MAX) + object.try_emplace("id", varID); + if (v.MightHaveChildren()) + object.try_emplace("variablesReference", variablesReference); + else + object.try_emplace("variablesReference", (int64_t)0); + lldb::SBStream evaluateStream; + v.GetExpressionPath(evaluateStream); + const char *evaluateName = evaluateStream.GetData(); + if (evaluateName && evaluateName[0]) + object.try_emplace("evaluateName", std::string(evaluateName)); + return llvm::json::Value(std::move(object)); +} + +} // namespace lldb_vscode + diff --git a/lldb/tools/lldb-vscode/JSONUtils.h b/lldb/tools/lldb-vscode/JSONUtils.h new file mode 100644 index 00000000000..c5d9ec17105 --- /dev/null +++ b/lldb/tools/lldb-vscode/JSONUtils.h @@ -0,0 +1,421 @@ +//===-- JSONUtils.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_JSONUTILS_H_ +#define LLDBVSCODE_JSONUTILS_H_ + +#include <stdint.h> +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include "VSCodeForward.h" + +namespace lldb_vscode { + +//------------------------------------------------------------------ +/// Extract simple values as a string. +/// +/// @param[in] value +/// A JSON value to extract the string from. +/// +/// @return +/// A llvm::StringRef that contains the string value, or an empty +/// string if \a value isn't a string. +//------------------------------------------------------------------ +llvm::StringRef GetAsString(const llvm::json::Value &value); + +//------------------------------------------------------------------ +/// Extract the string value for the specified key from the +/// specified object. +/// +/// @param[in] obj +/// A JSON object that we will attempt to extract the value from +/// +/// @param[in] key +/// The key to use when extracting the value +/// +/// @return +/// A llvm::StringRef that contains the string value for the +/// specified \a key, or an empty string if there is no key that +/// matches or if the value is not a string. +//------------------------------------------------------------------ +llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key); +llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key); + +//------------------------------------------------------------------ +/// Extract the unsigned integer value for the specified key from +/// the specified object. +/// +/// @param[in] obj +/// A JSON object that we will attempt to extract the value from +/// +/// @param[in] key +/// The key to use when extracting the value +/// +/// @return +/// The unsigned integer value for the specified \a key, or +/// \a fail_value if there is no key that matches or if the +/// value is not an integer. +//------------------------------------------------------------------ +uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key, + uint64_t fail_value); +uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key, + uint64_t fail_value); + +//------------------------------------------------------------------ +/// Extract the boolean value for the specified key from the +/// specified object. +/// +/// @param[in] obj +/// A JSON object that we will attempt to extract the value from +/// +/// @param[in] key +/// The key to use when extracting the value +/// +/// @return +/// The boolean value for the specified \a key, or \a fail_value +/// if there is no key that matches or if the value is not a +/// boolean value of an integer. +//------------------------------------------------------------------ +bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key, + bool fail_value); +bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key, + bool fail_value); + +//------------------------------------------------------------------ +/// Extract the signed integer for the specified key from the +/// specified object. +/// +/// @param[in] obj +/// A JSON object that we will attempt to extract the value from +/// +/// @param[in] key +/// The key to use when extracting the value +/// +/// @return +/// The signed integer value for the specified \a key, or +/// \a fail_value if there is no key that matches or if the +/// value is not an integer. +//------------------------------------------------------------------ +int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key, + int64_t fail_value); +int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key, + int64_t fail_value); + +//------------------------------------------------------------------ +/// Check if the specified key exists in the specified object. +/// +/// @param[in] obj +/// A JSON object that we will attempt to extract the value from +/// +/// @param[in] key +/// The key to check for +/// +/// @return +/// \b True if the key exists in the \a obj, \b False otherwise. +//------------------------------------------------------------------ +bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key); + +//------------------------------------------------------------------ +/// Extract an array of strings for the specified key from an object. +/// +/// String values in the array will be extracted without any quotes +/// around them. Numbers and Booleans will be converted into +/// strings. Any NULL, array or objects values in the array will be +/// ignored. +/// +/// @param[in] obj +/// A JSON object that we will attempt to extract the array from +/// +/// @param[in] key +/// The key to use when extracting the value +/// +/// @return +/// An array of string values for the specified \a key, or +/// \a fail_value if there is no key that matches or if the +/// value is not an array or all items in the array are not +/// strings, numbers or booleans. +//------------------------------------------------------------------ +std::vector<std::string> GetStrings(const llvm::json::Object *obj, + llvm::StringRef key); + +//------------------------------------------------------------------ +/// Fill a response object given the request object. +/// +/// The \a response object will get its "type" set to "response", +/// the "seq" set to zero, "response_seq" set to the "seq" value from +/// \a request, "command" set to the "command" from \a request, +/// and "success" set to true. +/// +/// @param[in] request +/// The request object received from a call to VSCode::ReadJSON(). +/// +/// @param[in,out] response +/// An empty llvm::json::Object object that will be filled +/// in as noted in description. +//------------------------------------------------------------------ +void FillResponse(const llvm::json::Object &request, + llvm::json::Object &response); + +//---------------------------------------------------------------------- +/// Emplace the string value from an SBValue into the supplied object +/// using \a key as the key that will contain the value. +/// +/// The value is what we will display in VS Code. Some SBValue objects +/// can have a value and/or a summary. If a value has both, we +/// combine the value and the summary into one string. If we only have a +/// value or summary, then that is considered the value. If there is +/// no value and no summary then the value is the type name followed by +/// the address of the type if it has an address. +/// +/// +/// @param[in] v +/// A lldb::SBValue object to extract the string value from +/// +/// +/// @param[in] object +/// The object to place the value object into +/// +/// +/// @param[in] key +/// The key name to use when inserting the value object we create +//---------------------------------------------------------------------- +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 +/// \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 +/// +/// @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); + +//---------------------------------------------------------------------- +/// Converts breakpoint location to a Visual Studio Code "Breakpoint" +/// JSON object and appends it to the \a breakpoints array. +/// +/// @param[in] bp_loc +/// A LLDB breakpoint location object to convert into a JSON value +/// +/// @return +/// A "Breakpoint" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateBreakpoint(lldb::SBBreakpointLocation &bp_loc); + +//---------------------------------------------------------------------- +/// Create a "Event" JSON object using \a event_name as the event name +/// +/// @param[in] event_name +/// The string value to use for the "event" key in the JSON object. +/// +/// @return +/// A "Event" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Object CreateEvent(const llvm::StringRef event_name); + +//---------------------------------------------------------------------- +/// Create a "ExceptionBreakpointsFilter" JSON object as described in +/// the Visual Studio Code debug adaptor definition. +/// +/// @param[in] bp +/// The exception breakppoint object to use +/// +/// @return +/// A "ExceptionBreakpointsFilter" JSON object with that follows +/// the formal JSON definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value +CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp); + +//---------------------------------------------------------------------- +/// Create a "Scope" JSON object as described in the Visual Studio Code +/// debug adaptor definition. +/// +/// @param[in] name +/// The value to place into the "name" key +// +/// @param[in] variablesReference +/// The value to place into the "variablesReference" key +// +/// @param[in] namedVariables +/// The value to place into the "namedVariables" key +// +/// @param[in] expensive +/// The value to place into the "expensive" key +/// +/// @return +/// A "Scope" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateScope(const llvm::StringRef name, + int64_t variablesReference, + int64_t namedVariables, bool expensive); + +//---------------------------------------------------------------------- +/// Create a "Source" JSON object as described in the Visual Studio Code +/// debug adaptor definition. +/// +/// @param[in] line_entry +/// The LLDB line table to use when populating out the "Source" +/// object +/// +/// @return +/// A "Source" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry); + +//---------------------------------------------------------------------- +/// Create a "Source" object for a given frame. +/// +/// When there is no source file information for a stack frame, we will +/// create disassembly for a function and store a permanent +/// "sourceReference" that contains the textual disassembly for a +/// function along with address to line information. The "Source" object +/// that is created will contain a "sourceReference" that the VSCode +/// protocol can later fetch as text in order to display disassembly. +/// The PC will be extracted from the frame and the disassembly line +/// within the source referred to by "sourceReference" will be filled +/// in. +/// +/// @param[in] frame +/// The LLDB stack frame to use when populating out the "Source" +/// object. +/// +/// @param[out] disasm_line +/// The line within the "sourceReference" file that the PC from +/// \a frame matches. +/// +/// @return +/// A "Source" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line); + +//---------------------------------------------------------------------- +/// Create a "StackFrame" object for a LLDB frame object. +/// +/// This function will fill in the following keys in the returned +/// object: +/// "id" - the stack frame ID as an integer +/// "name" - the function name as a string +/// "source" - source file information as a "Source" VSCode object +/// "line" - the source file line number as an integer +/// "column" - the source file column number as an integer +/// +/// @param[in] frame +/// The LLDB stack frame to use when populating out the "StackFrame" +/// object. +/// +/// @return +/// A "StackFrame" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateStackFrame(lldb::SBFrame &frame); + +//---------------------------------------------------------------------- +/// Create a "Thread" object for a LLDB thread object. +/// +/// This function will fill in the following keys in the returned +/// object: +/// "id" - the thread ID as an integer +/// "name" - the thread name as a string which combines the LLDB +/// thread index ID along with the string name of the thread +/// from the OS if it has a name. +/// +/// @param[in] thread +/// The LLDB thread to use when populating out the "Thread" +/// object. +/// +/// @return +/// A "Thread" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateThread(lldb::SBThread &thread); + +//---------------------------------------------------------------------- +/// Create a "StoppedEvent" object for a LLDB thread object. +/// +/// This function will fill in the following keys in the returned +/// object's "body" object: +/// "reason" - With a valid stop reason enumeration string value +/// that Microsoft specifies +/// "threadId" - The thread ID as an integer +/// "description" - a stop description (like "breakpoint 12.3") as a +/// string +/// "preserveFocusHint" - a boolean value that states if this thread +/// should keep the focus in the GUI. +/// "allThreadsStopped" - set to True to indicate that all threads +/// stop when any thread stops. +/// +/// @param[in] thread +/// The LLDB thread to use when populating out the "StoppedEvent" +/// object. +/// +/// @return +/// A "StoppedEvent" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, uint32_t stop_id); + +//---------------------------------------------------------------------- +/// Create a "Variable" object for a LLDB thread object. +/// +/// This function will fill in the following keys in the returned +/// object: +/// "name" - the name of the variable +/// "value" - the value of the variable as a string +/// "type" - the typename of the varaible as a string +/// "id" - a unique identifier for a value in case there are multiple +/// variables with the same name. Other parts of the VSCode +/// protocol refer to values by name so this can help +/// disambiguate such cases if a IDE passes this "id" value +/// back down. +/// "variablesReference" - Zero if the variable has no children, +/// non-zero integer otherwise which can be used to expand +/// the variable. +/// "evaluateName" - The name of the variable to use in expressions +/// as a string. +/// +/// @param[in] v +/// The LLDB value to use when populating out the "Variable" +/// object. +/// +/// @param[in] variablesReference +/// The variable reference. Zero if this value isn't structured +/// and has no children, non-zero if it does have children and +/// might be asked to expand itself. +/// +/// @param[in] varID +/// A unique variable indentifier to help in properly identifying +/// variables with the same name. This is an extension to the +/// VS protocol. +/// +/// @param[in] format_hex +/// It set to true the variable will be formatted as hex in +/// the "value" key value pair for the value of the variable. +/// +/// @return +/// A "Variable" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +//---------------------------------------------------------------------- +llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference, + int64_t varID, bool format_hex); + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/LLDBUtils.cpp b/lldb/tools/lldb-vscode/LLDBUtils.cpp new file mode 100644 index 00000000000..797eda09ccd --- /dev/null +++ b/lldb/tools/lldb-vscode/LLDBUtils.cpp @@ -0,0 +1,73 @@ +//===-- LLDBUtils.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LLDBUtils.h" +#include "VSCode.h" + +namespace lldb_vscode { + +void RunLLDBCommands(llvm::StringRef prefix, + const llvm::ArrayRef<std::string> &commands, + llvm::raw_ostream &strm) { + if (commands.empty()) + return; + lldb::SBCommandInterpreter interp = g_vsc.debugger.GetCommandInterpreter(); + if (!prefix.empty()) + strm << prefix << "\n"; + for (const auto &command : commands) { + lldb::SBCommandReturnObject result; + strm << "(lldb) " << command << "\n"; + interp.HandleCommand(command.c_str(), result); + auto output_len = result.GetOutputSize(); + if (output_len) { + const char *output = result.GetOutput(); + strm << output; + } + auto error_len = result.GetErrorSize(); + if (error_len) { + const char *error = result.GetError(); + strm << error; + } + } +} + +std::string RunLLDBCommands(llvm::StringRef prefix, + const llvm::ArrayRef<std::string> &commands) { + std::string s; + llvm::raw_string_ostream strm(s); + RunLLDBCommands(prefix, commands, strm); + strm.flush(); + return s; +} + +bool ThreadHasStopReason(lldb::SBThread &thread) { + switch (thread.GetStopReason()) { + case lldb::eStopReasonTrace: + case lldb::eStopReasonPlanComplete: + case lldb::eStopReasonBreakpoint: + case lldb::eStopReasonWatchpoint: + case lldb::eStopReasonInstrumentation: + case lldb::eStopReasonSignal: + case lldb::eStopReasonException: + case lldb::eStopReasonExec: + return true; + case lldb::eStopReasonThreadExiting: + case lldb::eStopReasonInvalid: + case lldb::eStopReasonNone: + break; + } + return false; +} + +int64_t MakeVSCodeFrameID(lldb::SBFrame &frame) { + return (int64_t)frame.GetThread().GetIndexID() << 32 | + (int64_t)frame.GetFrameID(); +} + +} // namespace lldb_vscode diff --git a/lldb/tools/lldb-vscode/LLDBUtils.h b/lldb/tools/lldb-vscode/LLDBUtils.h new file mode 100644 index 00000000000..bf89021d3f9 --- /dev/null +++ b/lldb/tools/lldb-vscode/LLDBUtils.h @@ -0,0 +1,94 @@ +//===-- LLDBUtils.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_LLDBUTILS_H_ +#define LLDBVSCODE_LLDBUTILS_H_ + +#include "VSCodeForward.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" +#include <string> +#include <vector> + +namespace lldb_vscode { + +///---------------------------------------------------------------------- +/// Run a list of LLDB commands in the LLDB command interpreter. +/// +/// All output from every command, including the prompt + the command +/// is placed into the "strm" argument. +/// +/// @param[in] prefix +/// A string that will be printed into \a strm prior to emitting +/// the prmopt + command and command output. Can be NULL. +/// +/// @param[in] commands +/// An array of LLDB commands to execute. +/// +/// @param[in] strm +/// The stream that will receive the prefix, prompt + command and +/// all command output. +//---------------------------------------------------------------------- +void RunLLDBCommands(llvm::StringRef prefix, + const llvm::ArrayRef<std::string> &commands, + llvm::raw_ostream &strm); + +///---------------------------------------------------------------------- +/// Run a list of LLDB commands in the LLDB command interpreter. +/// +/// All output from every command, including the prompt + the command +/// is returned in the std::string return value. +/// +/// @param[in] prefix +/// A string that will be printed into \a strm prior to emitting +/// the prmopt + command and command output. Can be NULL. +/// +/// @param[in] commands +/// An array of LLDB commands to execute. +/// +/// @return +/// A std::string that contains the prefix and all commands and +/// command output +//---------------------------------------------------------------------- +std::string RunLLDBCommands(llvm::StringRef prefix, + const llvm::ArrayRef<std::string> &commands); + +///---------------------------------------------------------------------- +/// Check if a thread has a stop reason. +/// +/// @param[in] thread +/// The LLDB thread object to check +/// +/// @return +/// \b True if the thread has a valid stop reason, \b false +/// otherwise. +//---------------------------------------------------------------------- +bool ThreadHasStopReason(lldb::SBThread &thread); + +///---------------------------------------------------------------------- +/// Given a LLDB frame, make a frame ID that is unique to a specific +/// thread and frame. +/// +/// VSCode requires a Stackframe "id" to be unique, so we use the frame +/// index in the lower 32 bits and the thread index ID in the upper 32 +/// bits. +/// +/// @param[in] frame +/// The LLDB stack frame object generate the ID for +/// +/// @return +/// A unique integer that allows us to easily find the right +/// stack frame within a thread on subsequent VS code requests. +//---------------------------------------------------------------------- +int64_t MakeVSCodeFrameID(lldb::SBFrame &frame); + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/README.md b/lldb/tools/lldb-vscode/README.md new file mode 100644 index 00000000000..2e786816bf0 --- /dev/null +++ b/lldb/tools/lldb-vscode/README.md @@ -0,0 +1,194 @@ + +# Table of Contents + +- [Introduction](#Introduction) +- [Intallation](#Intallation Visual Studio Code) +- [Configurations](#configurations) + - [Launch Configuration Settings](#launch configuration settings) + - [Attach Configuration Settings](#attach configuration settings) + - [Example configurations](#example configurations) + - [Launching](#launching) + - [Attach to process using process ID](#attach using pid) + - [Attach to process by name](#attach by name) + - [Loading a core file](#loading a core file) + +# Introduction + +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. +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. + +# Intallation 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 +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: + +``` +$ mkdir -p ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0/bin +$ cp package.json ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0 +$ cd ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0/bin +$ cp /path/to/a/built/lldb-vscode . +$ cp /path/to/a/built/liblldb.so . +``` + + +If you want to make a stand alone plug-in that you can send to others on macOS systems: + +``` +$ mkdir -p ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0/bin +$ cp package.json ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0 +$ cd ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0/bin +$ cp /path/to/a/built/lldb-vscode . +$ rsync -av /path/to/a/built/LLDB.framework LLDB.framework +``` + +You might need to create additional directories for the `liblldb.so` or `LLDB.framework` inside or next to the `bin` folder depending on how the [rpath](https://en.wikipedia.org/wiki/Rpath) is set in your `lldb-vscode` binary. By default the `Debug` builds of LLDB usually includes +the current executable directory in the rpath, so these steps should work for most people. + +To create a plug-in that symlinks into your `lldb-vscode` in your build directory: + +``` +$ mkdir -p ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0/bin +$ cp package.json ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0 +$ cd ~/.vscode/extensions/llvm-org.lldb-vscode-0.1.0/bin +$ ln -s /path/to/a/built/lldb-vscode +``` + +This is handy if you want to debug and develope the `lldb-vscode` executable when adding features or fixing bugs. + +# Configurations + +Launching to attaching require you to create a [launch configuration](https://code.visualstudio.com/Docs/editor/debugging#_launch-configurations). This file +defines arguments that get passed to `lldb-vscode` and the configuration settings +control how the launch or attach happens. + +## Launch Configuration Settings + +When you launch a program with Visual Studio Code you will need to create a [launch.json](https://code.visualstudio.com/Docs/editor/debugging#_launch-configurations) +file that defines how your program will be run. The JSON configuration file can contain the following `lldb-vscode` specific launch key/value pairs: + +|parameter |type|req | | +|-------------------|----|:--:|---------| +|**name** |string|Y| A configuration name that will be displayed in the IDE. +|**type** |string|Y| Must be "lldb-vscode". +|**request** |string|Y| Must be "launch". +|**program** |string|Y| Path to the executable to launch. +|**args** |[string]|| An array of command line argument strings to be passed to the program being launched. +|**cwd** |string| | The program working directory. +|**env** |dictionary| | Environment variables to set when launching the program. The format of each environment variable string is "VAR=VALUE" for environment variables with values or just "VAR" for environment variables with no values. +|**stopOnEntry** |boolean| | Whether to stop program immediately after launching. +|**initCommands** |[string]| | LLDB commands executed upon debugger startup prior to creating a the LLDB target. Commands and command output will be sent to the debugger console when they are executed. +|**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. +|**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. + +## Attaching Settings + +When attaching to a process using LLDB you can attach in a few ways + +1. Attach to an existing process using the process ID +2. Attach to an existing process by name +3. Attach by name by waiting for the next instance of a process to launch + +The JSON configuration file can contain the following `lldb-vscode` specific launch key/value pairs: + +|parameter |type |req | | +|-------------------|--------|:--:|---------| +|**name** |string |Y| A configuration name that will be displayed in the IDE. +|**type** |string |Y| Must be "lldb-vscode". +|**request** |string |Y| Must be "attach". +|**program** |string | | Path to the executable to attach to. This value is optional but can help to resolve breakpoints prior the attaching to the program. +|**pid** |number | | The process id of the process you wish to attach to. If **pid** is omitted, the debugger will attempt to attach to the program by finding a process whose file name matches the file name from **porgram**. Setting this value to `${command:pickMyProcess}` will allow interactive process selection in the IDE. +|**stopOnEntry** |boolean| | Whether to stop program immediately after launching. +|**waitFor** |boolean | | Wait for the process to launch. +|**initCommands** |[string]| | LLDB commands executed upon debugger startup prior to creating a the LLDB target. Commands and command output will be sent to the debugger console when they are executed. +|**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. +|**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. + + +## Example configurations + +### Launching + +This will launch `/tmp/a.out` with arguments `one`, `two`, and `three` and +adds `FOO=1` and `bar` to the environment: + +```javascript +{ + "type": "lldb-vscode", + "request": "launch", + "name": "Debug", + "program": "/tmp/a.out", + "args": [ "one", "two", "three" ], + "env": [ "FOO=1", "BAR" ], +} +``` + +### Attach using PID + +This will attach to a process `a.out` whose process ID is 123: + +```javascript +{ + "type": "lldb-vscode", + "request": "attach", + "name": "Attach to PID", + "program": "/tmp/a.out", + "pid": 123 +} +``` + +### Attach by Name + +This will attach to an existing process whose base +name matches `a.out`. All we have to do is leave the `pid` value out of the +above configuration: + +```javascript +{ + "name": "Attach to Name", + "type": "lldb-vscode", + "request": "attach", + "program": "/tmp/a.out", +} +``` + +If you want to ignore any existing a.out processes and wait for the next instance +to be launched you can add the "waitFor" key value pair: + +```javascript +{ + "name": "Attach to Name (wait)", + "type": "lldb-vscode", + "request": "attach", + "program": "/tmp/a.out", + "waitFor": true +} +``` + +This will work as long as the architecture, vendor and OS supports waiting +for processes. Currently MacOS is the only platform that supports this. + + +### Loading a Core File + +Loading a core file can use the `"attach"` request along with the +`"attachCommands"` to implement a custom attach: + +```javascript +{ + "name": "Attach to Name (wait)", + "type": "lldb-vscode", + "request": "attach", + "attachCommands": ["target create -c /path/to/123.core /path/to/executable"], +} +``` diff --git a/lldb/tools/lldb-vscode/SourceBreakpoint.cpp b/lldb/tools/lldb-vscode/SourceBreakpoint.cpp new file mode 100644 index 00000000000..f2a286c9eca --- /dev/null +++ b/lldb/tools/lldb-vscode/SourceBreakpoint.cpp @@ -0,0 +1,27 @@ +//===-- SourceBreakpoint.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SourceBreakpoint.h" +#include "VSCode.h" + +namespace lldb_vscode { + +SourceBreakpoint::SourceBreakpoint(const llvm::json::Object &obj) + : BreakpointBase(obj), line(GetUnsigned(obj, "line", 0)), + column(GetUnsigned(obj, "column", 0)) {} + +void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) { + bp = g_vsc.target.BreakpointCreateByLocation(source_path.str().c_str(), line); + if (!condition.empty()) + SetCondition(); + if (!hitCondition.empty()) + SetHitCondition(); +} + +} // namespace lldb_vscode diff --git a/lldb/tools/lldb-vscode/SourceBreakpoint.h b/lldb/tools/lldb-vscode/SourceBreakpoint.h new file mode 100644 index 00000000000..8af647ca8fe --- /dev/null +++ b/lldb/tools/lldb-vscode/SourceBreakpoint.h @@ -0,0 +1,39 @@ +//===-- SourceBreakpoint.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_SOURCEBREAKPOINT_H_ +#define LLDBVSCODE_SOURCEBREAKPOINT_H_ + +#include "llvm/ADT/StringRef.h" +#include "BreakpointBase.h" + +namespace lldb_vscode { + +struct SourceBreakpoint : public BreakpointBase { + + uint32_t line; ///< The source line of the breakpoint or logpoint + uint32_t column; ///< An optional source column of the breakpoint + + SourceBreakpoint() : BreakpointBase(), line(0), column(0) {} + SourceBreakpoint(const llvm::json::Object &obj); + + // Set this breakpoint in LLDB as a new breakpoint + void SetBreakpoint(const llvm::StringRef source_path); +}; + +inline bool operator<(const SourceBreakpoint &lhs, + const SourceBreakpoint &rhs) { + if (lhs.line == rhs.line) + return lhs.column < rhs.column; + return lhs.line < rhs.line; +} + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/SourceReference.h b/lldb/tools/lldb-vscode/SourceReference.h new file mode 100644 index 00000000000..f047143e6c8 --- /dev/null +++ b/lldb/tools/lldb-vscode/SourceReference.h @@ -0,0 +1,33 @@ +//===-- SourceReference.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_SOURCEREFERENCE_H_ +#define LLDBVSCODE_SOURCEREFERENCE_H_ + +#include "lldb/lldb-types.h" +#include "llvm/ADT/DenseMap.h" +#include <string> + +namespace lldb_vscode { + +struct SourceReference { + std::string content; + llvm::DenseMap<lldb::addr_t, int64_t> addr_to_line; + + int64_t GetLineForPC(lldb::addr_t pc) const { + auto addr_line = addr_to_line.find(pc); + if (addr_line != addr_to_line.end()) + return addr_line->second; + return 0; + } +}; + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp new file mode 100644 index 00000000000..c9072ce9c2e --- /dev/null +++ b/lldb/tools/lldb-vscode/VSCode.cpp @@ -0,0 +1,336 @@ +//===-- VSCode.cpp ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <stdarg.h> +#include <fstream> +#include <mutex> + +#include "VSCode.h" +#include "LLDBUtils.h" + +using namespace lldb_vscode; + +namespace { + inline bool IsEmptyLine(llvm::StringRef S) { + return S.ltrim().empty(); + } +} // namespace + +namespace lldb_vscode { + +VSCode g_vsc; + +VSCode::VSCode() + : in(stdin), out(stdout), launch_info(nullptr), variables(), + broadcaster("lldb-vscode"), num_regs(0), num_locals(0), num_globals(0), + log(), exception_breakpoints( + {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus}, + {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus}, + {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC}, + {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC}, + {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift}, + {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}), + focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false), + stop_at_entry(false) { + const char *log_file_path = getenv("LLDBVSCODE_LOG"); + if (log_file_path) + log.reset(new std::ofstream(log_file_path)); +} + +VSCode::~VSCode() { + CloseInputStream(); + CloseOutputStream(); +} + +void VSCode::CloseInputStream() { + if (in != stdin) { + fclose(in); + in = nullptr; + } +} + +void VSCode::CloseOutputStream() { + if (out != stdout) { + fclose(out); + out = nullptr; + } +} + +int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const { + auto pos = source_map.find(sourceReference); + if (pos != source_map.end()) + return pos->second.GetLineForPC(pc); + return 0; +} + +ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) { + for (auto &bp : exception_breakpoints) { + if (bp.filter == filter) + return &bp; + } + return nullptr; +} + +ExceptionBreakpoint * +VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { + for (auto &bp : exception_breakpoints) { + if (bp.bp.GetID() == bp_id) + return &bp; + } + return nullptr; +} + +//---------------------------------------------------------------------- +// Send the JSON in "json_str" to the "out" stream. Correctly send the +// "Content-Length:" field followed by the length, followed by the raw +// JSON bytes. +//---------------------------------------------------------------------- +void VSCode::SendJSON(const std::string &json_str) { + fprintf(out, "Content-Length: %u\r\n\r\n%s", (uint32_t)json_str.size(), + json_str.c_str()); + fflush(out); + if (log) { + *log << "<-- " << std::endl + << "Content-Length: " << json_str.size() << "\r\n\r\n" + << json_str << std::endl; + } +} + +//---------------------------------------------------------------------- +// Serialize the JSON value into a string and send the JSON packet to +// the "out" stream. +//---------------------------------------------------------------------- +void VSCode::SendJSON(const llvm::json::Value &json) { + std::string s; + llvm::raw_string_ostream strm(s); + strm << json; + static std::mutex mutex; + std::lock_guard<std::mutex> locker(mutex); + SendJSON(strm.str()); +} + +//---------------------------------------------------------------------- +// Read a JSON packet from the "in" stream. +//---------------------------------------------------------------------- +std::string VSCode::ReadJSON() { + static std::string header("Content-Length: "); + + uint32_t packet_len = 0; + std::string json_str; + char line[1024]; + + while (fgets(line, sizeof(line), in)) { + if (strncmp(line, header.data(), header.size()) == 0) { + packet_len = atoi(line + header.size()); + if (fgets(line, sizeof(line), in)) { + if (!IsEmptyLine(line)) + if (log) + *log << "warning: expected empty line but got: \"" << line << "\"" + << std::endl; + break; + } + } else { + if (log) + *log << "warning: expected \"" << header << "\" but got: \"" << line + << "\"" << std::endl; + } + } + // This is followed by two windows newline sequences ("\r\n\r\n") so eat + // two the newline sequences + if (packet_len > 0) { + json_str.resize(packet_len); + auto bytes_read = fread(&json_str[0], 1, packet_len, in); + if (bytes_read < packet_len) { + if (log) + *log << "error: read fewer bytes (" << bytes_read + << ") than requested (" << packet_len << ")" << std::endl; + json_str.erase(bytes_read); + } + if (log) { + *log << "--> " << std::endl; + *log << header << packet_len << "\r\n\r\n" << json_str << std::endl; + } + } + return json_str; +} + +//---------------------------------------------------------------------- +// "OutputEvent": { +// "allOf": [ { "$ref": "#/definitions/Event" }, { +// "type": "object", +// "description": "Event message for 'output' event type. The event +// indicates that the target has produced some output.", +// "properties": { +// "event": { +// "type": "string", +// "enum": [ "output" ] +// }, +// "body": { +// "type": "object", +// "properties": { +// "category": { +// "type": "string", +// "description": "The output category. If not specified, +// 'console' is assumed.", +// "_enum": [ "console", "stdout", "stderr", "telemetry" ] +// }, +// "output": { +// "type": "string", +// "description": "The output to report." +// }, +// "variablesReference": { +// "type": "number", +// "description": "If an attribute 'variablesReference' exists +// and its value is > 0, the output contains +// objects which can be retrieved by passing +// variablesReference to the VariablesRequest." +// }, +// "source": { +// "$ref": "#/definitions/Source", +// "description": "An optional source location where the output +// was produced." +// }, +// "line": { +// "type": "integer", +// "description": "An optional source location line where the +// output was produced." +// }, +// "column": { +// "type": "integer", +// "description": "An optional source location column where the +// output was produced." +// }, +// "data": { +// "type":["array","boolean","integer","null","number","object", +// "string"], +// "description": "Optional data to report. For the 'telemetry' +// category the data will be sent to telemetry, for +// the other categories the data is shown in JSON +// format." +// } +// }, +// "required": ["output"] +// } +// }, +// "required": [ "event", "body" ] +// }] +// } +//---------------------------------------------------------------------- +void VSCode::SendOutput(OutputType o, const llvm::StringRef output) { + if (output.empty()) + return; + + llvm::json::Object event(CreateEvent("output")); + llvm::json::Object body; + const char *category = nullptr; + switch (o) { + case OutputType::Console: + category = "console"; + break; + case OutputType::Stdout: + category = "stdout"; + break; + case OutputType::Stderr: + category = "stderr"; + break; + case OutputType::Telemetry: + category = "telemetry"; + break; + } + body.try_emplace("category", category); + body.try_emplace("output", output.str()); + event.try_emplace("body", std::move(body)); + SendJSON(llvm::json::Value(std::move(event))); +} + +void __attribute__((format(printf, 3, 4))) +VSCode::SendFormattedOutput(OutputType o, const char *format, ...) { + char buffer[1024]; + va_list args; + va_start(args, format); + int actual_length = vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + SendOutput(o, llvm::StringRef(buffer, + std::min<int>(actual_length, sizeof(buffer)))); +} + +int64_t VSCode::GetNextSourceReference() { + static int64_t ref = 0; + return ++ref; +} + +ExceptionBreakpoint * +VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) { + const auto num = thread.GetStopReasonDataCount(); + // Check to see if have hit an exception breakpoint and change the + // reason to "exception", but only do so if all breakpoints that were + // hit are exception breakpoints. + ExceptionBreakpoint *exc_bp = nullptr; + for (size_t i = 0; i < num; i += 2) { + // thread.GetStopReasonDataAtIndex(i) will return the bp ID and + // thread.GetStopReasonDataAtIndex(i+1) will return the location + // within that breakpoint. We only care about the bp ID so we can + // see if this is an exception breakpoint that is getting hit. + lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i); + exc_bp = GetExceptionBreakpoint(bp_id); + // If any breakpoint is not an exception breakpoint, then stop and + // report this as a normal breakpoint + if (exc_bp == nullptr) + return nullptr; + } + return exc_bp; +} + +lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) { + auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID); + return target.GetProcess().GetThreadByID(tid); +} + +lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) { + const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX); + lldb::SBProcess process = target.GetProcess(); + // Upper 32 bits is the thread index ID + lldb::SBThread thread = process.GetThreadByIndexID(frame_id >> 32); + // Lower 32 bits is the frame index + return thread.GetFrameAtIndex(frame_id & 0xffffffffu); +} + +llvm::json::Value VSCode::CreateTopLevelScopes() { + llvm::json::Array scopes; + scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false)); + scopes.emplace_back( + CreateScope("Globals", VARREF_GLOBALS, num_globals, false)); + scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false)); + return llvm::json::Value(std::move(scopes)); +} + +void VSCode::RunLLDBCommands(llvm::StringRef prefix, + const std::vector<std::string> &commands) { + SendOutput(OutputType::Console, + llvm::StringRef(::RunLLDBCommands(prefix, commands))); +} + +void VSCode::RunInitCommands() { + RunLLDBCommands("Running initCommands:", init_commands); +} + +void VSCode::RunPreRunCommands() { + RunLLDBCommands("Running preRunCommands:", pre_run_commands); +} + +void VSCode::RunStopCommands() { + RunLLDBCommands("Running stopCommands:", stop_commands); +} + +void VSCode::RunExitCommands() { + RunLLDBCommands("Running exitCommands:", exit_commands); +} + +} // namespace lldb_vscode + diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h new file mode 100644 index 00000000000..142be331836 --- /dev/null +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -0,0 +1,146 @@ +//===-- VSCode.h ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_VSCODE_H_ +#define LLDBVSCODE_VSCODE_H_ + +#include <iosfwd> +#include <map> +#include <set> +#include <stdio.h> +#include <thread> + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" + +#include "lldb/API/SBAttachInfo.h" +#include "lldb/API/SBBreakpoint.h" +#include "lldb/API/SBBreakpointLocation.h" +#include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBCommandReturnObject.h" +#include "lldb/API/SBCommunication.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBEvent.h" +#include "lldb/API/SBHostOS.h" +#include "lldb/API/SBInstruction.h" +#include "lldb/API/SBInstructionList.h" +#include "lldb/API/SBLanguageRuntime.h" +#include "lldb/API/SBLaunchInfo.h" +#include "lldb/API/SBLineEntry.h" +#include "lldb/API/SBListener.h" +#include "lldb/API/SBProcess.h" +#include "lldb/API/SBStream.h" +#include "lldb/API/SBStringList.h" +#include "lldb/API/SBTarget.h" +#include "lldb/API/SBThread.h" + +#include "ExceptionBreakpoint.h" +#include "FunctionBreakpoint.h" +#include "SourceBreakpoint.h" +#include "SourceReference.h" + +#define VARREF_LOCALS (int64_t)1 +#define VARREF_GLOBALS (int64_t)2 +#define VARREF_REGS (int64_t)3 +#define VARREF_FIRST_VAR_IDX (int64_t)4 +#define VARREF_IS_SCOPE(v) (VARREF_LOCALS <= 1 && v < VARREF_FIRST_VAR_IDX) +#define VARIDX_TO_VARREF(i) ((i) + VARREF_FIRST_VAR_IDX) +#define VARREF_TO_VARIDX(v) ((v)-VARREF_FIRST_VAR_IDX) +#define NO_TYPENAME "<no-type>" + +namespace lldb_vscode { + +typedef llvm::DenseMap<uint32_t, SourceBreakpoint> SourceBreakpointMap; +typedef llvm::StringMap<FunctionBreakpoint> FunctionBreakpointMap; +enum class OutputType { Console, Stdout, Stderr, Telemetry }; + +struct VSCode { + FILE *in; + FILE *out; + lldb::SBDebugger debugger; + lldb::SBTarget target; + lldb::SBAttachInfo attach_info; + lldb::SBLaunchInfo launch_info; + lldb::SBValueList variables; + lldb::SBBroadcaster broadcaster; + int64_t num_regs; + int64_t num_locals; + int64_t num_globals; + std::thread event_thread; + std::unique_ptr<std::ofstream> log; + llvm::DenseMap<lldb::addr_t, int64_t> addr_to_source_ref; + llvm::DenseMap<int64_t, SourceReference> source_map; + llvm::StringMap<SourceBreakpointMap> source_breakpoints; + FunctionBreakpointMap function_breakpoints; + std::vector<ExceptionBreakpoint> exception_breakpoints; + std::vector<std::string> init_commands; + std::vector<std::string> pre_run_commands; + std::vector<std::string> exit_commands; + std::vector<std::string> stop_commands; + lldb::tid_t focus_tid; + bool sent_terminated_event; + bool stop_at_entry; + // Keep track of the last stop thread index IDs as threads won't go away + // unless we send a "thread" event to indicate the thread exited. + llvm::DenseSet<lldb::tid_t> thread_ids; + VSCode(); + ~VSCode(); + VSCode(const VSCode &rhs) = delete; + void operator=(const VSCode &rhs) = delete; + void CloseInputStream(); + void CloseOutputStream(); + int64_t GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const; + ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); + ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); + //---------------------------------------------------------------------- + // Send the JSON in "json_str" to the "out" stream. Correctly send the + // "Content-Length:" field followed by the length, followed by the raw + // JSON bytes. + //---------------------------------------------------------------------- + void SendJSON(const std::string &json_str); + + //---------------------------------------------------------------------- + // Serialize the JSON value into a string and send the JSON packet to + // the "out" stream. + //---------------------------------------------------------------------- + void SendJSON(const llvm::json::Value &json); + + std::string ReadJSON(); + + void SendOutput(OutputType o, const llvm::StringRef output); + + void __attribute__((format(printf, 3, 4))) + SendFormattedOutput(OutputType o, const char *format, ...); + + static int64_t GetNextSourceReference(); + + ExceptionBreakpoint *GetExceptionBPFromStopReason(lldb::SBThread &thread); + + lldb::SBThread GetLLDBThread(const llvm::json::Object &arguments); + + lldb::SBFrame GetLLDBFrame(const llvm::json::Object &arguments); + + llvm::json::Value CreateTopLevelScopes(); + + void RunLLDBCommands(llvm::StringRef prefix, + const std::vector<std::string> &commands); + + void RunInitCommands(); + void RunPreRunCommands(); + void RunStopCommands(); + void RunExitCommands(); +}; + +extern VSCode g_vsc; + +} // namespace lldb_vscode + +#endif diff --git a/lldb/tools/lldb-vscode/VSCodeForward.h b/lldb/tools/lldb-vscode/VSCodeForward.h new file mode 100644 index 00000000000..d5b7f8d53f7 --- /dev/null +++ b/lldb/tools/lldb-vscode/VSCodeForward.h @@ -0,0 +1,47 @@ +//===-- VSCodeForward.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDBVSCODE_VSCODEFORWARD_H_ +#define LLDBVSCODE_VSCODEFORWARD_H_ + + +namespace lldb_vscode { +struct BreakpointBase; +struct ExceptionBreakpoint; +struct FunctionBreakpoint; +struct SourceBreakpoint; +struct SourceReference; +} // namespace lldb_vscode + +namespace lldb { +class SBAttachInfo; +class SBBreakpoint; +class SBBreakpointLocation; +class SBCommandInterpreter; +class SBCommandReturnObject; +class SBCommunication; +class SBDebugger; +class SBEvent; +class SBFrame; +class SBHostOS; +class SBInstruction; +class SBInstructionList; +class SBLanguageRuntime; +class SBLaunchInfo; +class SBLineEntry; +class SBListener; +class SBProcess; +class SBStream; +class SBStringList; +class SBTarget; +class SBThread; +class SBValue; +} // namespace lldb + +#endif diff --git a/lldb/tools/lldb-vscode/lldb-vscode-Info.plist b/lldb/tools/lldb-vscode/lldb-vscode-Info.plist new file mode 100644 index 00000000000..a6b82437254 --- /dev/null +++ b/lldb/tools/lldb-vscode/lldb-vscode-Info.plist @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleIdentifier</key> + <string>com.apple.lldb-vscode</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>lldb-vscode</string> + <key>CFBundleVersion</key> + <string>360.99.0</string> + <key>SecTaskAccess</key> + <array> + <string>allowed</string> + <string>debug</string> + </array> +</dict> +</plist> diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp new file mode 100644 index 00000000000..d41fcdc519d --- /dev/null +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -0,0 +1,2706 @@ +//===-- lldb-vscode.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <assert.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#if defined(_WIN32) +// We need to #define NOMINMAX in order to skip `min()` and `max()` macro +// definitions that conflict with other system headers. +// We also need to #undef GetObject (which is defined to GetObjectW) because +// the JSON code we use also has methods named `GetObject()` and we conflict +// against these. +#define NOMINMAX +#include <windows.h> +#undef GetObject +#else +#include <netinet/in.h> +#include <sys/socket.h> +#include <unistd.h> +#endif + +#include <algorithm> +#include <chrono> +#include <fstream> +#include <map> +#include <memory> +#include <mutex> +#include <set> +#include <sstream> +#include <thread> + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" + +#include "JSONUtils.h" +#include "LLDBUtils.h" +#include "VSCode.h" + +#if defined(_WIN32) +#define PATH_MAX MAX_PATH +typedef int socklen_t; +constexpr const char *dev_null_path = "nul"; + +#else +typedef int SOCKET; +constexpr const char *dev_null_path = "/dev/null"; + +#endif + +using namespace lldb_vscode; + +namespace { + +typedef void (*RequestCallback)(const llvm::json::Object &command); + +enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; + +enum VSCodeBroadcasterBits { eBroadcastBitStopEventThread = 1u << 0 }; + +int AcceptConnection(int portno) { + // Accept a socket connection from any host on "portno". + int newsockfd = -1; + struct sockaddr_in serv_addr, cli_addr; + SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + if (g_vsc.log) + *g_vsc.log << "error: opening socket (" << strerror(errno) << ")" + << std::endl; + } else { + memset((char *)&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + // serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + serv_addr.sin_port = htons(portno); + if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { + if (g_vsc.log) + *g_vsc.log << "error: binding socket (" << strerror(errno) << ")" + << std::endl; + } else { + listen(sockfd, 5); + socklen_t clilen = sizeof(cli_addr); + newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen); + if (newsockfd < 0) + if (g_vsc.log) + *g_vsc.log << "error: accept (" << strerror(errno) << ")" + << std::endl; + } +#if defined(_WIN32) + closesocket(sockfd); +#else + close(sockfd); +#endif + } + return newsockfd; +} + +std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) { + // Create and return an array of "const char *", one for each C string in + // "strs" and terminate the list with a NULL. This can be used for argument + // vectors (argv) or environment vectors (envp) like those passed to the + // "main" function in C programs. + std::vector<const char *> argv; + for (const auto &s : strs) + argv.push_back(s.c_str()); + argv.push_back(nullptr); + return argv; +} + +//---------------------------------------------------------------------- +// Send a "exited" event to indicate the process has exited. +//---------------------------------------------------------------------- +void SendProcessExitedEvent(lldb::SBProcess &process) { + llvm::json::Object event(CreateEvent("exited")); + llvm::json::Object body; + body.try_emplace("exitCode", (int64_t)process.GetExitStatus()); + event.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(event))); +} + +void SendThreadExitedEvent(lldb::tid_t tid) { + llvm::json::Object event(CreateEvent("thread")); + llvm::json::Object body; + body.try_emplace("reason", "exited"); + body.try_emplace("threadId", (int64_t)tid); + event.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(event))); +} + +//---------------------------------------------------------------------- +// Send a "terminated" event to indicate the process is done being +// debugged. +//---------------------------------------------------------------------- +void SendTerminatedEvent() { + if (!g_vsc.sent_terminated_event) { + g_vsc.sent_terminated_event = true; + // Send a "terminated" event + llvm::json::Object event(CreateEvent("terminated")); + g_vsc.SendJSON(llvm::json::Value(std::move(event))); + } +} + +//---------------------------------------------------------------------- +// Send a thread stopped event for all threads as lons as the process +// is stopped. +//---------------------------------------------------------------------- +void SendThreadStoppedEvent() { + lldb::SBProcess process = g_vsc.target.GetProcess(); + if (process.IsValid()) { + auto state = process.GetState(); + if (state == lldb::eStateStopped) { + llvm::DenseSet<lldb::tid_t> old_thread_ids; + old_thread_ids.swap(g_vsc.thread_ids); + uint32_t stop_id = process.GetStopID(); + const uint32_t num_threads = process.GetNumThreads(); + + // First make a pass through the threads to see if the focused thread + // has a stop reason. In case the focus thread doesn't have a stop + // reason, remember the first thread that has a stop reason so we can + // set it as the focus thread if below if needed. + lldb::tid_t first_tid_with_reason = LLDB_INVALID_THREAD_ID; + uint32_t num_threads_with_reason = 0; + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + const lldb::tid_t tid = thread.GetThreadID(); + const bool has_reason = ThreadHasStopReason(thread); + // If the focus thread doesn't have a stop reason, clear the thread ID + if (tid == g_vsc.focus_tid && !has_reason) + g_vsc.focus_tid = LLDB_INVALID_THREAD_ID; + if (has_reason) { + ++num_threads_with_reason; + if (first_tid_with_reason == LLDB_INVALID_THREAD_ID) + first_tid_with_reason = tid; + } + } + + // We will have cleared g_vsc.focus_tid if he focus thread doesn't + // have a stop reason, so if it was cleared, or wasn't set, then set the + // focus thread to the first thread with a stop reason. + if (g_vsc.focus_tid == LLDB_INVALID_THREAD_ID) + g_vsc.focus_tid = first_tid_with_reason; + + // If no threads stopped with a reaspon, then report the first one so + // we at least let the UI know we stopped. + if (num_threads_with_reason == 0) { + lldb::SBThread thread = process.GetThreadAtIndex(0); + g_vsc.SendJSON(CreateThreadStopped(thread, stop_id)); + } else { + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + g_vsc.thread_ids.insert(thread.GetThreadID()); + if (ThreadHasStopReason(thread)) { + g_vsc.SendJSON(CreateThreadStopped(thread, stop_id)); + } + } + } + + for (auto tid : old_thread_ids) { + auto end = g_vsc.thread_ids.end(); + auto pos = g_vsc.thread_ids.find(tid); + if (pos == end) + SendThreadExitedEvent(tid); + } + } else { + if (g_vsc.log) + *g_vsc.log << "error: SendThreadStoppedEvent() when process" + " isn't stopped (" + << lldb::SBDebugger::StateAsCString(state) << ')' + << std::endl; + } + } else { + if (g_vsc.log) + *g_vsc.log << "error: SendThreadStoppedEvent() invalid process" + << std::endl; + } + g_vsc.RunStopCommands(); +} + +//---------------------------------------------------------------------- +// "ProcessEvent": { +// "allOf": [ +// { "$ref": "#/definitions/Event" }, +// { +// "type": "object", +// "description": "Event message for 'process' event type. The event +// indicates that the debugger has begun debugging a +// new process. Either one that it has launched, or one +// that it has attached to.", +// "properties": { +// "event": { +// "type": "string", +// "enum": [ "process" ] +// }, +// "body": { +// "type": "object", +// "properties": { +// "name": { +// "type": "string", +// "description": "The logical name of the process. This is +// usually the full path to process's executable +// file. Example: /home/myproj/program.js." +// }, +// "systemProcessId": { +// "type": "integer", +// "description": "The system process id of the debugged process. +// This property will be missing for non-system +// processes." +// }, +// "isLocalProcess": { +// "type": "boolean", +// "description": "If true, the process is running on the same +// computer as the debug adapter." +// }, +// "startMethod": { +// "type": "string", +// "enum": [ "launch", "attach", "attachForSuspendedLaunch" ], +// "description": "Describes how the debug engine started +// debugging this process.", +// "enumDescriptions": [ +// "Process was launched under the debugger.", +// "Debugger attached to an existing process.", +// "A project launcher component has launched a new process in +// a suspended state and then asked the debugger to attach." +// ] +// } +// }, +// "required": [ "name" ] +// } +// }, +// "required": [ "event", "body" ] +// } +// ] +// } +//---------------------------------------------------------------------- +void SendProcessEvent(LaunchMethod launch_method) { + lldb::SBFileSpec exe_fspec = g_vsc.target.GetExecutable(); + char exe_path[PATH_MAX]; + exe_fspec.GetPath(exe_path, sizeof(exe_path)); + llvm::json::Object event(CreateEvent("process")); + llvm::json::Object body; + body.try_emplace("name", std::string(exe_path)); + const auto pid = g_vsc.target.GetProcess().GetProcessID(); + body.try_emplace("systemProcessId", (int64_t)pid); + body.try_emplace("isLocalProcess", true); + const char *startMethod = nullptr; + switch (launch_method) { + case Launch: + startMethod = "launch"; + break; + case Attach: + startMethod = "attach"; + break; + case AttachForSuspendedLaunch: + startMethod = "attachForSuspendedLaunch"; + break; + } + body.try_emplace("startMethod", startMethod); + event.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(event))); +} + +//---------------------------------------------------------------------- +// Grab any STDOUT and STDERR from the process and send it up to VS Code +// via an "output" event to the "stdout" and "stderr" categories. +//---------------------------------------------------------------------- +void SendStdOutStdErr(lldb::SBProcess &process) { + char buffer[1024]; + size_t count; + while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0) + g_vsc.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); + while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0) + g_vsc.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count)); +} + +//---------------------------------------------------------------------- +// All events from a the debugger, target, process, thread and frames are +// received in this function that runs in its own thread. We are using a +// "FILE *" to output packets back to VS Code and they have mutexes in them +// them prevent multiple threads from writing simultaneously so no locking +// is required. +//---------------------------------------------------------------------- +void EventThreadFunction() { + lldb::SBEvent event; + lldb::SBListener listener = g_vsc.debugger.GetListener(); + bool done = false; + while (!done) { + if (listener.WaitForEvent(1, event)) { + const auto event_mask = event.GetType(); + if (lldb::SBProcess::EventIsProcessEvent(event)) { + lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); + if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { + auto state = lldb::SBProcess::GetStateFromEvent(event); + switch (state) { + case lldb::eStateInvalid: + // Not a state event + break; + case lldb::eStateUnloaded: + break; + case lldb::eStateConnected: + break; + case lldb::eStateAttaching: + break; + case lldb::eStateLaunching: + break; + case lldb::eStateStepping: + break; + case lldb::eStateCrashed: + break; + case lldb::eStateDetached: + break; + case lldb::eStateSuspended: + break; + case lldb::eStateStopped: + // Only report a stopped event if the process was not restarted. + if (!lldb::SBProcess::GetRestartedFromEvent(event)) { + SendStdOutStdErr(process); + SendThreadStoppedEvent(); + } + break; + case lldb::eStateRunning: + break; + case lldb::eStateExited: { + // Run any exit LLDB commands the user specified in the + // launch.json + g_vsc.RunExitCommands(); + SendProcessExitedEvent(process); + SendTerminatedEvent(); + done = true; + } break; + } + } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || + (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { + SendStdOutStdErr(process); + } + } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { + if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { + auto event_type = + lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); + const auto num_locs = + lldb::SBBreakpoint::GetNumBreakpointLocationsFromEvent(event); + auto bp = lldb::SBBreakpoint::GetBreakpointFromEvent(event); + bool added = event_type & lldb::eBreakpointEventTypeLocationsAdded; + bool removed = + event_type & lldb::eBreakpointEventTypeLocationsRemoved; + if (added || removed) { + for (size_t i = 0; i < num_locs; ++i) { + auto bp_loc = + lldb::SBBreakpoint::GetBreakpointLocationAtIndexFromEvent( + event, i); + auto bp_event = CreateEvent("breakpoint"); + llvm::json::Object body; + body.try_emplace("breakpoint", CreateBreakpoint(bp_loc)); + if (added) + body.try_emplace("reason", "new"); + else + body.try_emplace("reason", "removed"); + bp_event.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(bp_event))); + } + } + } + } else if (event.BroadcasterMatchesRef(g_vsc.broadcaster)) { + if (event_mask & eBroadcastBitStopEventThread) { + done = true; + } + } + } + } +} + +//---------------------------------------------------------------------- +// Both attach and launch take a either a sourcePath or sourceMap +// argument (or neither), from which we need to set the target.source-map. +//---------------------------------------------------------------------- +void SetSourceMapFromArguments(const llvm::json::Object &arguments) { + const char *sourceMapHelp = + "source must be be an array of two-element arrays, " + "each containing a source and replacement path string.\n"; + + std::string sourceMapCommand; + llvm::raw_string_ostream strm(sourceMapCommand); + strm << "settings set target.source-map "; + auto sourcePath = GetString(arguments, "sourcePath"); + + // sourceMap is the new, more general form of sourcePath and overrides it. + auto sourceMap = arguments.getArray("sourceMap"); + if (sourceMap) { + for (const auto &value : *sourceMap) { + auto mapping = value.getAsArray(); + if (mapping == nullptr || mapping->size() != 2 || + (*mapping)[0].kind() != llvm::json::Value::String || + (*mapping)[1].kind() != llvm::json::Value::String) { + g_vsc.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); + return; + } + auto mapFrom = GetAsString((*mapping)[0]); + auto mapTo = GetAsString((*mapping)[1]); + strm << "\"" << mapFrom << "\" \"" << mapTo << "\""; + } + } else { + if (ObjectContainsKey(arguments, "sourceMap")) { + g_vsc.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); + return; + } + if (sourcePath.empty()) + return; + // Do any source remapping needed before we create our targets + strm << "\".\" \"" << sourcePath << "\""; + } + strm.flush(); + if (!sourceMapCommand.empty()) { + g_vsc.RunLLDBCommands("Setting source map:", {sourceMapCommand}); + } +} + +//---------------------------------------------------------------------- +// "AttachRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Attach request; value of command field is 'attach'.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "attach" ] +// }, +// "arguments": { +// "$ref": "#/definitions/AttachRequestArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "AttachRequestArguments": { +// "type": "object", +// "description": "Arguments for 'attach' request.\nThe attach request has no +// standardized attributes." +// }, +// "AttachResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'attach' request. This is just an +// acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_attach(const llvm::json::Object &request) { + llvm::json::Object response; + lldb::SBError error; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + const lldb::pid_t pid = + GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID); + if (pid != LLDB_INVALID_PROCESS_ID) + g_vsc.attach_info.SetProcessID(pid); + const auto wait_for = GetBoolean(arguments, "waitFor", false); + g_vsc.attach_info.SetWaitForLaunch(wait_for, false /*async*/); + g_vsc.init_commands = GetStrings(arguments, "initCommands"); + g_vsc.pre_run_commands = GetStrings(arguments, "preRunCommands"); + g_vsc.stop_commands = GetStrings(arguments, "stopCommands"); + g_vsc.exit_commands = GetStrings(arguments, "exitCommands"); + auto attachCommands = GetStrings(arguments, "attachCommands"); + g_vsc.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false); + const auto debuggerRoot = GetString(arguments, "debuggerRoot"); + + // This is a hack for loading DWARF in .o files on Mac where the .o files + // in the debug map of the main executable have relative paths which require + // the lldb-vscode binary to have its working directory set to that relative + // root for the .o files in order to be able to load debug info. + if (!debuggerRoot.empty()) { + llvm::sys::fs::set_current_path(debuggerRoot.data()); + } + + // Run any initialize LLDB commands the user specified in the launch.json + g_vsc.RunInitCommands(); + + // Grab the name of the program we need to debug and set it as the first + // argument that will be passed to the program we will debug. + const auto program = GetString(arguments, "program"); + if (!program.empty()) { + lldb::SBFileSpec program_fspec(program.data(), true /*resolve_path*/); + + g_vsc.launch_info.SetExecutableFile(program_fspec, + false /*add_as_first_arg*/); + const char *target_triple = nullptr; + const char *uuid_cstr = nullptr; + // Stand alone debug info file if different from executable + const char *symfile = nullptr; + g_vsc.target.AddModule(program.data(), target_triple, uuid_cstr, symfile); + if (error.Fail()) { + response.try_emplace("success", false); + response.try_emplace("message", std::string(error.GetCString())); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + } + + const bool detatchOnError = GetBoolean(arguments, "detachOnError", false); + g_vsc.launch_info.SetDetachOnError(detatchOnError); + + // Run any pre run LLDB commands the user specified in the launch.json + g_vsc.RunPreRunCommands(); + + if (pid == LLDB_INVALID_PROCESS_ID && wait_for) { + char attach_info[256]; + auto attach_info_len = + snprintf(attach_info, sizeof(attach_info), + "Waiting to attach to \"%s\"...", program.data()); + g_vsc.SendOutput(OutputType::Console, llvm::StringRef(attach_info, + attach_info_len)); + } + if (attachCommands.empty()) { + // No "attachCommands", just attach normally. + // Disable async events so the attach will be successful when we return from + // the launch call and the launch will happen synchronously + g_vsc.debugger.SetAsync(false); + g_vsc.target.Attach(g_vsc.attach_info, error); + // Reenable async events + g_vsc.debugger.SetAsync(true); + } else { + // We have "attachCommands" that are a set of commands that are expected + // to execute the commands after which a process should be created. If there + // is no valid process after running these commands, we have failed. + g_vsc.RunLLDBCommands("Running attachCommands:", attachCommands); + // The custom commands might have created a new target so we should use the + // selected target after these commands are run. + g_vsc.target = g_vsc.debugger.GetSelectedTarget(); + } + + SetSourceMapFromArguments(*arguments); + + if (error.Success()) { + auto attached_pid = g_vsc.target.GetProcess().GetProcessID(); + if (attached_pid == LLDB_INVALID_PROCESS_ID) { + if (attachCommands.empty()) + error.SetErrorString("failed to attach to a process"); + else + error.SetErrorString("attachCommands failed to attach to a process"); + } + } + + if (error.Fail()) { + response.try_emplace("success", false); + response.try_emplace("message", std::string(error.GetCString())); + } + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + if (error.Success()) { + SendProcessEvent(Attach); + g_vsc.SendJSON(CreateEvent("initialized")); + // SendThreadStoppedEvent(); + } +} + +//---------------------------------------------------------------------- +// "ContinueRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Continue request; value of command field is 'continue'. +// The request starts the debuggee to run again.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "continue" ] +// }, +// "arguments": { +// "$ref": "#/definitions/ContinueArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "ContinueArguments": { +// "type": "object", +// "description": "Arguments for 'continue' request.", +// "properties": { +// "threadId": { +// "type": "integer", +// "description": "Continue execution for the specified thread (if +// possible). If the backend cannot continue on a single +// thread but will continue on all threads, it should +// set the allThreadsContinued attribute in the response +// to true." +// } +// }, +// "required": [ "threadId" ] +// }, +// "ContinueResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'continue' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "allThreadsContinued": { +// "type": "boolean", +// "description": "If true, the continue request has ignored the +// specified thread and continued all threads +// instead. If this attribute is missing a value +// of 'true' is assumed for backward +// compatibility." +// } +// } +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_continue(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + lldb::SBProcess process = g_vsc.target.GetProcess(); + auto arguments = request.getObject("arguments"); + // Remember the thread ID that caused the resume so we can set the + // "threadCausedFocus" boolean value in the "stopped" events. + g_vsc.focus_tid = GetUnsigned(arguments, "threadId", LLDB_INVALID_THREAD_ID); + lldb::SBError error = process.Continue(); + llvm::json::Object body; + body.try_emplace("allThreadsContinued", true); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "ConfigurationDoneRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "ConfigurationDone request; value of command field +// is 'configurationDone'.\nThe client of the debug protocol must +// send this request at the end of the sequence of configuration +// requests (which was started by the InitializedEvent).", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "configurationDone" ] +// }, +// "arguments": { +// "$ref": "#/definitions/ConfigurationDoneArguments" +// } +// }, +// "required": [ "command" ] +// }] +// }, +// "ConfigurationDoneArguments": { +// "type": "object", +// "description": "Arguments for 'configurationDone' request.\nThe +// configurationDone request has no standardized attributes." +// }, +// "ConfigurationDoneResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'configurationDone' request. This is +// just an acknowledgement, so no body field is required." +// }] +// }, +//---------------------------------------------------------------------- +void request_configurationDone(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + if (g_vsc.stop_at_entry) + SendThreadStoppedEvent(); + else + g_vsc.target.GetProcess().Continue(); +} + +//---------------------------------------------------------------------- +// "DisconnectRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Disconnect request; value of command field is +// 'disconnect'.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "disconnect" ] +// }, +// "arguments": { +// "$ref": "#/definitions/DisconnectArguments" +// } +// }, +// "required": [ "command" ] +// }] +// }, +// "DisconnectArguments": { +// "type": "object", +// "description": "Arguments for 'disconnect' request.", +// "properties": { +// "terminateDebuggee": { +// "type": "boolean", +// "description": "Indicates whether the debuggee should be terminated +// when the debugger is disconnected. If unspecified, +// the debug adapter is free to do whatever it thinks +// is best. A client can only rely on this attribute +// being properly honored if a debug adapter returns +// true for the 'supportTerminateDebuggee' capability." +// }, +// "restart": { +// "type": "boolean", +// "description": "Indicates whether the debuggee should be restart +// the process." +// } +// } +// }, +// "DisconnectResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'disconnect' request. This is just an +// acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_disconnect(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + + 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: + case lldb::eStateDetached: + case lldb::eStateExited: + break; + case lldb::eStateConnected: + case lldb::eStateAttaching: + case lldb::eStateLaunching: + case lldb::eStateStepping: + case lldb::eStateCrashed: + case lldb::eStateSuspended: + case lldb::eStateStopped: + case lldb::eStateRunning: + g_vsc.debugger.SetAsync(false); + if (terminateDebuggee) + process.Kill(); + else + process.Detach(); + g_vsc.debugger.SetAsync(true); + break; + } + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + SendTerminatedEvent(); + if (g_vsc.event_thread.joinable()) { + g_vsc.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); + g_vsc.event_thread.join(); + } +} + +void request_exceptionInfo(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + llvm::json::Object body; + lldb::SBThread thread = g_vsc.GetLLDBThread(*arguments); + if (thread.IsValid()) { + auto stopReason = thread.GetStopReason(); + if (stopReason == lldb::eStopReasonSignal) + body.try_emplace("exceptionId", "signal"); + else if (stopReason == lldb::eStopReasonBreakpoint) { + ExceptionBreakpoint *exc_bp = g_vsc.GetExceptionBPFromStopReason(thread); + if (exc_bp) { + body.try_emplace("exceptionId", exc_bp->filter); + body.try_emplace("description", exc_bp->label); + } else { + body.try_emplace("exceptionId", "exception"); + } + } else { + body.try_emplace("exceptionId", "exception"); + } + if (!ObjectContainsKey(body, "description")) { + char description[1024]; + if (thread.GetStopDescription(description, sizeof(description))) { + body.try_emplace("description", std::string(description)); + } + } + body.try_emplace("breakMode", "always"); + // auto excInfoCount = thread.GetStopReasonDataCount(); + // for (auto i=0; i<excInfoCount; ++i) { + // uint64_t exc_data = thread.GetStopReasonDataAtIndex(i); + // } + } else { + response.try_emplace("success", false); + } + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "EvaluateRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Evaluate request; value of command field is 'evaluate'. +// Evaluates the given expression in the context of the +// top most stack frame. The expression has access to any +// variables and arguments that are in scope.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "evaluate" ] +// }, +// "arguments": { +// "$ref": "#/definitions/EvaluateArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "EvaluateArguments": { +// "type": "object", +// "description": "Arguments for 'evaluate' request.", +// "properties": { +// "expression": { +// "type": "string", +// "description": "The expression to evaluate." +// }, +// "frameId": { +// "type": "integer", +// "description": "Evaluate the expression in the scope of this stack +// frame. If not specified, the expression is evaluated +// in the global scope." +// }, +// "context": { +// "type": "string", +// "_enum": [ "watch", "repl", "hover" ], +// "enumDescriptions": [ +// "evaluate is run in a watch.", +// "evaluate is run from REPL console.", +// "evaluate is run from a data hover." +// ], +// "description": "The context in which the evaluate request is run." +// }, +// "format": { +// "$ref": "#/definitions/ValueFormat", +// "description": "Specifies details on how to format the Evaluate +// result." +// } +// }, +// "required": [ "expression" ] +// }, +// "EvaluateResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'evaluate' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "result": { +// "type": "string", +// "description": "The result of the evaluate request." +// }, +// "type": { +// "type": "string", +// "description": "The optional type of the evaluate result." +// }, +// "presentationHint": { +// "$ref": "#/definitions/VariablePresentationHint", +// "description": "Properties of a evaluate result that can be +// used to determine how to render the result in +// the UI." +// }, +// "variablesReference": { +// "type": "number", +// "description": "If variablesReference is > 0, the evaluate +// result is structured and its children can be +// retrieved by passing variablesReference to the +// VariablesRequest." +// }, +// "namedVariables": { +// "type": "number", +// "description": "The number of named child variables. The +// client can use this optional information to +// present the variables in a paged UI and fetch +// them in chunks." +// }, +// "indexedVariables": { +// "type": "number", +// "description": "The number of indexed child variables. The +// client can use this optional information to +// present the variables in a paged UI and fetch +// them in chunks." +// } +// }, +// "required": [ "result", "variablesReference" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_evaluate(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + llvm::json::Object body; + auto arguments = request.getObject("arguments"); + lldb::SBFrame frame = g_vsc.GetLLDBFrame(*arguments); + const auto expression = GetString(arguments, "expression"); + + if (!expression.empty() && expression[0] == '`') { + body.try_emplace("result", + RunLLDBCommands(llvm::StringRef(), + {expression.substr(1)})); + body.try_emplace("variablesReference", (int64_t)0); + } else { + // Always try to get the answer from the local variables if possible. If + // this fails, then actually evaluate an expression using the expression + // parser. "frame variable" is more reliable than the expression parser in + // many cases and it is faster. + lldb::SBValue value = frame.GetValueForVariablePath( + expression.data(), lldb::eDynamicDontRunTarget); + if (value.GetError().Fail()) + value = frame.EvaluateExpression(expression.data()); + if (value.GetError().Fail()) { + response.try_emplace("success", false); + const char *error_cstr = value.GetError().GetCString(); + if (error_cstr && error_cstr[0]) + response.try_emplace("message", std::string(error_cstr)); + else + response.try_emplace("message", "evaluate failed"); + } else { + SetValueForKey(value, body, "result"); + auto value_typename = value.GetType().GetDisplayTypeName(); + body.try_emplace("type", value_typename ? value_typename : NO_TYPENAME); + if (value.MightHaveChildren()) { + auto variablesReference = VARIDX_TO_VARREF(g_vsc.variables.GetSize()); + g_vsc.variables.Append(value); + body.try_emplace("variablesReference", variablesReference); + } else { + body.try_emplace("variablesReference", (int64_t)0); + } + } + } + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "InitializeRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Initialize request; value of command field is +// 'initialize'.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "initialize" ] +// }, +// "arguments": { +// "$ref": "#/definitions/InitializeRequestArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "InitializeRequestArguments": { +// "type": "object", +// "description": "Arguments for 'initialize' request.", +// "properties": { +// "clientID": { +// "type": "string", +// "description": "The ID of the (frontend) client using this adapter." +// }, +// "adapterID": { +// "type": "string", +// "description": "The ID of the debug adapter." +// }, +// "locale": { +// "type": "string", +// "description": "The ISO-639 locale of the (frontend) client using +// this adapter, e.g. en-US or de-CH." +// }, +// "linesStartAt1": { +// "type": "boolean", +// "description": "If true all line numbers are 1-based (default)." +// }, +// "columnsStartAt1": { +// "type": "boolean", +// "description": "If true all column numbers are 1-based (default)." +// }, +// "pathFormat": { +// "type": "string", +// "_enum": [ "path", "uri" ], +// "description": "Determines in what format paths are specified. The +// default is 'path', which is the native format." +// }, +// "supportsVariableType": { +// "type": "boolean", +// "description": "Client supports the optional type attribute for +// variables." +// }, +// "supportsVariablePaging": { +// "type": "boolean", +// "description": "Client supports the paging of variables." +// }, +// "supportsRunInTerminalRequest": { +// "type": "boolean", +// "description": "Client supports the runInTerminal request." +// } +// }, +// "required": [ "adapterID" ] +// }, +// "InitializeResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'initialize' request.", +// "properties": { +// "body": { +// "$ref": "#/definitions/Capabilities", +// "description": "The capabilities of this debug adapter." +// } +// } +// }] +// } +//---------------------------------------------------------------------- +void request_initialize(const llvm::json::Object &request) { + g_vsc.debugger = lldb::SBDebugger::Create(true /*source_init_files*/); + // Create an empty target right away since we might get breakpoint requests + // before we are given an executable to launch in a "launch" request, or a + // executable when attaching to a process by process ID in a "attach" + // request. + FILE *out = fopen(dev_null_path, "w"); + if (out) { + // Set the output and error file handles to redirect into nothing otherwise + // if any code in LLDB prints to the debugger file handles, the output and + // error file handles are initialized to STDOUT and STDERR and any output + // will kill our debug session. + g_vsc.debugger.SetOutputFileHandle(out, true); + g_vsc.debugger.SetErrorFileHandle(out, false); + } + + g_vsc.target = g_vsc.debugger.CreateTarget(nullptr); + lldb::SBListener listener = g_vsc.debugger.GetListener(); + listener.StartListeningForEvents( + g_vsc.target.GetBroadcaster(), + lldb::SBTarget::eBroadcastBitBreakpointChanged); + listener.StartListeningForEvents(g_vsc.broadcaster, + eBroadcastBitStopEventThread); + // Start our event thread so we can receive events from the debugger, target, + // process and more. + g_vsc.event_thread = std::thread(EventThreadFunction); + + llvm::json::Object response; + FillResponse(request, response); + llvm::json::Object body; + // The debug adapter supports the configurationDoneRequest. + body.try_emplace("supportsConfigurationDoneRequest", true); + // The debug adapter supports function breakpoints. + body.try_emplace("supportsFunctionBreakpoints", true); + // The debug adapter supports conditional breakpoints. + body.try_emplace("supportsConditionalBreakpoints", true); + // The debug adapter supports breakpoints that break execution after a + // specified number of hits. + body.try_emplace("supportsHitConditionalBreakpoints", true); + // The debug adapter supports a (side effect free) evaluate request for + // data hovers. + body.try_emplace("supportsEvaluateForHovers", true); + // Available filters or options for the setExceptionBreakpoints request. + llvm::json::Array filters; + for (const auto &exc_bp : g_vsc.exception_breakpoints) { + filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp)); + } + body.try_emplace("exceptionBreakpointFilters", std::move(filters)); + // The debug adapter supports stepping back via the stepBack and + // reverseContinue requests. + body.try_emplace("supportsStepBack", false); + // The debug adapter supports setting a variable to a value. + body.try_emplace("supportsSetVariable", true); + // The debug adapter supports restarting a frame. + body.try_emplace("supportsRestartFrame", false); + // The debug adapter supports the gotoTargetsRequest. + body.try_emplace("supportsGotoTargetsRequest", false); + // The debug adapter supports the stepInTargetsRequest. + body.try_emplace("supportsStepInTargetsRequest", false); + // The debug adapter supports the completionsRequest. + body.try_emplace("supportsCompletionsRequest", false); + // The debug adapter supports the modules request. + body.try_emplace("supportsModulesRequest", false); + // The set of additional module information exposed by the debug adapter. + // body.try_emplace("additionalModuleColumns"] = ColumnDescriptor + // Checksum algorithms supported by the debug adapter. + // body.try_emplace("supportedChecksumAlgorithms"] = ChecksumAlgorithm + // The debug adapter supports the RestartRequest. In this case a client + // should not implement 'restart' by terminating and relaunching the adapter + // but by calling the RestartRequest. + body.try_emplace("supportsRestartRequest", false); + // The debug adapter supports 'exceptionOptions' on the + // setExceptionBreakpoints request. + body.try_emplace("supportsExceptionOptions", true); + // The debug adapter supports a 'format' attribute on the stackTraceRequest, + // variablesRequest, and evaluateRequest. + body.try_emplace("supportsValueFormattingOptions", true); + // The debug adapter supports the exceptionInfo request. + body.try_emplace("supportsExceptionInfoRequest", true); + // The debug adapter supports the 'terminateDebuggee' attribute on the + // 'disconnect' request. + body.try_emplace("supportTerminateDebuggee", true); + // The debug adapter supports the delayed loading of parts of the stack, + // which requires that both the 'startFrame' and 'levels' arguments and the + // 'totalFrames' result of the 'StackTrace' request are supported. + body.try_emplace("supportsDelayedStackTraceLoading", true); + // The debug adapter supports the 'loadedSources' request. + body.try_emplace("supportsLoadedSourcesRequest", false); + + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "LaunchRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Launch request; value of command field is 'launch'.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "launch" ] +// }, +// "arguments": { +// "$ref": "#/definitions/LaunchRequestArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "LaunchRequestArguments": { +// "type": "object", +// "description": "Arguments for 'launch' request.", +// "properties": { +// "noDebug": { +// "type": "boolean", +// "description": "If noDebug is true the launch request should launch +// the program without enabling debugging." +// } +// } +// }, +// "LaunchResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'launch' request. This is just an +// acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_launch(const llvm::json::Object &request) { + llvm::json::Object response; + lldb::SBError error; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + g_vsc.init_commands = GetStrings(arguments, "initCommands"); + g_vsc.pre_run_commands = GetStrings(arguments, "preRunCommands"); + g_vsc.stop_commands = GetStrings(arguments, "stopCommands"); + g_vsc.exit_commands = GetStrings(arguments, "exitCommands"); + g_vsc.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false); + const auto debuggerRoot = GetString(arguments, "debuggerRoot"); + + // This is a hack for loading DWARF in .o files on Mac where the .o files + // in the debug map of the main executable have relative paths which require + // the lldb-vscode binary to have its working directory set to that relative + // root for the .o files in order to be able to load debug info. + if (!debuggerRoot.empty()) { + llvm::sys::fs::set_current_path(debuggerRoot.data()); + } + + SetSourceMapFromArguments(*arguments); + + // Run any initialize LLDB commands the user specified in the launch.json + g_vsc.RunInitCommands(); + + // Grab the current working directory if there is one and set it in the + // launch info. + const auto cwd = GetString(arguments, "cwd"); + if (!cwd.empty()) + g_vsc.launch_info.SetWorkingDirectory(cwd.data()); + + // Grab the name of the program we need to debug and set it as the first + // argument that will be passed to the program we will debug. + const auto program = GetString(arguments, "program"); + if (!program.empty()) { + lldb::SBFileSpec program_fspec(program.data(), true /*resolve_path*/); + + g_vsc.launch_info.SetExecutableFile(program_fspec, + true /*add_as_first_arg*/); + const char *target_triple = nullptr; + const char *uuid_cstr = nullptr; + // Stand alone debug info file if different from executable + const char *symfile = nullptr; + g_vsc.target.AddModule(program.data(), target_triple, uuid_cstr, symfile); + if (error.Fail()) { + response.try_emplace("success", false); + response.try_emplace("message", std::string(error.GetCString())); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + } + } + + // Extract any extra arguments and append them to our program arguments for + // when we launch + auto args = GetStrings(arguments, "args"); + if (!args.empty()) + g_vsc.launch_info.SetArguments(MakeArgv(args).data(), true); + + // Pass any environment variables along that the user specified. + auto envs = GetStrings(arguments, "env"); + if (!envs.empty()) + g_vsc.launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true); + + auto flags = g_vsc.launch_info.GetLaunchFlags(); + + if (GetBoolean(arguments, "disableASLR", true)) + flags |= lldb::eLaunchFlagDisableASLR; + if (GetBoolean(arguments, "disableSTDIO", false)) + flags |= lldb::eLaunchFlagDisableSTDIO; + if (GetBoolean(arguments, "shellExpandArguments", false)) + flags |= lldb::eLaunchFlagShellExpandArguments; + const bool detatchOnError = GetBoolean(arguments, "detachOnError", false); + g_vsc.launch_info.SetDetachOnError(detatchOnError); + g_vsc.launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug | + lldb::eLaunchFlagStopAtEntry); + + // Run any pre run LLDB commands the user specified in the launch.json + g_vsc.RunPreRunCommands(); + + // Disable async events so the launch will be successful when we return from + // the launch call and the launch will happen synchronously + g_vsc.debugger.SetAsync(false); + g_vsc.target.Launch(g_vsc.launch_info, error); + if (error.Fail()) { + response.try_emplace("success", false); + response.try_emplace("message", std::string(error.GetCString())); + } + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + + SendProcessEvent(Launch); + g_vsc.SendJSON(llvm::json::Value(CreateEvent("initialized"))); + // Reenable async events and start the event thread to catch async events. + g_vsc.debugger.SetAsync(true); +} + +//---------------------------------------------------------------------- +// "NextRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Next request; value of command field is 'next'. The +// request starts the debuggee to run again for one step. +// The debug adapter first sends the NextResponse and then +// a StoppedEvent (event type 'step') after the step has +// completed.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "next" ] +// }, +// "arguments": { +// "$ref": "#/definitions/NextArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "NextArguments": { +// "type": "object", +// "description": "Arguments for 'next' request.", +// "properties": { +// "threadId": { +// "type": "integer", +// "description": "Execute 'next' for this thread." +// } +// }, +// "required": [ "threadId" ] +// }, +// "NextResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'next' request. This is just an +// acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_next(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + lldb::SBThread thread = g_vsc.GetLLDBThread(*arguments); + if (thread.IsValid()) { + // Remember the thread ID that caused the resume so we can set the + // "threadCausedFocus" boolean value in the "stopped" events. + g_vsc.focus_tid = thread.GetThreadID(); + thread.StepOver(); + } else { + response.try_emplace("success", false); + } + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "PauseRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Pause request; value of command field is 'pause'. The +// request suspenses the debuggee. The debug adapter first sends the +// PauseResponse and then a StoppedEvent (event type 'pause') after the +// thread has been paused successfully.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "pause" ] +// }, +// "arguments": { +// "$ref": "#/definitions/PauseArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "PauseArguments": { +// "type": "object", +// "description": "Arguments for 'pause' request.", +// "properties": { +// "threadId": { +// "type": "integer", +// "description": "Pause execution for this thread." +// } +// }, +// "required": [ "threadId" ] +// }, +// "PauseResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'pause' request. This is just an +// acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_pause(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + lldb::SBProcess process = g_vsc.target.GetProcess(); + lldb::SBError error = process.Stop(); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "ScopesRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Scopes request; value of command field is 'scopes'. The +// request returns the variable scopes for a given stackframe ID.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "scopes" ] +// }, +// "arguments": { +// "$ref": "#/definitions/ScopesArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "ScopesArguments": { +// "type": "object", +// "description": "Arguments for 'scopes' request.", +// "properties": { +// "frameId": { +// "type": "integer", +// "description": "Retrieve the scopes for this stackframe." +// } +// }, +// "required": [ "frameId" ] +// }, +// "ScopesResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'scopes' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "scopes": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/Scope" +// }, +// "description": "The scopes of the stackframe. If the array has +// length zero, there are no scopes available." +// } +// }, +// "required": [ "scopes" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_scopes(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + llvm::json::Object body; + auto arguments = request.getObject("arguments"); + lldb::SBFrame frame = g_vsc.GetLLDBFrame(*arguments); + g_vsc.variables.Clear(); + g_vsc.variables.Append(frame.GetVariables(true, // arguments + true, // locals + false, // statics + true)); // in_scope_only + g_vsc.num_locals = g_vsc.variables.GetSize(); + g_vsc.variables.Append(frame.GetVariables(false, // arguments + false, // locals + true, // statics + true)); // in_scope_only + g_vsc.num_globals = g_vsc.variables.GetSize() - (g_vsc.num_locals); + g_vsc.variables.Append(frame.GetRegisters()); + g_vsc.num_regs = + g_vsc.variables.GetSize() - (g_vsc.num_locals + g_vsc.num_globals); + body.try_emplace("scopes", g_vsc.CreateTopLevelScopes()); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "SetBreakpointsRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "SetBreakpoints request; value of command field is +// 'setBreakpoints'. Sets multiple breakpoints for a single source and +// clears all previous breakpoints in that source. To clear all breakpoint +// for a source, specify an empty array. When a breakpoint is hit, a +// StoppedEvent (event type 'breakpoint') is generated.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "setBreakpoints" ] +// }, +// "arguments": { +// "$ref": "#/definitions/SetBreakpointsArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "SetBreakpointsArguments": { +// "type": "object", +// "description": "Arguments for 'setBreakpoints' request.", +// "properties": { +// "source": { +// "$ref": "#/definitions/Source", +// "description": "The source location of the breakpoints; either +// source.path or source.reference must be specified." +// }, +// "breakpoints": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/SourceBreakpoint" +// }, +// "description": "The code locations of the breakpoints." +// }, +// "lines": { +// "type": "array", +// "items": { +// "type": "integer" +// }, +// "description": "Deprecated: The code locations of the breakpoints." +// }, +// "sourceModified": { +// "type": "boolean", +// "description": "A value of true indicates that the underlying source +// has been modified which results in new breakpoint locations." +// } +// }, +// "required": [ "source" ] +// }, +// "SetBreakpointsResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'setBreakpoints' request. Returned is +// information about each breakpoint created by this request. This includes +// the actual code location and whether the breakpoint could be verified. +// The breakpoints returned are in the same order as the elements of the +// 'breakpoints' (or the deprecated 'lines') in the +// SetBreakpointsArguments.", "properties": { +// "body": { +// "type": "object", +// "properties": { +// "breakpoints": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/Breakpoint" +// }, +// "description": "Information about the breakpoints. The array +// elements are in the same order as the elements of the +// 'breakpoints' (or the deprecated 'lines') in the +// SetBreakpointsArguments." +// } +// }, +// "required": [ "breakpoints" ] +// } +// }, +// "required": [ "body" ] +// }] +// }, +// "SourceBreakpoint": { +// "type": "object", +// "description": "Properties of a breakpoint or logpoint passed to the +// setBreakpoints request.", "properties": { +// "line": { +// "type": "integer", +// "description": "The source line of the breakpoint or logpoint." +// }, +// "column": { +// "type": "integer", +// "description": "An optional source column of the breakpoint." +// }, +// "condition": { +// "type": "string", +// "description": "An optional expression for conditional breakpoints." +// }, +// "hitCondition": { +// "type": "string", +// "description": "An optional expression that controls how many hits of +// the breakpoint are ignored. The backend is expected to interpret the +// expression as needed." +// }, +// "logMessage": { +// "type": "string", +// "description": "If this attribute exists and is non-empty, the backend +// must not 'break' (stop) but log the message instead. Expressions within +// {} are interpolated." +// } +// }, +// "required": [ "line" ] +// } +//---------------------------------------------------------------------- +void request_setBreakpoints(const llvm::json::Object &request) { + llvm::json::Object response; + lldb::SBError error; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + auto source = arguments->getObject("source"); + const auto path = GetString(source, "path"); + auto breakpoints = arguments->getArray("breakpoints"); + llvm::json::Array response_breakpoints; + // Decode the source breakpoint infos for this "setBreakpoints" request + SourceBreakpointMap request_bps; + for (const auto &bp : *breakpoints) { + auto bp_obj = bp.getAsObject(); + if (bp_obj) { + SourceBreakpoint src_bp(*bp_obj); + request_bps[src_bp.line] = std::move(src_bp); + } + } + + // See if we already have breakpoints set for this source file from a + // previous "setBreakpoints" request + auto old_src_bp_pos = g_vsc.source_breakpoints.find(path); + if (old_src_bp_pos != g_vsc.source_breakpoints.end()) { + + // We have already set breakpoints in this source file and they are giving + // use a new list of lines to set breakpoints on. Some breakpoints might + // already be set, and some might not. We need to remove any breakpoints + // whose lines are not contained in the any breakpoints lines in in the + // "breakpoints" array. + + // Delete any breakpoints in this source file that aren't in the + // request_bps set. There is no call to remove breakpoints other than + // calling this function with a smaller or empty "breakpoints" list. + std::vector<uint32_t> remove_lines; + for (auto &pair: old_src_bp_pos->second) { + auto request_pos = request_bps.find(pair.first); + if (request_pos == request_bps.end()) { + // This breakpoint no longer exists in this source file, delete it + g_vsc.target.BreakpointDelete(pair.second.bp.GetID()); + remove_lines.push_back(pair.first); + } else { + pair.second.UpdateBreakpoint(request_pos->second); + // Remove this breakpopint from the request breakpoints since we have + // handled it here and we don't need to set a new breakpoint below. + request_bps.erase(request_pos); + // Add this breakpoint info to the response + AppendBreakpoint(pair.second.bp, response_breakpoints); + } + } + // Remove any lines from this existing source breakpoint map + for (auto line: remove_lines) + old_src_bp_pos->second.erase(line); + + // Now add any breakpoint infos left over in request_bps are the + // breakpoints that weren't set in this source file yet. We need to update + // thread source breakpoint info for the source file in the variable + // "old_src_bp_pos->second" so the info for this source file is up to date. + for (auto &pair : request_bps) { + pair.second.SetBreakpoint(path.data()); + // Add this breakpoint info to the response + AppendBreakpoint(pair.second.bp, response_breakpoints); + old_src_bp_pos->second[pair.first] = std::move(pair.second); + } + } else { + // No breakpoints were set for this source file yet. Set all breakpoints + // for each line and add them to the response and create an entry in + // g_vsc.source_breakpoints for this source file. + for (auto &pair : request_bps) { + pair.second.SetBreakpoint(path.data()); + // Add this breakpoint info to the response + AppendBreakpoint(pair.second.bp, response_breakpoints); + } + g_vsc.source_breakpoints[path] = std::move(request_bps); + } + + llvm::json::Object body; + body.try_emplace("breakpoints", std::move(response_breakpoints)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "SetExceptionBreakpointsRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "SetExceptionBreakpoints request; value of command field +// is 'setExceptionBreakpoints'. The request configures the debuggers +// response to thrown exceptions. If an exception is configured to break, a +// StoppedEvent is fired (event type 'exception').", "properties": { +// "command": { +// "type": "string", +// "enum": [ "setExceptionBreakpoints" ] +// }, +// "arguments": { +// "$ref": "#/definitions/SetExceptionBreakpointsArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "SetExceptionBreakpointsArguments": { +// "type": "object", +// "description": "Arguments for 'setExceptionBreakpoints' request.", +// "properties": { +// "filters": { +// "type": "array", +// "items": { +// "type": "string" +// }, +// "description": "IDs of checked exception options. The set of IDs is +// returned via the 'exceptionBreakpointFilters' capability." +// }, +// "exceptionOptions": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/ExceptionOptions" +// }, +// "description": "Configuration options for selected exceptions." +// } +// }, +// "required": [ "filters" ] +// }, +// "SetExceptionBreakpointsResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'setExceptionBreakpoints' request. This is +// just an acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_setExceptionBreakpoints(const llvm::json::Object &request) { + llvm::json::Object response; + lldb::SBError error; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + auto filters = arguments->getArray("filters"); + // Keep a list of any exception breakpoint filter names that weren't set + // so we can clear any exception breakpoints if needed. + std::set<std::string> unset_filters; + for (const auto &bp : g_vsc.exception_breakpoints) + unset_filters.insert(bp.filter); + + for (const auto &value : *filters) { + const auto filter = GetAsString(value); + auto exc_bp = g_vsc.GetExceptionBreakpoint(filter); + if (exc_bp) { + exc_bp->SetBreakpoint(); + unset_filters.erase(filter); + } + } + for (const auto &filter : unset_filters) { + auto exc_bp = g_vsc.GetExceptionBreakpoint(filter); + if (exc_bp) + exc_bp->ClearBreakpoint(); + } + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "SetFunctionBreakpointsRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "SetFunctionBreakpoints request; value of command field is +// 'setFunctionBreakpoints'. Sets multiple function breakpoints and clears +// all previous function breakpoints. To clear all function breakpoint, +// specify an empty array. When a function breakpoint is hit, a StoppedEvent +// (event type 'function breakpoint') is generated.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "setFunctionBreakpoints" ] +// }, +// "arguments": { +// "$ref": "#/definitions/SetFunctionBreakpointsArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "SetFunctionBreakpointsArguments": { +// "type": "object", +// "description": "Arguments for 'setFunctionBreakpoints' request.", +// "properties": { +// "breakpoints": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/FunctionBreakpoint" +// }, +// "description": "The function names of the breakpoints." +// } +// }, +// "required": [ "breakpoints" ] +// }, +// "FunctionBreakpoint": { +// "type": "object", +// "description": "Properties of a breakpoint passed to the +// setFunctionBreakpoints request.", "properties": { +// "name": { +// "type": "string", +// "description": "The name of the function." +// }, +// "condition": { +// "type": "string", +// "description": "An optional expression for conditional breakpoints." +// }, +// "hitCondition": { +// "type": "string", +// "description": "An optional expression that controls how many hits of +// the breakpoint are ignored. The backend is expected to interpret the +// expression as needed." +// } +// }, +// "required": [ "name" ] +// }, +// "SetFunctionBreakpointsResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'setFunctionBreakpoints' request. Returned is +// information about each breakpoint created by this request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "breakpoints": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/Breakpoint" +// }, +// "description": "Information about the breakpoints. The array +// elements correspond to the elements of the 'breakpoints' array." +// } +// }, +// "required": [ "breakpoints" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_setFunctionBreakpoints(const llvm::json::Object &request) { + llvm::json::Object response; + lldb::SBError error; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + auto breakpoints = arguments->getArray("breakpoints"); + FunctionBreakpointMap request_bps; + llvm::json::Array response_breakpoints; + for (const auto &value : *breakpoints) { + auto bp_obj = value.getAsObject(); + if (bp_obj == nullptr) + continue; + FunctionBreakpoint func_bp(*bp_obj); + request_bps[func_bp.functionName] = std::move(func_bp); + } + + std::vector<llvm::StringRef> remove_names; + // Disable any function breakpoints that aren't in the request_bps. + // There is no call to remove function breakpoints other than calling this + // function with a smaller or empty "breakpoints" list. + for (auto &pair: g_vsc.function_breakpoints) { + auto request_pos = request_bps.find(pair.first()); + if (request_pos == request_bps.end()) { + // This function breakpoint no longer exists delete it from LLDB + g_vsc.target.BreakpointDelete(pair.second.bp.GetID()); + remove_names.push_back(pair.first()); + } else { + // Update the existing breakpoint as any setting withing the function + // breakpoint might have changed. + pair.second.UpdateBreakpoint(request_pos->second); + // Remove this breakpopint from the request breakpoints since we have + // handled it here and we don't need to set a new breakpoint below. + request_bps.erase(request_pos); + // Add this breakpoint info to the response + AppendBreakpoint(pair.second.bp, response_breakpoints); + } + } + // Remove any breakpoints that are no longer in our list + for (const auto &name: remove_names) + g_vsc.function_breakpoints.erase(name); + + // Any breakpoints that are left in "request_bps" are breakpoints that + // need to be set. + for (auto &pair : request_bps) { + pair.second.SetBreakpoint(); + // Add this breakpoint info to the response + AppendBreakpoint(pair.second.bp, response_breakpoints); + g_vsc.function_breakpoints[pair.first()] = std::move(pair.second); + } + + llvm::json::Object body; + body.try_emplace("breakpoints", std::move(response_breakpoints)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "SourceRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Source request; value of command field is 'source'. The +// request retrieves the source code for a given source reference.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "source" ] +// }, +// "arguments": { +// "$ref": "#/definitions/SourceArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "SourceArguments": { +// "type": "object", +// "description": "Arguments for 'source' request.", +// "properties": { +// "source": { +// "$ref": "#/definitions/Source", +// "description": "Specifies the source content to load. Either +// source.path or source.sourceReference must be specified." +// }, +// "sourceReference": { +// "type": "integer", +// "description": "The reference to the source. This is the same as +// source.sourceReference. This is provided for backward compatibility +// since old backends do not understand the 'source' attribute." +// } +// }, +// "required": [ "sourceReference" ] +// }, +// "SourceResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'source' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "content": { +// "type": "string", +// "description": "Content of the source reference." +// }, +// "mimeType": { +// "type": "string", +// "description": "Optional content type (mime type) of the source." +// } +// }, +// "required": [ "content" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_source(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + llvm::json::Object body; + + auto arguments = request.getObject("arguments"); + auto source = arguments->getObject("source"); + auto sourceReference = GetSigned(source, "sourceReference", -1); + auto pos = g_vsc.source_map.find((lldb::addr_t)sourceReference); + if (pos != g_vsc.source_map.end()) { + body.try_emplace("content", pos->second.content); + } else { + response.try_emplace("success", false); + } + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "StackTraceRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "StackTrace request; value of command field is +// 'stackTrace'. The request returns a stacktrace from the current execution +// state.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "stackTrace" ] +// }, +// "arguments": { +// "$ref": "#/definitions/StackTraceArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "StackTraceArguments": { +// "type": "object", +// "description": "Arguments for 'stackTrace' request.", +// "properties": { +// "threadId": { +// "type": "integer", +// "description": "Retrieve the stacktrace for this thread." +// }, +// "startFrame": { +// "type": "integer", +// "description": "The index of the first frame to return; if omitted +// frames start at 0." +// }, +// "levels": { +// "type": "integer", +// "description": "The maximum number of frames to return. If levels is +// not specified or 0, all frames are returned." +// }, +// "format": { +// "$ref": "#/definitions/StackFrameFormat", +// "description": "Specifies details on how to format the stack frames." +// } +// }, +// "required": [ "threadId" ] +// }, +// "StackTraceResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'stackTrace' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "stackFrames": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/StackFrame" +// }, +// "description": "The frames of the stackframe. If the array has +// length zero, there are no stackframes available. This means that +// there is no location information available." +// }, +// "totalFrames": { +// "type": "integer", +// "description": "The total number of frames available." +// } +// }, +// "required": [ "stackFrames" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_stackTrace(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + lldb::SBError error; + auto arguments = request.getObject("arguments"); + lldb::SBThread thread = g_vsc.GetLLDBThread(*arguments); + llvm::json::Array stackFrames; + llvm::json::Object body; + + if (thread.IsValid()) { + const auto startFrame = GetUnsigned(arguments, "startFrame", 0); + const auto levels = GetUnsigned(arguments, "levels", 0); + const auto endFrame = (levels == 0) ? INT64_MAX : (startFrame + levels); + for (uint32_t i = startFrame; i < endFrame; ++i) { + auto frame = thread.GetFrameAtIndex(i); + if (!frame.IsValid()) + break; + stackFrames.emplace_back(CreateStackFrame(frame)); + } + } + body.try_emplace("stackFrames", std::move(stackFrames)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "StepInRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "StepIn request; value of command field is 'stepIn'. The +// request starts the debuggee to step into a function/method if possible. +// If it cannot step into a target, 'stepIn' behaves like 'next'. The debug +// adapter first sends the StepInResponse and then a StoppedEvent (event +// type 'step') after the step has completed. If there are multiple +// function/method calls (or other targets) on the source line, the optional +// argument 'targetId' can be used to control into which target the 'stepIn' +// should occur. The list of possible targets for a given source line can be +// retrieved via the 'stepInTargets' request.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "stepIn" ] +// }, +// "arguments": { +// "$ref": "#/definitions/StepInArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "StepInArguments": { +// "type": "object", +// "description": "Arguments for 'stepIn' request.", +// "properties": { +// "threadId": { +// "type": "integer", +// "description": "Execute 'stepIn' for this thread." +// }, +// "targetId": { +// "type": "integer", +// "description": "Optional id of the target to step into." +// } +// }, +// "required": [ "threadId" ] +// }, +// "StepInResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'stepIn' request. This is just an +// acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_stepIn(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + lldb::SBThread thread = g_vsc.GetLLDBThread(*arguments); + if (thread.IsValid()) { + // Remember the thread ID that caused the resume so we can set the + // "threadCausedFocus" boolean value in the "stopped" events. + g_vsc.focus_tid = thread.GetThreadID(); + thread.StepInto(); + } else { + response.try_emplace("success", false); + } + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "StepOutRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "StepOut request; value of command field is 'stepOut'. The +// request starts the debuggee to run again for one step. The debug adapter +// first sends the StepOutResponse and then a StoppedEvent (event type +// 'step') after the step has completed.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "stepOut" ] +// }, +// "arguments": { +// "$ref": "#/definitions/StepOutArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "StepOutArguments": { +// "type": "object", +// "description": "Arguments for 'stepOut' request.", +// "properties": { +// "threadId": { +// "type": "integer", +// "description": "Execute 'stepOut' for this thread." +// } +// }, +// "required": [ "threadId" ] +// }, +// "StepOutResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'stepOut' request. This is just an +// acknowledgement, so no body field is required." +// }] +// } +//---------------------------------------------------------------------- +void request_stepOut(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + auto arguments = request.getObject("arguments"); + lldb::SBThread thread = g_vsc.GetLLDBThread(*arguments); + if (thread.IsValid()) { + // Remember the thread ID that caused the resume so we can set the + // "threadCausedFocus" boolean value in the "stopped" events. + g_vsc.focus_tid = thread.GetThreadID(); + thread.StepOut(); + } else { + response.try_emplace("success", false); + } + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "ThreadsRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Thread request; value of command field is 'threads'. The +// request retrieves a list of all threads.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "threads" ] +// } +// }, +// "required": [ "command" ] +// }] +// }, +// "ThreadsResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'threads' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "threads": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/Thread" +// }, +// "description": "All threads." +// } +// }, +// "required": [ "threads" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_threads(const llvm::json::Object &request) { + + lldb::SBProcess process = g_vsc.target.GetProcess(); + llvm::json::Object response; + FillResponse(request, response); + + const uint32_t num_threads = process.GetNumThreads(); + llvm::json::Array threads; + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + threads.emplace_back(CreateThread(thread)); + } + if (threads.size() == 0) { + response.try_emplace("success", false); + } + llvm::json::Object body; + body.try_emplace("threads", std::move(threads)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "SetVariableRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "setVariable request; value of command field is +// 'setVariable'. Set the variable with the given name in the variable +// container to a new value.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "setVariable" ] +// }, +// "arguments": { +// "$ref": "#/definitions/SetVariableArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "SetVariableArguments": { +// "type": "object", +// "description": "Arguments for 'setVariable' request.", +// "properties": { +// "variablesReference": { +// "type": "integer", +// "description": "The reference of the variable container." +// }, +// "name": { +// "type": "string", +// "description": "The name of the variable." +// }, +// "value": { +// "type": "string", +// "description": "The value of the variable." +// }, +// "format": { +// "$ref": "#/definitions/ValueFormat", +// "description": "Specifies details on how to format the response value." +// } +// }, +// "required": [ "variablesReference", "name", "value" ] +// }, +// "SetVariableResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'setVariable' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "value": { +// "type": "string", +// "description": "The new value of the variable." +// }, +// "type": { +// "type": "string", +// "description": "The type of the new value. Typically shown in the +// UI when hovering over the value." +// }, +// "variablesReference": { +// "type": "number", +// "description": "If variablesReference is > 0, the new value is +// structured and its children can be retrieved by passing +// variablesReference to the VariablesRequest." +// }, +// "namedVariables": { +// "type": "number", +// "description": "The number of named child variables. The client +// can use this optional information to present the variables in a +// paged UI and fetch them in chunks." +// }, +// "indexedVariables": { +// "type": "number", +// "description": "The number of indexed child variables. The client +// can use this optional information to present the variables in a +// paged UI and fetch them in chunks." +// } +// }, +// "required": [ "value" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_setVariable(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + llvm::json::Array variables; + llvm::json::Object body; + auto arguments = request.getObject("arguments"); + // This is a reference to the containing variable/scope + const auto variablesReference = + GetUnsigned(arguments, "variablesReference", 0); + const auto name = GetString(arguments, "name"); + const auto value = GetString(arguments, "value"); + // Set success to false just in case we don't find the variable by name + response.try_emplace("success", false); + + lldb::SBValue variable; + int64_t newVariablesReference = 0; + + // The "id" is the unique integer ID that is unique within the enclosing + // variablesReference. It is optionally added to any "interface Variable" + // objects to uniquely identify a variable within an enclosing + // variablesReference. It helps to disambiguate between two variables that + // have the same name within the same scope since the "setVariables" request + // only specifies the variable reference of the enclosing scope/variable, and + // the name of the variable. We could have two shadowed variables with the + // same name in "Locals" or "Globals". In our case the "id" absolute index + // of the variable within the the g_vsc.variables list. + const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX); + if (id_value != UINT64_MAX) { + variable = g_vsc.variables.GetValueAtIndex(id_value); + } else if (VARREF_IS_SCOPE(variablesReference)) { + // variablesReference is one of our scopes, not an actual variable it is + // asking for a variable inlocals or globals or registers + int64_t start_idx = 0; + int64_t end_idx = 0; + switch (variablesReference) { + case VARREF_LOCALS: + start_idx = 0; + end_idx = start_idx + g_vsc.num_locals; + break; + case VARREF_GLOBALS: + start_idx = g_vsc.num_locals; + end_idx = start_idx + g_vsc.num_globals; + break; + case VARREF_REGS: + start_idx = g_vsc.num_locals + g_vsc.num_globals; + end_idx = start_idx + g_vsc.num_regs; + break; + default: + break; + } + + // Find the variable by name in the correct scope and hope we don't have + // multiple variables with the same name. We search backwards because + // the list of variables has the top most variables first and variables + // in deeper scopes are last. This means we will catch the deepest + // variable whose name matches which is probably what the user wants. + for (int64_t i = end_idx - 1; i >= start_idx; --i) { + auto curr_variable = g_vsc.variables.GetValueAtIndex(i); + llvm::StringRef variable_name(curr_variable.GetName()); + if (variable_name == name) { + variable = curr_variable; + if (curr_variable.MightHaveChildren()) + newVariablesReference = i; + break; + } + } + } else { + // We have a named item within an actual variable so we need to find it + // withing the container variable by name. + const int64_t var_idx = VARREF_TO_VARIDX(variablesReference); + lldb::SBValue container = g_vsc.variables.GetValueAtIndex(var_idx); + variable = container.GetChildMemberWithName(name.data()); + if (!variable.IsValid()) { + if (name.startswith("[")) { + llvm::StringRef index_str(name.drop_front(1)); + uint64_t index = 0; + if (!index_str.consumeInteger(0, index)) { + if (index_str == "]") + variable = container.GetChildAtIndex(index); + } + } + } + + // We don't know the index of the variable in our g_vsc.variables + if (variable.IsValid()) { + if (variable.MightHaveChildren()) { + newVariablesReference = VARIDX_TO_VARREF(g_vsc.variables.GetSize()); + g_vsc.variables.Append(variable); + } + } + } + + if (variable.IsValid()) { + lldb::SBError error; + bool success = variable.SetValueFromCString(value.data(), error); + if (success) { + SetValueForKey(variable, body, "value"); + body.try_emplace("type", variable.GetType().GetDisplayTypeName()); + body.try_emplace("variablesReference", newVariablesReference); + } else { + body.try_emplace("message", std::string(error.GetCString())); + } + response.try_emplace("success", success); + } + + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +//---------------------------------------------------------------------- +// "VariablesRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "Variables request; value of command field is 'variables'. +// Retrieves all child variables for the given variable reference. An +// optional filter can be used to limit the fetched children to either named +// or indexed children.", "properties": { +// "command": { +// "type": "string", +// "enum": [ "variables" ] +// }, +// "arguments": { +// "$ref": "#/definitions/VariablesArguments" +// } +// }, +// "required": [ "command", "arguments" ] +// }] +// }, +// "VariablesArguments": { +// "type": "object", +// "description": "Arguments for 'variables' request.", +// "properties": { +// "variablesReference": { +// "type": "integer", +// "description": "The Variable reference." +// }, +// "filter": { +// "type": "string", +// "enum": [ "indexed", "named" ], +// "description": "Optional filter to limit the child variables to either +// named or indexed. If ommited, both types are fetched." +// }, +// "start": { +// "type": "integer", +// "description": "The index of the first variable to return; if omitted +// children start at 0." +// }, +// "count": { +// "type": "integer", +// "description": "The number of variables to return. If count is missing +// or 0, all variables are returned." +// }, +// "format": { +// "$ref": "#/definitions/ValueFormat", +// "description": "Specifies details on how to format the Variable +// values." +// } +// }, +// "required": [ "variablesReference" ] +// }, +// "VariablesResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to 'variables' request.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "variables": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/Variable" +// }, +// "description": "All (or a range) of variables for the given +// variable reference." +// } +// }, +// "required": [ "variables" ] +// } +// }, +// "required": [ "body" ] +// }] +// } +//---------------------------------------------------------------------- +void request_variables(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + llvm::json::Array variables; + auto arguments = request.getObject("arguments"); + const auto variablesReference = + GetUnsigned(arguments, "variablesReference", 0); + const int64_t start = GetSigned(arguments, "start", 0); + const int64_t count = GetSigned(arguments, "count", 0); + bool hex = false; + auto format = arguments->getObject("format"); + if (format) + hex = GetBoolean(format, "hex", false); + + if (VARREF_IS_SCOPE(variablesReference)) { + // variablesReference is one of our scopes, not an actual variable it is + // asking for the list of args, locals or globals. + int64_t start_idx = 0; + int64_t num_children = 0; + switch (variablesReference) { + case VARREF_LOCALS: + start_idx = start; + num_children = g_vsc.num_locals; + break; + case VARREF_GLOBALS: + start_idx = start + g_vsc.num_locals + start; + num_children = g_vsc.num_globals; + break; + case VARREF_REGS: + start_idx = start + g_vsc.num_locals + g_vsc.num_globals; + num_children = g_vsc.num_regs; + break; + default: + break; + } + const int64_t end_idx = start_idx + ((count == 0) ? num_children : count); + for (auto i = start_idx; i < end_idx; ++i) { + lldb::SBValue variable = g_vsc.variables.GetValueAtIndex(i); + if (!variable.IsValid()) + break; + variables.emplace_back( + CreateVariable(variable, VARIDX_TO_VARREF(i), i, hex)); + } + } else { + // We are expanding a variable that has children, so we will return its + // children. + const int64_t var_idx = VARREF_TO_VARIDX(variablesReference); + lldb::SBValue variable = g_vsc.variables.GetValueAtIndex(var_idx); + if (variable.IsValid()) { + const auto num_children = variable.GetNumChildren(); + const int64_t end_idx = start + ((count == 0) ? num_children : count); + for (auto i = start; i < end_idx; ++i) { + lldb::SBValue child = variable.GetChildAtIndex(i); + if (!child.IsValid()) + break; + if (child.MightHaveChildren()) { + const int64_t var_idx = g_vsc.variables.GetSize(); + auto childVariablesReferences = VARIDX_TO_VARREF(var_idx); + variables.emplace_back( + CreateVariable(child, childVariablesReferences, var_idx, hex)); + g_vsc.variables.Append(child); + } else { + variables.emplace_back(CreateVariable(child, 0, INT64_MAX, hex)); + } + } + } + } + llvm::json::Object body; + body.try_emplace("variables", std::move(variables)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +// A request used in testing to get the details on all breakpoints that are +// currently set in the target. This helps us to test "setBreakpoints" and +// "setFunctionBreakpoints" requests to verify we have the correct set of +// breakpoints currently set in LLDB. +void request__testGetTargetBreakpoints(const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + llvm::json::Array response_breakpoints; + for (uint32_t i = 0; g_vsc.target.GetBreakpointAtIndex(i).IsValid(); ++i) { + auto bp = g_vsc.target.GetBreakpointAtIndex(i); + AppendBreakpoint(bp, response_breakpoints); + } + llvm::json::Object body; + body.try_emplace("breakpoints", std::move(response_breakpoints)); + response.try_emplace("body", std::move(body)); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); +} + +const std::map<std::string, RequestCallback> &GetRequestHandlers() { +#define REQUEST_CALLBACK(name) \ + { #name, request_##name } + static std::map<std::string, RequestCallback> g_request_handlers = { + // VSCode Debug Adaptor requests + REQUEST_CALLBACK(attach), + REQUEST_CALLBACK(continue), + REQUEST_CALLBACK(configurationDone), + REQUEST_CALLBACK(disconnect), + REQUEST_CALLBACK(evaluate), + REQUEST_CALLBACK(exceptionInfo), + REQUEST_CALLBACK(initialize), + REQUEST_CALLBACK(launch), + REQUEST_CALLBACK(next), + REQUEST_CALLBACK(pause), + REQUEST_CALLBACK(scopes), + REQUEST_CALLBACK(setBreakpoints), + REQUEST_CALLBACK(setExceptionBreakpoints), + REQUEST_CALLBACK(setFunctionBreakpoints), + REQUEST_CALLBACK(setVariable), + REQUEST_CALLBACK(source), + REQUEST_CALLBACK(stackTrace), + REQUEST_CALLBACK(stepIn), + REQUEST_CALLBACK(stepOut), + REQUEST_CALLBACK(threads), + REQUEST_CALLBACK(variables), + // Testing requests + REQUEST_CALLBACK(_testGetTargetBreakpoints), + }; +#undef REQUEST_CALLBACK + return g_request_handlers; +} + +} // anonymous namespace + +int main(int argc, char *argv[]) { + + // Initialize LLDB first before we do anything. + lldb::SBDebugger::Initialize(); + + if (argc == 2) { + const char *arg = argv[1]; +#if !defined(_WIN32) + if (strcmp(arg, "-g") == 0) { + printf("Paused waiting for debugger to attach (pid = %i)...\n", getpid()); + pause(); + } else { +#else + { +#endif + int portno = atoi(arg); + printf("Listening on port %i...\n", portno); + int socket_fd = AcceptConnection(portno); + if (socket_fd >= 0) { + // We must open two FILE objects, one for reading and one for writing + // the FILE objects have a mutex in them that won't allow reading and + // writing to the socket stream. + g_vsc.in = fdopen(socket_fd, "r"); + g_vsc.out = fdopen(socket_fd, "w"); + if (g_vsc.in == nullptr || g_vsc.out == nullptr) { + if (g_vsc.log) + *g_vsc.log << "fdopen failed (" << strerror(errno) << ")" + << std::endl; + exit(1); + } + } else { + exit(1); + } + } + } + auto request_handlers = GetRequestHandlers(); + uint32_t packet_idx = 0; + while (true) { + std::string json = g_vsc.ReadJSON(); + if (json.empty()) + break; + + llvm::StringRef json_sref(json); + llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref); + if (!json_value) { + auto error = json_value.takeError(); + if (g_vsc.log) { + std::string error_str; + llvm::raw_string_ostream strm(error_str); + strm << error; + strm.flush(); + + *g_vsc.log << "error: failed to parse JSON: " << error_str << std::endl + << json << std::endl; + } + return 1; + } + + auto object = json_value->getAsObject(); + if (!object) { + if (g_vsc.log) + *g_vsc.log << "error: json packet isn't a object" << std::endl; + return 1; + } + + const auto packet_type = GetString(object, "type"); + if (packet_type == "request") { + const auto command = GetString(object, "command"); + auto handler_pos = request_handlers.find(command); + if (handler_pos != request_handlers.end()) { + handler_pos->second(*object); + } else { + if (g_vsc.log) + *g_vsc.log << "error: unhandled command \"" << command.data() << std::endl; + return 1; + } + } + ++packet_idx; + } + + // We must terminate the debugger in a thread before the C++ destructor + // chain messes everything up. + lldb::SBDebugger::Terminate(); + return 0; +} diff --git a/lldb/tools/lldb-vscode/package.json b/lldb/tools/lldb-vscode/package.json new file mode 100644 index 00000000000..aa3de65c172 --- /dev/null +++ b/lldb/tools/lldb-vscode/package.json @@ -0,0 +1,242 @@ +{ + "name": "lldb-vscode", + "displayName": "LLDB native Debug stub", + "version": "0.1.0", + "publisher": "llvm.org", + "description": "Debug adapter for LLDB which uses a C++ tool to interface directly with LLDB.", + "author": { + "name": "Greg Clayton", + "email": "clayborg@gmail.com" + }, + "license": "LLVM", + "keywords": [ + "multi-root ready" + ], + "engines": { + "vscode": "^1.18.0", + "node": "^7.9.0" + }, + "icon": "images/lldb.png", + "categories": [ + "Debuggers" + ], + "private": true, + "devDependencies": { + "@types/node": "7.0.43", + "@types/mocha": "2.2.45", + "typescript": "2.6.2", + "mocha": "4.0.1", + "vscode": "1.1.10", + "vscode-debugadapter-testsupport": "1.25.0", + "tslint": "5.8.0", + "vsce": "1.35.0" + }, + "contributes": { + "debuggers": [ + { + "type": "lldb-vscode", + "label": "Native LLDB Debugger", + "enableBreakpointsFor": { + "languageIds": [ + "ada", + "arm", + "asm", + "c", + "cpp", + "crystal", + "d", + "fortan", + "fortran-modern", + "nim", + "objective-c", + "objectpascal", + "pascal", + "rust", + "swift" + ] + }, + "program": "./bin/lldb-vscode", + "configurationAttributes": { + "launch": { + "required": [ + "program" + ], + "properties": { + "program": { + "type": "string", + "description": "Path to the program to debug." + }, + "args": { + "type": [ "array", "string" ], + "description": "Program arguments.", + "default": [] + }, + "cwd": { + "type": "string", + "description": "Program working directory.", + "default": "${workspaceRoot}" + }, + "env": { + "type": "array", + "description": "Additional environment variables.", + "default": [] + }, + "stopOnEntry": { + "type": "boolean", + "description": "Automatically stop after launch.", + "default": false + }, + "disableASLR": { + "type": "boolean", + "description": "Enable or disable Address space layout randomization if the debugger supports it.", + "default": true + }, + "disableSTDIO": { + "type": "boolean", + "description": "Don't retrieve STDIN, STDOUT and STDERR as the program is running.", + "default": false + }, + "shellExpandArguments": { + "type": "boolean", + "description": "Expand program arguments as a shell would without actually launching the program in a shell.", + "default": false + }, + "detachOnError": { + "type": "boolean", + "description": "Detach from the program.", + "default": false + }, + "trace": { + "type": "boolean", + "description": "Enable logging of the Debug Adapter Protocol.", + "default": true + }, + "sourcePath": { + "type": "string", + "description": "Specify a source path to remap \"./\" to allow full paths to be used when setting breakpoints in binaries that have relative source paths." + }, + "sourceMap": { + "type": "array", + "description": "Specify an array of path remappings; each element must itself be a two element array containing a source and desination pathname. Overrides sourcePath.", + "default": [] + }, + "debuggerRoot": { + "type": "string", + "description": "Specify a working directory to set the debug adaptor to so relative object files can be located." + }, + "initCommands": { + "type": "array", + "description": "Initialization commands executed upon debugger startup.", + "default": [] + }, + "preRunCommands": { + "type": "array", + "description": "Commands executed just before the program is launched.", + "default": [] + }, + "stopCommands": { + "type": "array", + "description": "Commands executed each time the program stops.", + "default": [] + }, + "exitCommands": { + "type": "array", + "description": "Commands executed at the end of debugging session.", + "default": [] + } + } + }, + "attach": { + "properties": { + "program": { + "type": "string", + "description": "Path to the program to attach to." + }, + "pid": { + "type": [ + "number", + "string" + ], + "description": "System process ID to attach to." + }, + "waitFor": { + "type": "boolean", + "description": "If set to true, then wait for the process to launch by looking for a process with a basename that matches `program`. No process ID needs to be specified when using this flag.", + "default": true + }, + "trace": { + "type": "boolean", + "description": "Enable logging of the Debug Adapter Protocol.", + "default": true + }, + "sourcePath": { + "type": "string", + "description": "Specify a source path to remap \"./\" to allow full paths to be used when setting breakpoints in binaries that have relative source paths." + }, + "sourceMap": { + "type": "array", + "description": "Specify an array of path remappings; each element must itself be a two element array containing a source and desination pathname. Overrides sourcePath.", + "default": [] + }, + "debuggerRoot": { + "type": "string", + "description": "Specify a working directory to set the debug adaptor to so relative object files can be located." + }, + "attachCommands": { + "type": "array", + "description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail.", + "default": [] + }, + "initCommands": { + "type": "array", + "description": "Initialization commands executed upon debugger startup.", + "default": [] + }, + "preRunCommands": { + "type": "array", + "description": "Commands executed just before the program is attached to.", + "default": [] + }, + "stopCommands": { + "type": "array", + "description": "Commands executed each time the program stops.", + "default": [] + }, + "exitCommands": { + "type": "array", + "description": "Commands executed at the end of debugging session.", + "default": [] + } + } + } + }, + "initialConfigurations": [ + { + "type": "lldb-vscode", + "request": "launch", + "name": "Debug", + "program": "${workspaceRoot}/<your program>", + "args": [], + "env": [], + "cwd": "${workspaceRoot}" + } + ], + "configurationSnippets": [ + { + "label": "LLDB: Launch", + "description": "", + "body": { + "type": "lldb-vscode", + "request": "launch", + "name": "${2:Launch}", + "program": "^\"\\${workspaceRoot}/${1:<your program>}\"", + "args": [], + "env": [], + "cwd": "^\"\\${workspaceRoot}\"" + } + } + ] + } + ] + } +} |