xref: /llvm-project/lldb/test/API/python_api/debugger/TestDebuggerAPI.py (revision 77369a7f1a81f7991a3df2dad1bc8e277bc7559d)
1"""
2Test Debugger APIs.
3"""
4
5import lldb
6
7from lldbsuite.test.decorators import *
8from lldbsuite.test.lldbtest import *
9from lldbsuite.test import lldbutil
10
11
12class DebuggerAPITestCase(TestBase):
13    NO_DEBUG_INFO_TESTCASE = True
14
15    def test_debugger_api_boundary_condition(self):
16        """Exercise SBDebugger APIs with boundary conditions."""
17        self.dbg.HandleCommand(None)
18        self.dbg.SetDefaultArchitecture(None)
19        self.dbg.GetScriptingLanguage(None)
20        self.dbg.CreateTarget(None)
21        self.dbg.CreateTarget(None, None, None, True, lldb.SBError())
22        self.dbg.CreateTargetWithFileAndTargetTriple(None, None)
23        self.dbg.CreateTargetWithFileAndArch(None, None)
24        self.dbg.FindTargetWithFileAndArch(None, None)
25        self.dbg.SetInternalVariable(None, None, None)
26        self.dbg.GetInternalVariableValue(None, None)
27        # FIXME (filcab): We must first allow for the swig bindings to know if
28        # a Python callback is set. (Check python-typemaps.swig)
29        # self.dbg.SetLoggingCallback(None)
30        self.dbg.SetPrompt(None)
31        self.dbg.SetCurrentPlatform(None)
32        self.dbg.SetCurrentPlatformSDKRoot(None)
33
34        fresh_dbg = lldb.SBDebugger()
35        self.assertEqual(len(fresh_dbg), 0)
36
37    def test_debugger_delete_invalid_target(self):
38        """SBDebugger.DeleteTarget() should not crash LLDB given and invalid target."""
39        target = lldb.SBTarget()
40        self.assertFalse(target.IsValid())
41        self.dbg.DeleteTarget(target)
42
43    def test_debugger_internal_variables(self):
44        """Ensure that SBDebugger reachs the same instance of properties
45        regardless CommandInterpreter's context initialization"""
46        self.build()
47        exe = self.getBuildArtifact("a.out")
48
49        # Create a target by the debugger.
50        target = self.dbg.CreateTarget(exe)
51        self.assertTrue(target, VALID_TARGET)
52
53        property_name = "target.process.memory-cache-line-size"
54
55        def get_cache_line_size():
56            value_list = lldb.SBStringList()
57            value_list = self.dbg.GetInternalVariableValue(
58                property_name, self.dbg.GetInstanceName()
59            )
60
61            self.assertEqual(value_list.GetSize(), 1)
62            try:
63                return int(value_list.GetStringAtIndex(0))
64            except ValueError as error:
65                self.fail("Value is not a number: " + error)
66
67        # Get global property value while there are no processes.
68        global_cache_line_size = get_cache_line_size()
69
70        # Run a process via SB interface. CommandInterpreter's execution context
71        # remains empty.
72        error = lldb.SBError()
73        launch_info = lldb.SBLaunchInfo(None)
74        launch_info.SetLaunchFlags(lldb.eLaunchFlagStopAtEntry)
75        process = target.Launch(launch_info, error)
76        self.assertTrue(process, PROCESS_IS_VALID)
77
78        # This should change the value of a process's local property.
79        new_cache_line_size = global_cache_line_size + 512
80        error = self.dbg.SetInternalVariable(
81            property_name, str(new_cache_line_size), self.dbg.GetInstanceName()
82        )
83        self.assertSuccess(error, property_name + " value was changed successfully")
84
85        # Check that it was set actually.
86        self.assertEqual(get_cache_line_size(), new_cache_line_size)
87
88        # Run any command to initialize CommandInterpreter's execution context.
89        self.runCmd("target list")
90
91        # Test the local property again, is it set to new_cache_line_size?
92        self.assertEqual(get_cache_line_size(), new_cache_line_size)
93
94    @expectedFailureAll(
95        hostoslist=["windows"],
96        remote=True,
97        bugnumber="github.com/llvm/llvm-project/issues/92419",
98    )
99    def test_CreateTarget_platform(self):
100        exe = self.getBuildArtifact("a.out")
101        self.yaml2obj("elf.yaml", exe)
102        error = lldb.SBError()
103        target1 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
104        self.assertSuccess(error)
105        platform1 = target1.GetPlatform()
106        platform1.SetWorkingDirectory("/foo/bar")
107
108        # Reuse a platform if it matches the currently selected one...
109        target2 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
110        self.assertSuccess(error)
111        platform2 = target2.GetPlatform()
112        self.assertTrue(
113            platform2.GetWorkingDirectory().endswith("bar"),
114            platform2.GetWorkingDirectory(),
115        )
116
117        # ... but create a new one if it doesn't.
118        self.dbg.SetSelectedPlatform(lldb.SBPlatform("remote-windows"))
119        target3 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
120        self.assertSuccess(error)
121        platform3 = target3.GetPlatform()
122        self.assertIsNone(platform3.GetWorkingDirectory())
123
124    def test_CreateTarget_arch(self):
125        exe = self.getBuildArtifact("a.out")
126        if lldbplatformutil.getHostPlatform() == "linux":
127            self.yaml2obj("macho.yaml", exe)
128            arch = "x86_64-apple-macosx"
129            expected_platform = "remote-macosx"
130        else:
131            self.yaml2obj("elf.yaml", exe)
132            arch = "x86_64-pc-linux"
133            expected_platform = "remote-linux"
134
135        fbsd = lldb.SBPlatform("remote-freebsd")
136        self.dbg.SetSelectedPlatform(fbsd)
137
138        error = lldb.SBError()
139        target1 = self.dbg.CreateTarget(exe, arch, None, False, error)
140        self.assertSuccess(error)
141        platform1 = target1.GetPlatform()
142        self.assertEqual(platform1.GetName(), expected_platform)
143        platform1.SetWorkingDirectory("/foo/bar")
144
145        # Reuse a platform even if it is not currently selected.
146        self.dbg.SetSelectedPlatform(fbsd)
147        target2 = self.dbg.CreateTarget(exe, arch, None, False, error)
148        self.assertSuccess(error)
149        platform2 = target2.GetPlatform()
150        self.assertEqual(platform2.GetName(), expected_platform)
151        self.assertTrue(
152            platform2.GetWorkingDirectory().endswith("bar"),
153            platform2.GetWorkingDirectory(),
154        )
155
156    def test_SetDestroyCallback(self):
157        destroy_dbg_id = None
158
159        def foo(dbg_id):
160            # Need nonlocal to modify closure variable.
161            nonlocal destroy_dbg_id
162            destroy_dbg_id = dbg_id
163
164        self.dbg.SetDestroyCallback(foo)
165
166        original_dbg_id = self.dbg.GetID()
167        self.dbg.Destroy(self.dbg)
168        self.assertEqual(destroy_dbg_id, original_dbg_id)
169
170    def test_AddDestroyCallback(self):
171        original_dbg_id = self.dbg.GetID()
172        called = []
173
174        def foo(dbg_id):
175            # Need nonlocal to modify closure variable.
176            nonlocal called
177            called += [('foo', dbg_id)]
178
179        def bar(dbg_id):
180            # Need nonlocal to modify closure variable.
181            nonlocal called
182            called += [('bar', dbg_id)]
183
184        token_foo = self.dbg.AddDestroyCallback(foo)
185        token_bar = self.dbg.AddDestroyCallback(bar)
186        self.dbg.Destroy(self.dbg)
187
188        # Should call both `foo()` and `bar()`.
189        self.assertEqual(called, [
190            ('foo', original_dbg_id),
191            ('bar', original_dbg_id),
192        ])
193
194    def test_RemoveDestroyCallback(self):
195        original_dbg_id = self.dbg.GetID()
196        called = []
197
198        def foo(dbg_id):
199            # Need nonlocal to modify closure variable.
200            nonlocal called
201            called += [('foo', dbg_id)]
202
203        def bar(dbg_id):
204            # Need nonlocal to modify closure variable.
205            nonlocal called
206            called += [('bar', dbg_id)]
207
208        token_foo = self.dbg.AddDestroyCallback(foo)
209        token_bar = self.dbg.AddDestroyCallback(bar)
210        ret = self.dbg.RemoveDestroyCallback(token_foo)
211        self.dbg.Destroy(self.dbg)
212
213        # `Remove` should be successful
214        self.assertTrue(ret)
215        # Should only call `bar()`
216        self.assertEqual(called, [('bar', original_dbg_id)])
217
218    def test_RemoveDestroyCallback_invalid_token(self):
219        original_dbg_id = self.dbg.GetID()
220        magic_token_that_should_not_exist = 32413
221        called = []
222
223        def foo(dbg_id):
224            # Need nonlocal to modify closure variable.
225            nonlocal called
226            called += [('foo', dbg_id)]
227
228        token_foo = self.dbg.AddDestroyCallback(foo)
229        ret = self.dbg.RemoveDestroyCallback(magic_token_that_should_not_exist)
230        self.dbg.Destroy(self.dbg)
231
232        # `Remove` should be unsuccessful
233        self.assertFalse(ret)
234        # Should call `foo()`
235        self.assertEqual(called, [('foo', original_dbg_id)])
236
237    def test_HandleDestroyCallback(self):
238        """
239        Validates:
240        1. AddDestroyCallback and RemoveDestroyCallback work during debugger destroy.
241        2. HandleDestroyCallback invokes all callbacks in FIFO order.
242        """
243        original_dbg_id = self.dbg.GetID()
244        events = []
245        bar_token = None
246
247        def foo(dbg_id):
248            # Need nonlocal to modify closure variable.
249            nonlocal events
250            events.append(('foo called', dbg_id))
251
252        def bar(dbg_id):
253            # Need nonlocal to modify closure variable.
254            nonlocal events
255            events.append(('bar called', dbg_id))
256
257        def add_foo(dbg_id):
258            # Need nonlocal to modify closure variable.
259            nonlocal events
260            events.append(('add_foo called', dbg_id))
261            events.append(('foo token', self.dbg.AddDestroyCallback(foo)))
262
263        def remove_bar(dbg_id):
264            # Need nonlocal to modify closure variable.
265            nonlocal events
266            events.append(('remove_bar called', dbg_id))
267            events.append(('remove bar ret', self.dbg.RemoveDestroyCallback(bar_token)))
268
269        # Setup
270        events.append(('add_foo token', self.dbg.AddDestroyCallback(add_foo)))
271        bar_token = self.dbg.AddDestroyCallback(bar)
272        events.append(('bar token', bar_token))
273        events.append(('remove_bar token', self.dbg.AddDestroyCallback(remove_bar)))
274        # Destroy
275        self.dbg.Destroy(self.dbg)
276
277        self.assertEqual(events, [
278            # Setup
279            ('add_foo token', 0), # add_foo should be added
280            ('bar token', 1), # bar should be added
281            ('remove_bar token', 2), # remove_bar should be added
282            # Destroy
283            ('add_foo called', original_dbg_id), # add_foo should be called
284            ('foo token', 3), # foo should be added
285            ('bar called', original_dbg_id), # bar should be called
286            ('remove_bar called', original_dbg_id), # remove_bar should be called
287            ('remove bar ret', False), # remove_bar should fail, because it's already invoked and removed
288            ('foo called', original_dbg_id), # foo should be called
289        ])
290