-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Add the ability to break on call-site locations, improve inline stepping #112939
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
Changes from 1 commit
9c6705b
7a60871
a19b3dc
b35e42b
ecda9b3
8662acf
38f883f
03764f2
594fb63
e3c26b0
9861c7d
a2aeab6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,10 +11,12 @@ | |||||||||
|
|
||||||||||
| #include <memory> | ||||||||||
| #include <mutex> | ||||||||||
| #include <optional> | ||||||||||
|
|
||||||||||
| #include "lldb/Breakpoint/BreakpointOptions.h" | ||||||||||
| #include "lldb/Breakpoint/StoppointHitCounter.h" | ||||||||||
| #include "lldb/Core/Address.h" | ||||||||||
| #include "lldb/Symbol/LineEntry.h" | ||||||||||
| #include "lldb/Utility/UserID.h" | ||||||||||
| #include "lldb/lldb-private.h" | ||||||||||
|
|
||||||||||
|
|
@@ -281,6 +283,18 @@ class BreakpointLocation | |||||||||
|
|
||||||||||
| /// Returns the breakpoint location ID. | ||||||||||
| lldb::break_id_t GetID() const { return m_loc_id; } | ||||||||||
|
|
||||||||||
| // Set the line entry that should be shown to users for this location. | ||||||||||
| // It is up to the caller to verify that this is a valid entry to show. | ||||||||||
| // The current use of this is to distinguish among line entries from a | ||||||||||
| // virtual inlined call stack that all share the same address. | ||||||||||
| void SetPreferredLineEntry(const LineEntry &line_entry) { | ||||||||||
| m_preferred_line_entry = line_entry; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const std::optional<LineEntry> GetPreferredLineEntry() { | ||||||||||
| return m_preferred_line_entry; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| protected: | ||||||||||
| friend class BreakpointSite; | ||||||||||
|
|
@@ -305,6 +319,16 @@ class BreakpointLocation | |||||||||
| /// It also takes care of decrementing the ignore counters. | ||||||||||
| /// If it returns false we should continue, otherwise stop. | ||||||||||
| bool IgnoreCountShouldStop(); | ||||||||||
|
|
||||||||||
| // If this location knows that the virtual stack frame it represents is | ||||||||||
| // not frame 0, return the suggested stack frame instead. This will happen | ||||||||||
| // when the location's address contains a "virtual inlined call stack" and the | ||||||||||
| // breakpoint was set on a file & line that are not at the bottom of that | ||||||||||
| // stack. For now we key off the "preferred line entry" - looking for that | ||||||||||
| // in the blocks that start with the stop PC. | ||||||||||
| // This version of the API doesn't take an "inlined" parameter because it | ||||||||||
| // only changes frames in the inline stack. | ||||||||||
|
||||||||||
| std::optional<uint32_t> GetSuggestedStackFrameIndex(); | ||||||||||
|
|
||||||||||
| private: | ||||||||||
| void SwapLocation(lldb::BreakpointLocationSP swap_from); | ||||||||||
|
|
@@ -369,6 +393,13 @@ class BreakpointLocation | |||||||||
| lldb::break_id_t m_loc_id; ///< Breakpoint location ID. | ||||||||||
| StoppointHitCounter m_hit_counter; ///< Number of times this breakpoint | ||||||||||
| /// location has been hit. | ||||||||||
| std::optional<LineEntry> m_preferred_line_entry; // If this exists, use it to print the stop | ||||||||||
|
||||||||||
| std::optional<LineEntry> m_preferred_line_entry; // If this exists, use it to print the stop | |
| /// If this exists, use it to print the stop description rather than the LineEntry | |
| /// ... | |
| std::optional<LineEntry> m_preferred_line_entry; |
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.
And if you use the Doxygen comment syntax, IDEs will know to associate the comment with the following declaration.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -508,8 +508,19 @@ void BreakpointLocation::GetDescription(Stream *s, | |||||||||
| s->PutCString("re-exported target = "); | ||||||||||
| else | ||||||||||
| s->PutCString("where = "); | ||||||||||
|
|
||||||||||
| // If there's a preferred line entry for printing, use that. | ||||||||||
| bool show_function_info = true; | ||||||||||
| if (GetPreferredLineEntry()) { | ||||||||||
| sc.line_entry = *GetPreferredLineEntry(); | ||||||||||
|
||||||||||
| if (GetPreferredLineEntry()) { | |
| sc.line_entry = *GetPreferredLineEntry(); | |
| if (auto may_be_line_entry = GetPreferredLineEntry()) { | |
| sc.line_entry = *may_be_line_entry; |
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.
Would a solution that changed GetPreferredLineEntry() to be GetPreferredSymbolContext() where it would return a SymbolContext object where anything that is valid in the SymbolContext would end up overriding the actual symbol context in sc? We might want to add a new function like:
SymbolContext BreakpointLocation::CalculateSymbolContext()
That would calll m_address.CalculateSymbolContext(&sc); and then insert any preferred entries into that symbol context. That would allow anyone to get the right symbol context for a BreakpointLocation easily. Then this function would change or need to know anything about the preferred stuff.
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 would it mean to replace the Module, the CompileUnit or the Function? This is for a breakpoint location, so whatever we do has to result in this being the same address. I was hesitant to introduce a more general "breakpoint gets to modify its symbol context" if I didn't know what it meant or how to ensure we didn't end up with a SymbolContext that wasn't at the breakpoint location's address. I was originally planning to JUST store the "inlined call stack depth" to make it clear this couldn't change the PC value, but that ended up being more annoying than helpful.
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.
I added one more change here, since the intention is that you cannot change the PC of the breakpoint Location by setting a PreferredLineEntry, I changed the code to actually check that when you call SetPreferredLineEntry. I put in an assert in the setter, so we can catch any violations of this in the testsuite, and then if this happens IRL, I made the BreakpointLocation log the event and ignore the setting.
jimingham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
jimingham marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
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.
Should there be a (if depth > 1000) return {} condition to deal with completely broken debug info?
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.
That would mean there were cycles in the blocks? I don't think we guard against that anywhere else that we're traversing the block structure.
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -251,7 +251,8 @@ void CompileUnit::ResolveSymbolContext( | |||||||
| SymbolContextItem resolve_scope, SymbolContextList &sc_list, | ||||||||
| RealpathPrefixes *realpath_prefixes) { | ||||||||
| const FileSpec file_spec = src_location_spec.GetFileSpec(); | ||||||||
| const uint32_t line = src_location_spec.GetLine().value_or(0); | ||||||||
| const uint32_t line = src_location_spec.GetLine().value_or(LLDB_INVALID_LINE_NUMBER); | ||||||||
| const uint32_t column_num = src_location_spec.GetColumn().value_or(LLDB_INVALID_COLUMN_NUMBER); | ||||||||
| const bool check_inlines = src_location_spec.GetCheckInlines(); | ||||||||
|
|
||||||||
| // First find all of the file indexes that match our "file_spec". If | ||||||||
|
|
@@ -311,6 +312,107 @@ void CompileUnit::ResolveSymbolContext( | |||||||
| line_idx = line_table->FindLineEntryIndexByFileIndex( | ||||||||
| 0, file_indexes, src_location_spec, &line_entry); | ||||||||
| } | ||||||||
|
|
||||||||
| // If we didn't manage to find a breakpoint that matched the line number | ||||||||
| // requested, that might be because it is only an inline call site, and | ||||||||
| // doesn't have a line entry in the line table. Scan for that here. | ||||||||
| // | ||||||||
| // We are making the assumption that if there was an inlined function it will | ||||||||
| // contribute at least 1 non-call-site entry to the line table. That's handy | ||||||||
| // because we don't move line breakpoints over function boundaries, so if we | ||||||||
| // found a hit, and there were also a call site entry, it would have to be in | ||||||||
| // the function containing the PC of the line table match. That way we can | ||||||||
| // limit the call site search to that function. | ||||||||
| // We will miss functions that ONLY exist as a call site entry. | ||||||||
|
|
||||||||
| if (line_entry.IsValid() && (line_entry.line != line || line_entry.column != column_num) | ||||||||
| && resolve_scope & eSymbolContextLineEntry && check_inlines) { | ||||||||
| // We don't move lines over function boundaries, so the address in the | ||||||||
| // line entry will be the in function that contained the line that might | ||||||||
| // be a CallSite, and we can just iterate over that function to find any | ||||||||
| // inline records, and dig up their call sites. | ||||||||
| Address start_addr = line_entry.range.GetBaseAddress(); | ||||||||
| Function *function = start_addr.CalculateSymbolContextFunction(); | ||||||||
|
|
||||||||
| Declaration sought_decl(file_spec, line, column_num); | ||||||||
| // We use this recursive function to descend the block structure looking | ||||||||
| // for a block that has this Declaration as in it's CallSite info. | ||||||||
| // This function recursively scans the sibling blocks of the incoming | ||||||||
| // block parameter. | ||||||||
| std::function<void(Block&)> examine_block = | ||||||||
| [&sought_decl, &sc_list, &src_location_spec, resolve_scope, &examine_block] (Block &block) -> void { | ||||||||
| // Iterate over the sibling child blocks of the incoming block. | ||||||||
| Block *sibling_block = block.GetFirstChild(); | ||||||||
| while (sibling_block) { | ||||||||
| // We only have to descend through the regular blocks, looking for | ||||||||
| // immediate inlines, since those are the only ones that will have this | ||||||||
| // callsite. | ||||||||
| const InlineFunctionInfo *inline_info = sibling_block->GetInlinedFunctionInfo(); | ||||||||
| if (inline_info) { | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that's right. We still need to do the bit after the if block to recurse the children and set the next sibling block. I'd have to have a "goto", you can't just continue here. |
||||||||
| // If this is the call-site we are looking for, record that: | ||||||||
| // We need to be careful because the call site from the debug info | ||||||||
| // will generally have a column, but the user might not have specified | ||||||||
| // it. | ||||||||
| Declaration found_decl = inline_info->GetCallSite(); | ||||||||
| uint32_t sought_column = sought_decl.GetColumn(); | ||||||||
| if (found_decl.FileAndLineEqual(sought_decl) | ||||||||
| && (sought_column == LLDB_INVALID_COLUMN_NUMBER | ||||||||
| || sought_column == found_decl.GetColumn())) { | ||||||||
| // If we found a call site, it belongs not in this inlined block, | ||||||||
| // but in the parent block that inlined it. | ||||||||
| Address parent_start_addr; | ||||||||
| if (sibling_block->GetParent()->GetStartAddress(parent_start_addr)) { | ||||||||
| SymbolContext sc; | ||||||||
| parent_start_addr.CalculateSymbolContext(&sc, resolve_scope); | ||||||||
| // Now swap out the line entry for the one we found. | ||||||||
| LineEntry call_site_line = sc.line_entry; | ||||||||
| call_site_line.line = found_decl.GetLine(); | ||||||||
| call_site_line.column = found_decl.GetColumn(); | ||||||||
| bool matches_spec = true; | ||||||||
| // If the user asked for an exact match, we need to make sure the | ||||||||
| // call site we found actually matches the location. | ||||||||
| if (src_location_spec.GetExactMatch()) { | ||||||||
| matches_spec = false; | ||||||||
| if ((src_location_spec.GetFileSpec() == sc.line_entry.GetFile()) | ||||||||
| && (src_location_spec.GetLine() && | ||||||||
| *src_location_spec.GetLine() == call_site_line.line) | ||||||||
| && (src_location_spec.GetColumn() && | ||||||||
| *src_location_spec.GetColumn() == call_site_line.column)) | ||||||||
| matches_spec = true; | ||||||||
| } | ||||||||
| if (matches_spec && | ||||||||
| sibling_block->GetRangeAtIndex(0, call_site_line.range)) { | ||||||||
| SymbolContext call_site_sc(sc.target_sp, sc.module_sp, sc.comp_unit, | ||||||||
| sc.function, sc.block, &call_site_line, | ||||||||
| sc.symbol); | ||||||||
| sc_list.Append(call_site_sc); | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| // Descend into the child blocks: | ||||||||
| examine_block(*sibling_block); | ||||||||
| // Now go to the next sibling: | ||||||||
| sibling_block = sibling_block->GetSibling(); | ||||||||
| } | ||||||||
| }; | ||||||||
|
|
||||||||
| if (function) { | ||||||||
| // We don't need to examine the function block, it can't be inlined. | ||||||||
| Block &func_block = function->GetBlock(true); | ||||||||
| examine_block(func_block); | ||||||||
| } | ||||||||
| // If we found entries here, we are done. We only get here because we | ||||||||
| // didn't find an exact line entry for this line & column, but if we found | ||||||||
| // an exact match from the call site info that's strictly better than | ||||||||
| // continuing to look for matches further on in the file. | ||||||||
| // FIXME: Should I also do this for "call site line exists between the | ||||||||
| // given line number and the later line we found in the line table"? That's | ||||||||
| // a closer approximation to our general sliding algorithm. | ||||||||
| if (sc_list.GetSize()) | ||||||||
| return; | ||||||||
| } | ||||||||
|
|
||||||||
| // If "exact == true", then "found_line" will be the same as "line". If | ||||||||
| // "exact == false", the "found_line" will be the closest line entry | ||||||||
|
|
||||||||
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: these should all be Doxygen comments using ///