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