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