xref: /llvm-project/lldb/test/API/functionalities/archives/TestBSDArchives.py (revision ec009994a06338995dfb6431c943b299f9327fd2)
1"""Test breaking inside functions defined within a BSD archive file libfoo.a."""
2
3
4import lldb
5from lldbsuite.test.decorators import *
6from lldbsuite.test.lldbtest import *
7from lldbsuite.test import lldbutil
8import os
9import time
10
11
12class BSDArchivesTestCase(TestBase):
13    # If your test case doesn't stress debug info, then
14    # set this to true.  That way it won't be run once for
15    # each debug info format.
16    NO_DEBUG_INFO_TESTCASE = True
17
18    def setUp(self):
19        # Call super's setUp().
20        TestBase.setUp(self)
21        # Find the line number in a(int) to break at.
22        self.line = line_number("a.c", "// Set file and line breakpoint inside a().")
23
24    def test(self):
25        """Break inside a() and b() defined within libfoo.a."""
26        self.build()
27
28        exe = self.getBuildArtifact("a.out")
29        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
30
31        # Break inside a() by file and line first.
32        lldbutil.run_break_set_by_file_and_line(
33            self, "a.c", self.line, num_expected_locations=1, loc_exact=True
34        )
35
36        self.runCmd("run", RUN_SUCCEEDED)
37
38        # The stop reason of the thread should be breakpoint.
39        self.expect(
40            "thread list",
41            STOPPED_DUE_TO_BREAKPOINT,
42            substrs=["stopped", "stop reason = breakpoint"],
43        )
44
45        # Break at a(int) first.
46        self.expect(
47            "frame variable", VARIABLES_DISPLAYED_CORRECTLY, substrs=["(int) arg = 1"]
48        )
49        self.expect_var_path("__a_global", type="int", value="1")
50
51        # Set breakpoint for b() next.
52        lldbutil.run_break_set_by_symbol(
53            self, "b", num_expected_locations=1, sym_exact=True
54        )
55
56        # Continue the program, we should break at b(int) next.
57        self.runCmd("continue")
58        self.expect(
59            "thread list",
60            STOPPED_DUE_TO_BREAKPOINT,
61            substrs=["stopped", "stop reason = breakpoint"],
62        )
63        self.expect(
64            "frame variable", VARIABLES_DISPLAYED_CORRECTLY, substrs=["(int) arg = 2"]
65        )
66        self.expect_var_path("__b_global", type="int", value="2")
67
68    def check_frame_variable_errors(self, thread, error_strings):
69        command_result = lldb.SBCommandReturnObject()
70        interp = self.dbg.GetCommandInterpreter()
71        result = interp.HandleCommand("frame variable", command_result)
72        self.assertEqual(
73            result, lldb.eReturnStatusFailed, "frame var succeeded unexpectedly"
74        )
75        command_error = command_result.GetError()
76
77        frame = thread.GetFrameAtIndex(0)
78        var_list = frame.GetVariables(True, True, False, True)
79        self.assertEqual(var_list.GetSize(), 0)
80        api_error = var_list.GetError().GetCString()
81
82        for s in error_strings:
83            self.assertIn(
84                s,
85                command_error,
86                'Make sure "%s" exists in the command error "%s"' % (s, command_error),
87            )
88        for s in error_strings:
89            self.assertIn(
90                s,
91                api_error,
92                'Make sure "%s" exists in the API error "%s"' % (s, api_error),
93            )
94
95    @skipIfRemote
96    @skipUnlessDarwin
97    def test_frame_var_errors_when_archive_missing(self):
98        """
99        Break inside a() and remove libfoo.a to make sure we can't load
100        the debug information and report an appropriate error when doing
101        'frame variable'.
102        """
103        self.build()
104        exe = self.getBuildArtifact("a.out")
105        libfoo_path = self.getBuildArtifact("libfoo.a")
106        # Delete the main.o file that contains the debug info so we force an
107        # error when we run to main and try to get variables for the a()
108        # function. Since the libfoo.a is missing, the debug info won't be
109        # loaded and we should see an error when trying to read varibles.
110        os.unlink(libfoo_path)
111
112        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(
113            self, "a", bkpt_module=exe
114        )
115
116        error_strings = [
117            'debug map object file "',
118            'libfoo.a(a.o)" containing debug info does not exist, debug info will not be loaded',
119        ]
120        self.check_frame_variable_errors(thread, error_strings)
121
122    @skipIfRemote
123    @skipIf(compiler="clang", compiler_version=["<", "12.0"])
124    def test_archive_specifications(self):
125        """
126        Create archives and make sure the information we get when retrieving
127        the modules specifications is correct.
128        """
129        self.build()
130        libbar_path = self.getBuildArtifact("libbar.a")
131        libfoo_path = self.getBuildArtifact("libfoo.a")
132        libfoothin_path = self.getBuildArtifact("libfoo-thin.a")
133        objfile_a = self.getBuildArtifact("a.o")
134        objfile_b = self.getBuildArtifact("b.o")
135        objfile_c = self.getBuildArtifact("c.o")
136        size_a = os.path.getsize(objfile_a)
137        size_b = os.path.getsize(objfile_b)
138        size_c = os.path.getsize(objfile_c)
139
140        # Test loading normal archives
141        module_specs = lldb.SBModuleSpecList.GetModuleSpecifications(libfoo_path)
142        num_specs = module_specs.GetSize()
143        self.assertEqual(num_specs, 2)
144        spec = module_specs.GetSpecAtIndex(0)
145        self.assertEqual(spec.GetObjectName(), "a.o")
146        self.assertEqual(spec.GetObjectSize(), size_a)
147        spec = module_specs.GetSpecAtIndex(1)
148        self.assertEqual(spec.GetObjectName(), "b.o")
149        self.assertEqual(spec.GetObjectSize(), size_b)
150
151        # Test loading thin archives
152        module_specs = lldb.SBModuleSpecList.GetModuleSpecifications(libbar_path)
153        num_specs = module_specs.GetSize()
154        self.assertEqual(num_specs, 1)
155        spec = module_specs.GetSpecAtIndex(0)
156        self.assertEqual(spec.GetObjectName(), "c.o")
157        self.assertEqual(spec.GetObjectSize(), size_c)
158
159        module_specs = lldb.SBModuleSpecList.GetModuleSpecifications(libfoothin_path)
160        num_specs = module_specs.GetSize()
161        self.assertEqual(num_specs, 2)
162        spec = module_specs.GetSpecAtIndex(0)
163        self.assertEqual(spec.GetObjectName(), "a.o")
164        self.assertEqual(spec.GetObjectSize(), size_a)
165        spec = module_specs.GetSpecAtIndex(1)
166        self.assertEqual(spec.GetObjectName(), "b.o")
167        self.assertEqual(spec.GetObjectSize(), size_b, libfoothin_path)
168
169    @skipIfRemote
170    @skipUnlessDarwin
171    def test_frame_var_errors_when_thin_archive_malformed(self):
172        """
173        Create thin archive libfoo.a and make it malformed to make sure
174        we don't crash and report an appropriate error when resolving
175        breakpoint using debug map.
176        """
177        self.build()
178        exe = self.getBuildArtifact("a.out")
179        libfoo_path = self.getBuildArtifact("libfoo.a")
180        libthin_path = self.getBuildArtifact("libfoo-thin.a")
181        objfile_a = self.getBuildArtifact("a.o")
182        objfile_b = self.getBuildArtifact("b.o")
183        objfile_c = self.getBuildArtifact("c.o")
184        # Replace the libfoo.a file with a thin archive containing the same
185        # debug information (a.o, b.o). Then remove a.o from the file system
186        # so we force an error when we set a breakpoint on a() function.
187        # Since the a.o is missing, the debug info won't be loaded and we
188        # should see an error when trying to break into a().
189        os.remove(libfoo_path)
190        shutil.copyfile(libthin_path, libfoo_path)
191        os.remove(objfile_a)
192
193        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
194        # We won't be able to see source file
195        self.expect(
196            "b a",
197            substrs=["Breakpoint 1: where = a.out`a, address ="],
198        )
199        # Break at a() should fail
200        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(
201            self, "a", bkpt_module=exe
202        )
203        error_strings = [
204            '"a.o" object from the "',
205            "libfoo.a\" archive: either the .o file doesn't exist in the archive or the modification time (0x",
206            ") of the .o file doesn't match",
207        ]
208        self.check_frame_variable_errors(thread, error_strings)
209
210        # Break at b() should succeed
211        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(
212            self, "b", bkpt_module=exe
213        )
214        self.expect(
215            "thread list",
216            STOPPED_DUE_TO_BREAKPOINT,
217            substrs=["stopped", "stop reason = breakpoint"],
218        )
219        self.expect(
220            "frame variable", VARIABLES_DISPLAYED_CORRECTLY, substrs=["(int) arg = 2"]
221        )
222
223    @skipIfRemote
224    @skipUnlessDarwin
225    def test_frame_var_errors_when_mtime_mistmatch_for_object_in_archive(self):
226        """
227        Break inside a() and modify the modification time for "a.o" within
228        libfoo.a to make sure we can't load the debug information and
229        report an appropriate error when doing 'frame variable'.
230        """
231        self.build()
232        exe = self.getBuildArtifact("a.out")
233        a_path = self.getBuildArtifact("a.o")
234
235        # Change the modification time of the a.o object file after sleeping for
236        # 2 seconds to ensure the modification time is different. The rebuild
237        # only the "libfoo.a" target. This means the modification time of the
238        # a.o within libfoo.a will not match the debug map's modification time
239        # in a.out and will cause the debug information to not be loaded and we
240        # should get an appropriate error when reading variables.
241        time.sleep(2)
242        os.utime(a_path, None)
243        self.build(make_targets=["libfoo.a"])
244
245        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(
246            self, "a", bkpt_module=exe
247        )
248
249        error_strings = [
250            '"a.o" object from the "',
251            "libfoo.a\" archive: either the .o file doesn't exist in the archive or the modification time (0x",
252            ") of the .o file doesn't match",
253        ]
254        self.check_frame_variable_errors(thread, error_strings)
255