Skip to content

Commit 8a33816

Browse files
igorkudrinaokblast
authored andcommitted
[lldb][test] Fix TestEmptyFuncThreadStepOut.py (llvm#161788)
The test did not work as intended when the empty function `done()` contained prologue/epilogue code, because a breakpoint was set before the last instruction of the function, which caused the test to pass even with the fix from llvm#126838 having been reverted. The test is intended to check a case when a breakpoint is set on a return instruction, which is the very last instruction of a function. When stepping out from this breakpoint, there is interaction between `ThreadPlanStepOut` and `ThreadPlanStepOverBreakpoint` that could lead to missing the stop location in the outer frame; the detailed explanation can be found in llvm#126838. On `Linux/AArch64`, the source is compiled into: ``` > objdump -d main.o 0000000000000000 <done>: 0: d65f03c0 ret ``` So, when the command `bt set -n done` from the original test sets a breakpoint to the first instruction of `done()`, this instruction luckily also happens to be the last one. On `Linux/x86_64`, it compiles into: ``` > objdump -d main.o 0000000000000000 <done>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 5d pop %rbp 5: c3 ret ``` In this case, setting a breakpoint by function name means setting it several instructions before `ret`, which does not provoke the interaction between `ThreadPlanStepOut` and `ThreadPlanStepOverBreakpoint`.
1 parent d846ac4 commit 8a33816

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

lldb/test/API/functionalities/thread/finish-from-empty-func/TestEmptyFuncThreadStepOut.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,31 @@ class FinishFromEmptyFunctionTestCase(TestBase):
1313

1414
@skipIf(compiler="clang", compiler_version=['<', '17.0'])
1515
def test_finish_from_empty_function(self):
16-
"""Test that when stopped at a breakpoint in an empty function, finish leaves it correctly."""
16+
"""Test that when stopped at a breakpoint located at the last instruction
17+
of a function, finish leaves it correctly."""
1718
self.build()
18-
exe = self.getBuildArtifact("a.out")
19-
target, process, thread, _ = lldbutil.run_to_name_breakpoint(
20-
self, "done", exe_name=exe
19+
target, _, thread, _ = lldbutil.run_to_source_breakpoint(
20+
self, "// Set breakpoint here", lldb.SBFileSpec("main.c")
2121
)
22+
# Find the address of the last instruction of 'done()' and set a breakpoint there.
23+
# Even though 'done()' is empty, it may contain prologue and epilogue code, so
24+
# simply setting a breakpoint at the function can place it before 'ret'.
25+
error = lldb.SBError()
26+
ret_bp_addr = lldb.SBAddress()
27+
while True:
28+
thread.StepInstruction(False, error)
29+
self.assertTrue(error.Success())
30+
frame = thread.GetSelectedFrame()
31+
if "done" in frame.GetFunctionName():
32+
ret_bp_addr = frame.GetPCAddress()
33+
elif ret_bp_addr.IsValid():
34+
# The entire function 'done()' has been stepped through, so 'ret_bp_addr'
35+
# now contains the address of its last instruction, i.e. 'ret'.
36+
break
37+
ret_bp = target.BreakpointCreateByAddress(ret_bp_addr.GetLoadAddress(target))
38+
self.assertTrue(ret_bp.IsValid())
39+
# Resume the execution and hit the new breakpoint.
40+
self.runCmd("cont")
2241
if self.TraceOn():
2342
self.runCmd("bt")
2443

@@ -29,7 +48,6 @@ def test_finish_from_empty_function(self):
2948
)
3049
self.assertTrue(safety_bp.IsValid())
3150

32-
error = lldb.SBError()
3351
thread.StepOut(error)
3452
self.assertTrue(error.Success())
3553

lldb/test/API/functionalities/thread/finish-from-empty-func/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
void done() {}
33
int main() {
44
puts("in main");
5+
done(); // Set breakpoint here
56
done();
67
puts("leaving main");
78
return 0;

0 commit comments

Comments
 (0)