xref: /llvm-project/lldb/test/API/functionalities/breakpoint/scripted_bkpt/TestScriptedResolver.py (revision 9c2468821ec51defd09c246fea4a47886fff8c01)
1"""
2Test setting breakpoints using a scripted resolver
3"""
4
5import os
6import lldb
7import lldbsuite.test.lldbutil as lldbutil
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10
11
12class TestScriptedResolver(TestBase):
13    NO_DEBUG_INFO_TESTCASE = True
14
15    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528")
16    def test_scripted_resolver(self):
17        """Use a scripted resolver to set a by symbol name breakpoint"""
18        self.build()
19        self.do_test()
20
21    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528")
22    def test_search_depths(self):
23        """Make sure we are called at the right depths depending on what we return
24        from __get_depth__"""
25        self.build()
26        self.do_test_depths()
27
28    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528")
29    def test_command_line(self):
30        """Test setting a resolver breakpoint from the command line"""
31        self.build()
32        self.do_test_cli()
33
34    def test_bad_command_lines(self):
35        """Make sure we get appropriate errors when we give invalid key/value
36        options"""
37        self.build()
38        self.do_test_bad_options()
39
40    def test_copy_from_dummy_target(self):
41        """Make sure we don't crash during scripted breakpoint copy from dummy target"""
42        self.build()
43        self.do_test_copy_from_dummy_target()
44
45    def make_target_and_import(self):
46        target = self.make_target()
47        self.import_resolver_script()
48        return target
49
50    def make_target(self):
51        return lldbutil.run_to_breakpoint_make_target(self)
52
53    def import_resolver_script(self):
54        interp = self.dbg.GetCommandInterpreter()
55        error = lldb.SBError()
56
57        script_name = os.path.join(self.getSourceDir(), "resolver.py")
58        source_name = os.path.join(self.getSourceDir(), "main.c")
59
60        command = "command script import " + script_name
61        result = lldb.SBCommandReturnObject()
62        interp.HandleCommand(command, result)
63        self.assertTrue(
64            result.Succeeded(), "com scr imp failed: %s" % (result.GetError())
65        )
66
67    def make_extra_args(self):
68        json_string = '{"symbol":"break_on_me", "test1": "value1"}'
69        json_stream = lldb.SBStream()
70        json_stream.Print(json_string)
71        extra_args = lldb.SBStructuredData()
72        error = extra_args.SetFromJSON(json_stream)
73        self.assertSuccess(error, "Error making SBStructuredData")
74        return extra_args
75
76    def do_test(self):
77        """This reads in a python file and sets a breakpoint using it."""
78
79        target = self.make_target_and_import()
80        extra_args = self.make_extra_args()
81
82        file_list = lldb.SBFileSpecList()
83        module_list = lldb.SBFileSpecList()
84
85        # Make breakpoints with this resolver using different filters, first ones that will take:
86        right = []
87        # one with no file or module spec - this one should fire:
88        right.append(
89            target.BreakpointCreateFromScript(
90                "resolver.Resolver", extra_args, module_list, file_list
91            )
92        )
93
94        # one with the right source file and no module - should also fire:
95        file_list.Append(lldb.SBFileSpec("main.c"))
96        right.append(
97            target.BreakpointCreateFromScript(
98                "resolver.Resolver", extra_args, module_list, file_list
99            )
100        )
101        # Make sure the help text shows up in the "break list" output:
102        self.expect(
103            "break list",
104            substrs=["I am a python breakpoint resolver"],
105            msg="Help is listed in break list",
106        )
107
108        # one with the right source file and right module - should also fire:
109        module_list.Append(lldb.SBFileSpec("a.out"))
110        right.append(
111            target.BreakpointCreateFromScript(
112                "resolver.Resolver", extra_args, module_list, file_list
113            )
114        )
115
116        # And one with no source file but the right module:
117        file_list.Clear()
118        right.append(
119            target.BreakpointCreateFromScript(
120                "resolver.Resolver", extra_args, module_list, file_list
121            )
122        )
123
124        # Make sure these all got locations:
125        for i in range(0, len(right)):
126            self.assertGreaterEqual(
127                right[i].GetNumLocations(), 1, "Breakpoint %d has no locations." % (i)
128            )
129
130        # Now some ones that won't take:
131
132        module_list.Clear()
133        file_list.Clear()
134        wrong = []
135
136        # one with the wrong module - should not fire:
137        module_list.Append(lldb.SBFileSpec("noSuchModule"))
138        wrong.append(
139            target.BreakpointCreateFromScript(
140                "resolver.Resolver", extra_args, module_list, file_list
141            )
142        )
143
144        # one with the wrong file - also should not fire:
145        file_list.Clear()
146        module_list.Clear()
147        file_list.Append(lldb.SBFileSpec("noFileOfThisName.xxx"))
148        wrong.append(
149            target.BreakpointCreateFromScript(
150                "resolver.Resolver", extra_args, module_list, file_list
151            )
152        )
153
154        # Now make sure the CU level iteration obeys the file filters:
155        file_list.Clear()
156        module_list.Clear()
157        file_list.Append(lldb.SBFileSpec("no_such_file.xxx"))
158        wrong.append(
159            target.BreakpointCreateFromScript(
160                "resolver.ResolverCUDepth", extra_args, module_list, file_list
161            )
162        )
163
164        # And the Module filters:
165        file_list.Clear()
166        module_list.Clear()
167        module_list.Append(lldb.SBFileSpec("NoSuchModule.dylib"))
168        wrong.append(
169            target.BreakpointCreateFromScript(
170                "resolver.ResolverCUDepth", extra_args, module_list, file_list
171            )
172        )
173
174        # Now make sure the Function level iteration obeys the file filters:
175        file_list.Clear()
176        module_list.Clear()
177        file_list.Append(lldb.SBFileSpec("no_such_file.xxx"))
178        wrong.append(
179            target.BreakpointCreateFromScript(
180                "resolver.ResolverFuncDepth", extra_args, module_list, file_list
181            )
182        )
183
184        # And the Module filters:
185        file_list.Clear()
186        module_list.Clear()
187        module_list.Append(lldb.SBFileSpec("NoSuchModule.dylib"))
188        wrong.append(
189            target.BreakpointCreateFromScript(
190                "resolver.ResolverFuncDepth", extra_args, module_list, file_list
191            )
192        )
193
194        # Make sure these didn't get locations:
195        for i in range(0, len(wrong)):
196            self.assertEqual(
197                wrong[i].GetNumLocations(), 0, "Breakpoint %d has locations." % (i)
198            )
199
200        # Now run to main and ensure we hit the breakpoints we should have:
201
202        lldbutil.run_to_breakpoint_do_run(self, target, right[0])
203
204        # Test the hit counts:
205        for i in range(0, len(right)):
206            self.assertEqual(
207                right[i].GetHitCount(), 1, "Breakpoint %d has the wrong hit count" % (i)
208            )
209
210        for i in range(0, len(wrong)):
211            self.assertEqual(
212                wrong[i].GetHitCount(), 0, "Breakpoint %d has the wrong hit count" % (i)
213            )
214
215    def do_test_depths(self):
216        """This test uses a class variable in resolver.Resolver which gets set to 1 if we saw
217        compile unit and 2 if we only saw modules.  If the search depth is module, you get passed just
218        the modules with no comp_unit.  If the depth is comp_unit you get comp_units.  So we can use
219        this to test that our callback gets called at the right depth."""
220
221        target = self.make_target_and_import()
222        extra_args = self.make_extra_args()
223
224        file_list = lldb.SBFileSpecList()
225        module_list = lldb.SBFileSpecList()
226        module_list.Append(lldb.SBFileSpec("a.out"))
227
228        # Make a breakpoint that has no __get_depth__, check that that is converted to eSearchDepthModule:
229        bkpt = target.BreakpointCreateFromScript(
230            "resolver.Resolver", extra_args, module_list, file_list
231        )
232        self.assertGreater(bkpt.GetNumLocations(), 0, "Resolver got no locations.")
233        self.expect(
234            "script print(resolver.Resolver.got_files)",
235            substrs=["2"],
236            msg="Was only passed modules",
237        )
238
239        # Make a breakpoint that asks for modules, check that we didn't get any files:
240        bkpt = target.BreakpointCreateFromScript(
241            "resolver.ResolverModuleDepth", extra_args, module_list, file_list
242        )
243        self.assertGreater(
244            bkpt.GetNumLocations(), 0, "ResolverModuleDepth got no locations."
245        )
246        self.expect(
247            "script print(resolver.Resolver.got_files)",
248            substrs=["2"],
249            msg="Was only passed modules",
250        )
251
252        # Make a breakpoint that asks for compile units, check that we didn't get any files:
253        bkpt = target.BreakpointCreateFromScript(
254            "resolver.ResolverCUDepth", extra_args, module_list, file_list
255        )
256        self.assertGreater(
257            bkpt.GetNumLocations(), 0, "ResolverCUDepth got no locations."
258        )
259        self.expect(
260            "script print(resolver.Resolver.got_files)",
261            substrs=["1"],
262            msg="Was passed compile units",
263        )
264
265        # Make a breakpoint that returns a bad value - we should convert that to "modules" so check that:
266        bkpt = target.BreakpointCreateFromScript(
267            "resolver.ResolverBadDepth", extra_args, module_list, file_list
268        )
269        self.assertGreater(
270            bkpt.GetNumLocations(), 0, "ResolverBadDepth got no locations."
271        )
272        self.expect(
273            "script print(resolver.Resolver.got_files)",
274            substrs=["2"],
275            msg="Was only passed modules",
276        )
277
278        # Make a breakpoint that searches at function depth:
279        bkpt = target.BreakpointCreateFromScript(
280            "resolver.ResolverFuncDepth", extra_args, module_list, file_list
281        )
282        self.assertGreater(
283            bkpt.GetNumLocations(), 0, "ResolverFuncDepth got no locations."
284        )
285        self.expect(
286            "script print(resolver.Resolver.got_files)",
287            substrs=["3"],
288            msg="Was only passed modules",
289        )
290        self.expect(
291            "script print(resolver.Resolver.func_list)",
292            substrs=["test_func", "break_on_me", "main"],
293            msg="Saw all the functions",
294        )
295
296    def do_test_cli(self):
297        target = self.make_target_and_import()
298
299        lldbutil.run_break_set_by_script(
300            self, "resolver.Resolver", extra_options="-k symbol -v break_on_me"
301        )
302
303        # Make sure setting a resolver breakpoint doesn't pollute further breakpoint setting
304        # by checking the description of a regular file & line breakpoint to make sure it
305        # doesn't mention the Python Resolver function:
306        bkpt_no = lldbutil.run_break_set_by_file_and_line(self, "main.c", 12)
307        bkpt = target.FindBreakpointByID(bkpt_no)
308        strm = lldb.SBStream()
309        bkpt.GetDescription(strm, False)
310        used_resolver = "I am a python breakpoint resolver" in strm.GetData()
311        self.assertFalse(
312            used_resolver,
313            "Found the resolver description in the file & line breakpoint description.",
314        )
315
316        # Also make sure the breakpoint was where we expected:
317        bp_loc = bkpt.GetLocationAtIndex(0)
318        bp_sc = bp_loc.GetAddress().GetSymbolContext(lldb.eSymbolContextEverything)
319        bp_se = bp_sc.GetLineEntry()
320        self.assertEqual(bp_se.GetLine(), 12, "Got the right line number")
321        self.assertEqual(
322            bp_se.GetFileSpec().GetFilename(), "main.c", "Got the right filename"
323        )
324
325    def do_test_bad_options(self):
326        target = self.make_target_and_import()
327
328        self.expect(
329            "break set -P resolver.Resolver -k a_key",
330            error=True,
331            msg="Missing value at end",
332            substrs=['Key: "a_key" missing value'],
333        )
334        self.expect(
335            "break set -P resolver.Resolver -v a_value",
336            error=True,
337            msg="Missing key at end",
338            substrs=['Value: "a_value" missing matching key'],
339        )
340        self.expect(
341            "break set -P resolver.Resolver -v a_value -k a_key -v another_value",
342            error=True,
343            msg="Missing key among args",
344            substrs=['Value: "a_value" missing matching key'],
345        )
346        self.expect(
347            "break set -P resolver.Resolver -k a_key -k a_key -v another_value",
348            error=True,
349            msg="Missing value among args",
350            substrs=['Key: "a_key" missing value'],
351        )
352
353    def do_test_copy_from_dummy_target(self):
354        # Import breakpoint scripted resolver.
355        self.import_resolver_script()
356
357        # Create a scripted breakpoint.
358        self.runCmd(
359            "breakpoint set -P resolver.Resolver -k symbol -v break_on_me",
360            BREAKPOINT_CREATED,
361        )
362
363        # This is the function to remove breakpoints from the dummy target
364        # to get a clean state for the next test case.
365        def cleanup():
366            self.runCmd("breakpoint delete -D -f", check=False)
367            self.runCmd("breakpoint list", check=False)
368
369        # Execute the cleanup function during test case tear down.
370        self.addTearDownHook(cleanup)
371
372        # Check that target creating doesn't crash.
373        target = self.make_target()
374