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