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