Files
clang-p2996/lldb/test/API/functionalities/unwind/zeroth_frame/TestZerothFrame.py
Kendal Harland 4c23625357 Fix flake in TestZerothFrame.py (#96685)
This test is currently flaky on a local Windows amd64 build. The reason
is that it relies on the order of `process.threads` but this order is
nondeterministic:

If we print lldb's inputs and outputs while running, we can see that the
breakpoints are always being set correctly, and always being hit:

```sh
runCmd: breakpoint set -f "main.c" -l 2
output: Breakpoint 1: where = a.out`func_inner + 1 at main.c:2:9, address = 0x0000000140001001

runCmd: breakpoint set -f "main.c" -l 7
output: Breakpoint 2: where = a.out`main + 17 at main.c:7:5, address = 0x0000000140001021

runCmd: run
output: Process 52328 launched: 'C:\workspace\llvm-project\llvm\build\lldb-test-build.noindex\functionalities\unwind\zeroth_frame\TestZerothFrame.test_dwarf\a.out' (x86_64)
Process 52328 stopped
* thread #1, stop reason = breakpoint 1.1
    frame #0: 0x00007ff68f6b1001 a.out`func_inner at main.c:2:9
   1    void func_inner() {
-> 2        int a = 1;  // Set breakpoint 1 here
                ^
   3    }
   4
   5    int main() {
   6        func_inner();
   7        return 0; // Set breakpoint 2 here
```

However, sometimes the backtrace printed in this test shows that the
process is stopped inside NtWaitForWorkViaWorkerFactory from
`ntdll.dll`:

```sh
Backtrace at the first breakpoint:
frame #0: 0x00007ffecc7b3bf4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
frame #1: 0x00007ffecc74585e ntdll.dll`RtlClearThreadWorkOnBehalfTicket + 862
frame #2: 0x00007ffecc3e257d kernel32.dll`BaseThreadInitThunk + 29
frame #3: 0x00007ffecc76af28 ntdll.dll`RtlUserThreadStart + 40
```

When this happens, the test fails with an assertion error that the
stopped thread's zeroth frame's current line number does not match the
expected line number. This is because the test is looking at the wrong
thread: `process.threads[0]`.

If we print the list of threads each time the test is run, we notice
that threads are sometimes in a different order, within
`process.threads`:

```sh
Thread 0: thread #4: tid = 0x9c38, 0x00007ffecc7b3bf4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
Thread 1: thread #2: tid = 0xa950, 0x00007ffecc7b3bf4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
Thread 2: thread #1: tid = 0xab18, 0x00007ff64bc81001 a.out`func_inner at main.c:2:9, stop reason = breakpoint 1.1
Thread 3: thread #3: tid = 0xc514, 0x00007ffecc7b3bf4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20

Thread 0: thread #3: tid = 0x018c, 0x00007ffecc7b3bf4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
Thread 1: thread #1: tid = 0x85c8, 0x00007ff7130c1001 a.out`func_inner at main.c:2:9, stop reason = breakpoint 1.1
Thread 2: thread #2: tid = 0xf344, 0x00007ffecc7b3bf4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
Thread 3: thread #4: tid = 0x6a50, 0x00007ffecc7b3bf4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
```

Use `self.thread()` to consistently select the correct thread, instead.

Co-authored-by: kendal <kendal@thebrowser.company>
2024-07-08 12:20:45 +02:00

89 lines
3.5 KiB
Python

"""
Test that line information is recalculated properly for a frame when it moves
from the middle of the backtrace to a zero index.
This is a regression test for a StackFrame bug, where whether frame is zero or
not depends on an internal field. When LLDB was updating its frame list value
of the field wasn't copied into existing StackFrame instances, so those
StackFrame instances, would use an incorrect line entry evaluation logic in
situations if it was in the middle of the stack frame list (not zeroth), and
then moved to the top position. The difference in logic is that for zeroth
frames line entry is returned for program counter, while for other frame
(except for those that "behave like zeroth") it is for the instruction
preceding PC, as PC points to the next instruction after function call. When
the bug is present, when execution stops at the second breakpoint
SBFrame.GetLineEntry() returns line entry for the previous line, rather than
the one with a breakpoint. Note that this is specific to
SBFrame.GetLineEntry(), SBFrame.GetPCAddress().GetLineEntry() would return
correct entry.
This bug doesn't reproduce through an LLDB interpretator, however it happens
when using API directly, for example in LLDB-MI.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class ZerothFrame(TestBase):
def test(self):
"""
Test that line information is recalculated properly for a frame when it moves
from the middle of the backtrace to a zero index.
"""
self.build()
self.setTearDownCleanup()
exe = self.getBuildArtifact("a.out")
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)
main_dot_c = lldb.SBFileSpec("main.c")
bp1 = target.BreakpointCreateBySourceRegex(
"// Set breakpoint 1 here", main_dot_c
)
bp2 = target.BreakpointCreateBySourceRegex(
"// Set breakpoint 2 here", main_dot_c
)
process = target.LaunchSimple(None, None, self.get_process_working_directory())
self.assertTrue(process, VALID_PROCESS)
thread = self.thread()
if self.TraceOn():
print("Backtrace at the first breakpoint:")
for f in thread.frames:
print(f)
# Check that we have stopped at correct breakpoint.
self.assertEqual(
thread.frame[0].GetLineEntry().GetLine(),
bp1.GetLocationAtIndex(0).GetAddress().GetLineEntry().GetLine(),
"LLDB reported incorrect line number.",
)
# Important to use SBProcess::Continue() instead of
# self.runCmd('continue'), because the problem doesn't reproduce with
# 'continue' command.
process.Continue()
if self.TraceOn():
print("Backtrace at the second breakpoint:")
for f in thread.frames:
print(f)
# Check that we have stopped at the breakpoint
self.assertEqual(
thread.frame[0].GetLineEntry().GetLine(),
bp2.GetLocationAtIndex(0).GetAddress().GetLineEntry().GetLine(),
"LLDB reported incorrect line number.",
)
# Double-check with GetPCAddress()
self.assertEqual(
thread.frame[0].GetLineEntry().GetLine(),
thread.frame[0].GetPCAddress().GetLineEntry().GetLine(),
"LLDB reported incorrect line number.",
)