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): 36 minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp") 37 self.yaml2obj(yaml_file, minidump_path) 38 self.target = self.dbg.CreateTarget(None) 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 270 def test_facebook_hash_match(self): 271 """ 272 Breakpad creates minidump files using CvRecord in each module whose 273 signature is set to PDB70 where the UUID is a hash generated by 274 breakpad of the .text section and Facebook modified this hash to 275 avoid collisions. This is only done when the executable has no ELF 276 build ID. 277 278 This test verifies that if we have a minidump with a 16 byte UUID, 279 that we are able to associate a symbol file with no ELF build ID 280 and match it up by hashing the .text section like Facebook does. 281 """ 282 so_path = self.getBuildArtifact("libbreakpad.so") 283 self.yaml2obj("libbreakpad.yaml", so_path) 284 cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) 285 self.dbg.HandleCommand(cmd) 286 modules = self.get_minidump_modules("linux-arm-facebook-uuid-match.yaml") 287 self.assertEqual(1, len(modules)) 288 # LLDB makes up it own UUID as well when there is no build ID so we 289 # will check that this matches. 290 self.verify_module(modules[0], so_path, "D9C480E8") 291 292 293 def test_relative_module_name(self): 294 old_cwd = os.getcwd() 295 self.addTearDownHook(lambda: os.chdir(old_cwd)) 296 os.chdir(self.getBuildDir()) 297 name = "file-with-a-name-unlikely-to-exist-in-the-current-directory.so" 298 open(name, "a").close() 299 modules = self.get_minidump_modules( 300 self.getSourcePath("relative_module_name.yaml")) 301 self.assertEqual(1, len(modules)) 302 self.verify_module(modules[0], name, None) 303 304 def test_add_module_build_id_16(self): 305 """ 306 Test that adding module with 16 byte UUID returns the existing 307 module or fails. 308 """ 309 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml") 310 self.assertEqual(2, len(modules)) 311 312 # Add the existing modules. 313 self.assertEqual(modules[0], self.target.AddModule( 314 "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10")) 315 self.assertEqual(modules[1], self.target.AddModule( 316 "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0")) 317 318 # Adding modules with non-existing UUID should fail. 319 self.assertFalse( 320 self.target.AddModule( 321 "a", "", "12345678-1234-1234-1234-123456789ABC").IsValid()) 322 self.assertFalse( 323 self.target.AddModule( 324 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678").IsValid()) 325 326 def test_add_module_build_id_20(self): 327 """ 328 Test that adding module with 20 byte UUID returns the existing 329 module or fails. 330 """ 331 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml") 332 333 # Add the existing modules. 334 self.assertEqual(modules[0], self.target.AddModule( 335 "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314")) 336 self.assertEqual(modules[1], self.target.AddModule( 337 "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8")) 338 339 # Adding modules with non-existing UUID should fail. 340 self.assertFalse( 341 self.target.AddModule( 342 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10").IsValid()) 343 self.assertFalse( 344 self.target.AddModule( 345 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678").IsValid()) 346 347 def test_add_module_build_id_4(self): 348 """ 349 Test that adding module with 4 byte UUID returns the existing 350 module or fails. 351 """ 352 modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-4.yaml") 353 354 # Add the existing modules. 355 self.assertEqual(modules[0], self.target.AddModule( 356 "/some/local/a.so", "", "01020304")) 357 self.assertEqual(modules[1], self.target.AddModule( 358 "/some/local/b.so", "", "0A141E28")) 359 360 # Adding modules with non-existing UUID should fail. 361 self.assertFalse( 362 self.target.AddModule( 363 "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10").IsValid()) 364 self.assertFalse(self.target.AddModule("a", "", "01020305").IsValid()) 365 366 def test_remove_placeholder_add_real_module(self): 367 """ 368 Test that removing a placeholder module and adding back the real 369 module succeeds. 370 """ 371 so_path = self.getBuildArtifact("libuuidmatch.so") 372 self.yaml2obj("libuuidmatch.yaml", so_path) 373 modules = self.get_minidump_modules("linux-arm-uuids-match.yaml") 374 375 uuid = "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116"; 376 self.assertEqual(1, len(modules)) 377 self.verify_module(modules[0], "/target/path/libuuidmatch.so",uuid) 378 379 self.target.RemoveModule(modules[0]) 380 new_module = self.target.AddModule(so_path, "", uuid) 381 382 self.verify_module(new_module, so_path, uuid) 383 self.assertEqual(new_module, self.target.modules[0]) 384 self.assertEqual(1, len(self.target.modules)) 385