1"""
2Test SBThread APIs.
3"""
4
5from __future__ import print_function
6
7
8import lldb
9from lldbsuite.test.decorators import *
10from lldbsuite.test.lldbtest import *
11from lldbsuite.test import lldbutil
12from lldbsuite.test.lldbutil import get_stopped_thread, get_caller_symbol
13
14
15class ThreadAPITestCase(TestBase):
16
17    mydir = TestBase.compute_mydir(__file__)
18
19    @add_test_categories(['pyapi'])
20    def test_get_process(self):
21        """Test Python SBThread.GetProcess() API."""
22        self.build()
23        self.get_process()
24
25    @add_test_categories(['pyapi'])
26    def test_get_stop_description(self):
27        """Test Python SBThread.GetStopDescription() API."""
28        self.build()
29        self.get_stop_description()
30
31    @add_test_categories(['pyapi'])
32    def test_run_to_address(self):
33        """Test Python SBThread.RunToAddress() API."""
34        # We build a different executable than the default build() does.
35        d = {'CXX_SOURCES': 'main2.cpp', 'EXE': self.exe_name}
36        self.build(dictionary=d)
37        self.setTearDownCleanup(dictionary=d)
38        self.run_to_address(self.exe_name)
39
40    @skipIfAsan # The output looks different under ASAN.
41    @add_test_categories(['pyapi'])
42    @expectedFailureAll(oslist=["linux"], archs=['arm'], bugnumber="llvm.org/pr45892")
43    @expectedFailureAll(oslist=["windows"])
44    def test_step_out_of_malloc_into_function_b(self):
45        """Test Python SBThread.StepOut() API to step out of a malloc call where the call site is at function b()."""
46        # We build a different executable than the default build() does.
47        d = {'CXX_SOURCES': 'main2.cpp', 'EXE': self.exe_name}
48        self.build(dictionary=d)
49        self.setTearDownCleanup(dictionary=d)
50        self.step_out_of_malloc_into_function_b(self.exe_name)
51
52    @add_test_categories(['pyapi'])
53    def test_step_over_3_times(self):
54        """Test Python SBThread.StepOver() API."""
55        # We build a different executable than the default build() does.
56        d = {'CXX_SOURCES': 'main2.cpp', 'EXE': self.exe_name}
57        self.build(dictionary=d)
58        self.setTearDownCleanup(dictionary=d)
59        self.step_over_3_times(self.exe_name)
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        # 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        self.after_3_step_overs = line_number(
72            "main2.cpp", "// we should reach here after 3 step-over's.")
73
74        # We'll use the test method name as the exe_name for executable
75        # compiled from main2.cpp.
76        self.exe_name = self.testMethodName
77
78    def get_process(self):
79        """Test Python SBThread.GetProcess() API."""
80        exe = self.getBuildArtifact("a.out")
81
82        target = self.dbg.CreateTarget(exe)
83        self.assertTrue(target, VALID_TARGET)
84
85        breakpoint = target.BreakpointCreateByLocation(
86            "main.cpp", self.break_line)
87        self.assertTrue(breakpoint, VALID_BREAKPOINT)
88        self.runCmd("breakpoint list")
89
90        # Launch the process, and do not stop at the entry point.
91        process = target.LaunchSimple(
92            None, None, self.get_process_working_directory())
93
94        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
95        self.assertTrue(
96            thread.IsValid(),
97            "There should be a thread stopped due to breakpoint")
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(
112            "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(
118            None, None, self.get_process_working_directory())
119
120        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
121        self.assertTrue(
122            thread.IsValid(),
123            "There should be a thread stopped due to breakpoint")
124
125        # Get the stop reason. GetStopDescription expects that we pass in the size of the description
126        # we expect plus an additional byte for the null terminator.
127
128        # Test with a buffer that is exactly as large as the expected stop reason.
129        self.assertEqual("breakpoint 1.1", thread.GetStopDescription(len('breakpoint 1.1') + 1))
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("breakpoint 1.1", thread.GetStopDescription(len('breakpoint 1.1') + 100))
138
139    def step_out_of_malloc_into_function_b(self, exe_name):
140        """Test Python SBThread.StepOut() API to step out of a malloc call where the call site is at function b()."""
141        exe = self.getBuildArtifact(exe_name)
142
143        target = self.dbg.CreateTarget(exe)
144        self.assertTrue(target, VALID_TARGET)
145
146        breakpoint = target.BreakpointCreateByName('malloc')
147        self.assertTrue(breakpoint, VALID_BREAKPOINT)
148
149        # Launch the process, and do not stop at the entry point.
150        process = target.LaunchSimple(
151            None, None, self.get_process_working_directory())
152
153        while True:
154            thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
155            self.assertTrue(
156                thread.IsValid(),
157                "There should be a thread stopped due to breakpoint")
158            caller_symbol = get_caller_symbol(thread)
159            if not caller_symbol:
160                self.fail(
161                    "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(thread.IsValid(),
169                                "Thread valid after stepping to outer malloc")
170                caller_symbol = get_caller_symbol(thread)
171
172            if caller_symbol == "b(int)":
173                break
174            process.Continue()
175
176        # On Linux malloc calls itself in some case. Remove the breakpoint because we don't want
177        # to hit it during step-out.
178        target.BreakpointDelete(breakpoint.GetID())
179
180        thread.StepOut()
181        self.runCmd("thread backtrace")
182        self.assertEqual(
183            thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.step_out_of_malloc,
184            "step out of malloc into function b is successful")
185
186    def step_over_3_times(self, exe_name):
187        """Test Python SBThread.StepOver() API."""
188        exe = self.getBuildArtifact(exe_name)
189
190        target = self.dbg.CreateTarget(exe)
191        self.assertTrue(target, VALID_TARGET)
192
193        breakpoint = target.BreakpointCreateByLocation(
194            'main2.cpp', self.step_out_of_malloc)
195        self.assertTrue(breakpoint, VALID_BREAKPOINT)
196        self.runCmd("breakpoint list")
197
198        # Launch the process, and do not stop at the entry point.
199        process = target.LaunchSimple(
200            None, None, self.get_process_working_directory())
201
202        self.assertTrue(process, PROCESS_IS_VALID)
203
204        # Frame #0 should be on self.step_out_of_malloc.
205        self.assertEqual(process.GetState(), lldb.eStateStopped)
206        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
207        self.assertTrue(
208            thread.IsValid(),
209            "There should be a thread stopped due to breakpoint condition")
210        self.runCmd("thread backtrace")
211        frame0 = thread.GetFrameAtIndex(0)
212        lineEntry = frame0.GetLineEntry()
213        self.assertEqual(lineEntry.GetLine(), self.step_out_of_malloc)
214
215        thread.StepOver()
216        thread.StepOver()
217        thread.StepOver()
218        self.runCmd("thread backtrace")
219
220        # Verify that we are stopped at the correct source line number in
221        # main2.cpp.
222        frame0 = thread.GetFrameAtIndex(0)
223        lineEntry = frame0.GetLineEntry()
224        self.assertEqual(thread.GetStopReason(), lldb.eStopReasonPlanComplete)
225        # Expected failure with clang as the compiler.
226        # rdar://problem/9223880
227        #
228        # Which has been fixed on the lldb by compensating for inaccurate line
229        # table information with r140416.
230        self.assertEqual(lineEntry.GetLine(), self.after_3_step_overs)
231
232    def run_to_address(self, exe_name):
233        """Test Python SBThread.RunToAddress() API."""
234        exe = self.getBuildArtifact(exe_name)
235
236        target = self.dbg.CreateTarget(exe)
237        self.assertTrue(target, VALID_TARGET)
238
239        breakpoint = target.BreakpointCreateByLocation(
240            'main2.cpp', self.step_out_of_malloc)
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(
246            None, None, self.get_process_working_directory())
247
248        self.assertTrue(process, PROCESS_IS_VALID)
249
250        # Frame #0 should be on self.step_out_of_malloc.
251        self.assertEqual(process.GetState(), lldb.eStateStopped)
252        thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
253        self.assertTrue(
254            thread.IsValid(),
255            "There should be a thread stopped due to breakpoint condition")
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