Fix call site breakpoint patch (#114158)
This fixes the two test suite failures that I missed in the PR: https://github.com/llvm/llvm-project/pull/112939 One was a poorly written test case - it assumed that on connect to a gdb-remote with a running process, lldb MUST have fetched all the frame 0 registers. In fact, there's no need for it to do so (as the CallSite patch showed...) and if we don't need to we shouldn't. So I fixed the test to only expect a `g` packet AFTER calling read_registers. The other was a place where some code had used 0 when it meant LLDB_INVALID_LINE_NUMBER, which I had fixed but missed one place where it was still compared to 0.
This commit is contained in:
parent
9cd30b1ef3
commit
7dbbd2b251
@ -11,10 +11,12 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "lldb/Breakpoint/BreakpointOptions.h"
|
#include "lldb/Breakpoint/BreakpointOptions.h"
|
||||||
#include "lldb/Breakpoint/StoppointHitCounter.h"
|
#include "lldb/Breakpoint/StoppointHitCounter.h"
|
||||||
#include "lldb/Core/Address.h"
|
#include "lldb/Core/Address.h"
|
||||||
|
#include "lldb/Symbol/LineEntry.h"
|
||||||
#include "lldb/Utility/UserID.h"
|
#include "lldb/Utility/UserID.h"
|
||||||
#include "lldb/lldb-private.h"
|
#include "lldb/lldb-private.h"
|
||||||
|
|
||||||
@ -282,6 +284,25 @@ public:
|
|||||||
/// Returns the breakpoint location ID.
|
/// Returns the breakpoint location ID.
|
||||||
lldb::break_id_t GetID() const { return m_loc_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.
|
||||||
|
/// The line entry must have the same start address as the address for this
|
||||||
|
/// location.
|
||||||
|
bool SetPreferredLineEntry(const LineEntry &line_entry) {
|
||||||
|
if (m_address == line_entry.range.GetBaseAddress()) {
|
||||||
|
m_preferred_line_entry = line_entry;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
assert(0 && "Tried to set a preferred line entry with a different address");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<LineEntry> GetPreferredLineEntry() {
|
||||||
|
return m_preferred_line_entry;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class BreakpointSite;
|
friend class BreakpointSite;
|
||||||
friend class BreakpointLocationList;
|
friend class BreakpointLocationList;
|
||||||
@ -306,6 +327,16 @@ protected:
|
|||||||
/// If it returns false we should continue, otherwise stop.
|
/// If it returns false we should continue, otherwise stop.
|
||||||
bool IgnoreCountShouldStop();
|
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:
|
private:
|
||||||
void SwapLocation(lldb::BreakpointLocationSP swap_from);
|
void SwapLocation(lldb::BreakpointLocationSP swap_from);
|
||||||
|
|
||||||
@ -369,6 +400,11 @@ private:
|
|||||||
lldb::break_id_t m_loc_id; ///< Breakpoint location ID.
|
lldb::break_id_t m_loc_id; ///< Breakpoint location ID.
|
||||||
StoppointHitCounter m_hit_counter; ///< Number of times this breakpoint
|
StoppointHitCounter m_hit_counter; ///< Number of times this breakpoint
|
||||||
/// location has been hit.
|
/// location has been hit.
|
||||||
|
/// If this exists, use it to print the stop description rather than the
|
||||||
|
/// LineEntry m_address resolves to directly. Use this for instance when the
|
||||||
|
/// location was given somewhere in the virtual inlined call stack since the
|
||||||
|
/// Address always resolves to the lowest entry in the stack.
|
||||||
|
std::optional<LineEntry> m_preferred_line_entry;
|
||||||
|
|
||||||
void SetShouldResolveIndirectFunctions(bool do_resolve) {
|
void SetShouldResolveIndirectFunctions(bool do_resolve) {
|
||||||
m_should_resolve_indirect_functions = do_resolve;
|
m_should_resolve_indirect_functions = do_resolve;
|
||||||
|
@ -170,6 +170,11 @@ public:
|
|||||||
/// \see lldb::DescriptionLevel
|
/// \see lldb::DescriptionLevel
|
||||||
void GetDescription(Stream *s, lldb::DescriptionLevel level);
|
void GetDescription(Stream *s, lldb::DescriptionLevel level);
|
||||||
|
|
||||||
|
// This runs through all the breakpoint locations owning this site and returns
|
||||||
|
// the greatest of their suggested stack frame indexes. This only handles
|
||||||
|
// inlined stack changes.
|
||||||
|
std::optional<uint32_t> GetSuggestedStackFrameIndex();
|
||||||
|
|
||||||
/// Tell whether a breakpoint has a location at this site.
|
/// Tell whether a breakpoint has a location at this site.
|
||||||
///
|
///
|
||||||
/// \param[in] bp_id
|
/// \param[in] bp_id
|
||||||
|
@ -84,10 +84,14 @@ public:
|
|||||||
/// \param[in] declaration
|
/// \param[in] declaration
|
||||||
/// The const Declaration object to compare with.
|
/// The const Declaration object to compare with.
|
||||||
///
|
///
|
||||||
|
/// \param[in] full
|
||||||
|
/// Same meaning as Full in FileSpec::Equal. True means an empty
|
||||||
|
/// directory is not equal to a specified one, false means it is equal.
|
||||||
|
///
|
||||||
/// \return
|
/// \return
|
||||||
/// Returns \b true if \b declaration is at the same file and
|
/// Returns \b true if \b declaration is at the same file and
|
||||||
/// line, \b false otherwise.
|
/// line, \b false otherwise.
|
||||||
bool FileAndLineEqual(const Declaration &declaration) const;
|
bool FileAndLineEqual(const Declaration &declaration, bool full) const;
|
||||||
|
|
||||||
/// Dump a description of this object to a Stream.
|
/// Dump a description of this object to a Stream.
|
||||||
///
|
///
|
||||||
|
@ -77,6 +77,18 @@ public:
|
|||||||
m_description.clear();
|
m_description.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This gives the StopInfo a chance to suggest a stack frame to select.
|
||||||
|
/// Passing true for inlined_stack will request changes to the inlined
|
||||||
|
/// call stack. Passing false will request changes to the real stack
|
||||||
|
/// frame. The inlined stack gets adjusted before we call into the thread
|
||||||
|
/// plans so they can reason based on the correct values. The real stack
|
||||||
|
/// adjustment is handled after the frame recognizers get a chance to adjust
|
||||||
|
/// the frame.
|
||||||
|
virtual std::optional<uint32_t>
|
||||||
|
GetSuggestedStackFrameIndex(bool inlined_stack) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool IsValidForOperatingSystemThread(Thread &thread) { return true; }
|
virtual bool IsValidForOperatingSystemThread(Thread &thread) { return true; }
|
||||||
|
|
||||||
/// A Continue operation can result in a false stop event
|
/// A Continue operation can result in a false stop event
|
||||||
|
@ -80,8 +80,8 @@ private:
|
|||||||
bool m_step_past_prologue; // FIXME: For now hard-coded to true, we could put
|
bool m_step_past_prologue; // FIXME: For now hard-coded to true, we could put
|
||||||
// a switch in for this if there's
|
// a switch in for this if there's
|
||||||
// demand for that.
|
// demand for that.
|
||||||
bool m_virtual_step; // true if we've just done a "virtual step", i.e. just
|
LazyBool m_virtual_step; // true if we've just done a "virtual step", i.e.
|
||||||
// moved the inline stack depth.
|
// just moved the inline stack depth.
|
||||||
ConstString m_step_into_target;
|
ConstString m_step_into_target;
|
||||||
ThreadPlanStepInRange(const ThreadPlanStepInRange &) = delete;
|
ThreadPlanStepInRange(const ThreadPlanStepInRange &) = delete;
|
||||||
const ThreadPlanStepInRange &
|
const ThreadPlanStepInRange &
|
||||||
|
@ -508,8 +508,20 @@ void BreakpointLocation::GetDescription(Stream *s,
|
|||||||
s->PutCString("re-exported target = ");
|
s->PutCString("re-exported target = ");
|
||||||
else
|
else
|
||||||
s->PutCString("where = ");
|
s->PutCString("where = ");
|
||||||
|
|
||||||
|
// If there's a preferred line entry for printing, use that.
|
||||||
|
bool show_function_info = true;
|
||||||
|
if (auto preferred = GetPreferredLineEntry()) {
|
||||||
|
sc.line_entry = *preferred;
|
||||||
|
// FIXME: We're going to get the function name wrong when the preferred
|
||||||
|
// line entry is not the lowest one. For now, just leave the function
|
||||||
|
// out in this case, but we really should also figure out how to easily
|
||||||
|
// fake the function name here.
|
||||||
|
show_function_info = false;
|
||||||
|
}
|
||||||
sc.DumpStopContext(s, m_owner.GetTarget().GetProcessSP().get(), m_address,
|
sc.DumpStopContext(s, m_owner.GetTarget().GetProcessSP().get(), m_address,
|
||||||
false, true, false, true, true, true);
|
false, true, false, show_function_info,
|
||||||
|
show_function_info, show_function_info);
|
||||||
} else {
|
} else {
|
||||||
if (sc.module_sp) {
|
if (sc.module_sp) {
|
||||||
s->EOL();
|
s->EOL();
|
||||||
@ -537,7 +549,10 @@ void BreakpointLocation::GetDescription(Stream *s,
|
|||||||
if (sc.line_entry.line > 0) {
|
if (sc.line_entry.line > 0) {
|
||||||
s->EOL();
|
s->EOL();
|
||||||
s->Indent("location = ");
|
s->Indent("location = ");
|
||||||
sc.line_entry.DumpStopContext(s, true);
|
if (auto preferred = GetPreferredLineEntry())
|
||||||
|
preferred->DumpStopContext(s, true);
|
||||||
|
else
|
||||||
|
sc.line_entry.DumpStopContext(s, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -656,6 +671,50 @@ void BreakpointLocation::SendBreakpointLocationChangedEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint32_t> BreakpointLocation::GetSuggestedStackFrameIndex() {
|
||||||
|
auto preferred_opt = GetPreferredLineEntry();
|
||||||
|
if (!preferred_opt)
|
||||||
|
return {};
|
||||||
|
LineEntry preferred = *preferred_opt;
|
||||||
|
SymbolContext sc;
|
||||||
|
if (!m_address.CalculateSymbolContext(&sc))
|
||||||
|
return {};
|
||||||
|
// Don't return anything special if frame 0 is the preferred line entry.
|
||||||
|
// We not really telling the stack frame list to do anything special in that
|
||||||
|
// case.
|
||||||
|
if (!LineEntry::Compare(sc.line_entry, preferred))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (!sc.block)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Blocks have their line info in Declaration form, so make one here:
|
||||||
|
Declaration preferred_decl(preferred.GetFile(), preferred.line,
|
||||||
|
preferred.column);
|
||||||
|
|
||||||
|
uint32_t depth = 0;
|
||||||
|
Block *inlined_block = sc.block->GetContainingInlinedBlock();
|
||||||
|
while (inlined_block) {
|
||||||
|
// If we've moved to a block that this isn't the start of, that's not
|
||||||
|
// our inlining info or call site, so we can stop here.
|
||||||
|
Address start_address;
|
||||||
|
if (!inlined_block->GetStartAddress(start_address) ||
|
||||||
|
start_address != m_address)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const InlineFunctionInfo *info = inlined_block->GetInlinedFunctionInfo();
|
||||||
|
if (info) {
|
||||||
|
if (preferred_decl == info->GetDeclaration())
|
||||||
|
return depth;
|
||||||
|
if (preferred_decl == info->GetCallSite())
|
||||||
|
return depth + 1;
|
||||||
|
}
|
||||||
|
inlined_block = inlined_block->GetInlinedParent();
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void BreakpointLocation::SwapLocation(BreakpointLocationSP swap_from) {
|
void BreakpointLocation::SwapLocation(BreakpointLocationSP swap_from) {
|
||||||
m_address = swap_from->m_address;
|
m_address = swap_from->m_address;
|
||||||
m_should_resolve_indirect_functions =
|
m_should_resolve_indirect_functions =
|
||||||
|
@ -340,6 +340,21 @@ void BreakpointResolver::AddLocation(SearchFilter &filter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
BreakpointLocationSP bp_loc_sp(AddLocation(line_start));
|
BreakpointLocationSP bp_loc_sp(AddLocation(line_start));
|
||||||
|
// If the address that we resolved the location to returns a different
|
||||||
|
// LineEntry from the one in the incoming SC, we're probably dealing with an
|
||||||
|
// inlined call site, so set that as the preferred LineEntry:
|
||||||
|
LineEntry resolved_entry;
|
||||||
|
if (!skipped_prologue && bp_loc_sp &&
|
||||||
|
line_start.CalculateSymbolContextLineEntry(resolved_entry) &&
|
||||||
|
LineEntry::Compare(resolved_entry, sc.line_entry)) {
|
||||||
|
// FIXME: The function name will also be wrong here. Do we need to record
|
||||||
|
// that as well, or can we figure that out again when we report this
|
||||||
|
// breakpoint location.
|
||||||
|
if (!bp_loc_sp->SetPreferredLineEntry(sc.line_entry)) {
|
||||||
|
LLDB_LOG(log, "Tried to add a preferred line entry that didn't have the "
|
||||||
|
"same address as this location's address.");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (log && bp_loc_sp && !GetBreakpoint()->IsInternal()) {
|
if (log && bp_loc_sp && !GetBreakpoint()->IsInternal()) {
|
||||||
StreamString s;
|
StreamString s;
|
||||||
bp_loc_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose);
|
bp_loc_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose);
|
||||||
|
@ -87,6 +87,23 @@ void BreakpointSite::GetDescription(Stream *s, lldb::DescriptionLevel level) {
|
|||||||
m_constituents.GetDescription(s, level);
|
m_constituents.GetDescription(s, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint32_t> BreakpointSite::GetSuggestedStackFrameIndex() {
|
||||||
|
|
||||||
|
std::optional<uint32_t> result;
|
||||||
|
std::lock_guard<std::recursive_mutex> guard(m_constituents_mutex);
|
||||||
|
for (BreakpointLocationSP loc_sp : m_constituents.BreakpointLocations()) {
|
||||||
|
std::optional<uint32_t> loc_frame_index =
|
||||||
|
loc_sp->GetSuggestedStackFrameIndex();
|
||||||
|
if (loc_frame_index) {
|
||||||
|
if (result)
|
||||||
|
result = std::max(*loc_frame_index, *result);
|
||||||
|
else
|
||||||
|
result = loc_frame_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
bool BreakpointSite::IsInternal() const { return m_constituents.IsInternal(); }
|
bool BreakpointSite::IsInternal() const { return m_constituents.IsInternal(); }
|
||||||
|
|
||||||
uint8_t *BreakpointSite::GetTrapOpcodeBytes() { return &m_trap_opcode[0]; }
|
uint8_t *BreakpointSite::GetTrapOpcodeBytes() { return &m_trap_opcode[0]; }
|
||||||
|
@ -70,8 +70,9 @@ int Declaration::Compare(const Declaration &a, const Declaration &b) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Declaration::FileAndLineEqual(const Declaration &declaration) const {
|
bool Declaration::FileAndLineEqual(const Declaration &declaration,
|
||||||
int file_compare = FileSpec::Compare(this->m_file, declaration.m_file, true);
|
bool full) const {
|
||||||
|
int file_compare = FileSpec::Compare(this->m_file, declaration.m_file, full);
|
||||||
return file_compare == 0 && this->m_line == declaration.m_line;
|
return file_compare == 0 && this->m_line == declaration.m_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ Block *Block::GetContainingInlinedBlockWithCallSite(
|
|||||||
const auto *function_info = inlined_block->GetInlinedFunctionInfo();
|
const auto *function_info = inlined_block->GetInlinedFunctionInfo();
|
||||||
|
|
||||||
if (function_info &&
|
if (function_info &&
|
||||||
function_info->GetCallSite().FileAndLineEqual(find_call_site))
|
function_info->GetCallSite().FileAndLineEqual(find_call_site, true))
|
||||||
return inlined_block;
|
return inlined_block;
|
||||||
inlined_block = inlined_block->GetInlinedParent();
|
inlined_block = inlined_block->GetInlinedParent();
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,10 @@ void CompileUnit::ResolveSymbolContext(
|
|||||||
SymbolContextItem resolve_scope, SymbolContextList &sc_list,
|
SymbolContextItem resolve_scope, SymbolContextList &sc_list,
|
||||||
RealpathPrefixes *realpath_prefixes) {
|
RealpathPrefixes *realpath_prefixes) {
|
||||||
const FileSpec file_spec = src_location_spec.GetFileSpec();
|
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();
|
const bool check_inlines = src_location_spec.GetCheckInlines();
|
||||||
|
|
||||||
// First find all of the file indexes that match our "file_spec". If
|
// First find all of the file indexes that match our "file_spec". If
|
||||||
@ -268,7 +271,7 @@ void CompileUnit::ResolveSymbolContext(
|
|||||||
SymbolContext sc(GetModule());
|
SymbolContext sc(GetModule());
|
||||||
sc.comp_unit = this;
|
sc.comp_unit = this;
|
||||||
|
|
||||||
if (line == 0) {
|
if (line == LLDB_INVALID_LINE_NUMBER) {
|
||||||
if (file_spec_matches_cu_file_spec && !check_inlines) {
|
if (file_spec_matches_cu_file_spec && !check_inlines) {
|
||||||
// only append the context if we aren't looking for inline call sites by
|
// only append the context if we aren't looking for inline call sites by
|
||||||
// file and line and if the file spec matches that of the compile unit
|
// file and line and if the file spec matches that of the compile unit
|
||||||
@ -312,6 +315,112 @@ void CompileUnit::ResolveSymbolContext(
|
|||||||
0, file_indexes, src_location_spec, &line_entry);
|
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) {
|
||||||
|
// 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, false) &&
|
||||||
|
(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
|
// If "exact == true", then "found_line" will be the same as "line". If
|
||||||
// "exact == false", the "found_line" will be the closest line entry
|
// "exact == false", the "found_line" will be the closest line entry
|
||||||
// with a line number greater than "line" and we will use this for our
|
// with a line number greater than "line" and we will use this for our
|
||||||
|
@ -85,121 +85,32 @@ void StackFrameList::ResetCurrentInlinedDepth() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> guard(m_mutex);
|
std::lock_guard<std::recursive_mutex> guard(m_mutex);
|
||||||
|
|
||||||
GetFramesUpTo(0, DoNotAllowInterruption);
|
|
||||||
if (m_frames.empty())
|
|
||||||
return;
|
|
||||||
if (!m_frames[0]->IsInlined()) {
|
|
||||||
m_current_inlined_depth = UINT32_MAX;
|
|
||||||
m_current_inlined_pc = LLDB_INVALID_ADDRESS;
|
|
||||||
Log *log = GetLog(LLDBLog::Step);
|
|
||||||
if (log && log->GetVerbose())
|
|
||||||
LLDB_LOGF(
|
|
||||||
log,
|
|
||||||
"ResetCurrentInlinedDepth: Invalidating current inlined depth.\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only need to do something special about inlined blocks when we are
|
m_current_inlined_pc = LLDB_INVALID_ADDRESS;
|
||||||
// at the beginning of an inlined function:
|
m_current_inlined_depth = UINT32_MAX;
|
||||||
// FIXME: We probably also have to do something special if the PC is at
|
|
||||||
// the END of an inlined function, which coincides with the end of either
|
|
||||||
// its containing function or another inlined function.
|
|
||||||
|
|
||||||
Block *block_ptr = m_frames[0]->GetFrameBlock();
|
|
||||||
if (!block_ptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Address pc_as_address;
|
|
||||||
lldb::addr_t curr_pc = m_thread.GetRegisterContext()->GetPC();
|
|
||||||
pc_as_address.SetLoadAddress(curr_pc, &(m_thread.GetProcess()->GetTarget()));
|
|
||||||
AddressRange containing_range;
|
|
||||||
if (!block_ptr->GetRangeContainingAddress(pc_as_address, containing_range) ||
|
|
||||||
pc_as_address != containing_range.GetBaseAddress())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If we got here because of a breakpoint hit, then set the inlined depth
|
|
||||||
// depending on where the breakpoint was set. If we got here because of a
|
|
||||||
// crash, then set the inlined depth to the deepest most block. Otherwise,
|
|
||||||
// we stopped here naturally as the result of a step, so set ourselves in the
|
|
||||||
// containing frame of the whole set of nested inlines, so the user can then
|
|
||||||
// "virtually" step into the frames one by one, or next over the whole mess.
|
|
||||||
// Note: We don't have to handle being somewhere in the middle of the stack
|
|
||||||
// here, since ResetCurrentInlinedDepth doesn't get called if there is a
|
|
||||||
// valid inlined depth set.
|
|
||||||
StopInfoSP stop_info_sp = m_thread.GetStopInfo();
|
StopInfoSP stop_info_sp = m_thread.GetStopInfo();
|
||||||
if (!stop_info_sp)
|
if (!stop_info_sp)
|
||||||
return;
|
return;
|
||||||
switch (stop_info_sp->GetStopReason()) {
|
|
||||||
case eStopReasonWatchpoint:
|
|
||||||
case eStopReasonException:
|
|
||||||
case eStopReasonExec:
|
|
||||||
case eStopReasonFork:
|
|
||||||
case eStopReasonVFork:
|
|
||||||
case eStopReasonVForkDone:
|
|
||||||
case eStopReasonSignal:
|
|
||||||
// In all these cases we want to stop in the deepest frame.
|
|
||||||
m_current_inlined_pc = curr_pc;
|
|
||||||
m_current_inlined_depth = 0;
|
|
||||||
break;
|
|
||||||
case eStopReasonBreakpoint: {
|
|
||||||
// FIXME: Figure out what this break point is doing, and set the inline
|
|
||||||
// depth appropriately. Be careful to take into account breakpoints that
|
|
||||||
// implement step over prologue, since that should do the default
|
|
||||||
// calculation. For now, if the breakpoints corresponding to this hit are
|
|
||||||
// all internal, I set the stop location to the top of the inlined stack,
|
|
||||||
// since that will make things like stepping over prologues work right.
|
|
||||||
// But if there are any non-internal breakpoints I do to the bottom of the
|
|
||||||
// stack, since that was the old behavior.
|
|
||||||
uint32_t bp_site_id = stop_info_sp->GetValue();
|
|
||||||
BreakpointSiteSP bp_site_sp(
|
|
||||||
m_thread.GetProcess()->GetBreakpointSiteList().FindByID(bp_site_id));
|
|
||||||
bool all_internal = true;
|
|
||||||
if (bp_site_sp) {
|
|
||||||
uint32_t num_owners = bp_site_sp->GetNumberOfConstituents();
|
|
||||||
for (uint32_t i = 0; i < num_owners; i++) {
|
|
||||||
Breakpoint &bp_ref =
|
|
||||||
bp_site_sp->GetConstituentAtIndex(i)->GetBreakpoint();
|
|
||||||
if (!bp_ref.IsInternal()) {
|
|
||||||
all_internal = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!all_internal) {
|
|
||||||
m_current_inlined_pc = curr_pc;
|
|
||||||
m_current_inlined_depth = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[[fallthrough]];
|
|
||||||
default: {
|
|
||||||
// Otherwise, we should set ourselves at the container of the inlining, so
|
|
||||||
// that the user can descend into them. So first we check whether we have
|
|
||||||
// more than one inlined block sharing this PC:
|
|
||||||
int num_inlined_functions = 0;
|
|
||||||
|
|
||||||
for (Block *container_ptr = block_ptr->GetInlinedParent();
|
bool inlined = true;
|
||||||
container_ptr != nullptr;
|
auto inline_depth = stop_info_sp->GetSuggestedStackFrameIndex(inlined);
|
||||||
container_ptr = container_ptr->GetInlinedParent()) {
|
// We're only adjusting the inlined stack here.
|
||||||
if (!container_ptr->GetRangeContainingAddress(pc_as_address,
|
Log *log = GetLog(LLDBLog::Step);
|
||||||
containing_range))
|
if (inline_depth) {
|
||||||
break;
|
m_current_inlined_depth = *inline_depth;
|
||||||
if (pc_as_address != containing_range.GetBaseAddress())
|
m_current_inlined_pc = m_thread.GetRegisterContext()->GetPC();
|
||||||
break;
|
|
||||||
|
|
||||||
num_inlined_functions++;
|
|
||||||
}
|
|
||||||
m_current_inlined_pc = curr_pc;
|
|
||||||
m_current_inlined_depth = num_inlined_functions + 1;
|
|
||||||
Log *log = GetLog(LLDBLog::Step);
|
|
||||||
if (log && log->GetVerbose())
|
if (log && log->GetVerbose())
|
||||||
LLDB_LOGF(log,
|
LLDB_LOGF(log,
|
||||||
"ResetCurrentInlinedDepth: setting inlined "
|
"ResetCurrentInlinedDepth: setting inlined "
|
||||||
"depth: %d 0x%" PRIx64 ".\n",
|
"depth: %d 0x%" PRIx64 ".\n",
|
||||||
m_current_inlined_depth, curr_pc);
|
m_current_inlined_depth, m_current_inlined_pc);
|
||||||
|
} else {
|
||||||
break;
|
if (log && log->GetVerbose())
|
||||||
}
|
LLDB_LOGF(
|
||||||
|
log,
|
||||||
|
"ResetCurrentInlinedDepth: Invalidating current inlined depth.\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -816,19 +727,48 @@ void StackFrameList::SelectMostRelevantFrame() {
|
|||||||
|
|
||||||
RecognizedStackFrameSP recognized_frame_sp = frame_sp->GetRecognizedFrame();
|
RecognizedStackFrameSP recognized_frame_sp = frame_sp->GetRecognizedFrame();
|
||||||
|
|
||||||
if (!recognized_frame_sp) {
|
if (recognized_frame_sp) {
|
||||||
LLDB_LOG(log, "Frame #0 not recognized");
|
if (StackFrameSP most_relevant_frame_sp =
|
||||||
return;
|
recognized_frame_sp->GetMostRelevantFrame()) {
|
||||||
|
LLDB_LOG(log, "Found most relevant frame at index {0}",
|
||||||
|
most_relevant_frame_sp->GetFrameIndex());
|
||||||
|
SetSelectedFrame(most_relevant_frame_sp.get());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LLDB_LOG(log, "Frame #0 not recognized");
|
||||||
|
|
||||||
|
// If this thread has a non-trivial StopInof, then let it suggest
|
||||||
|
// a most relevant frame:
|
||||||
|
StopInfoSP stop_info_sp = m_thread.GetStopInfo();
|
||||||
|
uint32_t stack_idx = 0;
|
||||||
|
bool found_relevant = false;
|
||||||
|
if (stop_info_sp) {
|
||||||
|
// Here we're only asking the stop info if it wants to adjust the real stack
|
||||||
|
// index. We have to ask about the m_inlined_stack_depth in
|
||||||
|
// Thread::ShouldStop since the plans need to reason with that info.
|
||||||
|
bool inlined = false;
|
||||||
|
std::optional<uint32_t> stack_opt =
|
||||||
|
stop_info_sp->GetSuggestedStackFrameIndex(inlined);
|
||||||
|
if (stack_opt) {
|
||||||
|
stack_idx = *stack_opt;
|
||||||
|
found_relevant = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StackFrameSP most_relevant_frame_sp =
|
frame_sp = GetFrameAtIndex(stack_idx);
|
||||||
recognized_frame_sp->GetMostRelevantFrame()) {
|
if (!frame_sp)
|
||||||
LLDB_LOG(log, "Found most relevant frame at index {0}",
|
LLDB_LOG(log, "Stop info suggested relevant frame {0} but it didn't exist",
|
||||||
most_relevant_frame_sp->GetFrameIndex());
|
stack_idx);
|
||||||
SetSelectedFrame(most_relevant_frame_sp.get());
|
else if (found_relevant)
|
||||||
} else {
|
LLDB_LOG(log, "Setting selected frame from stop info to {0}", stack_idx);
|
||||||
|
// Note, we don't have to worry about "inlined" frames here, because we've
|
||||||
|
// already calculated the inlined frame in Thread::ShouldStop, and
|
||||||
|
// SetSelectedFrame will take care of that adjustment for us.
|
||||||
|
SetSelectedFrame(frame_sp.get());
|
||||||
|
|
||||||
|
if (!found_relevant)
|
||||||
LLDB_LOG(log, "No relevant frame!");
|
LLDB_LOG(log, "No relevant frame!");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t StackFrameList::GetSelectedFrameIndex(
|
uint32_t StackFrameList::GetSelectedFrameIndex(
|
||||||
@ -841,6 +781,7 @@ uint32_t StackFrameList::GetSelectedFrameIndex(
|
|||||||
// isn't set, then don't force a selection here, just return 0.
|
// isn't set, then don't force a selection here, just return 0.
|
||||||
if (!select_most_relevant)
|
if (!select_most_relevant)
|
||||||
return 0;
|
return 0;
|
||||||
|
// If the inlined stack frame is set, then use that:
|
||||||
m_selected_frame_idx = 0;
|
m_selected_frame_idx = 0;
|
||||||
}
|
}
|
||||||
return *m_selected_frame_idx;
|
return *m_selected_frame_idx;
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include "lldb/Breakpoint/WatchpointResource.h"
|
#include "lldb/Breakpoint/WatchpointResource.h"
|
||||||
#include "lldb/Core/Debugger.h"
|
#include "lldb/Core/Debugger.h"
|
||||||
#include "lldb/Expression/UserExpression.h"
|
#include "lldb/Expression/UserExpression.h"
|
||||||
|
#include "lldb/Symbol/Block.h"
|
||||||
#include "lldb/Target/Process.h"
|
#include "lldb/Target/Process.h"
|
||||||
#include "lldb/Target/StopInfo.h"
|
#include "lldb/Target/StopInfo.h"
|
||||||
#include "lldb/Target/Target.h"
|
#include "lldb/Target/Target.h"
|
||||||
@ -246,6 +247,22 @@ public:
|
|||||||
return m_description.c_str();
|
return m_description.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint32_t>
|
||||||
|
GetSuggestedStackFrameIndex(bool inlined_stack) override {
|
||||||
|
if (!inlined_stack)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
ThreadSP thread_sp(m_thread_wp.lock());
|
||||||
|
if (!thread_sp)
|
||||||
|
return {};
|
||||||
|
BreakpointSiteSP bp_site_sp(
|
||||||
|
thread_sp->GetProcess()->GetBreakpointSiteList().FindByID(m_value));
|
||||||
|
if (!bp_site_sp)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return bp_site_sp->GetSuggestedStackFrameIndex();
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool ShouldStop(Event *event_ptr) override {
|
bool ShouldStop(Event *event_ptr) override {
|
||||||
// This just reports the work done by PerformAction or the synchronous
|
// This just reports the work done by PerformAction or the synchronous
|
||||||
@ -1164,6 +1181,44 @@ public:
|
|||||||
else
|
else
|
||||||
return m_description.c_str();
|
return m_description.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint32_t>
|
||||||
|
GetSuggestedStackFrameIndex(bool inlined_stack) override {
|
||||||
|
// Trace only knows how to adjust inlined stacks:
|
||||||
|
if (!inlined_stack)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
ThreadSP thread_sp = GetThread();
|
||||||
|
StackFrameSP frame_0_sp = thread_sp->GetStackFrameAtIndex(0);
|
||||||
|
if (!frame_0_sp)
|
||||||
|
return {};
|
||||||
|
if (!frame_0_sp->IsInlined())
|
||||||
|
return {};
|
||||||
|
Block *block_ptr = frame_0_sp->GetFrameBlock();
|
||||||
|
if (!block_ptr)
|
||||||
|
return {};
|
||||||
|
Address pc_address = frame_0_sp->GetFrameCodeAddress();
|
||||||
|
AddressRange containing_range;
|
||||||
|
if (!block_ptr->GetRangeContainingAddress(pc_address, containing_range) ||
|
||||||
|
pc_address != containing_range.GetBaseAddress())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
int num_inlined_functions = 0;
|
||||||
|
|
||||||
|
for (Block *container_ptr = block_ptr->GetInlinedParent();
|
||||||
|
container_ptr != nullptr;
|
||||||
|
container_ptr = container_ptr->GetInlinedParent()) {
|
||||||
|
if (!container_ptr->GetRangeContainingAddress(pc_address,
|
||||||
|
containing_range))
|
||||||
|
break;
|
||||||
|
if (pc_address != containing_range.GetBaseAddress())
|
||||||
|
break;
|
||||||
|
|
||||||
|
num_inlined_functions++;
|
||||||
|
}
|
||||||
|
inlined_stack = true;
|
||||||
|
return num_inlined_functions + 1;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// StopInfoException
|
// StopInfoException
|
||||||
|
@ -619,6 +619,14 @@ void Thread::WillStop() {
|
|||||||
|
|
||||||
void Thread::SetupForResume() {
|
void Thread::SetupForResume() {
|
||||||
if (GetResumeState() != eStateSuspended) {
|
if (GetResumeState() != eStateSuspended) {
|
||||||
|
// First check whether this thread is going to "actually" resume at all.
|
||||||
|
// For instance, if we're stepping from one level to the next of an
|
||||||
|
// virtual inlined call stack, we just change the inlined call stack index
|
||||||
|
// without actually running this thread. In that case, for this thread we
|
||||||
|
// shouldn't push a step over breakpoint plan or do that work.
|
||||||
|
if (GetCurrentPlan()->IsVirtualStep())
|
||||||
|
return;
|
||||||
|
|
||||||
// If we're at a breakpoint push the step-over breakpoint plan. Do this
|
// If we're at a breakpoint push the step-over breakpoint plan. Do this
|
||||||
// before telling the current plan it will resume, since we might change
|
// before telling the current plan it will resume, since we might change
|
||||||
// what the current plan is.
|
// what the current plan is.
|
||||||
|
@ -41,7 +41,7 @@ ThreadPlanStepInRange::ThreadPlanStepInRange(
|
|||||||
"Step Range stepping in", thread, range, addr_context,
|
"Step Range stepping in", thread, range, addr_context,
|
||||||
stop_others),
|
stop_others),
|
||||||
ThreadPlanShouldStopHere(this), m_step_past_prologue(true),
|
ThreadPlanShouldStopHere(this), m_step_past_prologue(true),
|
||||||
m_virtual_step(false), m_step_into_target(step_into_target) {
|
m_virtual_step(eLazyBoolCalculate), m_step_into_target(step_into_target) {
|
||||||
SetCallbacks();
|
SetCallbacks();
|
||||||
SetFlagsToDefault();
|
SetFlagsToDefault();
|
||||||
SetupAvoidNoDebug(step_in_avoids_code_without_debug_info,
|
SetupAvoidNoDebug(step_in_avoids_code_without_debug_info,
|
||||||
@ -149,7 +149,7 @@ bool ThreadPlanStepInRange::ShouldStop(Event *event_ptr) {
|
|||||||
m_sub_plan_sp.reset();
|
m_sub_plan_sp.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_virtual_step) {
|
if (m_virtual_step == eLazyBoolYes) {
|
||||||
// If we've just completed a virtual step, all we need to do is check for a
|
// If we've just completed a virtual step, all we need to do is check for a
|
||||||
// ShouldStopHere plan, and otherwise we're done.
|
// ShouldStopHere plan, and otherwise we're done.
|
||||||
// FIXME - This can be both a step in and a step out. Probably should
|
// FIXME - This can be both a step in and a step out. Probably should
|
||||||
@ -431,7 +431,7 @@ bool ThreadPlanStepInRange::DoPlanExplainsStop(Event *event_ptr) {
|
|||||||
|
|
||||||
bool return_value = false;
|
bool return_value = false;
|
||||||
|
|
||||||
if (m_virtual_step) {
|
if (m_virtual_step == eLazyBoolYes) {
|
||||||
return_value = true;
|
return_value = true;
|
||||||
} else {
|
} else {
|
||||||
StopInfoSP stop_info_sp = GetPrivateStopInfo();
|
StopInfoSP stop_info_sp = GetPrivateStopInfo();
|
||||||
@ -460,10 +460,13 @@ bool ThreadPlanStepInRange::DoPlanExplainsStop(Event *event_ptr) {
|
|||||||
|
|
||||||
bool ThreadPlanStepInRange::DoWillResume(lldb::StateType resume_state,
|
bool ThreadPlanStepInRange::DoWillResume(lldb::StateType resume_state,
|
||||||
bool current_plan) {
|
bool current_plan) {
|
||||||
m_virtual_step = false;
|
m_virtual_step = eLazyBoolCalculate;
|
||||||
if (resume_state == eStateStepping && current_plan) {
|
if (resume_state == eStateStepping && current_plan) {
|
||||||
Thread &thread = GetThread();
|
Thread &thread = GetThread();
|
||||||
// See if we are about to step over a virtual inlined call.
|
// See if we are about to step over a virtual inlined call.
|
||||||
|
// But if we already know we're virtual stepping, don't decrement the
|
||||||
|
// inlined depth again...
|
||||||
|
|
||||||
bool step_without_resume = thread.DecrementCurrentInlinedDepth();
|
bool step_without_resume = thread.DecrementCurrentInlinedDepth();
|
||||||
if (step_without_resume) {
|
if (step_without_resume) {
|
||||||
Log *log = GetLog(LLDBLog::Step);
|
Log *log = GetLog(LLDBLog::Step);
|
||||||
@ -476,11 +479,20 @@ bool ThreadPlanStepInRange::DoWillResume(lldb::StateType resume_state,
|
|||||||
// FIXME: Maybe it would be better to create a InlineStep stop reason, but
|
// FIXME: Maybe it would be better to create a InlineStep stop reason, but
|
||||||
// then
|
// then
|
||||||
// the whole rest of the world would have to handle that stop reason.
|
// the whole rest of the world would have to handle that stop reason.
|
||||||
m_virtual_step = true;
|
m_virtual_step = eLazyBoolYes;
|
||||||
}
|
}
|
||||||
return !step_without_resume;
|
return !step_without_resume;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ThreadPlanStepInRange::IsVirtualStep() { return m_virtual_step; }
|
bool ThreadPlanStepInRange::IsVirtualStep() {
|
||||||
|
if (m_virtual_step == eLazyBoolCalculate) {
|
||||||
|
Thread &thread = GetThread();
|
||||||
|
if (thread.GetCurrentInlinedDepth() == UINT32_MAX)
|
||||||
|
m_virtual_step = eLazyBoolNo;
|
||||||
|
else
|
||||||
|
m_virtual_step = eLazyBoolYes;
|
||||||
|
}
|
||||||
|
return m_virtual_step == eLazyBoolYes;
|
||||||
|
}
|
||||||
|
@ -402,7 +402,7 @@ bool ThreadPlanStepOverRange::DoWillResume(lldb::StateType resume_state,
|
|||||||
if (in_inlined_stack) {
|
if (in_inlined_stack) {
|
||||||
Log *log = GetLog(LLDBLog::Step);
|
Log *log = GetLog(LLDBLog::Step);
|
||||||
LLDB_LOGF(log,
|
LLDB_LOGF(log,
|
||||||
"ThreadPlanStepInRange::DoWillResume: adjusting range to "
|
"ThreadPlanStepOverRange::DoWillResume: adjusting range to "
|
||||||
"the frame at inlined depth %d.",
|
"the frame at inlined depth %d.",
|
||||||
thread.GetCurrentInlinedDepth());
|
thread.GetCurrentInlinedDepth());
|
||||||
StackFrameSP stack_sp = thread.GetStackFrameAtIndex(0);
|
StackFrameSP stack_sp = thread.GetStackFrameAtIndex(0);
|
||||||
|
@ -132,12 +132,39 @@ class TestGDBRemoteClient(GDBRemoteTestBase):
|
|||||||
target = self.createTarget("a.yaml")
|
target = self.createTarget("a.yaml")
|
||||||
process = self.connect(target)
|
process = self.connect(target)
|
||||||
|
|
||||||
self.assertEqual(1, self.server.responder.packetLog.count("g"))
|
# We want to make sure that the process is using the g packet, but it's
|
||||||
self.server.responder.packetLog = []
|
# not required the "connect" should read all registers. However, it might
|
||||||
|
# have... So we need to wait till we explicitly 'read_registers' to do
|
||||||
|
# test.
|
||||||
|
# Also, even with the use-g-packet-for-reading lldb will sometimes send p0
|
||||||
|
# early on to see if the packet is supported. So we can't say that there
|
||||||
|
# will be NO p packets.
|
||||||
|
# But there certainly should be no p packets after the g packet.
|
||||||
|
|
||||||
self.read_registers(process)
|
self.read_registers(process)
|
||||||
# Reading registers should not cause any 'p' packets to be exchanged.
|
print(f"\nPACKET LOG:\n{self.server.responder.packetLog}\n")
|
||||||
|
g_pos = 0
|
||||||
|
try:
|
||||||
|
g_pos = self.server.responder.packetLog.index("g")
|
||||||
|
except err:
|
||||||
|
self.fail("'g' packet not found after fetching registers")
|
||||||
|
|
||||||
|
try:
|
||||||
|
second_g = self.server.responder.packetLog.index("g", g_pos)
|
||||||
|
self.fail("Found more than one 'g' packet")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Make sure there aren't any `p` packets after the `g` packet:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
0, len([p for p in self.server.responder.packetLog if p.startswith("p")])
|
0,
|
||||||
|
len(
|
||||||
|
[
|
||||||
|
p
|
||||||
|
for p in self.server.responder.packetLog[g_pos:]
|
||||||
|
if p.startswith("p")
|
||||||
|
]
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_read_registers_using_p_packets(self):
|
def test_read_registers_using_p_packets(self):
|
||||||
|
@ -32,6 +32,12 @@ class TestInlineStepping(TestBase):
|
|||||||
self.build()
|
self.build()
|
||||||
self.step_in_template()
|
self.step_in_template()
|
||||||
|
|
||||||
|
@add_test_categories(["pyapi"])
|
||||||
|
def test_virtual_inline_stepping(self):
|
||||||
|
"""Test stepping through a virtual inlined call stack"""
|
||||||
|
self.build()
|
||||||
|
self.virtual_inline_stepping()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Call super's setUp().
|
# Call super's setUp().
|
||||||
TestBase.setUp(self)
|
TestBase.setUp(self)
|
||||||
@ -357,3 +363,60 @@ class TestInlineStepping(TestBase):
|
|||||||
|
|
||||||
step_sequence = [["// In max_value specialized", "into"]]
|
step_sequence = [["// In max_value specialized", "into"]]
|
||||||
self.run_step_sequence(step_sequence)
|
self.run_step_sequence(step_sequence)
|
||||||
|
|
||||||
|
def run_to_call_site_and_step(self, source_regex, func_name, start_pos):
|
||||||
|
main_spec = lldb.SBFileSpec("calling.cpp")
|
||||||
|
# Set the breakpoint by file and line, not sourced regex because
|
||||||
|
# we want to make sure we can set breakpoints on call sites:
|
||||||
|
call_site_line_num = line_number(self.main_source, source_regex)
|
||||||
|
target, process, thread, bkpt = lldbutil.run_to_line_breakpoint(
|
||||||
|
self, main_spec, call_site_line_num
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure that the location is at the call site (run_to_line_breakpoint already asserted
|
||||||
|
# that there's one location.):
|
||||||
|
bkpt_loc = bkpt.location[0]
|
||||||
|
strm = lldb.SBStream()
|
||||||
|
result = bkpt_loc.GetDescription(strm, lldb.eDescriptionLevelFull)
|
||||||
|
|
||||||
|
self.assertTrue(result, "Got a location description")
|
||||||
|
desc = strm.GetData()
|
||||||
|
self.assertIn(f"calling.cpp:{call_site_line_num}", desc, "Right line listed")
|
||||||
|
# We don't get the function name right yet - so we omit it in printing.
|
||||||
|
# Turn on this test when that is working.
|
||||||
|
# self.assertIn(func_name, desc, "Right function listed")
|
||||||
|
|
||||||
|
pc = thread.frame[0].pc
|
||||||
|
for i in range(start_pos, 3):
|
||||||
|
thread.StepInto()
|
||||||
|
frame_0 = thread.frame[0]
|
||||||
|
|
||||||
|
trivial_line_num = line_number(
|
||||||
|
self.main_source, f"In caller_trivial_inline_{i}."
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
frame_0.line_entry.line,
|
||||||
|
trivial_line_num,
|
||||||
|
f"Stepped into the caller_trivial_inline_{i}",
|
||||||
|
)
|
||||||
|
if pc != frame_0.pc:
|
||||||
|
# If we get here, we stepped to the expected line number, but
|
||||||
|
# the compiler on this system has decided to insert an instruction
|
||||||
|
# between the call site of an inlined function with no arguments,
|
||||||
|
# returning void, and its immediate call to another void inlined function
|
||||||
|
# with no arguments. We aren't going to be testing virtual inline
|
||||||
|
# stepping for this function...
|
||||||
|
break
|
||||||
|
|
||||||
|
process.Kill()
|
||||||
|
target.Clear()
|
||||||
|
|
||||||
|
def virtual_inline_stepping(self):
|
||||||
|
"""Use the Python API's to step through a virtual inlined stack"""
|
||||||
|
self.run_to_call_site_and_step("At caller_trivial_inline_1", "main", 1)
|
||||||
|
self.run_to_call_site_and_step(
|
||||||
|
"In caller_trivial_inline_1", "caller_trivial_inline_1", 2
|
||||||
|
)
|
||||||
|
self.run_to_call_site_and_step(
|
||||||
|
"In caller_trivial_inline_2", "caller_trivial_inline_2", 3
|
||||||
|
)
|
||||||
|
@ -13,6 +13,12 @@ int called_by_inline_ref (int &value);
|
|||||||
inline void inline_trivial_1 () __attribute__((always_inline));
|
inline void inline_trivial_1 () __attribute__((always_inline));
|
||||||
inline void inline_trivial_2 () __attribute__((always_inline));
|
inline void inline_trivial_2 () __attribute__((always_inline));
|
||||||
|
|
||||||
|
// These three should share the same initial pc so we can test
|
||||||
|
// virtual inline stepping.
|
||||||
|
inline void caller_trivial_inline_1() __attribute__((always_inline));
|
||||||
|
inline void caller_trivial_inline_2() __attribute__((always_inline));
|
||||||
|
inline void caller_trivial_inline_3() __attribute__((always_inline));
|
||||||
|
|
||||||
void caller_trivial_1 ();
|
void caller_trivial_1 ();
|
||||||
void caller_trivial_2 ();
|
void caller_trivial_2 ();
|
||||||
|
|
||||||
@ -79,6 +85,23 @@ caller_trivial_2 ()
|
|||||||
inline_value += 1; // At increment in caller_trivial_2.
|
inline_value += 1; // At increment in caller_trivial_2.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When you call caller_trivial_inline_1, the inlined call-site
|
||||||
|
// should share a PC with all three of the following inlined
|
||||||
|
// functions, so we can exercise "virtual inline stepping".
|
||||||
|
void caller_trivial_inline_1() {
|
||||||
|
caller_trivial_inline_2(); // In caller_trivial_inline_1.
|
||||||
|
inline_value += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void caller_trivial_inline_2() {
|
||||||
|
caller_trivial_inline_3(); // In caller_trivial_inline_2.
|
||||||
|
inline_value += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void caller_trivial_inline_3() {
|
||||||
|
inline_value += 1; // In caller_trivial_inline_3.
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
called_by_inline_trivial ()
|
called_by_inline_trivial ()
|
||||||
{
|
{
|
||||||
@ -132,5 +155,7 @@ main (int argc, char **argv)
|
|||||||
max_value(123, 456); // Call max_value template
|
max_value(123, 456); // Call max_value template
|
||||||
max_value(std::string("abc"), std::string("0022")); // Call max_value specialized
|
max_value(std::string("abc"), std::string("0022")); // Call max_value specialized
|
||||||
|
|
||||||
|
caller_trivial_inline_1(); // At caller_trivial_inline_1.
|
||||||
|
|
||||||
return 0; // About to return from main.
|
return 0; // About to return from main.
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user