xref: /llvm-project/lldb/test/API/python_api/thread/TestThreadAPI.py (revision 07b3e2c0c68b93a3d4d89426dc7fd14cc31ca6be)
1"""
2Test SBThread APIs.
3"""
4
5import lldb
6from lldbsuite.test.decorators import *
7from lldbsuite.test.lldbtest import *
8from lldbsuite.test import lldbutil
9from lldbsuite.test.lldbutil import get_stopped_thread, get_caller_symbol
10
11
12class ThreadAPITestCase(TestBase):
13    def test_get_process(self):
14        """Test Python SBThread.GetProcess() API."""
15        self.build()
16        self.get_process()
17
18    def test_get_stop_description(self):
19        """Test Python SBThread.GetStopDescription() API."""
20        self.build()
21        self.get_stop_description()
22
23    def test_run_to_address(self):
24        """Test Python SBThread.RunToAddress() API."""
25        # We build a different executable than the default build() does.
26        d = {"CXX_SOURCES": "main2.cpp", "EXE": self.exe_name}
27        self.build(dictionary=d)
28        self.setTearDownCleanup(dictionary=d)
29        self.run_to_address(self.exe_name)
30
31    @skipIfAsan  # The output looks different under ASAN.
32    @expectedFailureAll(oslist=["linux"], archs=["arm"], bugnumber="llvm.org/pr45892")
33    @expectedFailureAll(oslist=["windows"])
34    def test_step_out_of_malloc_into_function_b(self):
35        """Test Python SBThread.StepOut() API to step out of a malloc call where the call site is at function b()."""
36        # We build a different executable than the default build() does.
37        d = {"CXX_SOURCES": "main2.cpp", "EXE": self.exe_name}
38        self.build(dictionary=d)
39        self.setTearDownCleanup(dictionary=d)
40        self.step_out_of_malloc_into_function_b(self.exe_name)
41
42    def test_step_over_3_times(self):
43        """Test Python SBThread.StepOver() API."""
44        # We build a different executable than the default build() does.
45        d = {"CXX_SOURCES": "main2.cpp", "EXE": self.exe_name}
46        self.build(dictionary=d)
47        self.setTearDownCleanup(dictionary=d)
48        self.step_over_3_times(self.exe_name)
49
50    def test_negative_indexing(self):
51        """Test SBThread.frame with negative indexes."""
52        self.build()
53        self.validate_negative_indexing()
54
55    @expectedFailureAll(oslist=["windows"])
56    def test_StepInstruction(self):
57        """Test that StepInstruction preserves the plan stack."""
58        self.build()
59        self.step_instruction_in_called_function()
60
61    def setUp(self):
62        # Call super's setUp().
63        TestBase.setUp(self)
64        # Find the line number within main.cpp to break inside main().
65        self.break_line = line_number(
66            "main.cpp", "// Set break point at this line and check variable 'my_char'."
67        )
68        # Find the line numbers within main2.cpp for
69        # step_out_of_malloc_into_function_b() and step_over_3_times().
70        self.step_out_of_malloc = line_number(
71            "main2.cpp", "// thread step-out of malloc into function b."
72        )
73        self.after_3_step_overs = line_number(
74            "main2.cpp", "// we should reach here after 3 step-over's."
75        )
76
77        # We'll use the test method name as the exe_name for executable
78        # compiled from main2.cpp.
79        self.exe_name = self.testMethodName
80
81    def get_process(self):
82        """Test Python SBThread.GetProcess() API."""
83        exe = self.getBuildArtifact("a.out")
84
85        target = self.dbg.CreateTarget(exe)
86        self.assertTrue(target, VALID_TARGET)
87
88        breakpoint = target.BreakpointCreateByLocation("main.cpp", self.break_line)
89        self.assertTrue(breakpoint, VALID_BREAKPOINT)
90        self.runCmd("breakpoint list")
91
92        # Launch the process, and do not stop at the entry point.
93        process = target.LaunchSimple(None, None, self.get_process_working_directory())
94
95        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
96        self.assertTrue(
97            thread.IsValid(), "There should be a thread stopped due to breakpoint"
98        )
99        self.runCmd("process status")
100
101        proc_of_thread = thread.GetProcess()
102        self.trace("proc_of_thread:", proc_of_thread)
103        self.assertEqual(proc_of_thread.GetProcessID(), process.GetProcessID())
104
105    def get_stop_description(self):
106        """Test Python SBThread.GetStopDescription() API."""
107        exe = self.getBuildArtifact("a.out")
108
109        target = self.dbg.CreateTarget(exe)
110        self.assertTrue(target, VALID_TARGET)
111
112        breakpoint = target.BreakpointCreateByLocation("main.cpp", self.break_line)
113        self.assertTrue(breakpoint, VALID_BREAKPOINT)
114        # self.runCmd("breakpoint list")
115
116        # Launch the process, and do not stop at the entry point.
117        process = target.LaunchSimple(None, None, self.get_process_working_directory())
118
119        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
120        self.assertTrue(
121            thread.IsValid(), "There should be a thread stopped due to breakpoint"
122        )
123
124        # Get the stop reason. GetStopDescription expects that we pass in the size of the description
125        # we expect plus an additional byte for the null terminator.
126
127        # Test with a buffer that is exactly as large as the expected stop reason.
128        self.assertEqual(
129            "breakpoint 1.1", thread.GetStopDescription(len("breakpoint 1.1") + 1)
130        )
131
132        # Test some smaller buffer sizes.
133        self.assertEqual("breakpoint", thread.GetStopDescription(len("breakpoint") + 1))
134        self.assertEqual("break", thread.GetStopDescription(len("break") + 1))
135        self.assertEqual("b", thread.GetStopDescription(len("b") + 1))
136
137        # Test that we can pass in a much larger size and still get the right output.
138        self.assertEqual(
139            "breakpoint 1.1", thread.GetStopDescription(len("breakpoint 1.1") + 100)
140        )
141
142    def step_out_of_malloc_into_function_b(self, exe_name):
143        """Test Python SBThread.StepOut() API to step out of a malloc call where the call site is at function b()."""
144        exe = self.getBuildArtifact(exe_name)
145
146        target = self.dbg.CreateTarget(exe)
147        self.assertTrue(target, VALID_TARGET)
148
149        breakpoint = target.BreakpointCreateByName("malloc")
150        self.assertTrue(breakpoint, VALID_BREAKPOINT)
151
152        # Launch the process, and do not stop at the entry point.
153        process = target.LaunchSimple(None, None, self.get_process_working_directory())
154
155        while True:
156            thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
157            self.assertTrue(
158                thread.IsValid(), "There should be a thread stopped due to breakpoint"
159            )
160            caller_symbol = get_caller_symbol(thread)
161            if not caller_symbol:
162                self.fail("Test failed: could not locate the caller symbol of malloc")
163
164            # Our top frame may be an inlined function in malloc() (e.g., on
165            # FreeBSD).  Apply a simple heuristic of stepping out until we find
166            # a non-malloc caller
167            while caller_symbol.startswith("malloc"):
168                thread.StepOut()
169                self.assertTrue(
170                    thread.IsValid(), "Thread valid after stepping to outer malloc"
171                )
172                caller_symbol = get_caller_symbol(thread)
173
174            if caller_symbol == "b(int)":
175                break
176            process.Continue()
177
178        # On Linux malloc calls itself in some case. Remove the breakpoint because we don't want
179        # to hit it during step-out.
180        target.BreakpointDelete(breakpoint.GetID())
181
182        thread.StepOut()
183        self.runCmd("thread backtrace")
184        self.assertEqual(
185            thread.GetFrameAtIndex(0).GetLineEntry().GetLine(),
186            self.step_out_of_malloc,
187            "step out of malloc into function b is successful",
188        )
189
190    def step_over_3_times(self, exe_name):
191        """Test Python SBThread.StepOver() API."""
192        exe = self.getBuildArtifact(exe_name)
193
194        target = self.dbg.CreateTarget(exe)
195        self.assertTrue(target, VALID_TARGET)
196
197        breakpoint = target.BreakpointCreateByLocation(
198            "main2.cpp", self.step_out_of_malloc
199        )
200        self.assertTrue(breakpoint, VALID_BREAKPOINT)
201        self.runCmd("breakpoint list")
202
203        # Launch the process, and do not stop at the entry point.
204        process = target.LaunchSimple(None, None, self.get_process_working_directory())
205
206        self.assertTrue(process, PROCESS_IS_VALID)
207
208        # Frame #0 should be on self.step_out_of_malloc.
209        self.assertState(process.GetState(), lldb.eStateStopped)
210        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
211        self.assertTrue(
212            thread.IsValid(),
213            "There should be a thread stopped due to breakpoint condition",
214        )
215        self.runCmd("thread backtrace")
216        frame0 = thread.GetFrameAtIndex(0)
217        lineEntry = frame0.GetLineEntry()
218        self.assertEqual(lineEntry.GetLine(), self.step_out_of_malloc)
219
220        thread.StepOver()
221        thread.StepOver()
222        thread.StepOver()
223        self.runCmd("thread backtrace")
224
225        # Verify that we are stopped at the correct source line number in
226        # main2.cpp.
227        frame0 = thread.GetFrameAtIndex(0)
228        lineEntry = frame0.GetLineEntry()
229        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonPlanComplete)
230        # Expected failure with clang as the compiler.
231        # rdar://problem/9223880
232        #
233        # Which has been fixed on the lldb by compensating for inaccurate line
234        # table information with r140416.
235        self.assertEqual(lineEntry.GetLine(), self.after_3_step_overs)
236
237    def run_to_address(self, exe_name):
238        """Test Python SBThread.RunToAddress() API."""
239        exe = self.getBuildArtifact(exe_name)
240
241        target = self.dbg.CreateTarget(exe)
242        self.assertTrue(target, VALID_TARGET)
243
244        breakpoint = target.BreakpointCreateByLocation(
245            "main2.cpp", self.step_out_of_malloc
246        )
247        self.assertTrue(breakpoint, VALID_BREAKPOINT)
248        self.runCmd("breakpoint list")
249
250        # Launch the process, and do not stop at the entry point.
251        process = target.LaunchSimple(None, None, self.get_process_working_directory())
252
253        self.assertTrue(process, PROCESS_IS_VALID)
254
255        # Frame #0 should be on self.step_out_of_malloc.
256        self.assertState(process.GetState(), lldb.eStateStopped)
257        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
258        self.assertTrue(
259            thread.IsValid(),
260            "There should be a thread stopped due to breakpoint condition",
261        )
262        self.runCmd("thread backtrace")
263        frame0 = thread.GetFrameAtIndex(0)
264        lineEntry = frame0.GetLineEntry()
265        self.assertEqual(lineEntry.GetLine(), self.step_out_of_malloc)
266
267        # Get the start/end addresses for this line entry.
268        start_addr = lineEntry.GetStartAddress().GetLoadAddress(target)
269        end_addr = lineEntry.GetEndAddress().GetLoadAddress(target)
270        if self.TraceOn():
271            print("start addr:", hex(start_addr))
272            print("end addr:", hex(end_addr))
273
274        # Disable the breakpoint.
275        self.assertTrue(target.DisableAllBreakpoints())
276        self.runCmd("breakpoint list")
277
278        thread.StepOver()
279        thread.StepOver()
280        thread.StepOver()
281        self.runCmd("thread backtrace")
282
283        # Now ask SBThread to run to the address 'start_addr' we got earlier, which
284        # corresponds to self.step_out_of_malloc line entry's start address.
285        thread.RunToAddress(start_addr)
286        self.runCmd("process status")
287        # self.runCmd("thread backtrace")
288
289    def validate_negative_indexing(self):
290        exe = self.getBuildArtifact("a.out")
291
292        target = self.dbg.CreateTarget(exe)
293        self.assertTrue(target, VALID_TARGET)
294
295        breakpoint = target.BreakpointCreateByLocation("main.cpp", self.break_line)
296        self.assertTrue(breakpoint, VALID_BREAKPOINT)
297        self.runCmd("breakpoint list")
298
299        # Launch the process, and do not stop at the entry point.
300        process = target.LaunchSimple(None, None, self.get_process_working_directory())
301
302        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
303        self.assertTrue(
304            thread.IsValid(), "There should be a thread stopped due to breakpoint"
305        )
306        self.runCmd("process status")
307
308        pos_range = range(thread.num_frames)
309        neg_range = range(thread.num_frames, 0, -1)
310        for pos, neg in zip(pos_range, neg_range):
311            self.assertEqual(thread.frame[pos].idx, thread.frame[-neg].idx)
312
313    def step_instruction_in_called_function(self):
314        main_file_spec = lldb.SBFileSpec("main.cpp")
315        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
316            self, "Set break point at this line", main_file_spec
317        )
318        options = lldb.SBExpressionOptions()
319        options.SetIgnoreBreakpoints(False)
320
321        call_me_bkpt = target.BreakpointCreateBySourceRegex(
322            "Set a breakpoint in call_me", main_file_spec
323        )
324        self.assertGreater(
325            call_me_bkpt.GetNumLocations(), 0, "Got at least one location in call_me"
326        )
327        # Now run the expression, this will fail because we stopped at a breakpoint:
328        self.runCmd("expr -i 0 -- call_me(true)", check=False)
329        # Now we should be stopped in call_me:
330        self.assertEqual(
331            thread.frames[0].name, "call_me(bool)", "Stopped in call_me(bool)"
332        )
333        # Now do a various API steps.  These should not cause the expression context to get unshipped:
334        thread.StepInstruction(False)
335        self.assertEqual(
336            thread.frames[0].name,
337            "call_me(bool)",
338            "Still in call_me(bool) after StepInstruction",
339        )
340        thread.StepInstruction(True)
341        self.assertEqual(
342            thread.frames[0].name,
343            "call_me(bool)",
344            "Still in call_me(bool) after NextInstruction",
345        )
346        thread.StepInto()
347        self.assertEqual(
348            thread.frames[0].name,
349            "call_me(bool)",
350            "Still in call_me(bool) after StepInto",
351        )
352        thread.StepOver(False)
353        self.assertEqual(
354            thread.frames[0].name,
355            "call_me(bool)",
356            "Still in call_me(bool) after StepOver",
357        )
358