xref: /llvm-project/lldb/test/API/commands/frame/var/TestFrameVar.py (revision d9cc37fea7b02954079ca59e8f7f28cffacc7e9e)
1"""
2Make sure the frame variable -g, -a, and -l flags work.
3"""
4
5
6import lldb
7import lldbsuite.test.lldbutil as lldbutil
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10import os
11import shutil
12import time
13
14
15class TestFrameVar(TestBase):
16    # If your test case doesn't stress debug info, then
17    # set this to true.  That way it won't be run once for
18    # each debug info format.
19    NO_DEBUG_INFO_TESTCASE = True
20
21    def test_frame_var(self):
22        self.build()
23        self.do_test()
24
25    def do_test(self):
26        target = self.createTestTarget()
27
28        # Now create a breakpoint in main.c at the source matching
29        # "Set a breakpoint here"
30        breakpoint = target.BreakpointCreateBySourceRegex(
31            "Set a breakpoint here", lldb.SBFileSpec("main.c")
32        )
33        self.assertTrue(
34            breakpoint and breakpoint.GetNumLocations() >= 1, VALID_BREAKPOINT
35        )
36
37        error = lldb.SBError()
38        # This is the launch info.  If you want to launch with arguments or
39        # environment variables, add them using SetArguments or
40        # SetEnvironmentEntries
41
42        launch_info = target.GetLaunchInfo()
43        process = target.Launch(launch_info, error)
44        self.assertTrue(process, PROCESS_IS_VALID)
45
46        # Did we hit our breakpoint?
47        from lldbsuite.test.lldbutil import get_threads_stopped_at_breakpoint
48
49        threads = get_threads_stopped_at_breakpoint(process, breakpoint)
50        self.assertEqual(
51            len(threads), 1, "There should be a thread stopped at our breakpoint"
52        )
53
54        # The hit count for the breakpoint should be 1.
55        self.assertEqual(breakpoint.GetHitCount(), 1)
56
57        frame = threads[0].GetFrameAtIndex(0)
58        command_result = lldb.SBCommandReturnObject()
59        interp = self.dbg.GetCommandInterpreter()
60
61        # Ensure --regex can find globals if it is the very first frame var command.
62        self.expect("frame var --regex g_", substrs=["g_var"])
63
64        # Ensure the requested scope is respected:
65        self.expect(
66            "frame var --regex argc --no-args",
67            error=True,
68            substrs=["no variables matched the regular expression 'argc'"],
69        )
70
71        # Just get args:
72        result = interp.HandleCommand("frame var -l", command_result)
73        self.assertEqual(
74            result, lldb.eReturnStatusSuccessFinishResult, "frame var -a didn't succeed"
75        )
76        output = command_result.GetOutput()
77        self.assertIn("argc", output, "Args didn't find argc")
78        self.assertIn("argv", output, "Args didn't find argv")
79        self.assertNotIn("test_var", output, "Args found a local")
80        self.assertNotIn("g_var", output, "Args found a global")
81
82        # Just get locals:
83        result = interp.HandleCommand("frame var -a", command_result)
84        self.assertEqual(
85            result, lldb.eReturnStatusSuccessFinishResult, "frame var -a didn't succeed"
86        )
87        output = command_result.GetOutput()
88        self.assertNotIn("argc", output, "Locals found argc")
89        self.assertNotIn("argv", output, "Locals found argv")
90        self.assertIn("test_var", output, "Locals didn't find test_var")
91        self.assertNotIn("g_var", output, "Locals found a global")
92
93        # Get the file statics:
94        result = interp.HandleCommand("frame var -l -a -g", command_result)
95        self.assertEqual(
96            result, lldb.eReturnStatusSuccessFinishResult, "frame var -a didn't succeed"
97        )
98        output = command_result.GetOutput()
99        self.assertNotIn("argc", output, "Globals found argc")
100        self.assertNotIn("argv", output, "Globals found argv")
101        self.assertNotIn("test_var", output, "Globals found test_var")
102        self.assertIn("g_var", output, "Globals didn't find g_var")
103
104    def check_frame_variable_errors(self, thread, error_strings):
105        command_result = lldb.SBCommandReturnObject()
106        interp = self.dbg.GetCommandInterpreter()
107        result = interp.HandleCommand("frame variable", command_result)
108        self.assertEqual(
109            result, lldb.eReturnStatusFailed, "frame var succeeded unexpectedly"
110        )
111        command_error = command_result.GetError()
112
113        frame = thread.GetFrameAtIndex(0)
114        var_list = frame.GetVariables(True, True, False, True)
115        self.assertEqual(var_list.GetSize(), 0)
116        api_error = var_list.GetError()
117        api_error_str = api_error.GetCString()
118
119        for s in error_strings:
120            self.assertIn(s, command_error)
121        for s in error_strings:
122            self.assertIn(s, api_error_str)
123
124        # Check the structured error data.
125        data = api_error.GetErrorData()
126        version = data.GetValueForKey("version")
127        self.assertEqual(version.GetIntegerValue(), 1)
128        err_ty = data.GetValueForKey("type")
129        self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeGeneric)
130        message = str(data.GetValueForKey("errors").GetItemAtIndex(0))
131        for s in error_strings:
132            self.assertIn(s, message)
133
134    @skipIfRemote
135    @skipUnlessDarwin
136    def test_darwin_dwarf_missing_obj(self):
137        """
138        Test that if we build a binary with DWARF in .o files and we remove
139        the .o file for main.cpp, that we get an appropriate error when we
140        do 'frame variable' that explains why we aren't seeing variables.
141        """
142        self.build(debug_info="dwarf")
143        exe = self.getBuildArtifact("a.out")
144        main_obj = self.getBuildArtifact("main.o")
145        # Delete the main.o file that contains the debug info so we force an
146        # error when we run to main and try to get variables
147        os.unlink(main_obj)
148
149        # We have to set a named breakpoint because we don't have any debug info
150        # because we deleted the main.o file.
151        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, "main")
152        error_strings = [
153            'debug map object file "',
154            'main.o" containing debug info does not exist, debug info will not be loaded',
155        ]
156        self.check_frame_variable_errors(thread, error_strings)
157
158    @skipIfRemote
159    @skipUnlessDarwin
160    def test_darwin_dwarf_obj_mod_time_mismatch(self):
161        """
162        Test that if we build a binary with DWARF in .o files and we update
163        the mod time of the .o file for main.cpp, that we get an appropriate
164        error when we do 'frame variable' that explains why we aren't seeing
165        variables.
166        """
167        self.build(debug_info="dwarf")
168        exe = self.getBuildArtifact("a.out")
169        main_obj = self.getBuildArtifact("main.o")
170
171        # Set the modification time for main.o file to the current time after
172        # sleeping for 2 seconds. This ensures the modification time will have
173        # changed and will not match the modification time in the debug map and
174        # force an error when we run to main and try to get variables
175        time.sleep(2)
176        os.utime(main_obj, None)
177
178        # We have to set a named breakpoint because we don't have any debug info
179        # because we deleted the main.o file since the mod times don't match
180        # and debug info won't be loaded
181        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, "main")
182
183        error_strings = [
184            'debug map object file "',
185            'main.o" changed (actual: 0x',
186            ", debug map: 0x",
187            ") since this executable was linked, debug info will not be loaded",
188        ]
189        self.check_frame_variable_errors(thread, error_strings)
190
191    @skipIfRemote
192    @skipIfWindows  # Windows can't set breakpoints by name 'main' in this case.
193    def test_gline_tables_only(self):
194        """
195        Test that if we build a binary with "-gline-tables-only" that we can
196        set a file and line breakpoint successfully, and get an error
197        letting us know that this build option was enabled when trying to
198        read variables.
199        """
200        self.build(dictionary={"CFLAGS_EXTRAS": "-gline-tables-only"})
201        exe = self.getBuildArtifact("a.out")
202
203        # We have to set a named breakpoint because we don't have any debug info
204        # because we deleted the main.o file since the mod times don't match
205        # and debug info won't be loaded
206        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, "main")
207        error_strings = [
208            "no variable information is available in debug info for this compile unit"
209        ]
210        self.check_frame_variable_errors(thread, error_strings)
211
212    @skipUnlessPlatform(["linux", "freebsd"])
213    @add_test_categories(["dwo"])
214    def test_fission_missing_dwo(self):
215        """
216        Test that if we build a binary with "-gsplit-dwarf" that we can
217        set a file and line breakpoint successfully, and get an error
218        letting us know we were unable to load the .dwo file.
219        """
220        self.build(dictionary={"CFLAGS_EXTRAS": "-gsplit-dwarf"})
221        exe = self.getBuildArtifact("a.out")
222        main_dwo = self.getBuildArtifact("main.dwo")
223
224        self.assertTrue(
225            os.path.exists(main_dwo), 'Make sure "%s" file exists' % (main_dwo)
226        )
227        # Delete the main.dwo file that contains the debug info so we force an
228        # error when we run to main and try to get variables.
229        os.unlink(main_dwo)
230
231        # We have to set a named breakpoint because we don't have any debug info
232        # because we deleted the main.o file since the mod times don't match
233        # and debug info won't be loaded
234        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, "main")
235        error_strings = [
236            'unable to locate .dwo debug file "',
237            'main.dwo" for skeleton DIE 0x',
238        ]
239        self.check_frame_variable_errors(thread, error_strings)
240
241    @skipUnlessPlatform(["linux", "freebsd"])
242    @add_test_categories(["dwo"])
243    def test_fission_invalid_dwo_objectfile(self):
244        """
245        Test that if we build a binary with "-gsplit-dwarf" that we can
246        set a file and line breakpoint successfully, and get an error
247        letting us know we were unable to load the .dwo file because it
248        existed, but it wasn't a valid object file.
249        """
250        self.build(dictionary={"CFLAGS_EXTRAS": "-gsplit-dwarf"})
251        exe = self.getBuildArtifact("a.out")
252        main_dwo = self.getBuildArtifact("main.dwo")
253
254        self.assertTrue(
255            os.path.exists(main_dwo), 'Make sure "%s" file exists' % (main_dwo)
256        )
257        # Overwrite the main.dwo with the main.c source file so that the .dwo
258        # file exists, but it isn't a valid object file as there is an error
259        # for this case.
260        shutil.copyfile(self.getSourcePath("main.c"), main_dwo)
261
262        # We have to set a named breakpoint because we don't have any debug info
263        # because we deleted the main.o file since the mod times don't match
264        # and debug info won't be loaded
265        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, "main")
266        error_strings = [
267            'unable to load object file for .dwo debug file "'
268            'main.dwo" for unit DIE 0x',
269        ]
270        self.check_frame_variable_errors(thread, error_strings)
271