Index: lldb.xcodeproj/project.pbxproj =================================================================== --- lldb.xcodeproj/project.pbxproj +++ lldb.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ AF90106415AB7D2900FF120D /* CopyFiles */, ); dependencies = ( + 26B5A86F2118E89200533A9D /* PBXTargetDependency */, 26B391F11A6DCCBE00456239 /* PBXTargetDependency */, 26CEF3B014FD591F007286B2 /* PBXTargetDependency */, 2687EACD1508115900DD8C2E /* PBXTargetDependency */, @@ -28,6 +29,7 @@ AF90106415AB7D2900FF120D /* CopyFiles */, ); dependencies = ( + 261E83A02118EA3A003A54E3 /* PBXTargetDependency */, 26CEF3BB14FD595B007286B2 /* PBXTargetDependency */, 26B391EF1A6DCCAF00456239 /* PBXTargetDependency */, 2687EACB1508115000DD8C2E /* PBXTargetDependency */, @@ -65,7 +67,6 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 228B1B672113340200E61C70 /* ClangHighlighter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58A080AB2112AABB00D5580F /* ClangHighlighter.cpp */; }; 268900E813353E6F00698AC0 /* ABI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 497E7B9D1188F6690065CCA1 /* ABI.cpp */; }; 26DB3E161379E7AD0080DC73 /* ABIMacOSX_arm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26DB3E071379E7AD0080DC73 /* ABIMacOSX_arm.cpp */; }; 26DB3E191379E7AD0080DC73 /* ABIMacOSX_arm64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26DB3E0B1379E7AD0080DC73 /* ABIMacOSX_arm64.cpp */; }; @@ -159,8 +160,7 @@ 268900D313353E6F00698AC0 /* ClangExternalASTSourceCallbacks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26E69030129C6BEF00DDECD9 /* ClangExternalASTSourceCallbacks.cpp */; }; 4966DCC4148978A10028481B /* ClangExternalASTSourceCommon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4966DCC3148978A10028481B /* ClangExternalASTSourceCommon.cpp */; }; 2689005F13353E0E00698AC0 /* ClangFunctionCaller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C98D3DA118FB96F00E575D0 /* ClangFunctionCaller.cpp */; }; - 58A080AC2112AABB00D5580F /* ClangHighlighter.cpp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 58A080AB2112AABB00D5580F /* ClangHighlighter.cpp */; }; - 58A080AE2112AAC500D5580F /* ClangHighlighter.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 58A080AD2112AAC500D5580F /* ClangHighlighter.h */; }; + 228B1B672113340200E61C70 /* ClangHighlighter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58A080AB2112AABB00D5580F /* ClangHighlighter.cpp */; }; 4CD44D5820C603CB0003557C /* ClangHost.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CD44D5620C603A80003557C /* ClangHost.cpp */; }; 4959511F1A1BC4BC00F6F8FC /* ClangModulesDeclVendor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4959511E1A1BC4BC00F6F8FC /* ClangModulesDeclVendor.cpp */; }; 2689006313353E0E00698AC0 /* ClangPersistentVariables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 49D4FE871210B61C00CDB854 /* ClangPersistentVariables.cpp */; }; @@ -370,6 +370,7 @@ 26BC179918C7F2B300D2196D /* JITLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC179718C7F2B300D2196D /* JITLoader.cpp */; }; AF2BCA6C18C7EFDE005B4526 /* JITLoaderGDB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF2BCA6918C7EFDE005B4526 /* JITLoaderGDB.cpp */; }; 26BC179A18C7F2B300D2196D /* JITLoaderList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC179818C7F2B300D2196D /* JITLoaderList.cpp */; }; + 26B5A8692118CE1800533A9D /* JSON.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26B5A8682118CE1800533A9D /* JSON.cpp */; }; 942829561A89614C00521B30 /* JSON.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 942829551A89614C00521B30 /* JSON.cpp */; }; 8C3BD9A01EF5D1FF0016C343 /* JSONTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C3BD99F1EF5D1B50016C343 /* JSONTest.cpp */; }; 6D0F61431C80AAAE00A4ECEE /* JavaASTContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D0F61411C80AAAA00A4ECEE /* JavaASTContext.cpp */; }; @@ -379,6 +380,7 @@ 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 */; }; + 26B5A86A2118CE2C00533A9D /* 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 */; }; @@ -865,6 +867,7 @@ 9A3D43ED1F3237F900EB767C /* StateTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A3D43E21F3237D500EB767C /* StateTest.cpp */; }; 492DB7EB1EC662E200B9E9AF /* Status.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 492DB7E81EC662D100B9E9AF /* Status.cpp */; }; 9A3D43D91F3151C400EB767C /* StatusTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A3D43C61F3150D200EB767C /* StatusTest.cpp */; }; + 26B5A8672118CDFA00533A9D /* StdStringExtractor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26B5A8662118CDFA00533A9D /* StdStringExtractor.cpp */; }; 268900F613353E6F00698AC0 /* StopInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2615DB861208A9E40021781D /* StopInfo.cpp */; }; 268900B513353E5000698AC0 /* StopInfoMachException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2615DBC81208B5FC0021781D /* StopInfoMachException.cpp */; }; 2689000B13353DB600698AC0 /* Stoppoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26BC7E1610F1B83100F91463 /* Stoppoint.cpp */; }; @@ -878,6 +881,7 @@ AFC2DCF91E6E318000283714 /* StreamGDBRemote.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AFC2DCF81E6E318000283714 /* StreamGDBRemote.cpp */; }; 26764CA21E48F547008D3573 /* StreamString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26764CA11E48F547008D3573 /* StreamString.cpp */; }; 58EAC73F2106A07B0029571E /* StreamTeeTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58EAC73D2106A0740029571E /* StreamTeeTest.cpp */; }; + 26B5A86D2118E86700533A9D /* StringConvert.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33E5E8411A672A240024ED68 /* StringConvert.cpp */; }; 33E5E8471A674FB60024ED68 /* StringConvert.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33E5E8411A672A240024ED68 /* StringConvert.cpp */; }; 2689011113353E8200698AC0 /* StringExtractor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2660D9F611922A1300958FBD /* StringExtractor.cpp */; }; 2689011213353E8200698AC0 /* StringExtractorGDBRemote.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2676A093119C93C8008A98EF /* StringExtractorGDBRemote.cpp */; }; @@ -1073,6 +1077,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, ); }; }; + 26B5A8622118CDAB00533A9D /* lldb-vscode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26B5A8612118CDAB00533A9D /* 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 */; }; @@ -1101,6 +1106,13 @@ remoteGlobalIDString = 23CB152F1D66DA9300EDDDE1; remoteInfo = "lldb-gtest-for-debugging"; }; + 261E839F2118EA3A003A54E3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 26B5A8532118CD7B00533A9D; + remoteInfo = "lldb-vscode"; + }; 262CFC7111A450CB00946C6C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 265E9BE1115C2BAA00D0DCCB /* debugserver.xcodeproj */; @@ -1164,6 +1176,20 @@ remoteGlobalIDString = 2690CD161A6DC0D000E717C8; remoteInfo = "lldb-mi"; }; + 26B5A86B2118CE3200533A9D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 26680206115FD0ED008E1FE4; + remoteInfo = LLDB; + }; + 26B5A86E2118E89200533A9D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 26B5A8532118CD7B00533A9D; + remoteInfo = "lldb-vscode"; + }; 26CE059F115C31E50022F371 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 265E9BE1115C2BAA00D0DCCB /* debugserver.xcodeproj */; @@ -1263,6 +1289,15 @@ name = "Copy Files"; runOnlyForDeploymentPostprocessing = 1; }; + 26B5A8522118CD7B00533A9D /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 940B04E31A89875C0045D5F7 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1496,6 +1531,7 @@ 23E2E5191D9036F2006F38BB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 23E2E5361D9048FB006F38BB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 2669415B1A6DC2AB0063BE93 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CMakeLists.txt; path = "tools/lldb-mi/CMakeLists.txt"; sourceTree = SOURCE_ROOT; }; + 26B5A85F2118CDAB00533A9D /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CMakeLists.txt; path = "tools/lldb-vscode/CMakeLists.txt"; sourceTree = ""; }; 9A1890311F47D5D400394BCA /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CMakeLists.txt; path = TestingSupport/CMakeLists.txt; sourceTree = ""; }; 4CB443BC1249920C00C13DC2 /* CPPLanguageRuntime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPPLanguageRuntime.cpp; path = source/Target/CPPLanguageRuntime.cpp; sourceTree = ""; }; 4CB443BB1249920C00C13DC2 /* CPPLanguageRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CPPLanguageRuntime.h; path = include/lldb/Target/CPPLanguageRuntime.h; sourceTree = ""; }; @@ -1976,6 +2012,7 @@ AF2BCA6A18C7EFDE005B4526 /* JITLoaderGDB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JITLoaderGDB.h; sourceTree = ""; }; 26BC179818C7F2B300D2196D /* JITLoaderList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JITLoaderList.cpp; path = source/Target/JITLoaderList.cpp; sourceTree = ""; }; 26BC179C18C7F2CB00D2196D /* JITLoaderList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JITLoaderList.h; path = include/lldb/Target/JITLoaderList.h; sourceTree = ""; }; + 26B5A8682118CE1800533A9D /* JSON.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JSON.cpp; path = tools/debugserver/source/JSON.cpp; sourceTree = ""; }; 942829551A89614C00521B30 /* JSON.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JSON.cpp; path = source/Utility/JSON.cpp; sourceTree = ""; }; 942829541A89614000521B30 /* JSON.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JSON.h; path = include/lldb/Utility/JSON.h; sourceTree = ""; }; 8C3BD99F1EF5D1B50016C343 /* JSONTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSONTest.cpp; sourceTree = ""; }; @@ -2879,6 +2916,7 @@ 492DB7E81EC662D100B9E9AF /* Status.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Status.cpp; path = source/Utility/Status.cpp; sourceTree = ""; }; 492DB7E61EC662B100B9E9AF /* Status.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Status.h; path = include/lldb/Utility/Status.h; sourceTree = ""; }; 9A3D43C61F3150D200EB767C /* StatusTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StatusTest.cpp; sourceTree = ""; }; + 26B5A8662118CDFA00533A9D /* StdStringExtractor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StdStringExtractor.cpp; path = tools/debugserver/source/StdStringExtractor.cpp; sourceTree = ""; }; 2615DB861208A9E40021781D /* StopInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StopInfo.cpp; path = source/Target/StopInfo.cpp; sourceTree = ""; }; 2615DB841208A9C90021781D /* StopInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StopInfo.h; path = include/lldb/Target/StopInfo.h; sourceTree = ""; }; 2615DBC81208B5FC0021781D /* StopInfoMachException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StopInfoMachException.cpp; path = Utility/StopInfoMachException.cpp; sourceTree = ""; }; @@ -3248,11 +3286,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 = ""; }; 26BC7C2910F1B3BC00F91463 /* lldb-types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "lldb-types.h"; path = "include/lldb/lldb-types.h"; sourceTree = ""; }; 94145430175D7FDE00284436 /* lldb-versioning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "lldb-versioning.h"; path = "include/lldb/lldb-versioning.h"; sourceTree = ""; }; + 26B5A8542118CD7B00533A9D /* lldb-vscode */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "lldb-vscode"; sourceTree = BUILT_PRODUCTS_DIR; }; + 26B5A8602118CDAB00533A9D /* 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 = ""; }; + 26B5A8612118CDAB00533A9D /* lldb-vscode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "lldb-vscode.cpp"; path = "tools/lldb-vscode/lldb-vscode.cpp"; sourceTree = ""; }; 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 = ""; }; AF90106315AB7C5700FF120D /* lldb.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = lldb.1; path = docs/lldb.1; sourceTree = ""; }; 26BC7E7410F1B85900F91463 /* lldb.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lldb.cpp; path = source/lldb.cpp; sourceTree = ""; }; 2669605E1199F4230075C61A /* lldb.swig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = lldb.swig; sourceTree = ""; }; 94E367CC140C4EC4001C7A5A /* modify-python-lldb.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = "modify-python-lldb.py"; sourceTree = ""; }; + 26B5A85E2118CDAB00533A9D /* package.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = package.json; path = "tools/lldb-vscode/package.json"; sourceTree = ""; }; 9A48A3A7124AAA5A00922451 /* python-extensions.swig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "python-extensions.swig"; sourceTree = ""; }; 944DC3481774C99000D7D884 /* python-swigsafecast.swig */ = {isa = PBXFileReference; lastKnownFileType = text; path = "python-swigsafecast.swig"; sourceTree = ""; }; 94E367CE140C4EEA001C7A5A /* python-typemaps.swig */ = {isa = PBXFileReference; lastKnownFileType = text; path = "python-typemaps.swig"; sourceTree = ""; }; @@ -3336,6 +3378,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 26B5A8512118CD7B00533A9D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 26B5A86A2118CE2C00533A9D /* LLDB.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 26DC6A0E1337FE6900FF7998 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -3435,6 +3485,7 @@ 942829C01A89835300521B30 /* lldb-argdumper */, 239504D41BDD451400963CEA /* lldb-gtest */, 23CB15561D66DA9300EDDDE1 /* lldb-gtest */, + 26B5A8542118CD7B00533A9D /* lldb-vscode */, ); name = Products; sourceTree = ""; @@ -4939,6 +4990,19 @@ name = Utility; sourceTree = ""; }; + 26B5A85D2118CD9000533A9D /* lldb-vscode */ = { + isa = PBXGroup; + children = ( + 26B5A85F2118CDAB00533A9D /* CMakeLists.txt */, + 26B5A8602118CDAB00533A9D /* lldb-vscode-Info.plist */, + 26B5A8612118CDAB00533A9D /* lldb-vscode.cpp */, + 26B5A8662118CDFA00533A9D /* StdStringExtractor.cpp */, + 26B5A8682118CE1800533A9D /* JSON.cpp */, + 26B5A85E2118CDAB00533A9D /* package.json */, + ); + name = "lldb-vscode"; + sourceTree = ""; + }; 26BC179F18C7F4CB00D2196D /* elf-core */ = { isa = PBXGroup; children = ( @@ -5847,6 +5911,7 @@ 265E9BE0115C2B8500D0DCCB /* debugserver */, 26F5C22510F3D956009D5894 /* Driver */, 2665CD0915080846002C8FAE /* install-headers */, + 26B5A85D2118CD9000533A9D /* lldb-vscode */, ); name = Tools; sourceTree = ""; @@ -7129,6 +7194,24 @@ productReference = 2690CD171A6DC0D000E717C8 /* lldb-mi */; productType = "com.apple.product-type.tool"; }; + 26B5A8532118CD7B00533A9D /* lldb-vscode */ = { + isa = PBXNativeTarget; + buildConfigurationList = 26B5A85C2118CD7B00533A9D /* Build configuration list for PBXNativeTarget "lldb-vscode" */; + buildPhases = ( + 26B5A8502118CD7B00533A9D /* Sources */, + 26B5A8512118CD7B00533A9D /* Frameworks */, + 26B5A8522118CD7B00533A9D /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 26B5A86C2118CE3200533A9D /* PBXTargetDependency */, + ); + name = "lldb-vscode"; + productName = "lldb-vscode"; + productReference = 26B5A8542118CD7B00533A9D /* lldb-vscode */; + productType = "com.apple.product-type.tool"; + }; 26DC6A0F1337FE6900FF7998 /* lldb-server */ = { isa = PBXNativeTarget; buildConfigurationList = 26DC6A1A1337FE8B00FF7998 /* Build configuration list for PBXNativeTarget "lldb-server" */; @@ -7201,6 +7284,10 @@ 2690CD161A6DC0D000E717C8 = { CreatedOnToolsVersion = 6.3; }; + 26B5A8532118CD7B00533A9D = { + CreatedOnToolsVersion = 9.4.1; + ProvisioningStyle = Automatic; + }; 942829BF1A89835300521B30 = { CreatedOnToolsVersion = 7.0; }; @@ -7238,6 +7325,7 @@ 2687EAC51508110B00DD8C2E /* install-headers */, 2690CD161A6DC0D000E717C8 /* lldb-mi */, 942829BF1A89835300521B30 /* lldb-argdumper */, + 26B5A8532118CD7B00533A9D /* lldb-vscode */, ); }; /* End PBXProject section */ @@ -8323,6 +8411,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 26B5A8502118CD7B00533A9D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 26B5A8692118CE1800533A9D /* JSON.cpp in Sources */, + 26B5A8622118CDAB00533A9D /* lldb-vscode.cpp in Sources */, + 26B5A86D2118E86700533A9D /* StringConvert.cpp in Sources */, + 26B5A8672118CDFA00533A9D /* StdStringExtractor.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 26DC6A0D1337FE6900FF7998 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -8363,6 +8462,11 @@ target = 23CB152F1D66DA9300EDDDE1 /* lldb-gtest-build */; targetProxy = 23E2E5471D904D72006F38BB /* PBXContainerItemProxy */; }; + 261E83A02118EA3A003A54E3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 26B5A8532118CD7B00533A9D /* lldb-vscode */; + targetProxy = 261E839F2118EA3A003A54E3 /* PBXContainerItemProxy */; + }; 262CFC7211A450CB00946C6C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = debugserver; @@ -8408,6 +8512,16 @@ target = 2690CD161A6DC0D000E717C8 /* lldb-mi */; targetProxy = 26B391F01A6DCCBE00456239 /* PBXContainerItemProxy */; }; + 26B5A86C2118CE3200533A9D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 26680206115FD0ED008E1FE4 /* LLDB */; + targetProxy = 26B5A86B2118CE3200533A9D /* PBXContainerItemProxy */; + }; + 26B5A86F2118E89200533A9D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 26B5A8532118CD7B00533A9D /* lldb-vscode */; + targetProxy = 26B5A86E2118E89200533A9D /* PBXContainerItemProxy */; + }; 26CEF3B014FD591F007286B2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 26F5C26910F3D9A4009D5894 /* lldb-tool */; @@ -9861,6 +9975,192 @@ }; name = BuildAndIntegration; }; + 26B5A8582118CD7B00533A9D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "tools/lldb-vscode/lldb-vscode-Info.plist"; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = "-Wl,-rpath,@loader_path/"; + 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"; + }; + name = Debug; + }; + 26B5A8592118CD7B00533A9D /* DebugClang */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "tools/lldb-vscode/lldb-vscode-Info.plist"; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = "-Wl,-rpath,@loader_path/"; + 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"; + }; + name = DebugClang; + }; + 26B5A85A2118CD7B00533A9D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "tools/lldb-vscode/lldb-vscode-Info.plist"; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = "-Wl,-rpath,@loader_path/"; + 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"; + }; + name = Release; + }; + 26B5A85B2118CD7B00533A9D /* BuildAndIntegration */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "tools/lldb-vscode/lldb-vscode-Info.plist"; + INSTALL_PATH = "$(LLDB_TOOLS_INSTALL_DIR)"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + "OTHER_LDFLAGS[sdk=iphoneos*]" = "-Wl,-rpath,@loader_path/../../../System/Library/PrivateFrameworks"; + "OTHER_LDFLAGS[sdk=macosx*]" = ( + "-Wl,-rpath,@loader_path/../../Library/PrivateFrameworks/", + "-Wl,-rpath,@loader_path/../../../SharedFrameworks", + "-Wl,-rpath,@loader_path/../../System/Library/PrivateFrameworks", + "-Wl,-rpath,@loader_path/../../Library/PrivateFrameworks", + ); + 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"; + }; + name = BuildAndIntegration; + }; 26CEF3AA14FD58BF007286B2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -10993,6 +11293,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = BuildAndIntegration; }; + 26B5A85C2118CD7B00533A9D /* Build configuration list for PBXNativeTarget "lldb-vscode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 26B5A8582118CD7B00533A9D /* Debug */, + 26B5A8592118CD7B00533A9D /* DebugClang */, + 26B5A85A2118CD7B00533A9D /* Release */, + 26B5A85B2118CD7B00533A9D /* BuildAndIntegration */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = BuildAndIntegration; + }; 26CEF3AD14FD58BF007286B2 /* Build configuration list for PBXAggregateTarget "desktop_no_xpc" */ = { isa = XCConfigurationList; buildConfigurations = ( Index: packages/Python/lldbsuite/test/dotest.py =================================================================== --- packages/Python/lldbsuite/test/dotest.py +++ packages/Python/lldbsuite/test/dotest.py @@ -646,6 +646,7 @@ 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 @@ # 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 @@ "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") Index: packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- packages/Python/lldbsuite/test/lldbtest.py +++ packages/Python/lldbsuite/test/lldbtest.py @@ -740,6 +740,11 @@ 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"]: Index: packages/Python/lldbsuite/test/tools/lldb-vscode/.categories =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/.categories +++ packages/Python/lldbsuite/test/tools/lldb-vscode/.categories @@ -0,0 +1 @@ +lldb-vscode Index: packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile +++ packages/Python/lldbsuite/test/tools/lldb-vscode/attach/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/attach/TestVSCode_attach.py @@ -0,0 +1,173 @@ +""" +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() + + + @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) + + @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) + # print('program is %s' % (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) + + @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) + + @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) Index: packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c +++ packages/Python/lldbsuite/test/tools/lldb-vscode/attach/main.c @@ -0,0 +1,8 @@ +#include +#include + +int main(int argc, char const *argv[]) { + printf("pid = %i\n", getpid()); + pause(); + return 0; // breakpoint 1 +} Index: packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile +++ packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py @@ -0,0 +1,207 @@ +""" +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__) + + @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") + + @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') Index: packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setExceptionBreakpoints.py @@ -0,0 +1,49 @@ +""" +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__) + + @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') Index: packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/TestVSCode_setFunctionBreakpoints.py @@ -0,0 +1,162 @@ +""" +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__) + + @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))) + + @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') Index: packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp +++ packages/Python/lldbsuite/test/tools/lldb-vscode/breakpoint/main.cpp @@ -0,0 +1,27 @@ +#include +#include + +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; +} Index: packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile +++ packages/Python/lldbsuite/test/tools/lldb-vscode/launch/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/launch/TestVSCode_launch.py @@ -0,0 +1,318 @@ +""" +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__) + + @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") + + @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') + + @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") + + @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() + + @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() + + @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") + + @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)) + + @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)) + + @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): + self.assertTrue(arg in lines[i], + 'arg[%i] "%s" not in "%s"' % (i+1, arg, lines[i])) + + @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)) + + @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) Index: packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c +++ packages/Python/lldbsuite/test/tools/lldb-vscode/launch/main.c @@ -0,0 +1,15 @@ +#include +#include +#include + +int main(int argc, char const *argv[], char const *envp[]) { + for (int i=0; i> 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'])) Index: packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile +++ packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/TestVSCode_stackTrace.py @@ -0,0 +1,158 @@ +""" +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)) + + @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') Index: packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c +++ packages/Python/lldbsuite/test/tools/lldb-vscode/stackTrace/main.c @@ -0,0 +1,13 @@ +#include +#include + +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; +} Index: packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile +++ packages/Python/lldbsuite/test/tools/lldb-vscode/step/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/step/TestVSCode_step.py @@ -0,0 +1,77 @@ +""" +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__) + + @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 Index: packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp +++ packages/Python/lldbsuite/test/tools/lldb-vscode/step/main.cpp @@ -0,0 +1,16 @@ +#include + +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; +} Index: packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile +++ packages/Python/lldbsuite/test/tools/lldb-vscode/variables/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py +++ packages/Python/lldbsuite/test/tools/lldb-vscode/variables/TestVSCode_variables.py @@ -0,0 +1,223 @@ +""" +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) + + @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)) Index: packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-vscode/variables/main.cpp +++ 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= 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 ' + ':. ' + '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() Index: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -5,6 +5,7 @@ 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() Index: tools/debugserver/source/JSON.h =================================================================== --- tools/debugserver/source/JSON.h +++ tools/debugserver/source/JSON.h @@ -63,7 +63,7 @@ ~JSONString() override = default; private: - static std::string json_string_quote_metachars(const std::string &); + static std::string encode_string(const std::string &); std::string m_data; }; @@ -185,6 +185,41 @@ ~JSONNull() override = default; }; +class JSONArray : public JSONValue { +public: + JSONArray(); + + JSONArray(const JSONArray &s) = delete; + JSONArray &operator=(const JSONArray &s) = delete; + + void Write(std::ostream &s) override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) { + return V->GetKind() == JSONValue::Kind::Array; + } + +private: + typedef std::vector Vector; + typedef Vector::iterator Iterator; + typedef Vector::size_type Index; + typedef Vector::size_type Size; + +public: + bool SetObject(Index i, JSONValue::SP value); + + bool AppendObject(JSONValue::SP value); + + JSONValue::SP GetObject(Index i) const; + + Size GetNumElements() const; + + ~JSONArray() override = default; + + Vector m_elements; +}; + class JSONObject : public JSONValue { public: JSONObject(); @@ -204,6 +239,43 @@ JSONValue::SP GetObject(const std::string &key) const; + bool SetObject(const std::string &key, bool b) { + if (b) + return SetObject(key, JSONValue::SP(new JSONTrue())); + else + return SetObject(key, JSONValue::SP(new JSONFalse())); + } + bool SetObject(const std::string &key, const char *s) { + return SetObject(key, JSONValue::SP(new JSONString(s))); + } + bool SetObject(const std::string &key, const std::string &s) { + return SetObject(key, JSONValue::SP(new JSONString(s))); + } + + template ::value && + std::is_unsigned::value>::type * = nullptr> + bool SetObject(const std::string &key, T n) { + return SetObject(key, JSONValue::SP(new JSONNumber(n))); + } + + template ::value && + std::is_signed::value>::type * = nullptr> + bool SetObject(const std::string &key, T n) { + return SetObject(key, JSONValue::SP(new JSONNumber(n))); + } + + template ::value>::type * = nullptr> + bool SetObject(const std::string &key, T n) { + return SetObject(key, JSONValue::SP(new JSONNumber(n))); + } + + bool ContainsObject(const std::string &key) const { + return m_elements.find(key) != m_elements.end(); + } + // ------------------------------------------------------------------------- /// Return keyed value as bool /// @@ -223,6 +295,25 @@ bool GetObjectAsString(const std::string &key, std::string &value) const; + // ------------------------------------------------------------------------- + /// Return keyed value as bool + /// + /// @param[in] key + /// The value of the key to lookup + /// + /// @param[in] fail_value + /// If the value isn't a bool, then return this fail value + /// + /// @return + /// true or false if the key was a boolean value, fail_value otherwise + // ------------------------------------------------------------------------- + bool GetBoolean(const std::string &key, bool fail_value) const; + int64_t GetInteger(const std::string &key, int64_t fail_value) const; + uint64_t GetUnsigned(const std::string &key, uint64_t fail_value) const; + std::string GetString(const std::string &key) const; + JSONArray::SP GetAsArray(const std::string &key) const; + JSONObject::SP GetAsObject(const std::string &key) const; + ~JSONObject() override = default; private: @@ -231,41 +322,15 @@ Map m_elements; }; -class JSONArray : public JSONValue { -public: - JSONArray(); +// Extract values from JSONValue objects. +JSONObject::SP GetAsObject(const JSONValue::SP &value); +JSONArray::SP GetAsArray(const JSONValue::SP &value); +bool GetBooleanValue(const JSONValue::SP &value, bool fail_value); +int64_t GetIntegerValue(const JSONValue::SP &value, int64_t fail_value); +uint64_t GetUnsignedValue(const JSONValue::SP &value, uint64_t fail_value); +std::string GetStringValue(const JSONValue::SP &value); - JSONArray(const JSONArray &s) = delete; - JSONArray &operator=(const JSONArray &s) = delete; - void Write(std::ostream &s) override; - - typedef std::shared_ptr SP; - - static bool classof(const JSONValue *V) { - return V->GetKind() == JSONValue::Kind::Array; - } - -private: - typedef std::vector Vector; - typedef Vector::iterator Iterator; - typedef Vector::size_type Index; - typedef Vector::size_type Size; - -public: - bool SetObject(Index i, JSONValue::SP value); - - bool AppendObject(JSONValue::SP value); - - JSONValue::SP GetObject(Index i); - - Size GetNumElements(); - - ~JSONArray() override = default; - - Vector m_elements; -}; - class JSONParser : public StdStringExtractor { public: enum Token { @@ -292,7 +357,7 @@ Token GetToken(std::string &value); - JSONValue::SP ParseJSONValue(); + JSONValue::SP ParseJSONValue(Token *error_token = nullptr); protected: JSONValue::SP ParseJSONObject(); Index: tools/debugserver/source/JSON.cpp =================================================================== --- tools/debugserver/source/JSON.cpp +++ tools/debugserver/source/JSON.cpp @@ -15,24 +15,35 @@ // C++ includes #include "lldb/Host/StringConvert.h" +#include #include #include using namespace lldb_private; -std::string JSONString::json_string_quote_metachars(const std::string &s) { - if (s.find('"') == std::string::npos) - return s; - +std::string JSONString::encode_string(const std::string &s) { std::string output; - const size_t s_size = s.size(); - const char *s_chars = s.c_str(); - for (size_t i = 0; i < s_size; i++) { - unsigned char ch = *(s_chars + i); - if (ch == '"') { - output.push_back('\\'); + output.reserve(s.size()); + for (size_t i = 0; i < s.size(); ++i) { + uint8_t ch = s[i]; + switch (ch) { + case '"': output.append("\\\""); break; + case '\\': output.append("\\\\"); break; + case '\b': output.append("\\b"); break; + case '\f': output.append("\\f"); break; + case '\n': output.append("\\n"); break; + case '\r': output.append("\\r"); break; + case '\t': output.append("\\t"); break; + default: + if (ch < 0x20 || ch == 0x7f) { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "\\u%04x", ch); + output.append(buffer); + } else { + output.append(1, ch); + } + break; } - output.push_back(ch); } return output; } @@ -46,7 +57,7 @@ : JSONValue(JSONValue::Kind::String), m_data(s) {} void JSONString::Write(std::ostream &s) { - s << "\"" << json_string_quote_metachars(m_data).c_str() << "\""; + s << "\"" << encode_string(m_data).c_str() << "\""; } uint64_t JSONNumber::GetAsUnsigned() const { @@ -58,6 +69,7 @@ case DataType::Double: return (uint64_t)m_data.m_double; } + return 0; } int64_t JSONNumber::GetAsSigned() const { @@ -69,6 +81,7 @@ case DataType::Double: return (int64_t)m_data.m_double; } + return 0; } double JSONNumber::GetAsDouble() const { @@ -80,6 +93,7 @@ case DataType::Double: return m_data.m_double; } + return 0.0; } void JSONNumber::Write(std::ostream &s) { @@ -138,12 +152,38 @@ } JSONValue::SP JSONObject::GetObject(const std::string &key) const { - auto iter = m_elements.find(key), end = m_elements.end(); - if (iter == end) + auto iter = m_elements.find(key); + if (iter == m_elements.end()) return JSONValue::SP(); return iter->second; } +JSONObject::SP JSONObject::GetAsObject(const std::string &key) const { + return ::GetAsObject(GetObject(key)); +} + +JSONArray::SP JSONObject::GetAsArray(const std::string &key) const { + return ::GetAsArray(GetObject(key)); +} + +bool JSONObject::GetBoolean(const std::string &key, bool fail_value) const { + return GetBooleanValue(GetObject(key), fail_value); +} + +int64_t +JSONObject::GetInteger(const std::string &key, int64_t fail_value) const { + return GetIntegerValue(GetObject(key), fail_value); +} + +uint64_t +JSONObject::GetUnsigned(const std::string &key, uint64_t fail_value) const { + return GetUnsignedValue(GetObject(key), fail_value); +} + +std::string JSONObject::GetString(const std::string &key) const { + return GetStringValue(GetObject(key)); +} + bool JSONObject::GetObjectAsBool(const std::string &key, bool &value) const { auto value_sp = GetObject(key); if (!value_sp) { @@ -217,13 +257,13 @@ return true; } -JSONValue::SP JSONArray::GetObject(Index i) { +JSONValue::SP JSONArray::GetObject(Index i) const { if (i < m_elements.size()) return m_elements[i]; return JSONValue::SP(); } -JSONArray::Size JSONArray::GetNumElements() { return m_elements.size(); } +JSONArray::Size JSONArray::GetNumElements() const { return m_elements.size(); } JSONParser::JSONParser(const char *cstr) : StdStringExtractor(cstr) {} @@ -517,7 +557,19 @@ std::string value; std::string key; while (1) { - JSONValue::SP value_sp = ParseJSONValue(); + // We pass "error_token" into the ParseJSONValue in case we get an + // ArrayEnd instead of a value. ParseJSONValue will fail to return an + // valid value in that case and it will fill in "error_token" with the + // token that caused the error. + JSONParser::Token error_token = JSONParser::Token::Invalid; + JSONValue::SP value_sp = ParseJSONValue(&error_token); + if (error_token != JSONParser::Token::Invalid) { + if (array_up->GetNumElements() == 0 && + error_token == JSONParser::Token::ArrayEnd) + return JSONValue::SP(array_up.release()); // Empty array is ok + else + break; + } if (value_sp) array_up->AppendObject(value_sp); else @@ -535,7 +587,7 @@ return JSONValue::SP(); } -JSONValue::SP JSONParser::ParseJSONValue() { +JSONValue::SP JSONParser::ParseJSONValue(JSONParser::Token *error_token) { std::string value; const JSONParser::Token token = GetToken(value); switch (token) { @@ -579,7 +631,64 @@ return JSONValue::SP(new JSONNull()); default: + if (error_token) + *error_token = token; break; } return JSONValue::SP(); } + +JSONObject::SP GetAsObject(const JSONValue::SP &value) { + if (value && value->GetKind() == JSONValue::Kind::Object) + return std::static_pointer_cast(value); + return JSONObject::SP(); +} + +JSONArray::SP GetAsArray(const JSONValue::SP &value) { + if (value && value->GetKind() == JSONValue::Kind::Array) + return std::static_pointer_cast(value); + return JSONArray::SP(); +} + +bool GetBooleanValue(const JSONValue::SP &value, bool fail_value) { + if (value) { + switch (value->GetKind()) { + case JSONValue::Kind::True: return true; + case JSONValue::Kind::False: return false; + default: break; + } + } + return fail_value; +} + +int64_t GetIntegerValue(const JSONValue::SP &value, int64_t fail_value) { + if (value) { + switch (value->GetKind()) { + case JSONValue::Kind::False: return 0; + case JSONValue::Kind::True: return 1; + case JSONValue::Kind::Number: + return static_cast(value.get())->GetAsSigned(); + default: break; + } + } + return fail_value; +} + +uint64_t GetUnsignedValue(const JSONValue::SP &value, uint64_t fail_value) { + if (value) { + switch (value->GetKind()) { + case JSONValue::Kind::False: return 0; + case JSONValue::Kind::True: return 1; + case JSONValue::Kind::Number: + return static_cast(value.get())->GetAsUnsigned(); + default: break; + } + } + return fail_value; +} + +std::string GetStringValue(const JSONValue::SP &value) { + if (value && value->GetKind() == JSONValue::Kind::String) + return static_cast(value.get())->GetData(); + return std::string(); +} Index: tools/lldb-vscode/CMakeLists.txt =================================================================== --- tools/lldb-vscode/CMakeLists.txt +++ tools/lldb-vscode/CMakeLists.txt @@ -0,0 +1,26 @@ +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 + ../../source/Host/common/StringConvert.cpp + ../../tools/debugserver/source/JSON.cpp + ../../tools/debugserver/source/StdStringExtractor.cpp + + LINK_LIBS + liblldb + ${host_lib} + ${extra_libs} + + LINK_COMPONENTS + Support + ) Index: tools/lldb-vscode/lldb-vscode-Info.plist =================================================================== --- tools/lldb-vscode/lldb-vscode-Info.plist +++ tools/lldb-vscode/lldb-vscode-Info.plist @@ -0,0 +1,21 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.lldb-vscode + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + lldb-vscode + CFBundleVersion + 2 + SecTaskAccess + + allowed + debug + + + Index: tools/lldb-vscode/lldb-vscode.cpp =================================================================== --- tools/lldb-vscode/lldb-vscode.cpp +++ tools/lldb-vscode/lldb-vscode.cpp @@ -0,0 +1,4146 @@ +//===-- 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 "../../tools/debugserver/source/JSON.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#undef GetObject +#else +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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/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" + +#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 "" + +#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 + +typedef void (*RequestCallback)(const JSONObject::SP &command); + +#pragma mark -- Prototypes + +JSONValue::SP +create_source(lldb::SBLineEntry &line_entry); + +void +run_lldb_commands(const char *prefix, const std::vector &commands); + +uint64_t string_to_unsigned(const char *s, int base, uint64_t fail_value) { + if (s && s[0]) { + char *end = nullptr; + uint64_t uval = strtoull(s, &end, base); + if (*end == '\0') + return uval; + } + return fail_value; +} + +struct ExceptionBreakpoint { + std::string filter; + std::string label; + lldb::LanguageType language; + bool value; + lldb::SBBreakpoint bp; + ExceptionBreakpoint(const char *f, const char *l, lldb::LanguageType lang) : + filter(f), label(l), language(lang), value(false), bp() {} + + void SetBreakpoint(); + void ClearBreakpoint(); +}; + +bool +thread_has_stop_reason(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; +} + +enum OutputType { + Console, + Stdout, + Stderr, + Telemetry +}; + +void +send_output(OutputType o, const char *output, size_t output_len = SIZE_MAX); +void __attribute__((format(printf, 2, 3))) +send_formatted_output(OutputType o, const char *format, ...); + +static bool ChangeDir(const char *path) { +#if defined(_WIN32) + return SetCurrentDirectoryA(path); +#else + return chdir(path) == 0; +#endif +} + +typedef std::map ProgressMap; + +ProgressMap *GetProgressMap() { + // NOTE: we will purposely leak g_progress_map to avoid global C++ + // destructor chain issues in case any progress callbacks come in after the + // main thread exits. + static ProgressMap *g_progress_map = nullptr; + static std::once_flag s_once_flag; + std::call_once(s_once_flag, [] { g_progress_map = new ProgressMap(); }); + return g_progress_map; +} + +#define LONG_RUNNING_PROGRESS_SEC 1 + +void LLDBProgressCallback(const char *message, uint64_t completed, + uint64_t total, void *baton) { + static std::mutex g_mutex; + auto progress_map = GetProgressMap(); + if (completed == total) { + // We completed our progress task, see if it is in our map and remove it + if (total != 0) { + std::lock_guard locker(g_mutex); + auto pos = progress_map->find(message); + if (pos != progress_map->end()) { + // If pos->second is still false, then this completed before + // LONG_RUNNING_PROGRESS_SEC have passed and we never notified the + // debugger about this progress. If pos->second is true then we need to + // notify that the long running task has completed. + if (pos->second) + send_formatted_output(Console, "%s DONE\n", message); + progress_map->erase(pos); + } + } + } else if (completed == 0) { + // We are starting a new progress, so insert the "const char *" message. + // The "const char *" message will not change over the lifetime of the + // progress and it will never go away (it is a ConstString in LLDB) so + // we will keep track of this message as the progress key. + { + std::lock_guard locker(g_mutex); + progress_map->insert(std::make_pair(message, false)); + } + // Now spawn a thread that will sleep for some time, then check if the + // progress has not completed by looking for "message" in the map. If + // message is still in there, then we should display the progress message + // to the conosle to notify the user of a long running task. Else we just + // don't say anything because the task completed quickly. This stops us + // from showing many progress items in the debugger. + std::thread([progress_map, message]() { + // Sleep for LONG_RUNNING_PROGRESS_SEC seconds, then check if progress + // hasn't completed yet by checking to see if "messaage" is in the map. + // If "message" is stil in the map we have a long running operation and + // we will notify the debugger. + std::this_thread::sleep_for( + std::chrono::seconds(LONG_RUNNING_PROGRESS_SEC)); + std::lock_guard locker(g_mutex); + auto pos = progress_map->find(message); + if (pos != progress_map->end()) { + // Progress hasn't completed since it is still in the map after our + // long running timeout (LONG_RUNNING_PROGRESS_SEC) so we need to send + // a notification that we have a long running operation. + send_formatted_output(Console, "%s...\n", message); + // Set pos->second to true to indicate that when the task completes we + // need to notify that the task is done. + progress_map->erase(pos); + progress_map->insert(std::make_pair(message, true)); + } + }) + .detach(); + } +} + +#pragma mark -- File types + +enum LaunchMethod { + Launch, + Attach, + AttachForSuspendedLaunch +}; + +enum VSCodeBroadcasterBits { + eBroadcastBitStopEventThread = 1u << 0 +}; + +struct SourceReference +{ + std::string content; + std::map 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; + } +}; + +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 JSONObject::SP &obj) : + condition(obj->GetString("condition")), + hitCondition(obj->GetString("hitCondition")), + logMessage(obj->GetString("logMessage")) { + } + + void SetCondition() { + bp.SetCondition(condition.c_str()); + } + + void SetHitCondition() { + auto hitCount = string_to_unsigned(hitCondition.c_str(), 0, 0); + if (hitCount > 1) + bp.SetIgnoreCount(hitCount - 1); + } + + void 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(); + } + } +}; + +struct FunctionBreakpoint : public BreakpointBase { + // The name of the function + std::string name; + + FunctionBreakpoint() = default; + FunctionBreakpoint(const JSONObject::SP &obj) : + BreakpointBase(obj), + name(obj->GetString("name")) {} + + // Set this breakpoint in LLDB as a new breakpoint + void SetBreakpoint(); +}; + +struct SourceBreakpoint : public BreakpointBase { + // The source line of the breakpoint or logpoint + uint32_t line; + // An optional source column of the breakpoint + uint32_t column; + + SourceBreakpoint() : BreakpointBase(), line(0), column(0) {} + SourceBreakpoint(const JSONObject::SP &obj) : + BreakpointBase(obj), + line(obj->GetUnsigned("line", 0)), + column(obj->GetUnsigned("column", 0)) { + } + + // Set this breakpoint in LLDB as a new breakpoint + void SetBreakpoint(const char *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; +} + + +typedef std::map SourceBreakpointMap; +typedef std::map FunctionBreakpointMap; + +struct State { + 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::thread forward_input_thread; +// std::thread forward_output_thread; + std::unique_ptr log; + std::map addr_to_source_ref; + std::map source_map; + std::map source_breakpoints; + FunctionBreakpointMap function_breakpoints; + std::vector exception_breakpoints; + std::vector init_commands; + std::vector pre_run_commands; + std::vector exit_commands; + std::vector 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. + std::set thread_ids; + State() : + 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("LLDB_VSCODE_LOG"); + if (log_file_path) + log.reset(new std::ofstream(log_file_path)); + } + ~State() { + CloseInputStream(); + CloseOutputStream(); + } + State(const State &rhs) = delete; + void operator=(const State &rhs) = delete; + void CloseInputStream() { + if (in != stdin) { + fclose(in); + in = nullptr; + } + } + void CloseOutputStream() { + if (out != stdout) { + fclose(out); + out = nullptr; + } + } + int64_t 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 *GetExceptionBreakpoint(const std::string &filter) { + for (auto &bp: exception_breakpoints) { + if (bp.filter == filter) + return &bp; + } + return nullptr; + } + + ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id) { + for (auto &bp: exception_breakpoints) { + if (bp.bp.GetID() == bp_id) + return &bp; + } + return nullptr; + } +}; + +State g_state; + +void SourceBreakpoint::SetBreakpoint(const char *source_path) { + bp = g_state.target.BreakpointCreateByLocation(source_path, line); + if (!condition.empty()) + SetCondition(); + if (!hitCondition.empty()) + SetHitCondition(); +} + +void FunctionBreakpoint::SetBreakpoint() { + if (name.empty()) + return; + bp = g_state.target.BreakpointCreateByName(name.c_str()); + if (!condition.empty()) + SetCondition(); + if (!hitCondition.empty()) + SetHitCondition(); +} + +void ExceptionBreakpoint::SetBreakpoint() { + if (!bp.IsValid()) { + bool catch_value = filter.find("_catch") != std::string::npos; + bool throw_value = filter.find("_throw") != std::string::npos; + bp = g_state.target.BreakpointCreateForException(language, + catch_value, + throw_value); + } +} + +void ExceptionBreakpoint::ClearBreakpoint() { + if (bp.IsValid()) { + g_state.target.BreakpointDelete(bp.GetID()); + bp = lldb::SBBreakpoint(); + } +} + +#pragma mark -- other utilities + +int accept_connection(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_state.log) + *g_state.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_state.log) + *g_state.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_state.log) + *g_state.log << "error: accept (" << strerror(errno) << ")" << + std::endl; + } +#if defined(_WIN32) + closesocket(sockfd); +#else + close(sockfd); +#endif + } + return newsockfd; +} + +const char *get_value(lldb::SBValue &v, std::string &storage) { + // Get the value from an SBValue that we will display in VS Code. Some + // values don't have C string values and C string summaries. If a value has + // both, we combine the value and the summary into one string. If we only + // have a value or summary, then we return only that. "storage" is used to + // put the two values together if we have both so we have one contiguous + // string that has both values. The "const char *" given by LLDB is a + // constant string that is owned by LLDB and we don't need storege for + // those if we only use one. + const char *value = v.GetValue(); + const char *summary = v.GetSummary(); + if (value && value[0]) { + if (summary && summary[0]) { + storage = value; + storage.append(1, ' '); + storage.append(summary); + return storage.c_str(); + } + } else { + value = summary; + } + if (value == nullptr) { + const char *type_name = v.GetType().GetDisplayTypeName(); + if (type_name) { + storage = type_name; + lldb::addr_t address = v.GetLoadAddress(); + if (address != LLDB_INVALID_ADDRESS) { + storage += " @ "; + char str[64]; + snprintf(str, sizeof(str), "0x%" PRIx64, address); + storage += str; + } + return storage.c_str(); + } + } + return value; +} + +static int64_t get_next_source_reference() { + static int64_t ref = 0; + return ++ref; +} + +bool is_empty_line(const char *l) { + while (*l == '\r' || *l == '\n') + ++l; + return l[0] == '\0'; +} + + +std::vector +make_argv(const std::vector &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 argv; + for (const auto &s: strs) + argv.push_back(s.c_str()); + argv.push_back(nullptr); + return argv; +} + +int64_t make_frame_id(lldb::SBFrame &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. + return (int64_t)frame.GetThread().GetIndexID() << 32 | + (int64_t)frame.GetFrameID(); +} + +#pragma mark -- picojson utilities + +//---------------------------------------------------------------------- +// "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" ] +// } +//---------------------------------------------------------------------- +JSONValue::SP +create_breakpoint(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. + JSONObject::SP object(new JSONObject()); + if (bp_loc.IsValid()) { + object->SetObject("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->SetObject("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->SetObject("line", line); + object->SetObject("source", create_source(line_entry)); + } + } + return object; +} + +void append_breakpoint(lldb::SBBreakpoint &bp, + const JSONArray::SP &breakpoints) { + if (bp.IsValid()) { + const auto num_locations = bp.GetNumLocations(); + if (num_locations > 0) { + for (size_t i = 0; i < num_locations; ++i) { + auto bp_loc = bp.GetLocationAtIndex(i); + breakpoints->AppendObject(create_breakpoint(bp_loc)); + } + } + } +} + +ExceptionBreakpoint *get_exc_bp_from_stop_reason(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 = g_state.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; +} + + +//---------------------------------------------------------------------- +// "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" ] +// } +//---------------------------------------------------------------------- +JSONObject::SP create_event(const char *event_name) { + JSONObject::SP event(new JSONObject()); + event->SetObject("seq", 0); + event->SetObject("type", "event"); + event->SetObject("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" ] +// } +//---------------------------------------------------------------------- +JSONValue::SP create_exception_bp_filter(const ExceptionBreakpoint &bp) { + JSONObject::SP object(new JSONObject()); + object->SetObject("filter", bp.filter); + object->SetObject("label", bp.label); + object->SetObject("default", bp.value); + return 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." +// } +// } +// } +//---------------------------------------------------------------------- +JSONValue::SP create_source(lldb::SBLineEntry &line_entry) { + JSONObject::SP object(new JSONObject()); + lldb::SBFileSpec file = line_entry.GetFileSpec(); + if (file.IsValid()) { + const char *name = file.GetFilename(); + if (name) + object->SetObject("name", name); + char path[PATH_MAX] = ""; + file.GetPath(path, sizeof(path)); + if (path[0]) + object->SetObject("path", path); + } + return object; +} + +JSONValue::SP create_source(lldb::SBFrame &frame, int64_t &disasm_line) { + disasm_line = 0; + auto line_entry = frame.GetLineEntry(); + if (line_entry.GetFileSpec().IsValid()) + return create_source(line_entry); + + JSONObject::SP object(new JSONObject()); + const auto pc = frame.GetPC(); + + lldb::SBInstructionList insts; + lldb::SBFunction function = frame.GetFunction(); + lldb::addr_t low_pc = LLDB_INVALID_ADDRESS; + lldb::addr_t high_pc = LLDB_INVALID_ADDRESS; + if (function.IsValid()) { + low_pc = function.GetStartAddress().GetLoadAddress(g_state.target); + high_pc = function.GetEndAddress().GetLoadAddress(g_state.target); + auto addr_srcref = g_state.addr_to_source_ref.find(low_pc); + if (addr_srcref != g_state.addr_to_source_ref.end()) { + // We have this disassembly cached already, return the existing + // sourceReference + object->SetObject("sourceReference", addr_srcref->second); + disasm_line = g_state.GetLineForPC(addr_srcref->second, pc); + } else { + insts = function.GetInstructions(g_state.target); + } + } else { + lldb::SBSymbol symbol = frame.GetSymbol(); + if (symbol.IsValid()) { + low_pc = symbol.GetStartAddress().GetLoadAddress(g_state.target); + high_pc = symbol.GetEndAddress().GetLoadAddress(g_state.target); + auto addr_srcref = g_state.addr_to_source_ref.find(low_pc); + if (addr_srcref != g_state.addr_to_source_ref.end()) { + // We have this disassembly cached already, return the existing + // sourceReference + object->SetObject("sourceReference", addr_srcref->second); + disasm_line = g_state.GetLineForPC(addr_srcref->second, pc); + } else { + insts = symbol.GetInstructions(g_state.target); + } + } + } + const auto num_insts = insts.GetSize(); + if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) { + object->SetObject("name", frame.GetFunctionName()); + SourceReference source; + for (size_t i=0; i%*s %12s %s", inst_addr, + inst_offset, spaces, "", m, o); + else + snprintf(str, sizeof(str), + "0x%16.16" PRIx64 ": <%+" PRIi64 ">%*s %12s %s", inst_addr, + inst_offset, spaces, "", m, o); + std::string line; + line = str; + 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.append(comment_row - line.size(), ' '); + line.append("# "); + line.append(c); + } + source.content.append(line); + source.content.append(1, '\n'); + source.addr_to_line[inst_addr] = i+1; + } + auto sourceReference = get_next_source_reference(); + g_state.source_map[sourceReference] = std::move(source); + g_state.addr_to_source_ref[low_pc] = sourceReference; + object->SetObject("sourceReference", sourceReference); + } + return 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" ] +// } +//---------------------------------------------------------------------- +JSONValue::SP create_StackFrame(lldb::SBFrame &frame) { + JSONObject::SP object(new JSONObject()); + int64_t frame_id = make_frame_id(frame); + object->SetObject("id", frame_id); + object->SetObject("name", frame.GetFunctionName()); + int64_t disasm_line = 0; + object->SetObject("source", create_source(frame, disasm_line)); + + auto line_entry = frame.GetLineEntry(); + if (disasm_line > 0) { + object->SetObject("line", disasm_line); + } else { + auto line = line_entry.GetLine(); + if (line == UINT32_MAX) + line = 0; + object->SetObject("line", line); + } + object->SetObject("column", line_entry.GetColumn()); + return 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" ] +// } +//---------------------------------------------------------------------- +JSONValue::SP +create_thread(lldb::SBThread &thread) { + JSONObject::SP object(new JSONObject()); + object->SetObject("id", 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->SetObject("name", name); + } else { + object->SetObject("name", thread_str); + } + return 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" ] +// }] +// } +//---------------------------------------------------------------------- +JSONValue::SP +create_thread_stopped(lldb::SBThread &thread, uint32_t stop_id) { + JSONObject::SP event(create_event("stopped")); + JSONObject::SP body(new JSONObject()); + switch (thread.GetStopReason()) { + case lldb::eStopReasonTrace: + case lldb::eStopReasonPlanComplete: + body->SetObject("reason", "step"); + break; + case lldb::eStopReasonBreakpoint: { + ExceptionBreakpoint *exc_bp = get_exc_bp_from_stop_reason(thread); + if (exc_bp) { + body->SetObject("reason", "exception"); + body->SetObject("description", exc_bp->label); + } else { + body->SetObject("reason", "breakpoint"); + } + } + break; + case lldb::eStopReasonWatchpoint: + case lldb::eStopReasonInstrumentation: + body->SetObject("reason", "breakpoint"); + break; + case lldb::eStopReasonSignal: + body->SetObject("reason", "exception"); + break; + case lldb::eStopReasonException: + body->SetObject("reason", "exception"); + break; + case lldb::eStopReasonExec: + body->SetObject("reason", "entry"); + break; + case lldb::eStopReasonThreadExiting: + case lldb::eStopReasonInvalid: + case lldb::eStopReasonNone: + break; + + } + if (stop_id == 0) + body->SetObject("reason", "entry"); + const lldb::tid_t tid = thread.GetThreadID(); + body->SetObject("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 (!body->ContainsObject("description")) { + char description[1024]; + if (thread.GetStopDescription(description, sizeof(description))) { + body->SetObject("description", description); + } + } + if (tid == g_state.focus_tid) { + body->SetObject("threadCausedFocus", true); + } + body->SetObject("preserveFocusHint", tid != g_state.focus_tid); + body->SetObject("allThreadsStopped", true); + event->SetObject("body", body); + return event; + +} + +//---------------------------------------------------------------------- +// "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" ] +// } +//---------------------------------------------------------------------- +JSONValue::SP create_Scope(const char *name, + int64_t variablesReference, + int64_t namedVariables, + bool expensive) { + JSONObject::SP object(new JSONObject()); + object->SetObject("name", name); + object->SetObject("variablesReference", variablesReference); + object->SetObject("expensive", expensive); + object->SetObject("namedVariables", namedVariables); + return object; + +} + +JSONValue::SP create_Scopes() { + JSONArray::SP scopes(new JSONArray()); + scopes->AppendObject(create_Scope("Locals", VARREF_LOCALS, + g_state.num_locals, false)); + scopes->AppendObject(create_Scope("Globals", VARREF_GLOBALS, + g_state.num_globals, false)); + scopes->AppendObject(create_Scope("Registers", VARREF_REGS, + g_state.num_regs, false)); + return scopes; +} + +//---------------------------------------------------------------------- +// "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" ] +// } +//---------------------------------------------------------------------- +JSONValue::SP create_Variable(lldb::SBValue v, + int64_t variablesReference, + int64_t varID, + bool format_hex) { + JSONObject::SP object(new JSONObject()); + auto name = v.GetName(); + object->SetObject("name", name ? name : ""); + if (format_hex) + v.SetFormat(lldb::eFormatHex); + std::string value_storage; + object->SetObject("value", get_value(v, value_storage)); + auto type_cstr = v.GetType().GetDisplayTypeName(); + object->SetObject("type", type_cstr ? type_cstr : NO_TYPENAME); + if (varID != INT64_MAX) + object->SetObject("id", varID); + if (v.MightHaveChildren()) + object->SetObject("variablesReference", variablesReference); + else + object->SetObject("variablesReference", (int64_t)0); + lldb::SBStream evaluateStream; + v.GetExpressionPath(evaluateStream); + const char *evaluateName = evaluateStream.GetData(); + if (evaluateName && evaluateName[0]) + object->SetObject("evaluateName", evaluateName); + return object; +} + +lldb::SBThread get_lldb_thread(const JSONObject::SP &arguments) { + auto tid = arguments->GetUnsigned("threadId", LLDB_INVALID_THREAD_ID); + return g_state.target.GetProcess().GetThreadByID(tid); +} + +lldb::SBFrame get_lldb_frame(const JSONObject::SP &arguments) { + const uint64_t frame_id = arguments->GetUnsigned("frameId", UINT64_MAX); + lldb::SBProcess process = g_state.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); +} + +std::string to_string(const JSONValue::SP &object) { + if (object) { + switch (object->GetKind()) { + case JSONValue::Kind::String: + return static_cast(object.get())->GetData(); + case JSONValue::Kind::Number: { + auto number = static_cast(object.get()); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%" PRIu64, number->GetAsUnsigned()); + return std::string(buffer); + } + case JSONValue::Kind::True: + return "true"; + case JSONValue::Kind::False: + return "false"; + case JSONValue::Kind::Null: + return "null"; + case JSONValue::Kind::Object: + case JSONValue::Kind::Array: + assert(!"Unhandled case"); + break; + } + } + return std::string(); +} + +std::vector +get_strings(const JSONObject::SP &object, std::string key) { + std::vector strs; + auto json_array = object->GetAsArray(key); + if (json_array) { + const auto n = json_array->GetNumElements(); + for (size_t i = 0; i < n; ++i) { + strs.push_back(to_string(json_array->GetObject(i))); + } + } + return strs; +} + +void +fill_response(const JSONObject::SP &request, const JSONObject::SP &response) { + // Fill in all of the needed response fields to a "request" and set "success" + // to true by default. + response->SetObject("type", "response"); + response->SetObject("seq", (int64_t)0); + response->SetObject("command", request->GetString("command")); + const int64_t seq = request->GetInteger("seq", 0); + response->SetObject("request_seq", seq); + response->SetObject("success", true); +} + +//---------------------------------------------------------------------- +// Read a JSON packet from the "in" stream. +//---------------------------------------------------------------------- +std::string read_json_packet(FILE *in) { + 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 (!is_empty_line(line)) + if (g_state.log) + *g_state.log << "warning: expected empty line but got: \"" << line << + "\"" << std::endl; + break; + } + } else { + if (g_state.log) + *g_state.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 (g_state.log) + *g_state.log << "error: read fewer bytes (" << bytes_read << + ") than requested (" << packet_len << ")" << std::endl; + json_str.erase(bytes_read); + } + if (g_state.log) { + *g_state.log << "--> " << std::endl; + *g_state.log << header << packet_len << "\r\n\r\n" << json_str << std::endl; + } + } + return json_str; +} + +//---------------------------------------------------------------------- +// 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 send_json_string(const std::string &json_str) { + fprintf(g_state.out, "Content-Length: %u\r\n\r\n%s", + (uint32_t)json_str.size(), json_str.c_str()); + fflush(g_state.out); + if (g_state.log) { + *g_state.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 send_json_packet(const JSONValue::SP &json) { + std::ostringstream s; + json->Write(s); + static std::mutex mutex; + std::lock_guard locker(mutex); + send_json_string(s.str()); +} + +//---------------------------------------------------------------------- +// Send a "exited" event to indicate the process has exited. +//---------------------------------------------------------------------- +void send_process_exited_event(lldb::SBProcess &process) { + JSONObject::SP event(create_event("exited")); + JSONObject::SP body(new JSONObject()); + body->SetObject("exitCode", (int64_t)process.GetExitStatus()); + event->SetObject("body", std::move(body)); + send_json_packet(event); +} + +void +send_thread_exited_event(lldb::tid_t tid) +{ + JSONObject::SP event(create_event("thread")); + JSONObject::SP body(new JSONObject()); + body->SetObject("reason", "exited"); + body->SetObject("threadId", (int64_t)tid); + event->SetObject("body", std::move(body)); + send_json_packet(event); +} + +//---------------------------------------------------------------------- +// Send a "terminated" event to indicate the process is done being +// debugged. +//---------------------------------------------------------------------- +void send_terminated_event() { + if (!g_state.sent_terminated_event) { + g_state.sent_terminated_event = true; + // Send a "terminated" event + JSONObject::SP event(create_event("terminated")); + send_json_packet(event); + } +} + +//---------------------------------------------------------------------- +// Send a thread stopped event for all threads as lons as the process +// is stopped. +//---------------------------------------------------------------------- +void send_thread_stopped_event() { + lldb::SBProcess process = g_state.target.GetProcess(); + if (process.IsValid()) { + auto state = process.GetState(); + if (state == lldb::eStateStopped) { + std::set old_thread_ids; + old_thread_ids.swap(g_state.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 = thread_has_stop_reason(thread); + // If the focus thread doesn't have a stop reason, clear the thread ID + if (tid == g_state.focus_tid && !has_reason) + g_state.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_state.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_state.focus_tid == LLDB_INVALID_THREAD_ID) + g_state.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); + send_json_packet(create_thread_stopped(thread, stop_id)); + } else { + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + g_state.thread_ids.insert(thread.GetThreadID()); + if (thread_has_stop_reason(thread)) { + send_json_packet(create_thread_stopped(thread, stop_id)); + } + } + } + + for (auto tid: old_thread_ids) { + auto end = g_state.thread_ids.end(); + auto pos = g_state.thread_ids.find(tid); + if (pos == end) + send_thread_exited_event(tid); + } + } else { + if (g_state.log) + *g_state.log << "error: send_thread_stopped_event() when process" + " isn't stopped (" << lldb::SBDebugger::StateAsCString(state) << + ')' << std::endl; + } + } else { + if (g_state.log) + *g_state.log << "error: send_thread_stopped_event() invalid process" << + std::endl; + } + run_lldb_commands("Running stopCommands:", g_state.stop_commands); +} + +//---------------------------------------------------------------------- +// "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 +send_output(OutputType o, const char *output, size_t output_len) { + if (output_len == 0) + return; + + if (output_len == SIZE_MAX) + output_len = strlen(output); + + JSONObject::SP event(create_event("output")); + JSONObject::SP body(new JSONObject()); + const char *category = nullptr; + switch (o) { + case Console: category = "console"; break; + case Stdout: category = "stdout"; break; + case Stderr: category = "stderr"; break; + case Telemetry: category = "telemetry"; break; + } + body->SetObject("category", category); + body->SetObject("output", std::string(output, output_len)); + event->SetObject("body", std::move(body)); + send_json_packet(event); +} + +void __attribute__((format(printf, 2, 3))) +send_formatted_output(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); + send_output(o, buffer, std::min(actual_length, sizeof(buffer))); +} + +//---------------------------------------------------------------------- +// "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 send_process_event(LaunchMethod launch_method) { + lldb::SBFileSpec exe_fspec = g_state.target.GetExecutable(); + char exe_path[PATH_MAX]; + exe_fspec.GetPath(exe_path, sizeof(exe_path)); + JSONObject::SP event(create_event("process")); + JSONObject::SP body(new JSONObject()); + body->SetObject("name", exe_path); + const auto pid = g_state.target.GetProcess().GetProcessID(); + body->SetObject("systemProcessId", (int64_t)pid); + body->SetObject("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->SetObject("startMethod", startMethod); + event->SetObject("body", std::move(body)); + send_json_packet(event); + +} + +#pragma mark -- LLDB Utilities + +//---------------------------------------------------------------------- +// Run a list of LLDB command in the LLDB command interpreter. All output +// is placed into the "strm" argument. +//---------------------------------------------------------------------- +void run_lldb_commands(const char *prefix, + const std::vector &commands, + lldb::SBStream &strm) { + if (commands.empty()) + return; + lldb::SBCommandInterpreter interp = g_state.debugger.GetCommandInterpreter(); + if (prefix) + strm.Printf("%s\n", prefix); + for (const auto &command: commands) { + lldb::SBCommandReturnObject result; + strm.Printf("(lldb) %s\n", command.c_str()); + interp.HandleCommand(command.c_str(), result); + auto output_len = result.GetOutputSize(); + if (output_len) { + const char *output = result.GetOutput(); + strm.Printf("%s", output); + } + auto error_len = result.GetErrorSize(); + if (error_len) { + const char *error = result.GetError(); + strm.Printf("%s", error); + } + } +} +//---------------------------------------------------------------------- +// Run a list of LLDB command in the LLDB command interpreter. All output +// and error info ends up inside the "result" variable and we can extract it +// and show it. The command itself and any output are sent to VS Code via +// an "output" event to the "console" category. +//---------------------------------------------------------------------- +void run_lldb_commands(const char *prefix, + const std::vector &commands) { + lldb::SBStream strm; + run_lldb_commands(prefix, commands, strm); + send_output(Console, strm.GetData(), strm.GetSize()); +} + +//---------------------------------------------------------------------- +// 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 check_for_process_stdout_stderr(lldb::SBProcess &process) { + char buffer [1024]; + size_t count; + while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0) + send_output(Stdout, buffer, count); + while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0) + send_output(Stderr, 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 event_thread_function() { + lldb::SBEvent event; + lldb::SBListener listener = g_state.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)) { + check_for_process_stdout_stderr(process); + send_thread_stopped_event(); + } + break; + case lldb::eStateRunning: + break; + case lldb::eStateExited: + { + // Run any exit LLDB commands the user specified in the + // launch.json + run_lldb_commands("Running exitCommands:", + g_state.exit_commands); + send_process_exited_event(process); + send_terminated_event(); + done = true; + } + break; + } + } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || + (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { + check_for_process_stdout_stderr(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 = create_event("breakpoint"); + JSONObject::SP body(new JSONObject()); + body->SetObject("breakpoint", create_breakpoint(bp_loc)); + if (added) + body->SetObject("reason", "new"); + else + body->SetObject("reason", "removed"); + bp_event->SetObject("body", std::move(body)); + send_json_packet(bp_event); + } + } + } + } else if (event.BroadcasterMatchesRef(g_state.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 set_source_map_from_arguments(const JSONObject::SP &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("settings set target.source-map"); + auto sourcePath = arguments->GetString("sourcePath"); + + // sourceMap is the new, more general form of sourcePath and overrides it. + auto sourceMap = arguments->GetObject("sourceMap"); + if (sourceMap != nullptr) { + auto sourceMapArray = GetAsArray(sourceMap); + if (sourceMapArray == nullptr) { + send_output(Console, sourceMapHelp); + return; + } + + auto numMappings = sourceMapArray->GetNumElements(); + + for (size_t i = 0; i < numMappings; i++) { + auto mapping = GetAsArray(sourceMapArray->GetObject(i)); + if (mapping == nullptr || mapping->GetNumElements() != 2 || + mapping->GetObject(0)->GetKind() != JSONValue::Kind::String || + mapping->GetObject(1)->GetKind() != JSONValue::Kind::String) { + send_output(Console, sourceMapHelp); + return; + } + + auto mapFrom = GetStringValue(mapping->GetObject(0)); + auto mapTo = GetStringValue(mapping->GetObject(1)); + + sourceMapCommand += " \"" + mapFrom + "\" \"" + mapTo + "\""; + } + } else if (!sourcePath.empty()) { + // Do any source remapping needed before we create our targets + sourceMapCommand += " \".\" \""; + sourceMapCommand += sourcePath; + sourceMapCommand += "\""; + } else { + return; + } + + if (!sourceMapCommand.empty()) { + run_lldb_commands("Setting source map:", {sourceMapCommand}); + } +} + +#pragma mark -- Visual Studio Code Requests + +//---------------------------------------------------------------------- +// "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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + lldb::SBError error; + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + const lldb::pid_t pid = arguments->GetUnsigned("pid", + LLDB_INVALID_PROCESS_ID); + if (pid != LLDB_INVALID_PROCESS_ID) + g_state.attach_info.SetProcessID(pid); + const auto wait_for = arguments->GetBoolean("waitFor", false); + g_state.attach_info.SetWaitForLaunch(wait_for, false /*async*/); + g_state.init_commands = get_strings(arguments, "initCommands"); + g_state.pre_run_commands = get_strings(arguments, "preRunCommands"); + g_state.stop_commands = get_strings(arguments, "stopCommands"); + g_state.exit_commands = get_strings(arguments, "exitCommands"); + auto attachCommands = get_strings(arguments, "attachCommands"); + g_state.stop_at_entry = arguments->GetBoolean("stopOnEntry", false); + std::string debuggerRoot = arguments->GetString("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()) { + ChangeDir(debuggerRoot.c_str()); + } + + // Run any initialize LLDB commands the user specified in the launch.json + run_lldb_commands("Running initCommands:", + g_state.init_commands); + + // 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 std::string program = arguments->GetString("program"); + if (!program.empty()) { + lldb::SBFileSpec program_fspec(program.c_str(), true /*resolve_path*/); + + g_state.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_state.target.AddModule(program.c_str(), target_triple, uuid_cstr, + symfile); + if (error.Fail()) { + response->SetObject("success", false); + response->SetObject("message", error.GetCString()); + send_json_packet(response); + } + } + + const bool detatchOnError = arguments->GetBoolean("detachOnError", false); + g_state.launch_info.SetDetachOnError(detatchOnError); + + // Run any pre run LLDB commands the user specified in the launch.json + run_lldb_commands("Running preRunCommands:", + g_state.pre_run_commands); + + 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.c_str()); + send_output(Console, 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_state.debugger.SetAsync(false); + g_state.target.Attach(g_state.attach_info, error); + // Reenable async events + g_state.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. + run_lldb_commands("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_state.target = g_state.debugger.GetSelectedTarget(); + } + + set_source_map_from_arguments(arguments); + + if (error.Success()) { + auto attached_pid = g_state.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->SetObject("success", false); + response->SetObject("message", error.GetCString()); + } + send_json_packet(response); + if (error.Success()) { + send_process_event(Attach); + send_json_packet(create_event("initialized")); + //send_thread_stopped_event(); + } +} + +//---------------------------------------------------------------------- +// "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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + lldb::SBProcess process = g_state.target.GetProcess(); + auto arguments = request->GetAsObject("arguments"); + // Remember the thread ID that caused the resume so we can set the + // "threadCausedFocus" boolean value in the "stopped" events. + g_state.focus_tid = arguments->GetUnsigned("threadId", + LLDB_INVALID_THREAD_ID); + lldb::SBError error = process.Continue(); + JSONObject::SP body(new JSONObject()); + body->SetObject("allThreadsContinued", true); + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + send_json_packet(response); + if (g_state.stop_at_entry) + send_thread_stopped_event(); + else + g_state.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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + + bool terminateDebuggee = arguments->GetBoolean("terminateDebuggee", false); + lldb::SBProcess process = g_state.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_state.debugger.SetAsync(false); + if (terminateDebuggee) + process.Kill(); + else + process.Detach(); + g_state.debugger.SetAsync(true); + break; + } + send_json_packet(response); + send_terminated_event(); + if (g_state.event_thread.joinable()) { + g_state.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); + g_state.event_thread.join(); + } +} + +void request_exceptionInfo(const JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + JSONObject::SP body(new JSONObject()); + lldb::SBThread thread = get_lldb_thread(arguments); + if (thread.IsValid()) { + auto stopReason = thread.GetStopReason(); + if (stopReason == lldb::eStopReasonSignal) + body->SetObject("exceptionId", "signal"); + else if (stopReason == lldb::eStopReasonBreakpoint) { + ExceptionBreakpoint *exc_bp = get_exc_bp_from_stop_reason(thread); + if (exc_bp) { + body->SetObject("exceptionId", exc_bp->filter); + body->SetObject("description", exc_bp->label); + } else { + body->SetObject("exceptionId", "exception"); + } + } else { + body->SetObject("exceptionId", "exception"); + } + if (!body->ContainsObject("description")) { + char description[1024]; + if (thread.GetStopDescription(description, sizeof(description))) { + body->SetObject("description", description); + } + } + body->SetObject("breakMode", "always"); + // auto excInfoCount = thread.GetStopReasonDataCount(); + // for (auto i=0; iSetObject("success", false); + } + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + JSONObject::SP body(new JSONObject()); + auto arguments = request->GetAsObject("arguments"); + lldb::SBFrame frame = get_lldb_frame(arguments); + std::string expression = arguments->GetString("expression"); + + if (!expression.empty() && expression[0] == '`') { + lldb::SBStream strm; + run_lldb_commands(nullptr, {expression.substr(1)}, strm); + body->SetObject("result", strm.GetData()); + body->SetObject("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.c_str(), lldb::eDynamicDontRunTarget); + if (value.GetError().Fail()) + value = frame.EvaluateExpression(expression.c_str()); + if (value.GetError().Fail()) { + response->SetObject("success", false); + const char *error_cstr = value.GetError().GetCString(); + if (error_cstr && error_cstr[0]) + response->SetObject("message", error_cstr); + else + response->SetObject("message", "evaluate failed"); + } else { + std::string storage; + body->SetObject("result", get_value(value, storage)); + auto value_typename = value.GetType().GetDisplayTypeName(); + body->SetObject("type", value_typename ? value_typename : NO_TYPENAME); + if (value.MightHaveChildren()) { + auto variablesReference = VARIDX_TO_VARREF(g_state.variables.GetSize()); + g_state.variables.Append(value); + body->SetObject("variablesReference", variablesReference); + } else { + body->SetObject("variablesReference", (int64_t)0); + } + } + } + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + g_state.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_state.debugger.SetOutputFileHandle(out, true); + g_state.debugger.SetErrorFileHandle(out, false); + } + + g_state.target = g_state.debugger.CreateTarget(nullptr); + lldb::SBListener listener = g_state.debugger.GetListener(); + listener.StartListeningForEvents(g_state.target.GetBroadcaster(), + lldb::SBTarget::eBroadcastBitBreakpointChanged); + listener.StartListeningForEvents(g_state.broadcaster, + eBroadcastBitStopEventThread); + // Start our event thread so we can receive events from the debugger, target, + // process and more. + g_state.event_thread = std::thread(event_thread_function); + + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + JSONObject::SP body(new JSONObject()); + // The debug adapter supports the configurationDoneRequest. + body->SetObject("supportsConfigurationDoneRequest", true); + // The debug adapter supports function breakpoints. + body->SetObject("supportsFunctionBreakpoints", true); + // The debug adapter supports conditional breakpoints. + body->SetObject("supportsConditionalBreakpoints", true); + // The debug adapter supports breakpoints that break execution after a + // specified number of hits. + body->SetObject("supportsHitConditionalBreakpoints", true); + // The debug adapter supports a (side effect free) evaluate request for + // data hovers. + body->SetObject("supportsEvaluateForHovers", true); + // Available filters or options for the setExceptionBreakpoints request. + JSONArray::SP filters(new JSONArray()); + for (const auto &exc_bp: g_state.exception_breakpoints) { + filters->AppendObject(create_exception_bp_filter(exc_bp)); + } + body->SetObject("exceptionBreakpointFilters", filters); + // The debug adapter supports stepping back via the stepBack and + // reverseContinue requests. + body->SetObject("supportsStepBack", false); + // The debug adapter supports setting a variable to a value. + body->SetObject("supportsSetVariable", true); + // The debug adapter supports restarting a frame. + body->SetObject("supportsRestartFrame", false); + // The debug adapter supports the gotoTargetsRequest. + body->SetObject("supportsGotoTargetsRequest", false); + // The debug adapter supports the stepInTargetsRequest. + body->SetObject("supportsStepInTargetsRequest", false); + // The debug adapter supports the completionsRequest. + body->SetObject("supportsCompletionsRequest", false); + // The debug adapter supports the modules request. + body->SetObject("supportsModulesRequest", false); + // The set of additional module information exposed by the debug adapter. + // body->SetObject("additionalModuleColumns"] = ColumnDescriptor + // Checksum algorithms supported by the debug adapter. + // body->SetObject("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->SetObject("supportsRestartRequest", false); + // The debug adapter supports 'exceptionOptions' on the + // setExceptionBreakpoints request. + body->SetObject("supportsExceptionOptions", true); + // The debug adapter supports a 'format' attribute on the stackTraceRequest, + // variablesRequest, and evaluateRequest. + body->SetObject("supportsValueFormattingOptions", true); + // The debug adapter supports the exceptionInfo request. + body->SetObject("supportsExceptionInfoRequest", true); + // The debug adapter supports the 'terminateDebuggee' attribute on the + // 'disconnect' request. + body->SetObject("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->SetObject("supportsDelayedStackTraceLoading", true); + // The debug adapter supports the 'loadedSources' request. + body->SetObject("supportsLoadedSourcesRequest", false); + + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + lldb::SBError error; + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + g_state.init_commands = get_strings(arguments, "initCommands"); + g_state.pre_run_commands = get_strings(arguments, "preRunCommands"); + g_state.stop_commands = get_strings(arguments, "stopCommands"); + g_state.exit_commands = get_strings(arguments, "exitCommands"); + g_state.stop_at_entry = arguments->GetBoolean("stopOnEntry", false); + std::string debuggerRoot = arguments->GetString("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()) { + ChangeDir(debuggerRoot.c_str()); + } + + set_source_map_from_arguments(arguments); + + // Run any initialize LLDB commands the user specified in the launch.json + run_lldb_commands("Running initCommands:", + g_state.init_commands); + + // Grab the current working directory if there is one and set it in the + // launch info. + const std::string cwd = arguments->GetString("cwd"); + if (!cwd.empty()) + g_state.launch_info.SetWorkingDirectory(cwd.c_str()); + + // 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 std::string program = arguments->GetString("program"); + if (!program.empty()) { + lldb::SBFileSpec program_fspec(program.c_str(), true /*resolve_path*/); + + g_state.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_state.target.AddModule(program.c_str(), target_triple, uuid_cstr, + symfile); + if (error.Fail()) { + response->SetObject("success", false); + response->SetObject("message", error.GetCString()); + send_json_packet(response); + } + } + + // Extract any extra arguments and append them to our program arguments for + // when we launch + auto args = get_strings(arguments, "args"); + if (!args.empty()) + g_state.launch_info.SetArguments(make_argv(args).data(), true); + + // Pass any environment variables along that the user specified. + auto envs = get_strings(arguments, "env"); + if (!envs.empty()) + g_state.launch_info.SetEnvironmentEntries(make_argv(envs).data(), true); + + auto flags = g_state.launch_info.GetLaunchFlags(); + + if (arguments->GetBoolean("disableASLR", true)) + flags |= lldb::eLaunchFlagDisableASLR; + if (arguments->GetBoolean("disableSTDIO", false)) + flags |= lldb::eLaunchFlagDisableSTDIO; + if (arguments->GetBoolean("shellExpandArguments", false)) + flags |= lldb::eLaunchFlagShellExpandArguments; + const bool detatchOnError = arguments->GetBoolean("detachOnError", false); + g_state.launch_info.SetDetachOnError(detatchOnError); + g_state.launch_info.SetLaunchFlags(flags | + lldb::eLaunchFlagDebug | + lldb::eLaunchFlagStopAtEntry ); + + // Run any pre run LLDB commands the user specified in the launch.json + run_lldb_commands("Running preRunCommands:", + g_state.pre_run_commands); + + // Disable async events so the launch will be successful when we return from + // the launch call and the launch will happen synchronously + g_state.debugger.SetAsync(false); + g_state.target.Launch(g_state.launch_info, error); + if (error.Fail()) { + response->SetObject("success", false); + response->SetObject("message", error.GetCString()); + } + send_json_packet(response); + + send_process_event(Launch); + send_json_packet(JSONValue::SP(create_event("initialized"))); + // Reenable async events and start the event thread to catch async events. + g_state.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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + lldb::SBThread thread = get_lldb_thread(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_state.focus_tid = thread.GetThreadID(); + thread.StepOver(); + } else { + response->SetObject("success", false); + } + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + lldb::SBProcess process = g_state.target.GetProcess(); + lldb::SBError error = process.Stop(); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + JSONObject::SP body(new JSONObject()); + auto arguments = request->GetAsObject("arguments"); + lldb::SBFrame frame = get_lldb_frame(arguments); + g_state.variables.Clear(); + g_state.variables.Append(frame.GetVariables(true, // arguments + true, // locals + false, // statics + true)); // in_scope_only + g_state.num_locals = g_state.variables.GetSize(); + g_state.variables.Append(frame.GetVariables(false, // arguments + false, // locals + true, // statics + true)); // in_scope_only + g_state.num_globals = g_state.variables.GetSize() - (g_state.num_locals); + g_state.variables.Append(frame.GetRegisters()); + g_state.num_regs = g_state.variables.GetSize() - (g_state.num_locals + + g_state.num_globals); + body->SetObject("scopes", create_Scopes()); + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + lldb::SBError error; + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + auto source = arguments->GetAsObject("source"); + std::string path = source->GetString("path"); + auto breakpoints = arguments->GetAsArray("breakpoints"); + JSONArray::SP response_breakpoints(new JSONArray()); + // Decode the source breakpoint infos for this "setBreakpoints" request + SourceBreakpointMap request_bps; + auto num_breakoints = breakpoints->GetNumElements(); + for (size_t i = 0; i < num_breakoints; ++i) { + SourceBreakpoint src_bp(GetAsObject(breakpoints->GetObject(i))); + 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_state.source_breakpoints.find(path); + if (old_src_bp_pos != g_state.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. + auto pos = old_src_bp_pos->second.begin(); + auto end = old_src_bp_pos->second.end(); + while (pos != end) { + auto request_pos = request_bps.find(pos->first); + if (request_pos == request_bps.end()) { + // This breakpoint no longer exists in this source file, delete it + g_state.target.BreakpointDelete(pos->second.bp.GetID()); + pos = old_src_bp_pos->second.erase(pos); + } else { + pos->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 + append_breakpoint(pos->second.bp, response_breakpoints); + ++pos; + } + } + + // 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.c_str()); + // Add this breakpoint info to the response + append_breakpoint(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_state.source_breakpoints for this source file. + for (auto &pair: request_bps) { + pair.second.SetBreakpoint(path.c_str()); + // Add this breakpoint info to the response + append_breakpoint(pair.second.bp, response_breakpoints); + } + g_state.source_breakpoints[path] = std::move(request_bps); + } + + JSONObject::SP body(new JSONObject()); + body->SetObject("breakpoints", response_breakpoints); + response->SetObject("body", std::move(body)); + send_json_packet(response); + +// run_lldb_commands("Running post \"setBreakpoints\" commands:", +// {"break list"}); + +} + +//---------------------------------------------------------------------- +// "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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + lldb::SBError error; + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + auto filters = arguments->GetAsArray("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 unset_filters; + for (const auto &bp: g_state.exception_breakpoints) + unset_filters.insert(bp.filter); + + auto num_filters = filters->GetNumElements(); + for (size_t i = 0; i < num_filters; ++i) { + const auto filter = GetStringValue(filters->GetObject(i)); + auto exc_bp = g_state.GetExceptionBreakpoint(filter); + if (exc_bp) { + exc_bp->SetBreakpoint(); + unset_filters.erase(filter); + } + } + for (const auto &filter: unset_filters) { + auto exc_bp = g_state.GetExceptionBreakpoint(filter); + if (exc_bp) + exc_bp->ClearBreakpoint(); + } + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + lldb::SBError error; + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + auto breakpoints = arguments->GetAsArray("breakpoints"); + FunctionBreakpointMap request_bps; + JSONArray::SP response_breakpoints(new JSONArray()); + auto num_breakoints = breakpoints->GetNumElements(); + for (size_t i = 0; i < num_breakoints; ++i) { + FunctionBreakpoint func_bp(GetAsObject(breakpoints->GetObject(i))); + request_bps[func_bp.name] = std::move(func_bp); + } + + // Delete 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. + auto pos = g_state.function_breakpoints.begin(); + auto end = g_state.function_breakpoints.end(); + while (pos != end) { + auto request_pos = request_bps.find(pos->first); + if (request_pos == request_bps.end()) { + // This breakpoint no longer exists in this source file, delete it + g_state.target.BreakpointDelete(pos->second.bp.GetID()); + pos = g_state.function_breakpoints.erase(pos); + } else { + pos->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 + append_breakpoint(pos->second.bp, response_breakpoints); + ++pos; + } + } + // 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 + append_breakpoint(pair.second.bp, response_breakpoints); + g_state.function_breakpoints[pair.first] = std::move(pair.second); + } + + JSONObject::SP body(new JSONObject()); + body->SetObject("breakpoints", response_breakpoints); + response->SetObject("body", std::move(body)); + send_json_packet(response); + +// run_lldb_commands("Running post \"setFunctionBreakpoints\" commands:", +// {"break list"}); +} + +//---------------------------------------------------------------------- +// "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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + JSONObject::SP body(new JSONObject()); + + auto arguments = request->GetAsObject("arguments"); + auto source = arguments->GetAsObject("source"); + auto sourceReference = source->GetInteger("sourceReference", -1); + auto pos = g_state.source_map.find((lldb::addr_t)sourceReference); + if (pos != g_state.source_map.end()) { + body->SetObject("content", pos->second.content); + } else { + response->SetObject("success", false); + } + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + lldb::SBError error; + auto arguments = request->GetAsObject("arguments"); + lldb::SBThread thread = get_lldb_thread(arguments); + JSONArray::SP stackFrames(new JSONArray()); + JSONObject::SP body(new JSONObject()); + + if (thread.IsValid()) { + const auto startFrame = arguments->GetUnsigned("startFrame", 0); + const auto levels = arguments->GetUnsigned("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->AppendObject(create_StackFrame(frame)); + } + } + body->SetObject("stackFrames", stackFrames); + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + lldb::SBThread thread = get_lldb_thread(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_state.focus_tid = thread.GetThreadID(); + thread.StepInto(); + } else { + response->SetObject("success", false); + } + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + auto arguments = request->GetAsObject("arguments"); + lldb::SBThread thread = get_lldb_thread(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_state.focus_tid = thread.GetThreadID(); + thread.StepOut(); + } else { + response->SetObject("success", false); + } + send_json_packet(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 JSONObject::SP &request) { + + lldb::SBProcess process = g_state.target.GetProcess(); + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + + const uint32_t num_threads = process.GetNumThreads(); + JSONArray::SP threads(new JSONArray()); + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + threads->AppendObject(create_thread(thread)); + } + if (threads->GetNumElements() == 0) { + response->SetObject("success", false); + } + JSONObject::SP body(new JSONObject()); + body->SetObject("threads", threads); + response->SetObject("body", std::move(body)); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + JSONArray::SP variables(new JSONArray()); + JSONObject::SP body(new JSONObject()); + auto arguments = request->GetAsObject("arguments"); + // This is a reference to the containing variable/scope + const auto variablesReference = arguments->GetUnsigned("variablesReference", + 0); + const auto name = arguments->GetString("name"); + const auto value = arguments->GetString("value"); + // Set success to false just in case we don't find the variable by name + response->SetObject("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_state.variables list. + const auto id_value = arguments->GetUnsigned("id", UINT64_MAX); + if (id_value != UINT64_MAX) { + variable = g_state.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_state.num_locals; + break; + case VARREF_GLOBALS: + start_idx = g_state.num_locals; + end_idx = start_idx + g_state.num_globals; + break; + case VARREF_REGS: + start_idx = g_state.num_locals + g_state.num_globals; + end_idx = start_idx + g_state.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_state.variables.GetValueAtIndex(i); + const char *variable_name = curr_variable.GetName(); + if (variable_name && strcmp(variable_name, name.c_str()) == 0) { + 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_state.variables.GetValueAtIndex(var_idx); + variable = container.GetChildMemberWithName(name.c_str()); + if (!variable.IsValid()) { + if (!name.empty() && name[0] == '[') { + char *end = nullptr; + const uint64_t index = strtoull(name.c_str()+1, &end, 0); + if (*end == ']') { + variable = container.GetChildAtIndex(index); + } + } + } + + // We don't know the index of the variable in our g_state.variables + if (variable.IsValid()) { + if (variable.MightHaveChildren()) { + newVariablesReference = VARIDX_TO_VARREF(g_state.variables.GetSize()); + g_state.variables.Append(variable); + } + } + } + + if (variable.IsValid()) { + lldb::SBError error; + bool success = variable.SetValueFromCString(value.c_str(), error); + if (success) { + std::string value_storage; + body->SetObject("value", get_value(variable, value_storage)); + body->SetObject("type", variable.GetType().GetDisplayTypeName()); + body->SetObject("variablesReference", newVariablesReference); + } else { + body->SetObject("message", error.GetCString()); + } + response->SetObject("success", success); + } + + response->SetObject("body", body); + send_json_packet(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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + JSONArray::SP variables(new JSONArray()); + auto arguments = request->GetAsObject("arguments"); + const auto variablesReference = arguments->GetUnsigned("variablesReference", + 0); + const int64_t start = arguments->GetInteger("start", 0); + const int64_t count = arguments->GetInteger("count", 0); + bool hex = false; + auto format = arguments->GetAsObject("format"); + if (format) + hex = format->GetBoolean("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_state.num_locals; + break; + case VARREF_GLOBALS: + start_idx = start + g_state.num_locals + start; + num_children = g_state.num_globals; + break; + case VARREF_REGS: + start_idx = start + g_state.num_locals + g_state.num_globals; + num_children = g_state.num_regs; + break; + default: + break; + } + const int64_t end_idx = start_idx + ((count == 0) ? num_children : count); + for (auto i=start_idx; iAppendObject(create_Variable(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_state.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; iAppendObject(create_Variable(child, + childVariablesReferences, + var_idx, hex)); + g_state.variables.Append(child); + } else { + variables->AppendObject(create_Variable(child, 0, INT64_MAX, hex)); + } + } + } + } + JSONObject::SP body(new JSONObject()); + body->SetObject("variables", variables); + response->SetObject("body", body); + send_json_packet(response); +} + +#pragma mark -- vccode.py test requests + +// 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 JSONObject::SP &request) { + JSONObject::SP response(new JSONObject()); + fill_response(request, response); + JSONArray::SP response_breakpoints(new JSONArray()); + for (uint32_t i=0; g_state.target.GetBreakpointAtIndex(i).IsValid(); ++i) { + auto bp = g_state.target.GetBreakpointAtIndex(i); + append_breakpoint(bp, response_breakpoints); + } + JSONObject::SP body(new JSONObject()); + body->SetObject("breakpoints", response_breakpoints); + response->SetObject("body", std::move(body)); + send_json_packet(response); +} + +const std::map &GetRequestHandlers() { + #define REQUEST_CALLBACK(name) { #name, request_##name } + static std::map 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; +} + + +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 = accept_connection(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_state.in = fdopen(socket_fd, "r"); + g_state.out = fdopen(socket_fd, "w"); + if (g_state.in == nullptr || g_state.out == nullptr) { + if (g_state.log) + *g_state.log << "fdopen failed (" << strerror(errno) << ")" << + std::endl; + exit(1); + } + } else { + exit(1); + } + } + } + auto request_handlers = GetRequestHandlers(); + uint32_t packet_idx = 0; + while (true) {//!feof(g_state.in) && !ferror(g_state.in)) { + std::string json = read_json_packet(g_state.in); + if (json.empty()) + break; + JSONParser parser(json.c_str()); + JSONValue::SP json_value = parser.ParseJSONValue(); + if (!json_value) { + if (g_state.log) + *g_state.log << "error: failed to parse JSON: " << std::endl << json + << std::endl; + return 1; + } + + auto object = GetAsObject(json_value); + if (!object) { + if (g_state.log) + *g_state.log << "error: json packet isn't a object" << std::endl; + return 1; + } + + const auto packet_type = object->GetString("type"); + if (packet_type == "request") { + const auto command = object->GetString("command"); + auto handler_pos = request_handlers.find(command); + if (handler_pos != request_handlers.end()) { + handler_pos->second(object); + } else { + if (g_state.log) + *g_state.log << "error: unhandled command \"" << command << 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; +} Index: tools/lldb-vscode/package.json =================================================================== --- tools/lldb-vscode/package.json +++ 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": true + }, + "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}/", + "args": [], + "env": [], + "cwd": "${workspaceRoot}" + } + ], + "configurationSnippets": [ + { + "label": "LLDB: Launch", + "description": "", + "body": { + "type": "lldb-vscode", + "request": "launch", + "name": "${2:Launch}", + "program": "^\"\\${workspaceRoot}/${1:}\"", + "args": [], + "env": [], + "cwd": "^\"\\${workspaceRoot}\"" + } + } + ] + } + ] + } +}