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