-
Notifications
You must be signed in to change notification settings - Fork 13.5k
Make breakpoint stop reason more accurate for function breakpoints #130841
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
base: main
Are you sure you want to change the base?
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-lldb Author: None (satyajanga) ChangesFull diff: https://github.com/llvm/llvm-project/pull/130841.diff 4 Files Affected:
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index 70b04b051e0ec..5faf83ca826a0 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -86,6 +86,7 @@ def verify_breakpoint_hit(self, breakpoint_ids):
if (
body["reason"] != "breakpoint"
and body["reason"] != "instruction breakpoint"
+ and body["reason"] != "function breakpoint"
):
continue
if "description" not in body:
@@ -100,7 +101,7 @@ def verify_breakpoint_hit(self, breakpoint_ids):
# location.
description = body["description"]
for breakpoint_id in breakpoint_ids:
- match_desc = "breakpoint %s." % (breakpoint_id)
+ match_desc = "%s %s." % (body["reason"], breakpoint_id)
if match_desc in description:
return
self.assertTrue(False, "breakpoint not hit")
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index edd3b31be8ff7..485c420dc59e8 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -1128,6 +1128,32 @@ DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) {
return inst_bp;
}
+FunctionBreakpoint *DAP::GetFunctionBPFromStopReason(lldb::SBThread &thread) {
+ const auto num = thread.GetStopReasonDataCount();
+ FunctionBreakpoint *func_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 function breakpoint that is getting hit.
+ lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
+ func_bp = GetFunctionBreakPoint(bp_id);
+ // If any breakpoint is not an function breakpoint, then stop and
+ // report this as a normal breakpoint
+ if (func_bp == nullptr)
+ return nullptr;
+ }
+ return func_bp;
+}
+
+FunctionBreakpoint *DAP::GetFunctionBreakPoint(const lldb::break_id_t bp_id) {
+ for (auto &bp : function_breakpoints) {
+ if (bp.second.bp.GetID() == bp_id)
+ return &bp.second;
+ }
+ return nullptr;
+}
+
lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
switch (variablesReference) {
case VARREF_LOCALS:
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 3ff1992b61f5b..ea72b41d02516 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -125,21 +125,21 @@ struct Variables {
struct StartDebuggingRequestHandler : public lldb::SBCommandPluginInterface {
DAP &dap;
- explicit StartDebuggingRequestHandler(DAP &d) : dap(d) {};
+ explicit StartDebuggingRequestHandler(DAP &d) : dap(d){};
bool DoExecute(lldb::SBDebugger debugger, char **command,
lldb::SBCommandReturnObject &result) override;
};
struct ReplModeRequestHandler : public lldb::SBCommandPluginInterface {
DAP &dap;
- explicit ReplModeRequestHandler(DAP &d) : dap(d) {};
+ explicit ReplModeRequestHandler(DAP &d) : dap(d){};
bool DoExecute(lldb::SBDebugger debugger, char **command,
lldb::SBCommandReturnObject &result) override;
};
struct SendEventRequestHandler : public lldb::SBCommandPluginInterface {
DAP &dap;
- explicit SendEventRequestHandler(DAP &d) : dap(d) {};
+ explicit SendEventRequestHandler(DAP &d) : dap(d){};
bool DoExecute(lldb::SBDebugger debugger, char **command,
lldb::SBCommandReturnObject &result) override;
};
@@ -383,6 +383,12 @@ struct DAP {
InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread);
+ FunctionBreakpoint *GetFunctionBPFromStopReason(lldb::SBThread &thread);
+
+ FunctionBreakpoint *GetFunctionBreakPoint(const lldb::break_id_t bp_id);
+
+ void WaitWorkerThreadsToExit();
+
private:
// Send the JSON in "json_str" to the "out" stream. Correctly send the
// "Content-Length:" field followed by the length, followed by the raw
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 932145b1799bd..cd0d518cd4f7d 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -967,17 +967,23 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,
body.try_emplace("reason", "exception");
EmplaceSafeString(body, "description", exc_bp->label);
} else {
+ std::string reason = "breakpoint";
InstructionBreakpoint *inst_bp =
dap.GetInstructionBPFromStopReason(thread);
if (inst_bp) {
- body.try_emplace("reason", "instruction breakpoint");
+ reason = "instruction breakpoint";
} else {
- body.try_emplace("reason", "breakpoint");
+ FunctionBreakpoint *function_bp =
+ dap.GetFunctionBPFromStopReason(thread);
+ if (function_bp) {
+ reason = "function breakpoint";
+ }
}
+ body.try_emplace("reason", reason);
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0);
lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(1);
std::string desc_str =
- llvm::formatv("breakpoint {0}.{1}", bp_id, bp_loc_id);
+ llvm::formatv("{0} {1}.{2}", reason, bp_id, bp_loc_id);
body.try_emplace("hitBreakpointIds",
llvm::json::Array{llvm::json::Value(bp_id)});
EmplaceSafeString(body, "description", desc_str);
|
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.
tools/lldb-dap/attach/TestDAP_attach.py : creates function breakpoints tests that they are hit.
tools/lldb-dap/breakpoint/TestDAP_setFunctionBreakpoints.py also creates breakpoints tests that they are hit.
Are these two tests checking "reason" field in stopped DAP event?
lldb/tools/lldb-dap/DAP.cpp
Outdated
FunctionBreakpoint *DAP::GetFunctionBPFromStopReason(lldb::SBThread &thread) { | ||
const auto num = thread.GetStopReasonDataCount(); | ||
FunctionBreakpoint *func_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 function breakpoint that is getting hit. | ||
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i); | ||
func_bp = GetFunctionBreakPoint(bp_id); | ||
// If any breakpoint is not an function breakpoint, then stop and | ||
// report this as a normal breakpoint | ||
if (func_bp == nullptr) | ||
return nullptr; | ||
} | ||
return func_bp; | ||
} | ||
|
||
FunctionBreakpoint *DAP::GetFunctionBreakPoint(const lldb::break_id_t bp_id) { | ||
for (auto &bp : function_breakpoints) { | ||
if (bp.second.bp.GetID() == bp_id) | ||
return &bp.second; | ||
} | ||
return nullptr; | ||
} | ||
|
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.
These two functions are exact the same logic as GetInstructionBPFromStopReason
and GetInstructionBreakpoint
. Can you refactor them into a common function with template for FunctionBreakpoint
vs InstructionBreakpoint
?
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.
Done. I also refactored in Exception Break points.
Curious to know your thoughts about putting the specializations in header file. Another thought was adding DAP-inl.h and add them there.
lldb/tools/lldb-dap/DAP.h
Outdated
explicit StartDebuggingRequestHandler(DAP &d) : dap(d){}; | ||
bool DoExecute(lldb::SBDebugger debugger, char **command, | ||
lldb::SBCommandReturnObject &result) override; | ||
}; | ||
|
||
struct ReplModeRequestHandler : public lldb::SBCommandPluginInterface { | ||
DAP &dap; | ||
explicit ReplModeRequestHandler(DAP &d) : dap(d) {}; | ||
explicit ReplModeRequestHandler(DAP &d) : dap(d){}; | ||
bool DoExecute(lldb::SBDebugger debugger, char **command, | ||
lldb::SBCommandReturnObject &result) override; | ||
}; | ||
|
||
struct SendEventRequestHandler : public lldb::SBCommandPluginInterface { | ||
DAP &dap; | ||
explicit SendEventRequestHandler(DAP &d) : dap(d) {}; | ||
explicit SendEventRequestHandler(DAP &d) : dap(d){}; |
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.
Undo these unrelated changes (you can do in VSCode's diff view).
lldb/tools/lldb-dap/DAP.h
Outdated
|
||
FunctionBreakpoint *GetFunctionBreakPoint(const lldb::break_id_t bp_id); | ||
|
||
void WaitWorkerThreadsToExit(); |
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.
What is this?
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.
Removed it, it got added when resolving the merge conflict.
lldb/tools/lldb-dap/JSONUtils.cpp
Outdated
@@ -967,17 +967,23 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, | |||
body.try_emplace("reason", "exception"); | |||
EmplaceSafeString(body, "description", exc_bp->label); | |||
} else { | |||
std::string reason = "breakpoint"; |
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.
You do not need to create a std::string
object, simply use StringRef or char * is enough.
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.
updated it
|
Yes. They call the |
6000508
to
6cc3b96
Compare
76f8213
to
dc97ca6
Compare
lldb/tools/lldb-dap/DAP.h
Outdated
template <> | ||
FunctionBreakpoint * | ||
GetBreakpoint<FunctionBreakpoint>(const lldb::break_id_t bp_id) { | ||
for (auto &bp : function_breakpoints) { |
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.
Because we're duplicating code here three times, would it make more sense that the template controls which breakpoint collection to grab, and then we just have the following logic in every case?
So we'd make GetBreakpoint
template <typename BreakpointType>
BreakpointType *GetBreakpoint(const lldb::break_id_t bp_id) {
for (auto &bp : GetBreakpoints<>) {
if (bp.second.bp.GetID() == bp_id)
return &bp.second;
}
return nullptr;
}
Where the new GetBreakpoints
call just gets the correct collection?
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.
Also, @satyajanga does the underlying collection support find()
? Can we call that instead of enumerating ourselves
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.
updated to use std:find_if
the types for the collections are different, so further templatization is challenging. Let me know if I am mis-undersanding.
exception_breakpoints -> std::optional<std::vector>
function_breakpoints -> llvm::StringMap
instruction_breakpoints -> llvm::DenseMap<lldb::addr_t, InstructionBreakpoint>
I am open to changing. just didn't see a way to control the return collection type for template function that returns the collections.
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.
Didn't realize they're all different. Let's not worry about it
lldb/tools/lldb-dap/JSONUtils.cpp
Outdated
@@ -962,28 +962,43 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, | |||
body.try_emplace("reason", "step"); | |||
break; | |||
case lldb::eStopReasonBreakpoint: { | |||
ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); | |||
ExceptionBreakpoint *exc_bp = |
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.
Nit, const?
(I feel like Miro!)
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.
changed it.
lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
Outdated
Show resolved
Hide resolved
edb06f3
to
23a710f
Compare
lldb/tools/lldb-dap/DAP.h
Outdated
return bp; | ||
} | ||
|
||
template <typename BreakpointType> BreakpointType *GetBreakpointCollection(); |
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.
We can remove this as unused
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.
Left one comment on the unused template. Otherwise LGTM, I'd wait for Jeffrey to give you a final sign off
✅ With the latest revision this PR passed the Python code formatter. |
9ae819f
to
8b5cd8f
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.
Pretty good start. There can be more than one breakpoint at a location and we need to be sure we test these cases. Like we can have an exception breakpoint (which might be at cxa_throw
) and we can have a function breakpoint for this as well. We need to make sure we document how we are doing to display this. If we must pick one, then lets be clear about:
- exception breakpoints take precedence
- then function breakpoints
- then source file and line breakpoints
If we have a free form text we can attach to the stop notification it might be nice to see "function breakpoint & exception breakpoint", but if we have to pick just one, then we need to be clear about what we are going to do.
Agree that there can be more than one breakpoint at a location. but 'reason' field in StoppedEvent only takes one string from fixed set of strings.
So the existing logic for instruction_breakpoint only sets the reason as 'instruction breakpoint' if all the breakpoints are instruction_breakpoints.
I think this is a reasonable behavior, I will document this in the code. @clayborg let me know what you think? |
lldb/tools/lldb-dap/DAP.h
Outdated
InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id); | ||
template <typename BreakpointType> | ||
BreakpointType *GetBreakpointFromStopReason(lldb::SBThread &thread) { | ||
// Check to see if have hit the <BreakpointType> breakpoint and change the |
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.
s/Check to see if have hit/Check to see if we have hit/
case lldb::eStopReasonWatchpoint: | ||
case lldb::eStopReasonWatchpoint: { | ||
// Assuming that all watch points are data breakpoints. | ||
body.try_emplace("reason", "data breakpoint"); |
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.
Are they called "data breakpoint" in VS Code? If so then this is ok. LLDB uses the term "watchpoint", but we are making this change for VS code.
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.
VS Code also calls them watchpoints.
"data breakpoint" is a DAP terminology.
https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetDataBreakpoints and they trigger SetDataBreakpointsRequestHandler
https://github.com/llvm/llvm-project/blob/main/lldb/tools/lldb-dap/Handler/SetDataBreakpointsRequestHandler.cpp
8b5cd8f
to
aa90036
Compare
VSCode uses DAP protocol (https://microsoft.github.io/debug-adapter-protocol/specification) to lldb-dap for native debugging. For breakpoint, current lldb-dap implementation simply returns "breakpoint" stop reason for most breakpoints.
However, DAP Stopped event (https://microsoft.github.io/debug-adapter-protocol/specification#Events_Stopped) actually supports more accurate breakpoint types such as '
function breakpoint
' | 'data breakpoint
which we did not leverage.This task aims to support showing function breakpoint and data breakpoint stop reason in VSCode.
Test Plan:
following tests already test the functionality for function and instruction breakpoints,
tools/lldb-dap/attach/TestDAP_attach.py : creates function breakpoints tests that they are hit.
tools/lldb-dap/breakpoint/TestDAP_setFunctionBreakpoints.py also creates breakpoints tests that they are hit.
They test the reason and description in DAPTestCaseBase.
verify_breakpoint_hit
For Data Break points (watch points) added a new testcase