xref: /llvm-project/lldb/test/API/python_api/debugger/TestDebuggerAPI.py (revision 59e2a6b08f3e40afea87da3838ba69e1e15b6672)
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        remote=True,
96        bugnumber="github.com/llvm/llvm-project/issues/92419",
97    )
98    def test_CreateTarget_platform(self):
99        exe = self.getBuildArtifact("a.out")
100        self.yaml2obj("elf.yaml", exe)
101        error = lldb.SBError()
102        target1 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
103        self.assertSuccess(error)
104        platform1 = target1.GetPlatform()
105        platform1.SetWorkingDirectory("/foo/bar")
106
107        # Reuse a platform if it matches the currently selected one...
108        target2 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
109        self.assertSuccess(error)
110        platform2 = target2.GetPlatform()
111        self.assertTrue(
112            platform2.GetWorkingDirectory().endswith("bar"),
113            platform2.GetWorkingDirectory(),
114        )
115
116        # ... but create a new one if it doesn't.
117        self.dbg.SetSelectedPlatform(lldb.SBPlatform("remote-windows"))
118        target3 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
119        self.assertSuccess(error)
120        platform3 = target3.GetPlatform()
121        self.assertIsNone(platform3.GetWorkingDirectory())
122
123    def test_CreateTarget_arch(self):
124        exe = self.getBuildArtifact("a.out")
125        if lldbplatformutil.getHostPlatform() == "linux":
126            self.yaml2obj("macho.yaml", exe)
127            arch = "x86_64-apple-macosx"
128            expected_platform = "remote-macosx"
129        else:
130            self.yaml2obj("elf.yaml", exe)
131            arch = "x86_64-pc-linux"
132            expected_platform = "remote-linux"
133
134        fbsd = lldb.SBPlatform("remote-freebsd")
135        self.dbg.SetSelectedPlatform(fbsd)
136
137        error = lldb.SBError()
138        target1 = self.dbg.CreateTarget(exe, arch, None, False, error)
139        self.assertSuccess(error)
140        platform1 = target1.GetPlatform()
141        self.assertEqual(platform1.GetName(), expected_platform)
142        platform1.SetWorkingDirectory("/foo/bar")
143
144        # Reuse a platform even if it is not currently selected.
145        self.dbg.SetSelectedPlatform(fbsd)
146        target2 = self.dbg.CreateTarget(exe, arch, None, False, error)
147        self.assertSuccess(error)
148        platform2 = target2.GetPlatform()
149        self.assertEqual(platform2.GetName(), expected_platform)
150        self.assertTrue(
151            platform2.GetWorkingDirectory().endswith("bar"),
152            platform2.GetWorkingDirectory(),
153        )
154
155    def test_SetDestroyCallback(self):
156        destroy_dbg_id = None
157
158        def foo(dbg_id):
159            # Need nonlocal to modify closure variable.
160            nonlocal destroy_dbg_id
161            destroy_dbg_id = dbg_id
162
163        self.dbg.SetDestroyCallback(foo)
164
165        original_dbg_id = self.dbg.GetID()
166        self.dbg.Destroy(self.dbg)
167        self.assertEqual(destroy_dbg_id, original_dbg_id)
168
169    def test_AddDestroyCallback(self):
170        original_dbg_id = self.dbg.GetID()
171        called = []
172
173        def foo(dbg_id):
174            # Need nonlocal to modify closure variable.
175            nonlocal called
176            called += [('foo', dbg_id)]
177
178        def bar(dbg_id):
179            # Need nonlocal to modify closure variable.
180            nonlocal called
181            called += [('bar', dbg_id)]
182
183        token_foo = self.dbg.AddDestroyCallback(foo)
184        token_bar = self.dbg.AddDestroyCallback(bar)
185        self.dbg.Destroy(self.dbg)
186
187        # Should call both `foo()` and `bar()`.
188        self.assertEqual(called, [
189            ('foo', original_dbg_id),
190            ('bar', original_dbg_id),
191        ])
192
193    def test_RemoveDestroyCallback(self):
194        original_dbg_id = self.dbg.GetID()
195        called = []
196
197        def foo(dbg_id):
198            # Need nonlocal to modify closure variable.
199            nonlocal called
200            called += [('foo', dbg_id)]
201
202        def bar(dbg_id):
203            # Need nonlocal to modify closure variable.
204            nonlocal called
205            called += [('bar', dbg_id)]
206
207        token_foo = self.dbg.AddDestroyCallback(foo)
208        token_bar = self.dbg.AddDestroyCallback(bar)
209        ret = self.dbg.RemoveDestroyCallback(token_foo)
210        self.dbg.Destroy(self.dbg)
211
212        # `Remove` should be successful
213        self.assertTrue(ret)
214        # Should only call `bar()`
215        self.assertEqual(called, [('bar', original_dbg_id)])
216
217    def test_RemoveDestroyCallback_invalid_token(self):
218        original_dbg_id = self.dbg.GetID()
219        magic_token_that_should_not_exist = 32413
220        called = []
221
222        def foo(dbg_id):
223            # Need nonlocal to modify closure variable.
224            nonlocal called
225            called += [('foo', dbg_id)]
226
227        token_foo = self.dbg.AddDestroyCallback(foo)
228        ret = self.dbg.RemoveDestroyCallback(magic_token_that_should_not_exist)
229        self.dbg.Destroy(self.dbg)
230
231        # `Remove` should be unsuccessful
232        self.assertFalse(ret)
233        # Should call `foo()`
234        self.assertEqual(called, [('foo', original_dbg_id)])
235
236    def test_HandleDestroyCallback(self):
237        """
238        Validates:
239        1. AddDestroyCallback and RemoveDestroyCallback work during debugger destroy.
240        2. HandleDestroyCallback invokes all callbacks in FIFO order.
241        """
242        original_dbg_id = self.dbg.GetID()
243        events = []
244        bar_token = None
245
246        def foo(dbg_id):
247            # Need nonlocal to modify closure variable.
248            nonlocal events
249            events.append(('foo called', dbg_id))
250
251        def bar(dbg_id):
252            # Need nonlocal to modify closure variable.
253            nonlocal events
254            events.append(('bar called', dbg_id))
255
256        def add_foo(dbg_id):
257            # Need nonlocal to modify closure variable.
258            nonlocal events
259            events.append(('add_foo called', dbg_id))
260            events.append(('foo token', self.dbg.AddDestroyCallback(foo)))
261
262        def remove_bar(dbg_id):
263            # Need nonlocal to modify closure variable.
264            nonlocal events
265            events.append(('remove_bar called', dbg_id))
266            events.append(('remove bar ret', self.dbg.RemoveDestroyCallback(bar_token)))
267
268        # Setup
269        events.append(('add_foo token', self.dbg.AddDestroyCallback(add_foo)))
270        bar_token = self.dbg.AddDestroyCallback(bar)
271        events.append(('bar token', bar_token))
272        events.append(('remove_bar token', self.dbg.AddDestroyCallback(remove_bar)))
273        # Destroy
274        self.dbg.Destroy(self.dbg)
275
276        self.assertEqual(events, [
277            # Setup
278            ('add_foo token', 0), # add_foo should be added
279            ('bar token', 1), # bar should be added
280            ('remove_bar token', 2), # remove_bar should be added
281            # Destroy
282            ('add_foo called', original_dbg_id), # add_foo should be called
283            ('foo token', 3), # foo should be added
284            ('bar called', original_dbg_id), # bar should be called
285            ('remove_bar called', original_dbg_id), # remove_bar should be called
286            ('remove bar ret', False), # remove_bar should fail, because it's already invoked and removed
287            ('foo called', original_dbg_id), # foo should be called
288        ])
289