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