summaryrefslogtreecommitdiff
path: root/lldb/test/API/python_api/debugger/TestDebuggerAPI.py
blob: 44b11832880173781a5d11c24112b02540f92dd3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
"""
Test Debugger APIs.
"""

import lldb

from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class DebuggerAPITestCase(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    def test_debugger_api_boundary_condition(self):
        """Exercise SBDebugger APIs with boundary conditions."""
        self.dbg.HandleCommand(None)
        self.dbg.SetDefaultArchitecture(None)
        self.dbg.GetScriptingLanguage(None)
        self.dbg.CreateTarget(None)
        self.dbg.CreateTarget(None, None, None, True, lldb.SBError())
        self.dbg.CreateTargetWithFileAndTargetTriple(None, None)
        self.dbg.CreateTargetWithFileAndArch(None, None)
        self.dbg.FindTargetWithFileAndArch(None, None)
        self.dbg.SetInternalVariable(None, None, None)
        self.dbg.GetInternalVariableValue(None, None)
        # FIXME (filcab): We must first allow for the swig bindings to know if
        # a Python callback is set. (Check python-typemaps.swig)
        # self.dbg.SetLoggingCallback(None)
        self.dbg.SetPrompt(None)
        self.dbg.SetCurrentPlatform(None)
        self.dbg.SetCurrentPlatformSDKRoot(None)

        fresh_dbg = lldb.SBDebugger()
        self.assertEqual(len(fresh_dbg), 0)

    def test_debugger_delete_invalid_target(self):
        """SBDebugger.DeleteTarget() should not crash LLDB given and invalid target."""
        target = lldb.SBTarget()
        self.assertFalse(target.IsValid())
        self.dbg.DeleteTarget(target)

    def test_debugger_internal_variables(self):
        """Ensure that SBDebugger reachs the same instance of properties
        regardless CommandInterpreter's context initialization"""
        self.build()
        exe = self.getBuildArtifact("a.out")

        # Create a target by the debugger.
        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target, VALID_TARGET)

        property_name = "target.process.memory-cache-line-size"

        def get_cache_line_size():
            value_list = lldb.SBStringList()
            value_list = self.dbg.GetInternalVariableValue(
                property_name, self.dbg.GetInstanceName()
            )

            self.assertEqual(value_list.GetSize(), 1)
            try:
                return int(value_list.GetStringAtIndex(0))
            except ValueError as error:
                self.fail("Value is not a number: " + error)

        # Get global property value while there are no processes.
        global_cache_line_size = get_cache_line_size()

        # Run a process via SB interface. CommandInterpreter's execution context
        # remains empty.
        error = lldb.SBError()
        launch_info = lldb.SBLaunchInfo(None)
        launch_info.SetLaunchFlags(lldb.eLaunchFlagStopAtEntry)
        process = target.Launch(launch_info, error)
        self.assertTrue(process, PROCESS_IS_VALID)

        # This should change the value of a process's local property.
        new_cache_line_size = global_cache_line_size + 512
        error = self.dbg.SetInternalVariable(
            property_name, str(new_cache_line_size), self.dbg.GetInstanceName()
        )
        self.assertSuccess(error, property_name + " value was changed successfully")

        # Check that it was set actually.
        self.assertEqual(get_cache_line_size(), new_cache_line_size)

        # Run any command to initialize CommandInterpreter's execution context.
        self.runCmd("target list")

        # Test the local property again, is it set to new_cache_line_size?
        self.assertEqual(get_cache_line_size(), new_cache_line_size)

    @expectedFailureAll(
        remote=True,
        bugnumber="github.com/llvm/llvm-project/issues/92419",
    )
    def test_CreateTarget_platform(self):
        exe = self.getBuildArtifact("a.out")
        self.yaml2obj("elf.yaml", exe)
        error = lldb.SBError()
        target1 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
        self.assertSuccess(error)
        platform1 = target1.GetPlatform()
        platform1.SetWorkingDirectory("/foo/bar")

        # Reuse a platform if it matches the currently selected one...
        target2 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
        self.assertSuccess(error)
        platform2 = target2.GetPlatform()
        self.assertTrue(
            platform2.GetWorkingDirectory().endswith("bar"),
            platform2.GetWorkingDirectory(),
        )

        # ... but create a new one if it doesn't.
        self.dbg.SetSelectedPlatform(lldb.SBPlatform("remote-windows"))
        target3 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error)
        self.assertSuccess(error)
        platform3 = target3.GetPlatform()
        self.assertIsNone(platform3.GetWorkingDirectory())

    def test_CreateTarget_arch(self):
        exe = self.getBuildArtifact("a.out")
        if lldbplatformutil.getHostPlatform() == "linux":
            self.yaml2obj("macho.yaml", exe)
            arch = "x86_64-apple-macosx"
            expected_platform = "remote-macosx"
        else:
            self.yaml2obj("elf.yaml", exe)
            arch = "x86_64-pc-linux"
            expected_platform = "remote-linux"

        fbsd = lldb.SBPlatform("remote-freebsd")
        self.dbg.SetSelectedPlatform(fbsd)

        error = lldb.SBError()
        target1 = self.dbg.CreateTarget(exe, arch, None, False, error)
        self.assertSuccess(error)
        platform1 = target1.GetPlatform()
        self.assertEqual(platform1.GetName(), expected_platform)
        platform1.SetWorkingDirectory("/foo/bar")

        # Reuse a platform even if it is not currently selected.
        self.dbg.SetSelectedPlatform(fbsd)
        target2 = self.dbg.CreateTarget(exe, arch, None, False, error)
        self.assertSuccess(error)
        platform2 = target2.GetPlatform()
        self.assertEqual(platform2.GetName(), expected_platform)
        self.assertTrue(
            platform2.GetWorkingDirectory().endswith("bar"),
            platform2.GetWorkingDirectory(),
        )

    def test_SetDestroyCallback(self):
        destroy_dbg_id = None

        def foo(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal destroy_dbg_id
            destroy_dbg_id = dbg_id

        self.dbg.SetDestroyCallback(foo)

        original_dbg_id = self.dbg.GetID()
        self.dbg.Destroy(self.dbg)
        self.assertEqual(destroy_dbg_id, original_dbg_id)

    def test_AddDestroyCallback(self):
        original_dbg_id = self.dbg.GetID()
        called = []

        def foo(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal called
            called += [('foo', dbg_id)]

        def bar(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal called
            called += [('bar', dbg_id)]

        token_foo = self.dbg.AddDestroyCallback(foo)
        token_bar = self.dbg.AddDestroyCallback(bar)
        self.dbg.Destroy(self.dbg)

        # Should call both `foo()` and `bar()`.
        self.assertEqual(called, [
            ('foo', original_dbg_id),
            ('bar', original_dbg_id),
        ])

    def test_RemoveDestroyCallback(self):
        original_dbg_id = self.dbg.GetID()
        called = []

        def foo(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal called
            called += [('foo', dbg_id)]

        def bar(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal called
            called += [('bar', dbg_id)]

        token_foo = self.dbg.AddDestroyCallback(foo)
        token_bar = self.dbg.AddDestroyCallback(bar)
        ret = self.dbg.RemoveDestroyCallback(token_foo)
        self.dbg.Destroy(self.dbg)

        # `Remove` should be successful
        self.assertTrue(ret)
        # Should only call `bar()`
        self.assertEqual(called, [('bar', original_dbg_id)])

    def test_RemoveDestroyCallback_invalid_token(self):
        original_dbg_id = self.dbg.GetID()
        magic_token_that_should_not_exist = 32413
        called = []

        def foo(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal called
            called += [('foo', dbg_id)]

        token_foo = self.dbg.AddDestroyCallback(foo)
        ret = self.dbg.RemoveDestroyCallback(magic_token_that_should_not_exist)
        self.dbg.Destroy(self.dbg)

        # `Remove` should be unsuccessful
        self.assertFalse(ret)
        # Should call `foo()`
        self.assertEqual(called, [('foo', original_dbg_id)])

    def test_HandleDestroyCallback(self):
        """
        Validates:
        1. AddDestroyCallback and RemoveDestroyCallback work during debugger destroy.
        2. HandleDestroyCallback invokes all callbacks in FIFO order.
        """
        original_dbg_id = self.dbg.GetID()
        events = []
        bar_token = None

        def foo(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal events
            events.append(('foo called', dbg_id))

        def bar(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal events
            events.append(('bar called', dbg_id))

        def add_foo(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal events
            events.append(('add_foo called', dbg_id))
            events.append(('foo token', self.dbg.AddDestroyCallback(foo)))

        def remove_bar(dbg_id):
            # Need nonlocal to modify closure variable.
            nonlocal events
            events.append(('remove_bar called', dbg_id))
            events.append(('remove bar ret', self.dbg.RemoveDestroyCallback(bar_token)))

        # Setup
        events.append(('add_foo token', self.dbg.AddDestroyCallback(add_foo)))
        bar_token = self.dbg.AddDestroyCallback(bar)
        events.append(('bar token', bar_token))
        events.append(('remove_bar token', self.dbg.AddDestroyCallback(remove_bar)))
        # Destroy
        self.dbg.Destroy(self.dbg)

        self.assertEqual(events, [
            # Setup
            ('add_foo token', 0), # add_foo should be added
            ('bar token', 1), # bar should be added
            ('remove_bar token', 2), # remove_bar should be added
            # Destroy
            ('add_foo called', original_dbg_id), # add_foo should be called
            ('foo token', 3), # foo should be added
            ('bar called', original_dbg_id), # bar should be called
            ('remove_bar called', original_dbg_id), # remove_bar should be called
            ('remove bar ret', False), # remove_bar should fail, because it's already invoked and removed
            ('foo called', original_dbg_id), # foo should be called
        ])

    def test_version(self):
        instance_str = self.dbg.GetVersionString()
        class_str = lldb.SBDebugger.GetVersionString()
        property_str = lldb.SBDebugger.version

        self.assertEqual(instance_str, class_str)
        self.assertEqual(class_str, property_str)

    def test_find_target_with_unique_id(self):
        """Test SBDebugger.FindTargetByGloballyUniqueID() functionality."""

        # Test with invalid ID - should return invalid target
        invalid_target = self.dbg.FindTargetByGloballyUniqueID(999999)
        self.assertFalse(invalid_target.IsValid())

        # Test with ID 0 - should return invalid target
        zero_target = self.dbg.FindTargetByGloballyUniqueID(0)
        self.assertFalse(zero_target.IsValid())

        # Build a real executable and create target with it
        self.build()
        exe = self.getBuildArtifact("a.out")
        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target.IsValid())

        # Find the target using its unique ID
        unique_id = target.GetGloballyUniqueID()
        self.assertNotEqual(unique_id, lldb.LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID)
        found_target = self.dbg.FindTargetByGloballyUniqueID(unique_id)
        self.assertTrue(found_target.IsValid())
        self.assertEqual(
            self.dbg.GetIndexOfTarget(target), self.dbg.GetIndexOfTarget(found_target)
        )
        self.assertEqual(found_target.GetGloballyUniqueID(), unique_id)

    def test_target_unique_id_uniqueness(self):
        """Test that Target.GetGloballyUniqueID() returns unique values across multiple targets."""

        # Create multiple targets and verify they all have unique IDs
        self.build()
        exe = self.getBuildArtifact("a.out")
        targets = []
        unique_ids = set()

        for i in range(10):
            target = self.dbg.CreateTarget(exe)
            self.assertTrue(target.IsValid())

            unique_id = target.GetGloballyUniqueID()
            self.assertNotEqual(unique_id, 0)

            # Verify this ID hasn't been used before
            self.assertNotIn(
                unique_id, unique_ids, f"Duplicate unique ID found: {unique_id}"
            )

            unique_ids.add(unique_id)
            targets.append(target)

        # Verify all targets can still be found by their IDs
        for target in targets:
            unique_id = target.GetGloballyUniqueID()
            found = self.dbg.FindTargetByGloballyUniqueID(unique_id)
            self.assertTrue(found.IsValid())
            self.assertEqual(found.GetGloballyUniqueID(), unique_id)

    def test_target_unique_id_uniqueness_after_deletion(self):
        """Test finding targets have unique ID after target deletion."""
        # Create two targets
        self.build()
        exe = self.getBuildArtifact("a.out")
        target1 = self.dbg.CreateTarget(exe)
        target2 = self.dbg.CreateTarget(exe)
        self.assertTrue(target1.IsValid())
        self.assertTrue(target2.IsValid())

        unique_id1 = target1.GetGloballyUniqueID()
        unique_id2 = target2.GetGloballyUniqueID()
        self.assertNotEqual(unique_id1, 0)
        self.assertNotEqual(unique_id2, 0)
        self.assertNotEqual(unique_id1, unique_id2)

        # Verify we can find them initially
        found_target1 = self.dbg.FindTargetByGloballyUniqueID(unique_id1)
        found_target2 = self.dbg.FindTargetByGloballyUniqueID(unique_id2)
        self.assertTrue(found_target1.IsValid())
        self.assertTrue(found_target2.IsValid())
        target2_index = self.dbg.GetIndexOfTarget(target2)

        # Delete target 2
        deleted = self.dbg.DeleteTarget(target2)
        self.assertTrue(deleted)

        # Try to find the deleted target - should not be found
        not_found_target = self.dbg.FindTargetByGloballyUniqueID(unique_id2)
        self.assertFalse(not_found_target.IsValid())

        # Create a new target
        target3 = self.dbg.CreateTarget(exe)
        self.assertTrue(target3.IsValid())
        # Target list index of target3 should be the same as target2's
        # since it was deleted, but it should have a distinct unique ID
        target3_index = self.dbg.GetIndexOfTarget(target3)
        unique_id3 = target3.GetGloballyUniqueID()
        self.assertEqual(target3_index, target2_index)
        self.assertNotEqual(unique_id3, unique_id2)
        self.assertNotEqual(unique_id3, unique_id1)
        # Make sure we can find the new target
        found_target3 = self.dbg.FindTargetByGloballyUniqueID(
            target3.GetGloballyUniqueID()
        )
        self.assertTrue(found_target3.IsValid())

    def test_target_globally_unique_id_across_debuggers(self):
        """Test that target IDs are globally unique across multiple debuggers."""
        self.build()
        exe = self.getBuildArtifact("a.out")

        # Create two debuggers with targets each
        debugger1 = lldb.SBDebugger.Create()
        debugger2 = lldb.SBDebugger.Create()
        self.addTearDownHook(lambda: lldb.SBDebugger.Destroy(debugger1))
        self.addTearDownHook(lambda: lldb.SBDebugger.Destroy(debugger2))

        # Create 2 targets per debugger
        targets_d1 = [debugger1.CreateTarget(exe), debugger1.CreateTarget(exe)]
        targets_d2 = [debugger2.CreateTarget(exe), debugger2.CreateTarget(exe)]
        targets = targets_d1 + targets_d2

        # Get all IDs and verify they're unique
        ids = [target.GetGloballyUniqueID() for target in targets]
        self.assertEqual(
            len(set(ids)), len(ids), f"IDs should be globally unique: {ids}"
        )
        self.assertTrue(
            all(uid != lldb.LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID for uid in ids),
            "All IDs should be valid",
        )

        # Verify targets can be found by their IDs in respective debuggers
        for debugger, target_pair in [
            (debugger1, targets[:2]),
            (debugger2, targets[2:]),
        ]:
            for target in target_pair:
                found = debugger.FindTargetByGloballyUniqueID(
                    target.GetGloballyUniqueID()
                )
                self.assertTrue(
                    found.IsValid(), "Target should be found by its unique ID"
                )
                self.assertEqual(
                    found.GetGloballyUniqueID(), target.GetGloballyUniqueID()
                )