-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[lldb-dap] Support throw and catch exception breakpoints for dynamica… #97871
[lldb-dap] Support throw and catch exception breakpoints for dynamica… #97871
Conversation
@llvm/pr-subscribers-lldb Author: Walter Erquinigo (walter-erquinigo) Changes…lly registered languages First of all, this is done to support exceptions for the Mojo language, but it's done in a way that will benefit any other plugin language.
Full diff: https://github.com/llvm/llvm-project/pull/97871.diff 7 Files Affected:
diff --git a/lldb/include/lldb/API/SBLanguageRuntime.h b/lldb/include/lldb/API/SBLanguageRuntime.h
index 38aac05d490c19..acdc256fa2ac5a 100644
--- a/lldb/include/lldb/API/SBLanguageRuntime.h
+++ b/lldb/include/lldb/API/SBLanguageRuntime.h
@@ -18,6 +18,32 @@ class SBLanguageRuntime {
static lldb::LanguageType GetLanguageTypeFromString(const char *string);
static const char *GetNameForLanguageType(lldb::LanguageType language);
+
+ /// Returns whether the given language is any version of C++.
+ static bool LanguageIsCPlusPlus(lldb::LanguageType language);
+
+ /// Returns whether the given language is Obj-C or Obj-C++.
+ static bool LanguageIsObjC(lldb::LanguageType language);
+
+ /// Returns whether the given language is any version of C, C++ or Obj-C.
+ static bool LanguageIsCFamily(lldb::LanguageType language);
+
+ /// Returns whether the given language supports exception breakpoints on
+ /// throw statements.
+ static bool SupportsExceptionBreakpointsOnThrow(lldb::LanguageType language);
+
+ /// Returns whether the given language supports exception breakpoints on
+ /// catch statements.
+ static bool SupportsExceptionBreakpointsOnCatch(lldb::LanguageType language);
+
+ /// Returns the keyword used for throw statements in the given language, e.g.
+ /// Python uses \b raise. Returns \b nullptr if the language is not supported.
+ static const char *GetThrowKeywordForLanguage(lldb::LanguageType language);
+
+ /// Returns the keyword used for catch statements in the given language, e.g.
+ /// Python uses \b except. Returns \b nullptr if the language is not
+ /// supported.
+ static const char *GetCatchKeywordForLanguage(lldb::LanguageType language);
};
} // namespace lldb
diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h
index 83bf7635e369a5..41d8eeef469eab 100644
--- a/lldb/include/lldb/Target/Language.h
+++ b/lldb/include/lldb/Target/Language.h
@@ -371,6 +371,14 @@ class Language : public PluginInterface {
/// a corresponding LanguageRuntime plugin.
virtual bool SupportsExceptionBreakpointsOnCatch() const { return false; }
+ /// Returns the keyword used for throw statements in this language, e.g.
+ /// Python uses \b raise. Defaults to \b throw.
+ virtual llvm::StringRef GetThrowKeyword() const { return "throw"; }
+
+ /// Returns the keyword used for catch statements in this language, e.g.
+ /// Python uses \b except. Defaults to \b catch.
+ virtual llvm::StringRef GetCatchKeyword() const { return "catch"; }
+
protected:
// Classes that inherit from Language can see and modify these
diff --git a/lldb/source/API/SBLanguageRuntime.cpp b/lldb/source/API/SBLanguageRuntime.cpp
index d571f282fce03d..3926c57efedf54 100644
--- a/lldb/source/API/SBLanguageRuntime.cpp
+++ b/lldb/source/API/SBLanguageRuntime.cpp
@@ -26,3 +26,43 @@ SBLanguageRuntime::GetNameForLanguageType(lldb::LanguageType language) {
return Language::GetNameForLanguageType(language);
}
+
+bool SBLanguageRuntime::LanguageIsCPlusPlus(lldb::LanguageType language) {
+ return Language::LanguageIsCPlusPlus(language);
+}
+
+bool SBLanguageRuntime::LanguageIsObjC(lldb::LanguageType language) {
+ return Language::LanguageIsObjC(language);
+}
+
+bool SBLanguageRuntime::LanguageIsCFamily(lldb::LanguageType language) {
+ return Language::LanguageIsCFamily(language);
+}
+
+bool SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(
+ lldb::LanguageType language) {
+ if (Language *lang_plugin = Language::FindPlugin(language))
+ return lang_plugin->SupportsExceptionBreakpointsOnThrow();
+ return false;
+}
+
+bool SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(
+ lldb::LanguageType language) {
+ if (Language *lang_plugin = Language::FindPlugin(language))
+ return lang_plugin->SupportsExceptionBreakpointsOnCatch();
+ return false;
+}
+
+const char *
+SBLanguageRuntime::GetThrowKeywordForLanguage(lldb::LanguageType language) {
+ if (Language *lang_plugin = Language::FindPlugin(language))
+ return lang_plugin->GetThrowKeyword().data();
+ return nullptr;
+}
+
+const char *
+SBLanguageRuntime::GetCatchKeywordForLanguage(lldb::LanguageType language) {
+ if (Language *lang_plugin = Language::FindPlugin(language))
+ return lang_plugin->GetCatchKeyword().data();
+ return nullptr;
+}
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 0196aed819f2b4..569f0539877e56 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -58,10 +58,17 @@ DAP::DAP()
DAP::~DAP() = default;
+/// Return string with first character capitalized.
+static std::string capitalize(llvm::StringRef str) {
+ if (str.empty())
+ return "";
+ return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str();
+}
+
void DAP::PopulateExceptionBreakpoints() {
llvm::call_once(init_exception_breakpoints_flag, [this]() {
exception_breakpoints = std::vector<ExceptionBreakpoint> {};
-
+
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
exception_breakpoints->emplace_back("cpp_catch", "C++ Catch",
lldb::eLanguageTypeC_plus_plus);
@@ -80,6 +87,46 @@ void DAP::PopulateExceptionBreakpoints() {
exception_breakpoints->emplace_back("swift_throw", "Swift Throw",
lldb::eLanguageTypeSwift);
}
+ // Besides handling the hardcoded list of languages from above, we try to
+ // find any other languages that support exception breakpoints using the
+ // SB API.
+ for (int raw_lang = lldb::eLanguageTypeUnknown;
+ raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
+ lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);
+
+ // We first discard any languages already handled above.
+ if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
+ lang == lldb::eLanguageTypeSwift)
+ continue;
+
+ if (!lldb::SBDebugger::SupportsLanguage(lang))
+ continue;
+
+ const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
+ if (!name)
+ continue;
+ std::string raw_lang_name = name;
+ std::string capitalized_lang_name = capitalize(name);
+ const char *raw_throw_keyword =
+ lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
+ const char *raw_catch_keyword =
+ lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
+ std::string throw_keyword =
+ raw_throw_keyword ? raw_throw_keyword : "throw";
+ std::string catch_keyword =
+ raw_catch_keyword ? raw_catch_keyword : "catch";
+
+ if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
+ exception_breakpoints->emplace_back(
+ raw_lang_name + "_" + throw_keyword,
+ capitalized_lang_name + " " + capitalize(throw_keyword), lang);
+ }
+ if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
+ exception_breakpoints->emplace_back(
+ raw_lang_name + "_" + catch_keyword,
+ capitalized_lang_name + " " + capitalize(catch_keyword), lang);
+ }
+ }
assert(!exception_breakpoints->empty() && "should not be empty");
});
}
@@ -514,6 +561,12 @@ llvm::Error DAP::RunInitCommands() {
return llvm::Error::success();
}
+llvm::Error DAP::RunPreInitCommands() {
+ if (!RunLLDBCommands("Running preInitCommands:", pre_init_commands))
+ return createRunLLDBCommandsErrorMessage("preInitCommands");
+ return llvm::Error::success();
+}
+
llvm::Error DAP::RunPreRunCommands() {
if (!RunLLDBCommands("Running preRunCommands:", pre_run_commands))
return createRunLLDBCommandsErrorMessage("preRunCommands");
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 37e57d58968d90..57562a14983519 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -158,6 +158,7 @@ struct DAP {
FunctionBreakpointMap function_breakpoints;
std::optional<std::vector<ExceptionBreakpoint>> exception_breakpoints;
llvm::once_flag init_exception_breakpoints_flag;
+ std::vector<std::string> pre_init_commands;
std::vector<std::string> init_commands;
std::vector<std::string> pre_run_commands;
std::vector<std::string> post_run_commands;
@@ -246,6 +247,7 @@ struct DAP {
llvm::Error RunAttachCommands(llvm::ArrayRef<std::string> attach_commands);
llvm::Error RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands);
+ llvm::Error RunPreInitCommands();
llvm::Error RunInitCommands();
llvm::Error RunPreRunCommands();
void RunPostRunCommands();
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index 571967b232b4a2..d7b4a065abec01 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -43,3 +43,11 @@ def debugger_pid: S<"debugger-pid">,
def repl_mode: S<"repl-mode">,
MetaVarName<"<mode>">,
HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
+
+def pre_init_command: S<"pre-init-command">,
+ MetaVarName<"<command>">,
+ HelpText<"A command to execute before the DAP initialization request and "
+ "right after a Debugger has been created.">;
+def: Separate<["-"], "c">,
+ Alias<pre_init_command>,
+ HelpText<"Alias for --pre-init-command">;
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index b74474b9d383c0..b50d40acb51a21 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -1600,6 +1600,10 @@ void request_modules(const llvm::json::Object &request) {
// }]
// }
void request_initialize(const llvm::json::Object &request) {
+ llvm::json::Object response;
+ FillResponse(request, response);
+ llvm::json::Object body;
+
auto log_cb = [](const char *buf, void *baton) -> void {
g_dap.SendOutput(OutputType::Console, llvm::StringRef{buf});
};
@@ -1611,6 +1615,13 @@ void request_initialize(const llvm::json::Object &request) {
bool source_init_file = GetBoolean(arguments, "sourceInitFile", true);
g_dap.debugger = lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
+ if (llvm::Error err = g_dap.RunPreInitCommands()) {
+ response["success"] = false;
+ EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+ g_dap.SendJSON(llvm::json::Value(std::move(response)));
+ return;
+ }
+
g_dap.PopulateExceptionBreakpoints();
auto cmd = g_dap.debugger.GetCommandInterpreter().AddMultiwordCommand(
"lldb-dap", "Commands for managing lldb-dap.");
@@ -1630,9 +1641,6 @@ void request_initialize(const llvm::json::Object &request) {
// process and more.
g_dap.event_thread = std::thread(EventThreadFunction);
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
// The debug adapter supports the configurationDoneRequest.
body.try_emplace("supportsConfigurationDoneRequest", true);
// The debug adapter supports function breakpoints.
@@ -4318,6 +4326,11 @@ int main(int argc, char *argv[]) {
g_dap.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false);
}
+ for (const std::string &arg :
+ input_args.getAllArgValues(OPT_pre_init_command)) {
+ g_dap.pre_init_commands.push_back(arg);
+ }
+
bool CleanExit = true;
if (auto Err = g_dap.Loop()) {
if (g_dap.log)
|
63ab70e
to
aa2ad3b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems okay to me, but I'm not exactly an expert when it comes to lldb-dap. You'll probably want to get sign-off from at least another person.
…lly registered languages First of all, this is done to support exceptions for the Mojo language, but it's done in a way that will benefit any other plugin language. 1. I added a new lldb-dap CLI argument (not DAP field) called `pre-init-commands`. These commands are executed before DAP initialization. The other `init-commands` are executed after DAP initialization. It's worth mentioning that the debug adapter returns to VSCode the list of supported exception breakpoints during DAP initialization, which means that I need to register the Mojo plugin before that initialization step, hence the need for `pre-init-commands`. In general, language plugins should be registered in that step, as they affect the capabilities of the debugger. 2. I added a set of APIs for lldb-dap to query information of each language related to exception breakpoints. E.g. whether a language supports throw or catch breakpoints, how the throw keyword is called in each particular language, etc. 3. I'm realizing that the Swift support for exception breakpoints in lldb-dap should have been implemented in this way, instead of hardcoding it.
aa2ad3b
to
114fd57
Compare
This is a very benign change, so I'll be merging it. Happy to make change post-merge if anyone provides any feedback. |
llvm#97871) …lly registered languages First of all, this is done to support exceptions for the Mojo language, but it's done in a way that will benefit any other plugin language. 1. I added a new lldb-dap CLI argument (not DAP field) called `pre-init-commands`. These commands are executed before DAP initialization. The other `init-commands` are executed after DAP initialization. It's worth mentioning that the debug adapter returns to VSCode the list of supported exception breakpoints during DAP initialization, which means that I need to register the Mojo plugin before that initialization step, hence the need for `pre-init-commands`. In general, language plugins should be registered in that step, as they affect the capabilities of the debugger. 2. I added a set of APIs for lldb-dap to query information of each language related to exception breakpoints. E.g. whether a language supports throw or catch breakpoints, how the throw keyword is called in each particular language, etc. 3. I'm realizing that the Swift support for exception breakpoints in lldb-dap should have been implemented in this way, instead of hardcoding it.
…lly registered languages
First of all, this is done to support exceptions for the Mojo language, but it's done in a way that will benefit any other plugin language.
pre-init-commands
. These commands are executed before DAP initialization. The otherinit-commands
are executed after DAP initialization. It's worth mentioning that the debug adapter returns to VSCode the list of supported exception breakpoints during DAP initialization, which means that I need to register the Mojo plugin before that initialization step, hence the need forpre-init-commands
. In general, language plugins should be registered in that step, as they affect the capabilities of the debugger.