1""" 2Test basics of Minidump debugging. 3""" 4 5from six import iteritems 6 7 8import lldb 9import os 10from lldbsuite.test.decorators import * 11from lldbsuite.test.lldbtest import * 12from lldbsuite.test import lldbutil 13 14 15@skipIfReproducer # Modules are not orphaned and it finds the module with the same UUID from test_partial_uuid_match. 16class MiniDumpUUIDTestCase(TestBase): 17 18 mydir = TestBase.compute_mydir(__file__) 19 20 NO_DEBUG_INFO_TESTCASE = True 21 22 def verify_module(self, module, verify_path, verify_uuid): 23 # Compare the filename and the directory separately. We are avoiding 24 # SBFileSpec.fullpath because it causes a slash/backslash confusion 25 # on Windows. Similarly, we compare the directories using normcase 26 # because they may contain a Linux-style relative path from the 27 # minidump appended to a Windows-style root path from the host. 28 self.assertEqual( 29 os.path.basename(verify_path), module.GetFileSpec().basename) 30 self.assertEqual( 31 os.path.normcase(os.path.dirname(verify_path)), 32 os.path.normcase(module.GetFileSpec().dirname or "")) 33 self.assertEqual(verify_uuid, module.GetUUIDString()) 34 35 def get_minidump_modules(self, yaml_file, exe = None): 36 minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp") 37 self.yaml2obj(yaml_file, minidump_path) 38 self.target = self.dbg.CreateTarget(exe) 39 self.process = self.target.LoadCore(minidump_path) 40 return self.target.modules 41 42 def test_zero_uuid_modules(self): 43 """ 44 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, 45 but contains a PDB70 value whose age is zero and whose UUID values are 46 all zero. Prior to a fix all such modules would be duplicated to the 47 first one since the UUIDs claimed to be valid and all zeroes. Now we 48 ensure that the UUID is not valid for each module and that we have 49 each of the modules in the target after loading the core 50 """ 51 modules = self.get_minidump_modules("linux-arm-zero-uuids.yaml") 52 self.assertEqual(2, len(modules)) 53 self.verify_module(modules[0], "/file/does/not/exist/a", None) 54 self.verify_module(modules[1], "/file/does/not/exist/b", None) 55 56 def test_uuid_modules_no_age(self): 57 """ 58 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, 59 and contains a PDB70 value whose age is zero and whose UUID values are 60 valid. Ensure we decode the UUID and don't include the age field in the UUID. 61 """ 62 modules = self.get_minidump_modules("linux-arm-uuids-no-age.yaml") 63 modules = self.target.modules 64 self.assertEqual(2, len(modules)) 65 self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10") 66 self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0") 67 68 def test_uuid_modules_no_age_apple(self): 69 """ 70 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, 71 and contains a PDB70 value whose age is zero and whose UUID values are 72 valid. Ensure we decode the UUID and don't include the age field in the UUID. 73 Also ensure that the first uint32_t is byte swapped, along with the next 74 two uint16_t values. Breakpad incorrectly byte swaps these values when it 75 saves Darwin minidump files. 76 """ 77 modules = self.get_minidump_modules("macos-arm-uuids-no-age.yaml") 78 modules = self.target.modules 79 self.assertEqual(2, len(modules)) 80 self.verify_module(modules[0], "/tmp/a", "04030201-0605-0807-090A-0B0C0D0E0F10") 81 self.verify_module(modules[1], "/tmp/b", "281E140A-3C32-5046-5A64-6E78828C96A0") 82 83 def test_uuid_modules_with_age(self): 84 """ 85 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, 86 and contains a PDB70 value whose age is valid and whose UUID values are 87 valid. Ensure we decode the UUID and include the age field in the UUID. 88 """ 89 modules = self.get_minidump_modules("linux-arm-uuids-with-age.yaml") 90 self.assertEqual(2, len(modules)) 91 self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-10101010") 92 self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-20202020") 93 94 def test_uuid_modules_elf_build_id_16(self): 95 """ 96 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, 97 and contains a ELF build ID whose value is valid and is 16 bytes long. 98 """ 99 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml") 100 self.assertEqual(2, len(modules)) 101 self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10") 102 self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0") 103 104 def test_uuid_modules_elf_build_id_20(self): 105 """ 106 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, 107 and contains a ELF build ID whose value is valid and is 20 bytes long. 108 """ 109 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml") 110 self.assertEqual(2, len(modules)) 111 self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314") 112 self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8") 113 114 def test_uuid_modules_elf_build_id_zero(self): 115 """ 116 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, 117 and contains a ELF build ID whose value is all zero. 118 """ 119 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-zero.yaml") 120 self.assertEqual(2, len(modules)) 121 self.verify_module(modules[0], "/not/exist/a", None) 122 self.verify_module(modules[1], "/not/exist/b", None) 123 124 def test_uuid_modules_elf_build_id_same(self): 125 """ 126 Test multiple modules having a MINIDUMP_MODULE.CvRecord that is 127 valid, and contains a ELF build ID whose value is the same. There 128 is an assert in the PlaceholderObjectFile that was firing when we 129 encountered this which was crashing the process that was checking 130 if PlaceholderObjectFile.m_base was the same as the address this 131 fake module was being loaded at. We need to ensure we don't crash 132 in such cases and that we add both modules even though they have 133 the same UUID. 134 """ 135 modules = self.get_minidump_modules("linux-arm-same-uuids.yaml") 136 self.assertEqual(2, len(modules)) 137 self.verify_module(modules[0], "/file/does/not/exist/a", 138 '11223344-1122-3344-1122-334411223344-11223344') 139 self.verify_module(modules[1], "/file/does/not/exist/b", 140 '11223344-1122-3344-1122-334411223344-11223344') 141 142 def test_partial_uuid_match(self): 143 """ 144 Breakpad has been known to create minidump files using CvRecord in each 145 module whose signature is set to PDB70 where the UUID only contains the 146 first 16 bytes of a 20 byte ELF build ID. Code was added to 147 ProcessMinidump.cpp to deal with this and allows partial UUID matching. 148 149 This test verifies that if we have a minidump with a 16 byte UUID, that 150 we are able to associate a symbol file with a 20 byte UUID only if the 151 first 16 bytes match. In this case we will see the path from the file 152 we found in the test directory and the 20 byte UUID from the actual 153 file, not the 16 byte shortened UUID from the minidump. 154 """ 155 so_path = self.getBuildArtifact("libuuidmatch.so") 156 self.yaml2obj("libuuidmatch.yaml", so_path) 157 cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) 158 self.dbg.HandleCommand(cmd) 159 modules = self.get_minidump_modules("linux-arm-partial-uuids-match.yaml") 160 self.assertEqual(1, len(modules)) 161 self.verify_module(modules[0], so_path, 162 "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116") 163 164 def test_partial_uuid_mismatch(self): 165 """ 166 Breakpad has been known to create minidump files using CvRecord in each 167 module whose signature is set to PDB70 where the UUID only contains the 168 first 16 bytes of a 20 byte ELF build ID. Code was added to 169 ProcessMinidump.cpp to deal with this and allows partial UUID matching. 170 171 This test verifies that if we have a minidump with a 16 byte UUID, that 172 we are not able to associate a symbol file with a 20 byte UUID only if 173 any of the first 16 bytes do not match. In this case we will see the UUID 174 from the minidump file and the path from the minidump file. 175 """ 176 so_path = self.getBuildArtifact("libuuidmismatch.so") 177 self.yaml2obj("libuuidmismatch.yaml", so_path) 178 cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) 179 self.dbg.HandleCommand(cmd) 180 modules = self.get_minidump_modules("linux-arm-partial-uuids-mismatch.yaml") 181 self.assertEqual(1, len(modules)) 182 self.verify_module(modules[0], 183 "/invalid/path/on/current/system/libuuidmismatch.so", 184 "7295E17C-6668-9E05-CBB5-DEE5003865D5") 185 186 def test_breakpad_hash_match(self): 187 """ 188 Breakpad creates minidump files using CvRecord in each module whose 189 signature is set to PDB70 where the UUID is a hash generated by 190 breakpad of the .text section. This is only done when the 191 executable has no ELF build ID. 192 193 This test verifies that if we have a minidump with a 16 byte UUID, 194 that we are able to associate a symbol file with no ELF build ID 195 and match it up by hashing the .text section. 196 """ 197 so_path = self.getBuildArtifact("libbreakpad.so") 198 self.yaml2obj("libbreakpad.yaml", so_path) 199 cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) 200 self.dbg.HandleCommand(cmd) 201 modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") 202 self.assertEqual(1, len(modules)) 203 # LLDB makes up it own UUID as well when there is no build ID so we 204 # will check that this matches. 205 self.verify_module(modules[0], so_path, "D9C480E8") 206 207 def test_breakpad_hash_match_sysroot(self): 208 """ 209 Check that we can match the breakpad .text section hash when the 210 module is located under a user-provided sysroot. 211 """ 212 sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot") 213 # Create the directory under the sysroot where the minidump reports 214 # the module. 215 so_dir = os.path.join(sysroot_path, "invalid", "path", "on", "current", "system") 216 so_path = os.path.join(so_dir, "libbreakpad.so") 217 lldbutil.mkdir_p(so_dir) 218 self.yaml2obj("libbreakpad.yaml", so_path) 219 self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path) 220 modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") 221 self.assertEqual(1, len(modules)) 222 # LLDB makes up its own UUID as well when there is no build ID so we 223 # will check that this matches. 224 self.verify_module(modules[0], so_path, "D9C480E8") 225 226 def test_breakpad_hash_match_sysroot_decoy(self): 227 """ 228 Check that we can match the breakpad .text section hash when there is 229 a module with the right name but wrong contents under a user-provided 230 sysroot, and the right module is at the given search path.. 231 """ 232 sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot") 233 # Create the directory under the sysroot where the minidump reports 234 # the module. 235 decoy_dir = os.path.join(sysroot_path, "invalid", "path", "on", "current", "system") 236 decoy_path = os.path.join(decoy_dir, "libbreakpad.so") 237 lldbutil.mkdir_p(decoy_dir) 238 self.yaml2obj("libbreakpad-decoy.yaml", decoy_path) 239 self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path) 240 so_dir = os.path.join(self.getBuildDir(), "searchpath_dir") 241 so_path = os.path.join(so_dir, "libbreakpad.so") 242 lldbutil.mkdir_p(so_dir) 243 self.yaml2obj("libbreakpad.yaml", so_path) 244 self.runCmd('settings set target.exec-search-paths "%s"' % so_dir) 245 modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") 246 self.assertEqual(1, len(modules)) 247 # LLDB makes up its own UUID as well when there is no build ID so we 248 # will check that this matches. 249 self.verify_module(modules[0], so_path, "D9C480E8") 250 251 def test_breakpad_overflow_hash_match(self): 252 """ 253 This is a similar to test_breakpad_hash_match, but it verifies that 254 if the .text section does not end on a 16 byte boundary, then it 255 will overflow into the next section's data by up to 15 bytes. This 256 verifies that we are able to match what breakpad does as it will do 257 this. 258 """ 259 so_path = self.getBuildArtifact("libbreakpad.so") 260 self.yaml2obj("libbreakpad-overflow.yaml", so_path) 261 cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) 262 self.dbg.HandleCommand(cmd) 263 modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") 264 self.assertEqual(1, len(modules)) 265 # LLDB makes up it own UUID as well when there is no build ID so we 266 # will check that this matches. 267 self.verify_module(modules[0], so_path, "48EB9FD7") 268 269 def test_breakpad_hash_match_exe_outside_sysroot(self): 270 """ 271 Check that we can match the breakpad .text section hash when the 272 module is specified as the exe during launch, and a syroot is 273 provided, which does not contain the exe. 274 """ 275 sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot") 276 lldbutil.mkdir_p(sysroot_path) 277 so_dir = os.path.join(self.getBuildDir(), "binary") 278 so_path = os.path.join(so_dir, "libbreakpad.so") 279 lldbutil.mkdir_p(so_dir) 280 self.yaml2obj("libbreakpad.yaml", so_path) 281 self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path) 282 modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml", so_path) 283 self.assertEqual(1, len(modules)) 284 # LLDB makes up its own UUID as well when there is no build ID so we 285 # will check that this matches. 286 self.verify_module(modules[0], so_path, "D9C480E8") 287 288 def test_facebook_hash_match(self): 289 """ 290 Breakpad creates minidump files using CvRecord in each module whose 291 signature is set to PDB70 where the UUID is a hash generated by 292 breakpad of the .text section and Facebook modified this hash to 293 avoid collisions. This is only done when the executable has no ELF 294 build ID. 295 296 This test verifies that if we have a minidump with a 16 byte UUID, 297 that we are able to associate a symbol file with no ELF build ID 298 and match it up by hashing the .text section like Facebook does. 299 """ 300 so_path = self.getBuildArtifact("libbreakpad.so") 301 self.yaml2obj("libbreakpad.yaml", so_path) 302 cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) 303 self.dbg.HandleCommand(cmd) 304 modules = self.get_minidump_modules("linux-arm-facebook-uuid-match.yaml") 305 self.assertEqual(1, len(modules)) 306 # LLDB makes up it own UUID as well when there is no build ID so we 307 # will check that this matches. 308 self.verify_module(modules[0], so_path, "D9C480E8") 309 310 311 def test_relative_module_name(self): 312 old_cwd = os.getcwd() 313 self.addTearDownHook(lambda: os.chdir(old_cwd)) 314 os.chdir(self.getBuildDir()) 315 name = "file-with-a-name-unlikely-to-exist-in-the-current-directory.so" 316 open(name, "a").close() 317 modules = self.get_minidump_modules( 318 self.getSourcePath("relative_module_name.yaml")) 319 self.assertEqual(1, len(modules)) 320 self.verify_module(modules[0], name, None) 321 322 def test_add_module_build_id_16(self): 323 """ 324 Test that adding module with 16 byte UUID returns the existing 325 module or fails. 326 """ 327 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml") 328 self.assertEqual(2, len(modules)) 329 330 # Add the existing modules. 331 self.assertEqual(modules[0], self.target.AddModule( 332 "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10")) 333 self.assertEqual(modules[1], self.target.AddModule( 334 "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0")) 335 336 # Adding modules with non-existing UUID should fail. 337 self.assertFalse( 338 self.target.AddModule( 339 "a", "", "12345678-1234-1234-1234-123456789ABC").IsValid()) 340 self.assertFalse( 341 self.target.AddModule( 342 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678").IsValid()) 343 344 def test_add_module_build_id_20(self): 345 """ 346 Test that adding module with 20 byte UUID returns the existing 347 module or fails. 348 """ 349 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml") 350 351 # Add the existing modules. 352 self.assertEqual(modules[0], self.target.AddModule( 353 "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314")) 354 self.assertEqual(modules[1], self.target.AddModule( 355 "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8")) 356 357 # Adding modules with non-existing UUID should fail. 358 self.assertFalse( 359 self.target.AddModule( 360 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10").IsValid()) 361 self.assertFalse( 362 self.target.AddModule( 363 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678").IsValid()) 364 365 def test_add_module_build_id_4(self): 366 """ 367 Test that adding module with 4 byte UUID returns the existing 368 module or fails. 369 """ 370 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-4.yaml") 371 372 # Add the existing modules. 373 self.assertEqual(modules[0], self.target.AddModule( 374 "/some/local/a.so", "", "01020304")) 375 self.assertEqual(modules[1], self.target.AddModule( 376 "/some/local/b.so", "", "0A141E28")) 377 378 # Adding modules with non-existing UUID should fail. 379 self.assertFalse( 380 self.target.AddModule( 381 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10").IsValid()) 382 self.assertFalse(self.target.AddModule("a", "", "01020305").IsValid()) 383 384 def test_remove_placeholder_add_real_module(self): 385 """ 386 Test that removing a placeholder module and adding back the real 387 module succeeds. 388 """ 389 so_path = self.getBuildArtifact("libuuidmatch.so") 390 self.yaml2obj("libuuidmatch.yaml", so_path) 391 modules = self.get_minidump_modules("linux-arm-uuids-match.yaml") 392 393 uuid = "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116"; 394 self.assertEqual(1, len(modules)) 395 self.verify_module(modules[0], "/target/path/libuuidmatch.so",uuid) 396 397 self.target.RemoveModule(modules[0]) 398 new_module = self.target.AddModule(so_path, "", uuid) 399 400 self.verify_module(new_module, so_path, uuid) 401 self.assertEqual(new_module, self.target.modules[0]) 402 self.assertEqual(1, len(self.target.modules)) 403