Skip to content

Commit f147437

Browse files
authored
Add the ability to break on call-site locations, improve inline stepping (#112939)
Previously lldb didn't support setting breakpoints on call site locations. This patch adds that ability. It would be very slow if we did this by searching all the debug information for every inlined subroutine record looking for a call-site match, so I added one restriction to the call-site support. This change will find all call sites for functions that also supply at least one line to the regular line table. That way we can use the fact that the line table search will move the location to that subsequent line (but only within the same function). When we find an actually moved source line match, we can search in the function that contained that line table entry for the call-site, and set the breakpoint location back to that. When I started writing tests for this new ability, it quickly became obvious that our support for virtual inline stepping was pretty buggy. We didn't print the right file & line number for the breakpoint, and we didn't set the position in the "virtual inlined stack" correctly when we hit the breakpoint. We also didn't step through the inlined frames correctly. There was code to try to detect the right inlined stack position, but it had been refactored a while back with the comment that it was super confusing and the refactor was supposed to make it clearer, but the refactor didn't work either. That code was made much clearer by abstracting the job of "handling the stack readjustment" to the various StopInfo's. Previously, there was a big (and buggy) switch over stop info's. Moving the responsibility to the stop info made this code much easier to reason about. We also had no tests for virtual inlined stepping (our inlined stepping test was actually written specifically to avoid the formation of a virtual inlined stack... So I also added tests for that along with the tests for setting the call-site breakpoints.
1 parent af7c58b commit f147437

18 files changed

+493
-131
lines changed

lldb/include/lldb/Breakpoint/BreakpointLocation.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
#include <memory>
1313
#include <mutex>
14+
#include <optional>
1415

1516
#include "lldb/Breakpoint/BreakpointOptions.h"
1617
#include "lldb/Breakpoint/StoppointHitCounter.h"
1718
#include "lldb/Core/Address.h"
19+
#include "lldb/Symbol/LineEntry.h"
1820
#include "lldb/Utility/UserID.h"
1921
#include "lldb/lldb-private.h"
2022

@@ -282,6 +284,25 @@ class BreakpointLocation
282284
/// Returns the breakpoint location ID.
283285
lldb::break_id_t GetID() const { return m_loc_id; }
284286

287+
/// Set the line entry that should be shown to users for this location.
288+
/// It is up to the caller to verify that this is a valid entry to show.
289+
/// The current use of this is to distinguish among line entries from a
290+
/// virtual inlined call stack that all share the same address.
291+
/// The line entry must have the same start address as the address for this
292+
/// location.
293+
bool SetPreferredLineEntry(const LineEntry &line_entry) {
294+
if (m_address == line_entry.range.GetBaseAddress()) {
295+
m_preferred_line_entry = line_entry;
296+
return true;
297+
}
298+
assert(0 && "Tried to set a preferred line entry with a different address");
299+
return false;
300+
}
301+
302+
const std::optional<LineEntry> GetPreferredLineEntry() {
303+
return m_preferred_line_entry;
304+
}
305+
285306
protected:
286307
friend class BreakpointSite;
287308
friend class BreakpointLocationList;
@@ -306,6 +327,16 @@ class BreakpointLocation
306327
/// If it returns false we should continue, otherwise stop.
307328
bool IgnoreCountShouldStop();
308329

330+
/// If this location knows that the virtual stack frame it represents is
331+
/// not frame 0, return the suggested stack frame instead. This will happen
332+
/// when the location's address contains a "virtual inlined call stack" and
333+
/// the breakpoint was set on a file & line that are not at the bottom of that
334+
/// stack. For now we key off the "preferred line entry" - looking for that
335+
/// in the blocks that start with the stop PC.
336+
/// This version of the API doesn't take an "inlined" parameter because it
337+
/// only changes frames in the inline stack.
338+
std::optional<uint32_t> GetSuggestedStackFrameIndex();
339+
309340
private:
310341
void SwapLocation(lldb::BreakpointLocationSP swap_from);
311342

@@ -369,6 +400,11 @@ class BreakpointLocation
369400
lldb::break_id_t m_loc_id; ///< Breakpoint location ID.
370401
StoppointHitCounter m_hit_counter; ///< Number of times this breakpoint
371402
/// location has been hit.
403+
/// If this exists, use it to print the stop description rather than the
404+
/// LineEntry m_address resolves to directly. Use this for instance when the
405+
/// location was given somewhere in the virtual inlined call stack since the
406+
/// Address always resolves to the lowest entry in the stack.
407+
std::optional<LineEntry> m_preferred_line_entry;
372408

373409
void SetShouldResolveIndirectFunctions(bool do_resolve) {
374410
m_should_resolve_indirect_functions = do_resolve;

lldb/include/lldb/Breakpoint/BreakpointSite.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ class BreakpointSite : public std::enable_shared_from_this<BreakpointSite>,
170170
/// \see lldb::DescriptionLevel
171171
void GetDescription(Stream *s, lldb::DescriptionLevel level);
172172

173+
// This runs through all the breakpoint locations owning this site and returns
174+
// the greatest of their suggested stack frame indexes. This only handles
175+
// inlined stack changes.
176+
std::optional<uint32_t> GetSuggestedStackFrameIndex();
177+
173178
/// Tell whether a breakpoint has a location at this site.
174179
///
175180
/// \param[in] bp_id

lldb/include/lldb/Core/Declaration.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,14 @@ class Declaration {
8484
/// \param[in] declaration
8585
/// The const Declaration object to compare with.
8686
///
87+
/// \param[in] full
88+
/// Same meaning as Full in FileSpec::Equal. True means an empty
89+
/// directory is not equal to a specified one, false means it is equal.
90+
///
8791
/// \return
8892
/// Returns \b true if \b declaration is at the same file and
8993
/// line, \b false otherwise.
90-
bool FileAndLineEqual(const Declaration &declaration) const;
94+
bool FileAndLineEqual(const Declaration &declaration, bool full) const;
9195

9296
/// Dump a description of this object to a Stream.
9397
///

lldb/include/lldb/Target/StopInfo.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ class StopInfo : public std::enable_shared_from_this<StopInfo> {
7777
m_description.clear();
7878
}
7979

80+
/// This gives the StopInfo a chance to suggest a stack frame to select.
81+
/// Passing true for inlined_stack will request changes to the inlined
82+
/// call stack. Passing false will request changes to the real stack
83+
/// frame. The inlined stack gets adjusted before we call into the thread
84+
/// plans so they can reason based on the correct values. The real stack
85+
/// adjustment is handled after the frame recognizers get a chance to adjust
86+
/// the frame.
87+
virtual std::optional<uint32_t>
88+
GetSuggestedStackFrameIndex(bool inlined_stack) {
89+
return {};
90+
}
91+
8092
virtual bool IsValidForOperatingSystemThread(Thread &thread) { return true; }
8193

8294
/// A Continue operation can result in a false stop event

lldb/include/lldb/Target/ThreadPlanStepInRange.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ class ThreadPlanStepInRange : public ThreadPlanStepRange,
8080
bool m_step_past_prologue; // FIXME: For now hard-coded to true, we could put
8181
// a switch in for this if there's
8282
// demand for that.
83-
bool m_virtual_step; // true if we've just done a "virtual step", i.e. just
84-
// moved the inline stack depth.
83+
LazyBool m_virtual_step; // true if we've just done a "virtual step", i.e.
84+
// just moved the inline stack depth.
8585
ConstString m_step_into_target;
8686
ThreadPlanStepInRange(const ThreadPlanStepInRange &) = delete;
8787
const ThreadPlanStepInRange &

lldb/source/Breakpoint/BreakpointLocation.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,20 @@ void BreakpointLocation::GetDescription(Stream *s,
508508
s->PutCString("re-exported target = ");
509509
else
510510
s->PutCString("where = ");
511+
512+
// If there's a preferred line entry for printing, use that.
513+
bool show_function_info = true;
514+
if (auto preferred = GetPreferredLineEntry()) {
515+
sc.line_entry = *preferred;
516+
// FIXME: We're going to get the function name wrong when the preferred
517+
// line entry is not the lowest one. For now, just leave the function
518+
// out in this case, but we really should also figure out how to easily
519+
// fake the function name here.
520+
show_function_info = false;
521+
}
511522
sc.DumpStopContext(s, m_owner.GetTarget().GetProcessSP().get(), m_address,
512-
false, true, false, true, true, true);
523+
false, true, false, show_function_info,
524+
show_function_info, show_function_info);
513525
} else {
514526
if (sc.module_sp) {
515527
s->EOL();
@@ -537,7 +549,10 @@ void BreakpointLocation::GetDescription(Stream *s,
537549
if (sc.line_entry.line > 0) {
538550
s->EOL();
539551
s->Indent("location = ");
540-
sc.line_entry.DumpStopContext(s, true);
552+
if (auto preferred = GetPreferredLineEntry())
553+
preferred->DumpStopContext(s, true);
554+
else
555+
sc.line_entry.DumpStopContext(s, true);
541556
}
542557

543558
} else {
@@ -656,6 +671,50 @@ void BreakpointLocation::SendBreakpointLocationChangedEvent(
656671
}
657672
}
658673

674+
std::optional<uint32_t> BreakpointLocation::GetSuggestedStackFrameIndex() {
675+
auto preferred_opt = GetPreferredLineEntry();
676+
if (!preferred_opt)
677+
return {};
678+
LineEntry preferred = *preferred_opt;
679+
SymbolContext sc;
680+
if (!m_address.CalculateSymbolContext(&sc))
681+
return {};
682+
// Don't return anything special if frame 0 is the preferred line entry.
683+
// We not really telling the stack frame list to do anything special in that
684+
// case.
685+
if (!LineEntry::Compare(sc.line_entry, preferred))
686+
return {};
687+
688+
if (!sc.block)
689+
return {};
690+
691+
// Blocks have their line info in Declaration form, so make one here:
692+
Declaration preferred_decl(preferred.GetFile(), preferred.line,
693+
preferred.column);
694+
695+
uint32_t depth = 0;
696+
Block *inlined_block = sc.block->GetContainingInlinedBlock();
697+
while (inlined_block) {
698+
// If we've moved to a block that this isn't the start of, that's not
699+
// our inlining info or call site, so we can stop here.
700+
Address start_address;
701+
if (!inlined_block->GetStartAddress(start_address) ||
702+
start_address != m_address)
703+
return {};
704+
705+
const InlineFunctionInfo *info = inlined_block->GetInlinedFunctionInfo();
706+
if (info) {
707+
if (preferred_decl == info->GetDeclaration())
708+
return depth;
709+
if (preferred_decl == info->GetCallSite())
710+
return depth + 1;
711+
}
712+
inlined_block = inlined_block->GetInlinedParent();
713+
depth++;
714+
}
715+
return {};
716+
}
717+
659718
void BreakpointLocation::SwapLocation(BreakpointLocationSP swap_from) {
660719
m_address = swap_from->m_address;
661720
m_should_resolve_indirect_functions =

lldb/source/Breakpoint/BreakpointResolver.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,21 @@ void BreakpointResolver::AddLocation(SearchFilter &filter,
340340
}
341341

342342
BreakpointLocationSP bp_loc_sp(AddLocation(line_start));
343+
// If the address that we resolved the location to returns a different
344+
// LineEntry from the one in the incoming SC, we're probably dealing with an
345+
// inlined call site, so set that as the preferred LineEntry:
346+
LineEntry resolved_entry;
347+
if (!skipped_prologue && bp_loc_sp &&
348+
line_start.CalculateSymbolContextLineEntry(resolved_entry) &&
349+
LineEntry::Compare(resolved_entry, sc.line_entry)) {
350+
// FIXME: The function name will also be wrong here. Do we need to record
351+
// that as well, or can we figure that out again when we report this
352+
// breakpoint location.
353+
if (!bp_loc_sp->SetPreferredLineEntry(sc.line_entry)) {
354+
LLDB_LOG(log, "Tried to add a preferred line entry that didn't have the "
355+
"same address as this location's address.");
356+
}
357+
}
343358
if (log && bp_loc_sp && !GetBreakpoint()->IsInternal()) {
344359
StreamString s;
345360
bp_loc_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose);

lldb/source/Breakpoint/BreakpointSite.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,23 @@ void BreakpointSite::GetDescription(Stream *s, lldb::DescriptionLevel level) {
8787
m_constituents.GetDescription(s, level);
8888
}
8989

90+
std::optional<uint32_t> BreakpointSite::GetSuggestedStackFrameIndex() {
91+
92+
std::optional<uint32_t> result;
93+
std::lock_guard<std::recursive_mutex> guard(m_constituents_mutex);
94+
for (BreakpointLocationSP loc_sp : m_constituents.BreakpointLocations()) {
95+
std::optional<uint32_t> loc_frame_index =
96+
loc_sp->GetSuggestedStackFrameIndex();
97+
if (loc_frame_index) {
98+
if (result)
99+
result = std::max(*loc_frame_index, *result);
100+
else
101+
result = loc_frame_index;
102+
}
103+
}
104+
return result;
105+
}
106+
90107
bool BreakpointSite::IsInternal() const { return m_constituents.IsInternal(); }
91108

92109
uint8_t *BreakpointSite::GetTrapOpcodeBytes() { return &m_trap_opcode[0]; }

lldb/source/Core/Declaration.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ int Declaration::Compare(const Declaration &a, const Declaration &b) {
7070
return 0;
7171
}
7272

73-
bool Declaration::FileAndLineEqual(const Declaration &declaration) const {
74-
int file_compare = FileSpec::Compare(this->m_file, declaration.m_file, true);
73+
bool Declaration::FileAndLineEqual(const Declaration &declaration,
74+
bool full) const {
75+
int file_compare = FileSpec::Compare(this->m_file, declaration.m_file, full);
7576
return file_compare == 0 && this->m_line == declaration.m_line;
7677
}
7778

lldb/source/Symbol/Block.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ Block *Block::GetContainingInlinedBlockWithCallSite(
230230
const auto *function_info = inlined_block->GetInlinedFunctionInfo();
231231

232232
if (function_info &&
233-
function_info->GetCallSite().FileAndLineEqual(find_call_site))
233+
function_info->GetCallSite().FileAndLineEqual(find_call_site, true))
234234
return inlined_block;
235235
inlined_block = inlined_block->GetInlinedParent();
236236
}

0 commit comments

Comments
 (0)