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