xref: /llvm-project/lldb/test/API/functionalities/breakpoint/serialize/TestBreakpointSerialization.py (revision 9c2468821ec51defd09c246fea4a47886fff8c01)
1"""
2Test breakpoint serialization.
3"""
4
5import os
6import json
7import lldb
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10from lldbsuite.test import lldbutil
11
12
13class BreakpointSerialization(TestBase):
14    NO_DEBUG_INFO_TESTCASE = True
15
16    @add_test_categories(["pyapi"])
17    def test_resolvers(self):
18        """Use Python APIs to test that we serialize resolvers."""
19        self.build()
20        self.setup_targets_and_cleanup()
21        self.do_check_resolvers()
22
23    def test_filters(self):
24        """Use Python APIs to test that we serialize search filters correctly."""
25        self.build()
26        self.setup_targets_and_cleanup()
27        self.do_check_filters()
28
29    def test_options(self):
30        """Use Python APIs to test that we serialize breakpoint options correctly."""
31        self.build()
32        self.setup_targets_and_cleanup()
33        self.do_check_options()
34
35    def test_appending(self):
36        """Use Python APIs to test that we serialize breakpoint options correctly."""
37        self.build()
38        self.setup_targets_and_cleanup()
39        self.do_check_appending()
40
41    def test_name_filters(self):
42        """Use python APIs to test that reading in by name works correctly."""
43        self.build()
44        self.setup_targets_and_cleanup()
45        self.do_check_names()
46
47    def test_scripted_extra_args(self):
48        self.build()
49        self.setup_targets_and_cleanup()
50        self.do_check_extra_args()
51
52    def test_resolver_serialization(self):
53        """Test that breakpoint resolvers contain the expected information"""
54        self.build()
55        self.setup_targets_and_cleanup()
56
57        exe_path = self.getBuildArtifact("a.out")
58        exe_module = self.orig_target.module["a.out"]
59        self.assertTrue(
60            exe_module.IsValid(), "Failed to find the executable module in target"
61        )
62        sym_ctx_list = exe_module.FindFunctions("main")
63        self.assertEqual(sym_ctx_list.GetSize(), 1, "Unable to find function 'main'")
64        sym_ctx = sym_ctx_list.GetContextAtIndex(0)
65        self.assertTrue(
66            sym_ctx.IsValid(), "SBSymbolContext representing function 'main' is invalid"
67        )
68        main_func = sym_ctx.GetFunction()
69        self.assertTrue(
70            main_func.IsValid(), "SBFunction representing 'main' is invalid"
71        )
72        main_addr = main_func.GetStartAddress()
73
74        bkpt = self.orig_target.BreakpointCreateBySBAddress(main_addr)
75        self.assertTrue(
76            bkpt.IsValid(), "Could not place breakpoint on 'main' by address"
77        )
78        stream = lldb.SBStream()
79        sd = bkpt.SerializeToStructuredData()
80        sd.GetAsJSON(stream)
81        serialized_data = json.loads(stream.GetData())
82
83        self.assertIn(
84            exe_path,
85            serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["ModuleName"],
86        )
87
88    def test_structured_data_serialization(self):
89        target = self.dbg.GetDummyTarget()
90        self.assertTrue(target.IsValid(), VALID_TARGET)
91
92        interpreter = self.dbg.GetCommandInterpreter()
93        result = lldb.SBCommandReturnObject()
94        interpreter.HandleCommand("br set -f foo -l 42", result)
95        result = lldb.SBCommandReturnObject()
96        interpreter.HandleCommand("br set -c 'argc == 1' -n main", result)
97
98        bkp1 = target.GetBreakpointAtIndex(0)
99        self.assertTrue(bkp1.IsValid(), VALID_BREAKPOINT)
100        stream = lldb.SBStream()
101        sd = bkp1.SerializeToStructuredData()
102        sd.GetAsJSON(stream)
103        serialized_data = json.loads(stream.GetData())
104        self.assertEqual(
105            serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["FileName"], "foo"
106        )
107        self.assertEqual(
108            serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["LineNumber"], 42
109        )
110
111        bkp2 = target.GetBreakpointAtIndex(1)
112        self.assertTrue(bkp2.IsValid(), VALID_BREAKPOINT)
113        stream = lldb.SBStream()
114        sd = bkp2.SerializeToStructuredData()
115        sd.GetAsJSON(stream)
116        serialized_data = json.loads(stream.GetData())
117        self.assertIn(
118            "main",
119            serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["SymbolNames"],
120        )
121        self.assertEqual(
122            serialized_data["Breakpoint"]["BKPTOptions"]["ConditionText"], "argc == 1"
123        )
124
125        invalid_bkp = lldb.SBBreakpoint()
126        self.assertFalse(invalid_bkp.IsValid(), "Breakpoint should not be valid.")
127        stream = lldb.SBStream()
128        sd = invalid_bkp.SerializeToStructuredData()
129        sd.GetAsJSON(stream)
130        self.assertFalse(
131            stream.GetData(), "Invalid breakpoint should have an empty structured data"
132        )
133
134    def setup_targets_and_cleanup(self):
135        def cleanup():
136            self.RemoveTempFile(self.bkpts_file_path)
137
138            if self.orig_target.IsValid():
139                self.dbg.DeleteTarget(self.orig_target)
140                self.dbg.DeleteTarget(self.copy_target)
141
142        self.addTearDownHook(cleanup)
143        self.RemoveTempFile(self.bkpts_file_path)
144
145        exe = self.getBuildArtifact("a.out")
146
147        # Create the targets we are making breakpoints in and copying them to:
148        self.orig_target = self.dbg.CreateTarget(exe)
149        self.assertTrue(self.orig_target, VALID_TARGET)
150
151        self.copy_target = self.dbg.CreateTarget(exe)
152        self.assertTrue(self.copy_target, VALID_TARGET)
153
154    def setUp(self):
155        # Call super's setUp().
156        TestBase.setUp(self)
157
158        self.bkpts_file_path = self.getBuildArtifact("breakpoints.json")
159        self.bkpts_file_spec = lldb.SBFileSpec(self.bkpts_file_path)
160
161    def check_equivalence(self, source_bps, do_write=True):
162        error = lldb.SBError()
163
164        if do_write:
165            error = self.orig_target.BreakpointsWriteToFile(
166                self.bkpts_file_spec, source_bps
167            )
168            self.assertSuccess(error, "Failed writing breakpoints to file")
169
170        copy_bps = lldb.SBBreakpointList(self.copy_target)
171        error = self.copy_target.BreakpointsCreateFromFile(
172            self.bkpts_file_spec, copy_bps
173        )
174        self.assertSuccess(error, "Failed reading breakpoints from file")
175
176        num_source_bps = source_bps.GetSize()
177        num_copy_bps = copy_bps.GetSize()
178        self.assertEqual(
179            num_source_bps,
180            num_copy_bps,
181            "Didn't get same number of input and output breakpoints - orig: %d copy: %d"
182            % (num_source_bps, num_copy_bps),
183        )
184
185        for i in range(0, num_source_bps):
186            source_bp = source_bps.GetBreakpointAtIndex(i)
187            source_desc = lldb.SBStream()
188            source_bp.GetDescription(source_desc, False)
189            source_text = source_desc.GetData()
190
191            # I am assuming here that the breakpoints will get written out in breakpoint ID order, and
192            # read back in ditto.  That is true right now, and I can't see any reason to do it differently
193            # but if we do we can go to writing the breakpoints one by one, or sniffing the descriptions to
194            # see which one is which.
195            copy_id = source_bp.GetID()
196            copy_bp = copy_bps.FindBreakpointByID(copy_id)
197            self.assertTrue(
198                copy_bp.IsValid(), "Could not find copy breakpoint %d." % (copy_id)
199            )
200
201            copy_desc = lldb.SBStream()
202            copy_bp.GetDescription(copy_desc, False)
203            copy_text = copy_desc.GetData()
204
205            # These two should be identical.
206            self.trace("Source text for %d is %s." % (i, source_text))
207            self.assertEqual(
208                source_text,
209                copy_text,
210                "Source and dest breakpoints are not identical: \nsource: %s\ndest: %s"
211                % (source_text, copy_text),
212            )
213
214    def do_check_resolvers(self):
215        """Use Python APIs to check serialization of breakpoint resolvers"""
216
217        empty_module_list = lldb.SBFileSpecList()
218        empty_cu_list = lldb.SBFileSpecList()
219        blubby_file_spec = lldb.SBFileSpec(
220            os.path.join(self.getSourceDir(), "blubby.c")
221        )
222
223        # It isn't actually important for these purposes that these breakpoint
224        # actually have locations.
225        source_bps = lldb.SBBreakpointList(self.orig_target)
226        source_bps.Append(self.orig_target.BreakpointCreateByLocation("blubby.c", 666))
227        # Make sure we do one breakpoint right:
228        self.check_equivalence(source_bps)
229        source_bps.Clear()
230
231        source_bps.Append(
232            self.orig_target.BreakpointCreateByName(
233                "blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list
234            )
235        )
236        source_bps.Append(
237            self.orig_target.BreakpointCreateByName(
238                "blubby", lldb.eFunctionNameTypeFull, empty_module_list, empty_cu_list
239            )
240        )
241        source_bps.Append(
242            self.orig_target.BreakpointCreateBySourceRegex(
243                "dont really care", blubby_file_spec
244            )
245        )
246
247        # And some number greater than one:
248        self.check_equivalence(source_bps)
249
250    def do_check_filters(self):
251        """Use Python APIs to check serialization of breakpoint filters."""
252        module_list = lldb.SBFileSpecList()
253        module_list.Append(lldb.SBFileSpec("SomeBinary"))
254        module_list.Append(lldb.SBFileSpec("SomeOtherBinary"))
255
256        cu_list = lldb.SBFileSpecList()
257        cu_list.Append(lldb.SBFileSpec("SomeCU.c"))
258        cu_list.Append(lldb.SBFileSpec("AnotherCU.c"))
259        cu_list.Append(lldb.SBFileSpec("ThirdCU.c"))
260
261        blubby_file_spec = lldb.SBFileSpec(
262            os.path.join(self.getSourceDir(), "blubby.c")
263        )
264
265        # It isn't actually important for these purposes that these breakpoint
266        # actually have locations.
267        source_bps = lldb.SBBreakpointList(self.orig_target)
268        bkpt = self.orig_target.BreakpointCreateByLocation(
269            blubby_file_spec, 666, 0, module_list
270        )
271        source_bps.Append(bkpt)
272
273        # Make sure we do one right:
274        self.check_equivalence(source_bps)
275        source_bps.Clear()
276
277        bkpt = self.orig_target.BreakpointCreateByName(
278            "blubby", lldb.eFunctionNameTypeAuto, module_list, cu_list
279        )
280        source_bps.Append(bkpt)
281        bkpt = self.orig_target.BreakpointCreateByName(
282            "blubby", lldb.eFunctionNameTypeFull, module_list, cu_list
283        )
284        source_bps.Append(bkpt)
285        bkpt = self.orig_target.BreakpointCreateBySourceRegex(
286            "dont really care", blubby_file_spec
287        )
288        source_bps.Append(bkpt)
289
290        # And some number greater than one:
291        self.check_equivalence(source_bps)
292
293    def do_check_options(self):
294        """Use Python APIs to check serialization of breakpoint options."""
295
296        empty_module_list = lldb.SBFileSpecList()
297        empty_cu_list = lldb.SBFileSpecList()
298        blubby_file_spec = lldb.SBFileSpec(
299            os.path.join(self.getSourceDir(), "blubby.c")
300        )
301
302        # It isn't actually important for these purposes that these breakpoint
303        # actually have locations.
304        source_bps = lldb.SBBreakpointList(self.orig_target)
305
306        bkpt = self.orig_target.BreakpointCreateByLocation(
307            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()
308        )
309        bkpt.SetEnabled(False)
310        bkpt.SetOneShot(True)
311        bkpt.SetThreadID(10)
312        source_bps.Append(bkpt)
313
314        # Make sure we get one right:
315        self.check_equivalence(source_bps)
316        source_bps.Clear()
317
318        bkpt = self.orig_target.BreakpointCreateByName(
319            "blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list
320        )
321        bkpt.SetIgnoreCount(10)
322        bkpt.SetThreadName("grubby")
323        source_bps.Append(bkpt)
324
325        bkpt = self.orig_target.BreakpointCreateByName(
326            "blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list
327        )
328        bkpt.SetCondition("gonna remove this")
329        bkpt.SetCondition("")
330        source_bps.Append(bkpt)
331
332        bkpt = self.orig_target.BreakpointCreateByName(
333            "blubby", lldb.eFunctionNameTypeFull, empty_module_list, empty_cu_list
334        )
335        bkpt.SetCondition("something != something_else")
336        bkpt.SetQueueName("grubby")
337        bkpt.AddName("FirstName")
338        bkpt.AddName("SecondName")
339        bkpt.SetScriptCallbackBody(
340            '\tprint("I am a function that prints.")\n\tprint("I don\'t do anything else")\n'
341        )
342        source_bps.Append(bkpt)
343
344        bkpt = self.orig_target.BreakpointCreateBySourceRegex(
345            "dont really care", blubby_file_spec
346        )
347        cmd_list = lldb.SBStringList()
348        cmd_list.AppendString("frame var")
349        cmd_list.AppendString("thread backtrace")
350
351        bkpt.SetCommandLineCommands(cmd_list)
352        source_bps.Append(bkpt)
353
354        self.check_equivalence(source_bps)
355
356    def do_check_appending(self):
357        """Use Python APIs to check appending to already serialized options."""
358
359        empty_module_list = lldb.SBFileSpecList()
360        empty_cu_list = lldb.SBFileSpecList()
361        blubby_file_spec = lldb.SBFileSpec(
362            os.path.join(self.getSourceDir(), "blubby.c")
363        )
364
365        # It isn't actually important for these purposes that these breakpoint
366        # actually have locations.
367
368        all_bps = lldb.SBBreakpointList(self.orig_target)
369        source_bps = lldb.SBBreakpointList(self.orig_target)
370
371        bkpt = self.orig_target.BreakpointCreateByLocation(
372            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()
373        )
374        bkpt.SetEnabled(False)
375        bkpt.SetOneShot(True)
376        bkpt.SetThreadID(10)
377        source_bps.Append(bkpt)
378        all_bps.Append(bkpt)
379
380        error = lldb.SBError()
381        error = self.orig_target.BreakpointsWriteToFile(
382            self.bkpts_file_spec, source_bps
383        )
384        self.assertSuccess(error, "Failed writing breakpoints to file")
385
386        source_bps.Clear()
387
388        bkpt = self.orig_target.BreakpointCreateByName(
389            "blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list
390        )
391        bkpt.SetIgnoreCount(10)
392        bkpt.SetThreadName("grubby")
393        source_bps.Append(bkpt)
394        all_bps.Append(bkpt)
395
396        bkpt = self.orig_target.BreakpointCreateByName(
397            "blubby", lldb.eFunctionNameTypeFull, empty_module_list, empty_cu_list
398        )
399        bkpt.SetCondition("something != something_else")
400        bkpt.SetQueueName("grubby")
401        bkpt.AddName("FirstName")
402        bkpt.AddName("SecondName")
403
404        source_bps.Append(bkpt)
405        all_bps.Append(bkpt)
406
407        error = self.orig_target.BreakpointsWriteToFile(
408            self.bkpts_file_spec, source_bps, True
409        )
410        self.assertSuccess(error, "Failed appending breakpoints to file")
411
412        self.check_equivalence(all_bps)
413
414    def do_check_names(self):
415        bkpt = self.orig_target.BreakpointCreateByLocation(
416            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()
417        )
418        good_bkpt_name = "GoodBreakpoint"
419        write_bps = lldb.SBBreakpointList(self.orig_target)
420        bkpt.AddName(good_bkpt_name)
421        write_bps.Append(bkpt)
422
423        error = lldb.SBError()
424        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
425        self.assertSuccess(error, "Failed writing breakpoints to file")
426
427        copy_bps = lldb.SBBreakpointList(self.copy_target)
428        names_list = lldb.SBStringList()
429        names_list.AppendString("NoSuchName")
430
431        error = self.copy_target.BreakpointsCreateFromFile(
432            self.bkpts_file_spec, names_list, copy_bps
433        )
434        self.assertSuccess(error, "Failed reading breakpoints from file")
435        self.assertEqual(
436            copy_bps.GetSize(), 0, "Found breakpoints with a nonexistent name."
437        )
438
439        names_list.AppendString(good_bkpt_name)
440        error = self.copy_target.BreakpointsCreateFromFile(
441            self.bkpts_file_spec, names_list, copy_bps
442        )
443        self.assertSuccess(error, "Failed reading breakpoints from file")
444        self.assertEqual(copy_bps.GetSize(), 1, "Found the matching breakpoint.")
445
446    def do_check_extra_args(self):
447        import side_effect
448
449        interp = self.dbg.GetCommandInterpreter()
450        error = lldb.SBError()
451
452        script_name = os.path.join(self.getSourceDir(), "resolver.py")
453
454        command = "command script import " + script_name
455        result = lldb.SBCommandReturnObject()
456        interp.HandleCommand(command, result)
457        self.assertTrue(
458            result.Succeeded(), "com scr imp failed: %s" % (result.GetError())
459        )
460
461        # First make sure a scripted breakpoint with no args works:
462        bkpt = self.orig_target.BreakpointCreateFromScript(
463            "resolver.Resolver",
464            lldb.SBStructuredData(),
465            lldb.SBFileSpecList(),
466            lldb.SBFileSpecList(),
467        )
468        self.assertTrue(bkpt.IsValid(), "Bkpt is valid")
469        write_bps = lldb.SBBreakpointList(self.orig_target)
470
471        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
472        self.assertSuccess(error, "Failed writing breakpoints")
473
474        side_effect.g_extra_args = None
475        copy_bps = lldb.SBBreakpointList(self.copy_target)
476        error = self.copy_target.BreakpointsCreateFromFile(
477            self.bkpts_file_spec, copy_bps
478        )
479        self.assertSuccess(error, "Failed reading breakpoints")
480
481        self.assertEqual(copy_bps.GetSize(), 1, "Got one breakpoint from file.")
482        no_keys = lldb.SBStringList()
483        side_effect.g_extra_args.GetKeys(no_keys)
484        self.assertEqual(no_keys.GetSize(), 0, "Should have no keys")
485
486        self.orig_target.DeleteAllBreakpoints()
487        self.copy_target.DeleteAllBreakpoints()
488
489        # Now try one with extra args:
490
491        extra_args = lldb.SBStructuredData()
492        stream = lldb.SBStream()
493        stream.Print('{"first_arg" : "first_value", "second_arg" : "second_value"}')
494        extra_args.SetFromJSON(stream)
495        self.assertTrue(extra_args.IsValid(), "SBStructuredData is valid.")
496
497        bkpt = self.orig_target.BreakpointCreateFromScript(
498            "resolver.Resolver",
499            extra_args,
500            lldb.SBFileSpecList(),
501            lldb.SBFileSpecList(),
502        )
503        self.assertTrue(bkpt.IsValid(), "Bkpt is valid")
504        write_bps = lldb.SBBreakpointList(self.orig_target)
505
506        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
507        self.assertSuccess(error, "Failed writing breakpoints")
508
509        orig_extra_args = side_effect.g_extra_args
510        self.assertTrue(orig_extra_args.IsValid(), "Extra args originally valid")
511
512        orig_keys = lldb.SBStringList()
513        orig_extra_args.GetKeys(orig_keys)
514        self.assertEqual(2, orig_keys.GetSize(), "Should have two keys")
515
516        side_effect.g_extra_args = None
517
518        copy_bps = lldb.SBBreakpointList(self.copy_target)
519        error = self.copy_target.BreakpointsCreateFromFile(
520            self.bkpts_file_spec, copy_bps
521        )
522        self.assertSuccess(error, "Failed reading breakpoints")
523
524        self.assertEqual(copy_bps.GetSize(), 1, "Got one breakpoint from file.")
525
526        copy_extra_args = side_effect.g_extra_args
527        copy_keys = lldb.SBStringList()
528        copy_extra_args.GetKeys(copy_keys)
529        self.assertEqual(2, copy_keys.GetSize(), "Copy should have two keys")
530
531        for idx in range(0, orig_keys.GetSize()):
532            key = orig_keys.GetStringAtIndex(idx)
533            copy_value = copy_extra_args.GetValueForKey(key).GetStringValue(100)
534
535            if key == "first_arg":
536                self.assertEqual(copy_value, "first_value")
537            elif key == "second_arg":
538                self.assertEqual(copy_value, "second_value")
539            else:
540                self.Fail("Unknown key: %s" % (key))
541