| """Test breaking inside functions defined within a BSD archive file libfoo.a.""" |
| |
| |
| import lldb |
| from lldbsuite.test.decorators import * |
| from lldbsuite.test.lldbtest import * |
| from lldbsuite.test import lldbutil |
| import os |
| import time |
| |
| |
| class BSDArchivesTestCase(TestBase): |
| # If your test case doesn't stress debug info, then |
| # set this to true. That way it won't be run once for |
| # each debug info format. |
| NO_DEBUG_INFO_TESTCASE = True |
| |
| def setUp(self): |
| # Call super's setUp(). |
| TestBase.setUp(self) |
| # Find the line number in a(int) to break at. |
| self.line = line_number("a.c", "// Set file and line breakpoint inside a().") |
| |
| @expectedFailureAll( |
| oslist=["windows"], |
| bugnumber="llvm.org/pr24527. Makefile.rules doesn't know how to build static libs on Windows", |
| ) |
| @expectedFailureAll(remote=True) |
| def test(self): |
| """Break inside a() and b() defined within libfoo.a.""" |
| self.build() |
| |
| exe = self.getBuildArtifact("a.out") |
| self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) |
| |
| # Break inside a() by file and line first. |
| lldbutil.run_break_set_by_file_and_line( |
| self, "a.c", self.line, num_expected_locations=1, loc_exact=True |
| ) |
| |
| self.runCmd("run", RUN_SUCCEEDED) |
| |
| # The stop reason of the thread should be breakpoint. |
| self.expect( |
| "thread list", |
| STOPPED_DUE_TO_BREAKPOINT, |
| substrs=["stopped", "stop reason = breakpoint"], |
| ) |
| |
| # Break at a(int) first. |
| self.expect( |
| "frame variable", VARIABLES_DISPLAYED_CORRECTLY, substrs=["(int) arg = 1"] |
| ) |
| self.expect_var_path("__a_global", type="int", value="1") |
| |
| # Set breakpoint for b() next. |
| lldbutil.run_break_set_by_symbol( |
| self, "b", num_expected_locations=1, sym_exact=True |
| ) |
| |
| # Continue the program, we should break at b(int) next. |
| self.runCmd("continue") |
| self.expect( |
| "thread list", |
| STOPPED_DUE_TO_BREAKPOINT, |
| substrs=["stopped", "stop reason = breakpoint"], |
| ) |
| self.expect( |
| "frame variable", VARIABLES_DISPLAYED_CORRECTLY, substrs=["(int) arg = 2"] |
| ) |
| self.expect_var_path("__b_global", type="int", value="2") |
| |
| def check_frame_variable_errors(self, thread, error_strings): |
| command_result = lldb.SBCommandReturnObject() |
| interp = self.dbg.GetCommandInterpreter() |
| result = interp.HandleCommand("frame variable", command_result) |
| self.assertEqual( |
| result, lldb.eReturnStatusFailed, "frame var succeeded unexpectedly" |
| ) |
| command_error = command_result.GetError() |
| |
| frame = thread.GetFrameAtIndex(0) |
| var_list = frame.GetVariables(True, True, False, True) |
| self.assertEqual(var_list.GetSize(), 0) |
| api_error = var_list.GetError().GetCString() |
| |
| for s in error_strings: |
| self.assertTrue( |
| s in command_error, |
| 'Make sure "%s" exists in the command error "%s"' % (s, command_error), |
| ) |
| for s in error_strings: |
| self.assertTrue( |
| s in api_error, |
| 'Make sure "%s" exists in the API error "%s"' % (s, api_error), |
| ) |
| |
| @skipIfRemote |
| @skipUnlessDarwin |
| def test_frame_var_errors_when_archive_missing(self): |
| """ |
| Break inside a() and remove libfoo.a to make sure we can't load |
| the debug information and report an appropriate error when doing |
| 'frame variable'. |
| """ |
| self.build() |
| exe = self.getBuildArtifact("a.out") |
| libfoo_path = self.getBuildArtifact("libfoo.a") |
| # Delete the main.o file that contains the debug info so we force an |
| # error when we run to main and try to get variables for the a() |
| # function. Since the libfoo.a is missing, the debug info won't be |
| # loaded and we should see an error when trying to read varibles. |
| os.unlink(libfoo_path) |
| |
| (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint( |
| self, "a", bkpt_module=exe |
| ) |
| |
| error_strings = [ |
| 'debug map object file "', |
| 'libfoo.a(a.o)" containing debug info does not exist, debug info will not be loaded', |
| ] |
| self.check_frame_variable_errors(thread, error_strings) |
| |
| @skipIfRemote |
| @skipIf(compiler="clang", compiler_version=["<", "12.0"]) |
| @expectedFailureAll( |
| oslist=["windows"], |
| bugnumber="llvm.org/pr24527. Makefile.rules doesn't know how to build static libs on Windows", |
| ) |
| def test_archive_specifications(self): |
| """ |
| Create archives and make sure the information we get when retrieving |
| the modules specifications is correct. |
| """ |
| self.build() |
| libbar_path = self.getBuildArtifact("libbar.a") |
| libfoo_path = self.getBuildArtifact("libfoo.a") |
| libfoothin_path = self.getBuildArtifact("libfoo-thin.a") |
| objfile_a = self.getBuildArtifact("a.o") |
| objfile_b = self.getBuildArtifact("b.o") |
| objfile_c = self.getBuildArtifact("c.o") |
| size_a = os.path.getsize(objfile_a) |
| size_b = os.path.getsize(objfile_b) |
| size_c = os.path.getsize(objfile_c) |
| |
| # Test loading normal archives |
| module_specs = lldb.SBModuleSpecList.GetModuleSpecifications(libfoo_path) |
| num_specs = module_specs.GetSize() |
| self.assertEqual(num_specs, 2) |
| spec = module_specs.GetSpecAtIndex(0) |
| self.assertEqual(spec.GetObjectName(), "a.o") |
| self.assertEqual(spec.GetObjectSize(), size_a) |
| spec = module_specs.GetSpecAtIndex(1) |
| self.assertEqual(spec.GetObjectName(), "b.o") |
| self.assertEqual(spec.GetObjectSize(), size_b) |
| |
| # Test loading thin archives |
| module_specs = lldb.SBModuleSpecList.GetModuleSpecifications(libbar_path) |
| num_specs = module_specs.GetSize() |
| self.assertEqual(num_specs, 1) |
| spec = module_specs.GetSpecAtIndex(0) |
| self.assertEqual(spec.GetObjectName(), "c.o") |
| self.assertEqual(spec.GetObjectSize(), size_c) |
| |
| module_specs = lldb.SBModuleSpecList.GetModuleSpecifications(libfoothin_path) |
| num_specs = module_specs.GetSize() |
| self.assertEqual(num_specs, 2) |
| spec = module_specs.GetSpecAtIndex(0) |
| self.assertEqual(spec.GetObjectName(), "a.o") |
| self.assertEqual(spec.GetObjectSize(), size_a) |
| spec = module_specs.GetSpecAtIndex(1) |
| self.assertEqual(spec.GetObjectName(), "b.o") |
| self.assertEqual(spec.GetObjectSize(), size_b, libfoothin_path) |
| |
| @skipIfRemote |
| @skipUnlessDarwin |
| def test_frame_var_errors_when_thin_archive_malformed(self): |
| """ |
| Create thin archive libfoo.a and make it malformed to make sure |
| we don't crash and report an appropriate error when resolving |
| breakpoint using debug map. |
| """ |
| self.build() |
| exe = self.getBuildArtifact("a.out") |
| libfoo_path = self.getBuildArtifact("libfoo.a") |
| libthin_path = self.getBuildArtifact("libfoo-thin.a") |
| objfile_a = self.getBuildArtifact("a.o") |
| objfile_b = self.getBuildArtifact("b.o") |
| objfile_c = self.getBuildArtifact("c.o") |
| # Replace the libfoo.a file with a thin archive containing the same |
| # debug information (a.o, b.o). Then remove a.o from the file system |
| # so we force an error when we set a breakpoint on a() function. |
| # Since the a.o is missing, the debug info won't be loaded and we |
| # should see an error when trying to break into a(). |
| os.remove(libfoo_path) |
| shutil.copyfile(libthin_path, libfoo_path) |
| os.remove(objfile_a) |
| |
| self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) |
| # We won't be able to see source file |
| self.expect( |
| "b a", |
| substrs=["Breakpoint 1: where = a.out`a, address ="], |
| ) |
| # Break at a() should fail |
| (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint( |
| self, "a", bkpt_module=exe |
| ) |
| error_strings = [ |
| '"a.o" object from the "', |
| "libfoo.a\" archive: either the .o file doesn't exist in the archive or the modification time (0x", |
| ") of the .o file doesn't match", |
| ] |
| self.check_frame_variable_errors(thread, error_strings) |
| |
| # Break at b() should succeed |
| (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint( |
| self, "b", bkpt_module=exe |
| ) |
| self.expect( |
| "thread list", |
| STOPPED_DUE_TO_BREAKPOINT, |
| substrs=["stopped", "stop reason = breakpoint"], |
| ) |
| self.expect( |
| "frame variable", VARIABLES_DISPLAYED_CORRECTLY, substrs=["(int) arg = 2"] |
| ) |
| |
| @skipIfRemote |
| @skipUnlessDarwin |
| def test_frame_var_errors_when_mtime_mistmatch_for_object_in_archive(self): |
| """ |
| Break inside a() and modify the modification time for "a.o" within |
| libfoo.a to make sure we can't load the debug information and |
| report an appropriate error when doing 'frame variable'. |
| """ |
| self.build() |
| exe = self.getBuildArtifact("a.out") |
| a_path = self.getBuildArtifact("a.o") |
| |
| # Change the modification time of the a.o object file after sleeping for |
| # 2 seconds to ensure the modification time is different. The rebuild |
| # only the "libfoo.a" target. This means the modification time of the |
| # a.o within libfoo.a will not match the debug map's modification time |
| # in a.out and will cause the debug information to not be loaded and we |
| # should get an appropriate error when reading variables. |
| time.sleep(2) |
| os.utime(a_path, None) |
| self.build(make_targets=["libfoo.a"]) |
| |
| (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint( |
| self, "a", bkpt_module=exe |
| ) |
| |
| error_strings = [ |
| '"a.o" object from the "', |
| "libfoo.a\" archive: either the .o file doesn't exist in the archive or the modification time (0x", |
| ") of the .o file doesn't match", |
| ] |
| self.check_frame_variable_errors(thread, error_strings) |