Skip to content

[lldb] Fix "in function" detection in "thread until" #123622

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

Merged
merged 9 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 30 additions & 38 deletions lldb/source/Commands/CommandObjectThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,6 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
}

LineEntry function_start;
uint32_t index_ptr = 0, end_ptr = UINT32_MAX;
std::vector<addr_t> address_list;

// Find the beginning & end index of the function, but first make
Expand All @@ -970,19 +969,14 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
return;
}

AddressRange fun_addr_range = sc.function->GetAddressRange();
Address fun_start_addr = fun_addr_range.GetBaseAddress();
line_table->FindLineEntryByAddress(fun_start_addr, function_start,
&index_ptr);

Address fun_end_addr(fun_start_addr.GetSection(),
fun_start_addr.GetOffset() +
fun_addr_range.GetByteSize());

bool all_in_function = true;
RangeVector<uint32_t, uint32_t> line_idx_ranges;
for (const AddressRange &range : sc.function->GetAddressRanges()) {
auto [begin, end] = line_table->GetLineEntryIndexRange(range);
line_idx_ranges.Append(begin, end - begin);
}
line_idx_ranges.Sort();

line_table->FindLineEntryByAddress(fun_end_addr, function_start,
&end_ptr);
bool found_something = false;

// Since not all source lines will contribute code, check if we are
// setting the breakpoint on the exact line number or the nearest
Expand All @@ -991,45 +985,43 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
for (uint32_t line_number : line_numbers) {
LineEntry line_entry;
bool exact = false;
uint32_t start_idx_ptr = index_ptr;
start_idx_ptr = sc.comp_unit->FindLineEntry(
index_ptr, line_number, nullptr, exact, &line_entry);
if (start_idx_ptr != UINT32_MAX)
line_number = line_entry.line;
if (sc.comp_unit->FindLineEntry(0, line_number, nullptr, exact,
&line_entry) == UINT32_MAX)
continue;

found_something = true;
line_number = line_entry.line;
exact = true;
start_idx_ptr = index_ptr;
while (start_idx_ptr <= end_ptr) {
start_idx_ptr = sc.comp_unit->FindLineEntry(
start_idx_ptr, line_number, nullptr, exact, &line_entry);
if (start_idx_ptr == UINT32_MAX)
break;

addr_t address =
line_entry.range.GetBaseAddress().GetLoadAddress(target);
if (address != LLDB_INVALID_ADDRESS) {
if (fun_addr_range.ContainsLoadAddress(address, target))
uint32_t end_func_idx = line_idx_ranges.GetMaxRangeEnd(0);
uint32_t idx = sc.comp_unit->FindLineEntry(
line_idx_ranges.GetMinRangeBase(UINT32_MAX), line_number, nullptr,
exact, &line_entry);
while (idx < end_func_idx) {
if (line_idx_ranges.FindEntryIndexThatContains(idx) != UINT32_MAX) {
addr_t address =
line_entry.range.GetBaseAddress().GetLoadAddress(target);
if (address != LLDB_INVALID_ADDRESS)
address_list.push_back(address);
else
all_in_function = false;
}
start_idx_ptr++;
idx = sc.comp_unit->FindLineEntry(idx + 1, line_number, nullptr,
exact, &line_entry);
}
}

for (lldb::addr_t address : m_options.m_until_addrs) {
if (fun_addr_range.ContainsLoadAddress(address, target))
AddressRange unused;
if (sc.function->GetRangeContainingLoadAddress(address, *target,
unused))
address_list.push_back(address);
else
all_in_function = false;
}

if (address_list.empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you only care here about found_something and address_list.size > 0 could you short-circuit this search when you find the first address that matches the line?

Copy link
Collaborator Author

@labath labath Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. address_list being empty is the "error" case. In case of success, the full list of addresses is used for stepping (line 1040)

if (all_in_function)
if (found_something)
result.AppendErrorWithFormat(
"No line entries matching until target.\n");
"Until target outside of the current function.\n");
else
result.AppendErrorWithFormat(
"Until target outside of the current function.\n");
"No line entries matching until target.\n");

return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ def setUp(self):
self.less_than_two = line_number("main.c", "Less than 2")
self.greater_than_two = line_number("main.c", "Greater than or equal to 2.")
self.back_out_in_main = line_number("main.c", "Back out in main")
self.in_foo = line_number("main.c", "In foo")

def _build_dict_for_discontinuity(self):
return dict(
CFLAGS_EXTRAS="-funique-basic-block-section-names "
+ "-ffunction-sections -fbasic-block-sections=list="
+ self.getSourcePath("function.list"),
LD_EXTRAS="-Wl,--script=" + self.getSourcePath("symbol.order"),
)

def common_setup(self, args):
self.build()
def _common_setup(self, build_dict, args):
self.build(dictionary=build_dict)
exe = self.getBuildArtifact("a.out")

target = self.dbg.CreateTarget(exe)
Expand All @@ -45,7 +54,7 @@ def common_setup(self, args):
return thread

def do_until(self, args, until_lines, expected_linenum):
thread = self.common_setup(args)
thread = self._common_setup(None, args)

cmd_interp = self.dbg.GetCommandInterpreter()
ret_obj = lldb.SBCommandReturnObject()
Expand Down Expand Up @@ -88,3 +97,30 @@ def test_missing_one(self):
self.do_until(
["foo", "bar", "baz"], [self.less_than_two], self.back_out_in_main
)

@no_debug_info_test
def test_bad_line(self):
"""Test that we get an error if attempting to step outside the current
function"""
thread = self._common_setup(None, None)
self.expect(
f"thread until {self.in_foo}",
substrs=["Until target outside of the current function"],
error=True,
)

@no_debug_info_test
@skipIf(oslist=lldbplatformutil.getDarwinOSTriples() + ["windows"])
@skipIf(archs=no_match(["x86_64", "aarch64"]))
def test_bad_line_discontinuous(self):
"""Test that we get an error if attempting to step outside the current
function -- and the function is discontinuous"""
self.build(dictionary=self._build_dict_for_discontinuity())
_, _, thread, _ = lldbutil.run_to_source_breakpoint(
self, "At the start", lldb.SBFileSpec(self.main_source)
)
self.expect(
f"thread until {self.in_foo}",
substrs=["Until target outside of the current function"],
error=True,
)