xref: /llvm-project/lldb/test/API/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py (revision 4d9e7b14e45120557e57da2f00f6d23cf122dd95)
1"""
2Test lldb breakpoint command add/list/delete.
3"""
4
5
6import lldb
7from lldbsuite.test.decorators import *
8from lldbsuite.test.lldbtest import *
9from lldbsuite.test import lldbutil, lldbplatformutil
10import json
11import os
12import side_effect
13
14
15class BreakpointCommandTestCase(TestBase):
16    NO_DEBUG_INFO_TESTCASE = True
17
18    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24528")
19    def test_breakpoint_command_sequence(self):
20        """Test a sequence of breakpoint command add, list, and delete."""
21        self.build()
22        self.breakpoint_command_sequence()
23
24    @skipIf(oslist=["windows"], bugnumber="llvm.org/pr44431")
25    def test_script_parameters(self):
26        """Test a sequence of breakpoint command add, list, and delete."""
27        self.build()
28        self.breakpoint_command_script_parameters()
29
30    def test_commands_on_creation(self):
31        self.build()
32        self.breakpoint_commands_on_creation()
33
34    @skipIf(oslist=["windows"])
35    @no_debug_info_test
36    def test_breakpoints_with_relative_path_line_tables(self):
37        """
38        Test that we can set breakpoints using a full or partial path when
39        line tables in the debug information has relative paths where the
40        relative path is either fully contained in the specified path, or if
41        the specified path also a relative path that is shorter than the
42        path in the debug info.
43
44        The "relative.yaml" contains a line table that is:
45
46        Line table for a/b/c/main.cpp in `a.out
47        0x0000000100003f94: a/b/c/main.cpp:1
48        0x0000000100003fb0: a/b/c/main.cpp:2:3
49        0x0000000100003fb8: a/b/c/main.cpp:2:3
50
51        So the line table contains relative paths. We should be able to set
52        breakpoints with any source path that matches this path which
53        includes paths that are longer than "a/b/c/main.cpp", but also any
54        relative path that is shorter than this while all specified relative
55        path components still match.
56        """
57        src_dir = self.getSourceDir()
58        yaml_path = os.path.join(src_dir, "relative.yaml")
59        yaml_base, ext = os.path.splitext(yaml_path)
60        obj_path = self.getBuildArtifact("a.out")
61        self.yaml2obj(yaml_path, obj_path)
62
63        # Create a target with the object file we just created from YAML
64        target = self.dbg.CreateTarget(obj_path)
65        # We now have debug information with line table paths that start are
66        # "./a/b/c/main.cpp".
67
68        # Make sure that all of the following paths create a breakpoint
69        # successfully. We have paths that are longer than our path, and also
70        # that are shorter where all relative directories still match.
71        valid_paths = [
72            "/x/a/b/c/main.cpp",
73            "/x/y/a/b/c/main.cpp",
74            "./x/y/a/b/c/main.cpp",
75            "x/y/a/b/c/main.cpp",
76            "./y/a/b/c/main.cpp",
77            "y/a/b/c/main.cpp",
78            "./a/b/c/main.cpp",
79            "a/b/c/main.cpp",
80            "./b/c/main.cpp",
81            "b/c/main.cpp",
82            "./c/main.cpp",
83            "c/main.cpp",
84            "./main.cpp",
85            "main.cpp",
86        ]
87        for path in valid_paths:
88            bkpt = target.BreakpointCreateByLocation(path, 2)
89            self.assertGreater(
90                bkpt.GetNumLocations(),
91                0,
92                'Couldn\'t resolve breakpoint using full path "%s" in executate "%s" with '
93                "debug info that has relative path with matching suffix"
94                % (path, self.getBuildArtifact("a.out")),
95            )
96        invalid_paths = [
97            "/x/b/c/main.cpp",
98            "/x/c/main.cpp",
99            "/x/main.cpp",
100            "./x/y/a/d/c/main.cpp",
101        ]
102        # Reset source map.
103        self.runCmd("settings clear target.source-map")
104        for path in invalid_paths:
105            bkpt = target.BreakpointCreateByLocation(path, 2)
106            self.assertEqual(
107                bkpt.GetNumLocations(),
108                0,
109                'Incorrectly resolved a breakpoint using full path "%s" with '
110                "debug info that has relative path with matching suffix" % (path),
111            )
112
113    @no_debug_info_test
114    def test_breakpoints_with_bad_aranges(self):
115        """
116        Test that we can set breakpoints in a file that has an invalid
117        .debug_aranges. Older versions of LLDB would find a line entry
118        in the line table and then would use the start address of the line
119        entry to do an address lookup on the entry from the line table. If
120        this address to symbol context lookup would fail, due to a bad
121        .debug_aranges, it would cause the breakpoint to not get resolved.
122        Verify that even in these conditions we are able to resolve a
123        breakpoint.
124
125        The "bad_aranges.yaml" contains a line table that is:
126
127        Line table for /tmp/ab/main.cpp in `a.out
128        0x0000000100003f94: /tmp/ab/main.cpp:1
129        0x0000000100003fb0: /tmp/ab/main.cpp:2:3
130        0x0000000100003fb8: /tmp/ab/main.cpp:2:3
131
132        The .debug_aranges has one range for this compile unit that is
133        invalid: [0x0000000200003f94-0x0000000200003fb8). This will cause
134        the resolving of the addresses to fail.
135        """
136        src_dir = self.getSourceDir()
137        yaml_path = os.path.join(src_dir, "bad_aranges.yaml")
138        yaml_base, ext = os.path.splitext(yaml_path)
139        obj_path = self.getBuildArtifact("a.out")
140        self.yaml2obj(yaml_path, obj_path)
141
142        # Create a target with the object file we just created from YAML
143        target = self.dbg.CreateTarget(obj_path)
144        src_path = "/tmp/ab/main.cpp"
145        bkpt = target.BreakpointCreateByLocation(src_path, 2)
146        self.assertGreater(
147            bkpt.GetNumLocations(),
148            0,
149            'Couldn\'t resolve breakpoint using "%s" in executate "%s" with '
150            "debug info that has a bad .debug_aranges section"
151            % (src_path, self.getBuildArtifact("a.out")),
152        )
153
154    def setUp(self):
155        # Call super's setUp().
156        TestBase.setUp(self)
157        # Find the line number to break inside main().
158        self.line = line_number("main.c", "// Set break point at this line.")
159        # disable "There is a running process, kill it and restart?" prompt
160        self.runCmd("settings set auto-confirm true")
161        self.addTearDownHook(lambda: self.runCmd("settings clear auto-confirm"))
162
163    def test_delete_all_breakpoints(self):
164        """Test that deleting all breakpoints works."""
165        self.build()
166        exe = self.getBuildArtifact("a.out")
167        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
168
169        lldbutil.run_break_set_by_symbol(self, "main")
170        lldbutil.run_break_set_by_file_and_line(
171            self, "main.c", self.line, num_expected_locations=1, loc_exact=True
172        )
173
174        self.runCmd("run", RUN_SUCCEEDED)
175
176        self.runCmd("breakpoint delete")
177        self.runCmd("process continue")
178        self.expect(
179            "process status",
180            PROCESS_STOPPED,
181            patterns=["Process .* exited with status = 0"],
182        )
183
184    def breakpoint_command_sequence(self):
185        """Test a sequence of breakpoint command add, list, and delete."""
186        exe = self.getBuildArtifact("a.out")
187        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
188
189        # Add three breakpoints on the same line.  The first time we don't specify the file,
190        # since the default file is the one containing main:
191        lldbutil.run_break_set_by_file_and_line(
192            self, None, self.line, num_expected_locations=1, loc_exact=True
193        )
194        lldbutil.run_break_set_by_file_and_line(
195            self, "main.c", self.line, num_expected_locations=1, loc_exact=True
196        )
197        lldbutil.run_break_set_by_file_and_line(
198            self, "main.c", self.line, num_expected_locations=1, loc_exact=True
199        )
200        # Breakpoint 4 - set at the same location as breakpoint 1 to test
201        # setting breakpoint commands on two breakpoints at a time
202        lldbutil.run_break_set_by_file_and_line(
203            self, None, self.line, num_expected_locations=1, loc_exact=True
204        )
205        # Make sure relative path source breakpoints work as expected. We test
206        # with partial paths with and without "./" prefixes.
207        lldbutil.run_break_set_by_file_and_line(
208            self, "./main.c", self.line, num_expected_locations=1, loc_exact=True
209        )
210        lldbutil.run_break_set_by_file_and_line(
211            self,
212            "breakpoint_command/main.c",
213            self.line,
214            num_expected_locations=1,
215            loc_exact=True,
216        )
217        lldbutil.run_break_set_by_file_and_line(
218            self,
219            "./breakpoint_command/main.c",
220            self.line,
221            num_expected_locations=1,
222            loc_exact=True,
223        )
224        lldbutil.run_break_set_by_file_and_line(
225            self,
226            "breakpoint/breakpoint_command/main.c",
227            self.line,
228            num_expected_locations=1,
229            loc_exact=True,
230        )
231        lldbutil.run_break_set_by_file_and_line(
232            self,
233            "./breakpoint/breakpoint_command/main.c",
234            self.line,
235            num_expected_locations=1,
236            loc_exact=True,
237        )
238        # Test relative breakpoints with incorrect paths and make sure we get
239        # no breakpoint locations
240        lldbutil.run_break_set_by_file_and_line(
241            self, "invalid/main.c", self.line, num_expected_locations=0, loc_exact=True
242        )
243        lldbutil.run_break_set_by_file_and_line(
244            self,
245            "./invalid/main.c",
246            self.line,
247            num_expected_locations=0,
248            loc_exact=True,
249        )
250        # Now add callbacks for the breakpoints just created.
251        self.runCmd(
252            "breakpoint command add -s command -o 'frame variable --show-types --scope' 1 4"
253        )
254        self.runCmd(
255            "breakpoint command add -s python -o 'import side_effect; side_effect.one_liner = \"one liner was here\"' 2"
256        )
257
258        import side_effect
259
260        self.runCmd("command script import --allow-reload ./bktptcmd.py")
261
262        self.runCmd("breakpoint command add --python-function bktptcmd.function 3")
263
264        # Check that the breakpoint commands are correctly set.
265
266        # The breakpoint list now only contains breakpoint 1.
267        self.expect(
268            "breakpoint list",
269            "Breakpoints 1 & 2 created",
270            substrs=[
271                "2: file = 'main.c', line = %d, exact_match = 0, locations = 1"
272                % self.line
273            ],
274            patterns=[
275                "1: file = '.*main.c', line = %d, exact_match = 0, locations = 1"
276                % self.line
277            ],
278        )
279
280        self.expect(
281            "breakpoint list -f",
282            "Breakpoints 1 & 2 created",
283            substrs=[
284                "2: file = 'main.c', line = %d, exact_match = 0, locations = 1"
285                % self.line
286            ],
287            patterns=[
288                "1: file = '.*main.c', line = %d, exact_match = 0, locations = 1"
289                % self.line,
290                "1.1: .+at main.c:%d:?[0-9]*, .+unresolved, hit count = 0" % self.line,
291                "2.1: .+at main.c:%d:?[0-9]*, .+unresolved, hit count = 0" % self.line,
292            ],
293        )
294
295        self.expect(
296            "breakpoint command list 1",
297            "Breakpoint 1 command ok",
298            substrs=["Breakpoint commands:", "frame variable --show-types --scope"],
299        )
300        self.expect(
301            "breakpoint command list 2",
302            "Breakpoint 2 command ok",
303            substrs=[
304                "Breakpoint commands (Python):",
305                "import side_effect",
306                "side_effect.one_liner",
307            ],
308        )
309        self.expect(
310            "breakpoint command list 3",
311            "Breakpoint 3 command ok",
312            substrs=[
313                "Breakpoint commands (Python):",
314                "bktptcmd.function(frame, bp_loc, internal_dict)",
315            ],
316        )
317
318        self.expect(
319            "breakpoint command list 4",
320            "Breakpoint 4 command ok",
321            substrs=["Breakpoint commands:", "frame variable --show-types --scope"],
322        )
323
324        self.runCmd("breakpoint delete 4")
325
326        # Next lets try some other breakpoint kinds.  First break with a regular expression
327        # and then specify only one file.  The first time we should get two locations,
328        # the second time only one:
329
330        lldbutil.run_break_set_by_regexp(
331            self, r"._MyFunction", num_expected_locations=2
332        )
333
334        lldbutil.run_break_set_by_regexp(
335            self, r"._MyFunction", extra_options="-f a.c", num_expected_locations=1
336        )
337
338        lldbutil.run_break_set_by_regexp(
339            self,
340            r"._MyFunction",
341            extra_options="-f a.c -f b.c",
342            num_expected_locations=2,
343        )
344
345        # Now try a source regex breakpoint:
346        lldbutil.run_break_set_by_source_regexp(
347            self,
348            r"is about to return [12]0",
349            extra_options="-f a.c -f b.c",
350            num_expected_locations=2,
351        )
352
353        lldbutil.run_break_set_by_source_regexp(
354            self,
355            r"is about to return [12]0",
356            extra_options="-f a.c",
357            num_expected_locations=1,
358        )
359
360        # Reset our canary variables and run the program.
361        side_effect.one_liner = None
362        side_effect.bktptcmd = None
363        self.runCmd("run", RUN_SUCCEEDED)
364
365        # Check the value of canary variables.
366        self.assertEqual("one liner was here", side_effect.one_liner)
367        self.assertEqual("function was here", side_effect.bktptcmd)
368
369        # Finish the program.
370        self.runCmd("process continue")
371
372        # Remove the breakpoint command associated with breakpoint 1.
373        self.runCmd("breakpoint command delete 1")
374
375        # Remove breakpoint 2.
376        self.runCmd("breakpoint delete 2")
377
378        self.expect(
379            "breakpoint command list 1",
380            startstr="Breakpoint 1 does not have an associated command.",
381        )
382        self.expect(
383            "breakpoint command list 2",
384            error=True,
385            startstr="error: '2' is not a currently valid breakpoint ID.",
386        )
387
388        # The breakpoint list now only contains breakpoint 1.
389        self.expect(
390            "breakpoint list -f",
391            "Breakpoint 1 exists",
392            patterns=[
393                "1: file = '.*main.c', line = %d, exact_match = 0, locations = 1, resolved = 1"
394                % self.line,
395                "hit count = 1",
396            ],
397        )
398
399        # Not breakpoint 2.
400        self.expect(
401            "breakpoint list -f",
402            "No more breakpoint 2",
403            matching=False,
404            substrs=[
405                "2: file = 'main.c', line = %d, exact_match = 0, locations = 1, resolved = 1"
406                % self.line
407            ],
408        )
409
410        # Run the program again, with breakpoint 1 remaining.
411        self.runCmd("run", RUN_SUCCEEDED)
412
413        # We should be stopped again due to breakpoint 1.
414
415        # The stop reason of the thread should be breakpoint.
416        self.expect(
417            "thread list",
418            STOPPED_DUE_TO_BREAKPOINT,
419            substrs=["stopped", "stop reason = breakpoint"],
420        )
421
422        # The breakpoint should have a hit count of 1, since we reset counts
423        # for each run.
424        lldbutil.check_breakpoint(self, bpno=1, expected_hit_count=1)
425
426    def breakpoint_command_script_parameters(self):
427        """Test that the frame and breakpoint location are being properly passed to the script breakpoint command function."""
428        exe = self.getBuildArtifact("a.out")
429        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
430
431        # Add a breakpoint.
432        lldbutil.run_break_set_by_file_and_line(
433            self, "main.c", self.line, num_expected_locations=1, loc_exact=True
434        )
435
436        # Now add callbacks for the breakpoints just created.
437        self.runCmd(
438            "breakpoint command add -s python -o 'import side_effect; side_effect.frame = str(frame); side_effect.bp_loc = str(bp_loc)' 1"
439        )
440
441        # Reset canary variables and run.
442        side_effect.frame = None
443        side_effect.bp_loc = None
444        self.runCmd("run", RUN_SUCCEEDED)
445
446        self.expect(side_effect.frame, exe=False, startstr="frame #0:")
447        self.expect(
448            side_effect.bp_loc,
449            exe=False,
450            patterns=["1.* where = .*main .* resolved,( hardware,)? hit count = 1"],
451        )
452
453    def breakpoint_commands_on_creation(self):
454        """Test that setting breakpoint commands when creating the breakpoint works"""
455        target = self.createTestTarget()
456
457        # Add a breakpoint.
458        lldbutil.run_break_set_by_file_and_line(
459            self,
460            "main.c",
461            self.line,
462            num_expected_locations=1,
463            loc_exact=True,
464            extra_options='-C bt -C "thread list" -C continue',
465        )
466
467        bkpt = target.FindBreakpointByID(1)
468        self.assertTrue(bkpt.IsValid(), "Couldn't find breakpoint 1")
469        com_list = lldb.SBStringList()
470        bkpt.GetCommandLineCommands(com_list)
471        self.assertEqual(com_list.GetSize(), 3, "Got the wrong number of commands")
472        self.assertEqual(com_list.GetStringAtIndex(0), "bt", "First bt")
473        self.assertEqual(
474            com_list.GetStringAtIndex(1), "thread list", "Next thread list"
475        )
476        self.assertEqual(com_list.GetStringAtIndex(2), "continue", "Last continue")
477
478    def test_add_commands_by_breakpoint_name(self):
479        """Make sure that when you specify a breakpoint name to "break command add"
480        it gets added to all the breakpoints marked with that name."""
481        self.build()
482        target = self.createTestTarget()
483
484        bp_ids = []
485        bp_names = ["main", "not_here", "main"]
486        for bp_name in bp_names:
487            bp = target.BreakpointCreateByName(bp_name)
488            bp.AddName("MyBKPTS")
489            bp_ids.append(bp.GetID())
490        # First do it with a script one-liner:
491        self.runCmd("breakpoint command add -s py -o 'print(\"some command\")' MyBKPTS")
492        for id in bp_ids:
493            self.expect(
494                "breakpoint command list {0}".format(id), patterns=["some command"]
495            )
496        # Now do the same thing with a python function:
497        import side_effect
498
499        self.runCmd("command script import --allow-reload ./bktptcmd.py")
500
501        self.runCmd(
502            "breakpoint command add --python-function bktptcmd.function MyBKPTS"
503        )
504        for id in bp_ids:
505            self.expect(
506                "breakpoint command list {0}".format(id), patterns=["bktptcmd.function"]
507            )
508
509    def test_breakpoint_delete_disabled(self):
510        """Test 'break delete --disabled' works"""
511        self.build()
512        target = self.createTestTarget()
513
514        bp_1 = target.BreakpointCreateByName("main")
515        bp_2 = target.BreakpointCreateByName("not_here")
516        bp_3 = target.BreakpointCreateByName("main")
517        bp_3.AddName("DeleteMeNot")
518
519        bp_1.SetEnabled(False)
520        bp_3.SetEnabled(False)
521
522        bp_id_1 = bp_1.GetID()
523        bp_id_2 = bp_2.GetID()
524        bp_id_3 = bp_3.GetID()
525
526        self.runCmd("breakpoint delete --disabled DeleteMeNot")
527
528        bp_1 = target.FindBreakpointByID(bp_id_1)
529        self.assertFalse(bp_1.IsValid(), "Didn't delete disabled breakpoint 1")
530
531        bp_2 = target.FindBreakpointByID(bp_id_2)
532        self.assertTrue(bp_2.IsValid(), "Deleted enabled breakpoint 2")
533
534        bp_3 = target.FindBreakpointByID(bp_id_3)
535        self.assertTrue(
536            bp_3.IsValid(), "DeleteMeNot didn't protect disabled breakpoint 3"
537        )
538
539        # Reset the first breakpoint, disable it, and do this again with no protected name:
540        bp_1 = target.BreakpointCreateByName("main")
541
542        bp_1.SetEnabled(False)
543
544        bp_id_1 = bp_1.GetID()
545
546        self.runCmd("breakpoint delete --disabled")
547
548        bp_1 = target.FindBreakpointByID(bp_id_1)
549        self.assertFalse(bp_1.IsValid(), "Didn't delete disabled breakpoint 1")
550
551        bp_2 = target.FindBreakpointByID(bp_id_2)
552        self.assertTrue(bp_2.IsValid(), "Deleted enabled breakpoint 2")
553
554        bp_3 = target.FindBreakpointByID(bp_id_3)
555        self.assertFalse(bp_3.IsValid(), "Didn't delete disabled breakpoint 3")
556
557    def get_source_map_json(self):
558        stream = lldb.SBStream()
559        self.dbg.GetSetting("target.source-map").GetAsJSON(stream)
560        return json.loads(stream.GetData())
561
562    def verify_source_map_entry_pair(self, entry, original, replacement):
563        self.assertEqual(
564            entry[0], original, "source map entry 'original' does not match"
565        )
566        self.assertEqual(
567            entry[1], replacement, "source map entry 'replacement' does not match"
568        )
569
570    def verify_source_map_deduce_statistics(self, target, expected_count):
571        stream = lldb.SBStream()
572        res = target.GetStatistics().GetAsJSON(stream)
573        self.assertTrue(res.Success())
574        debug_stats = json.loads(stream.GetData())
575        self.assertIn(
576            "targets",
577            debug_stats,
578            'Make sure the "targets" key in in target.GetStatistics()',
579        )
580        target_stats = debug_stats["targets"][0]
581        self.assertNotEqual(target_stats, None)
582        self.assertEqual(target_stats["sourceMapDeduceCount"], expected_count)
583
584    @no_debug_info_test
585    def test_breakpoints_auto_source_map_relative(self):
586        """
587        Test that with target.auto-source-map-relative settings.
588
589        The "relative.yaml" contains a line table that is:
590
591        Line table for a/b/c/main.cpp in `a.out
592        0x0000000100003f94: a/b/c/main.cpp:1
593        0x0000000100003fb0: a/b/c/main.cpp:2:3
594        0x0000000100003fb8: a/b/c/main.cpp:2:3
595        """
596        src_dir = self.getSourceDir()
597        yaml_path = os.path.join(src_dir, "relative.yaml")
598        yaml_base, ext = os.path.splitext(yaml_path)
599        obj_path = self.getBuildArtifact("a.out")
600        self.yaml2obj(yaml_path, obj_path)
601
602        # Create a target with the object file we just created from YAML
603        target = self.dbg.CreateTarget(obj_path)
604        # We now have debug information with line table paths that start are
605        # "./a/b/c/main.cpp".
606
607        source_map_json = self.get_source_map_json()
608        self.assertEqual(
609            len(source_map_json), 0, "source map should be empty initially"
610        )
611        self.verify_source_map_deduce_statistics(target, 0)
612
613        # Verify auto deduced source map when file path in debug info
614        # is a suffix of request breakpoint file path.
615        # Note the path must be absolute.
616        path = (
617            "/x/y/a/b/c/main.cpp"
618            if lldbplatformutil.getHostPlatform() != "windows"
619            else r"C:\x\y\a\b\c\main.cpp"
620        )
621        bp = target.BreakpointCreateByLocation(path, 2)
622        self.assertGreater(
623            bp.GetNumLocations(),
624            0,
625            'Couldn\'t resolve breakpoint using full path "%s" in executate "%s" with '
626            "debug info that has relative path with matching suffix"
627            % (path, self.getBuildArtifact("a.out")),
628        )
629
630        source_map_json = self.get_source_map_json()
631        self.assertEqual(len(source_map_json), 1, "source map should not be empty")
632        self.verify_source_map_entry_pair(
633            source_map_json[0],
634            ".",
635            "/x/y" if lldbplatformutil.getHostPlatform() != "windows" else r"C:\x\y",
636        )
637        self.verify_source_map_deduce_statistics(target, 1)
638
639        # Reset source map.
640        self.runCmd("settings clear target.source-map")
641
642        # Verify source map will not auto deduced when file path of request breakpoint
643        # equals the file path in debug info.
644        path = "a/b/c/main.cpp"
645        bp = target.BreakpointCreateByLocation(path, 2)
646        self.assertGreater(
647            bp.GetNumLocations(),
648            0,
649            'Couldn\'t resolve breakpoint using full path "%s" in executate "%s" with '
650            "debug info that has relative path with matching suffix"
651            % (path, self.getBuildArtifact("a.out")),
652        )
653
654        source_map_json = self.get_source_map_json()
655        self.assertEqual(len(source_map_json), 0, "source map should not be deduced")
656
657    def test_breakpoint_statistics_hitcount(self):
658        """Test breakpoints statistics have hitCount field."""
659        self.build()
660        target = self.createTestTarget()
661
662        lldbutil.run_break_set_by_file_and_line(
663            self, "main.c", self.line, num_expected_locations=1, loc_exact=True
664        )
665
666        stream = lldb.SBStream()
667        res = target.GetStatistics().GetAsJSON(stream)
668        self.assertTrue(res.Success())
669        debug_stats = json.loads(stream.GetData())
670        self.assertIn(
671            "targets",
672            debug_stats,
673            'Make sure the "targets" key in in target.GetStatistics()',
674        )
675        target_stats = debug_stats["targets"][0]
676        self.assertNotEqual(target_stats, None)
677
678        breakpoints_stats = target_stats["breakpoints"]
679        self.assertNotEqual(breakpoints_stats, None)
680        for breakpoint_stats in breakpoints_stats:
681            self.assertIn("hitCount", breakpoint_stats)
682
683    @skipIf(oslist=no_match(["linux"]))
684    def test_break_at__dl_debug_state(self):
685        """
686        Test lldb is able to stop at _dl_debug_state if it is set before the
687        process is launched.
688        """
689        self.build()
690        exe = self.getBuildArtifact("a.out")
691        self.runCmd("target create %s" % exe)
692        bpid = lldbutil.run_break_set_by_symbol(
693            self, "_dl_debug_state", num_expected_locations=-2
694        )
695        self.runCmd("run")
696        self.assertIsNotNone(
697            lldbutil.get_one_thread_stopped_at_breakpoint_id(self.process(), bpid)
698        )
699