1import lldb 2from intelpt_testcase import * 3from lldbsuite.test.lldbtest import * 4from lldbsuite.test import lldbutil 5from lldbsuite.test.decorators import * 6 7 8class TestTraceLoad(TraceIntelPTTestCaseBase): 9 NO_DEBUG_INFO_TESTCASE = True 10 11 @testSBAPIAndCommands 12 def testLoadMultiCoreTrace(self): 13 src_dir = self.getSourceDir() 14 trace_description_file_path = os.path.join( 15 src_dir, "intelpt-multi-core-trace", "trace.json" 16 ) 17 self.traceLoad( 18 traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"] 19 ) 20 self.expect( 21 "thread trace dump instructions 2 -t", 22 substrs=[ 23 "19526: [19691636.212 ns] (error) decoding truncated: TSC 40450075478109270 exceeds maximum TSC value 40450075477704372, will skip decoding the remaining data of the PSB (skipping 774 of 825 bytes)", 24 "m.out`foo() + 65 at multi_thread.cpp:12:21", 25 "9524: [19691632.221 ns] 0x0000000000400ba7 jg 0x400bb3", 26 ], 27 ) 28 self.expect( 29 "thread trace dump instructions 3 -t", 30 substrs=[ 31 "61831: [19736132.088 ns] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", 32 "m.out`bar() + 26 at multi_thread.cpp:20:6", 33 ], 34 ) 35 36 self.expect( 37 "thread trace dump info --json", 38 substrs=[ 39 """{ 40 "traceTechnology": "intel-pt", 41 "threadStats": { 42 "tid": 3497234, 43 "traceItemsCount": 0, 44 "memoryUsage": { 45 "totalInBytes": "0", 46 "avgPerItemInBytes": null 47 }, 48 "timingInSeconds": { 49 "Decoding instructions": """, 50 """ 51 }, 52 "events": { 53 "totalCount": 0, 54 "individualCounts": {} 55 }, 56 "errors": { 57 "totalCount": 0, 58 "libiptErrors": {}, 59 "fatalErrors": 0, 60 "otherErrors": 0 61 }, 62 "continuousExecutions": 0, 63 "PSBBlocks": 0 64 }, 65 "globalStats": { 66 "timingInSeconds": { 67 "Context switch and Intel PT traces correlation": 0 68 }, 69 "totalUnattributedPSBBlocks": 0, 70 "totalCountinuosExecutions": 153, 71 "totalPSBBlocks": 5, 72 "totalContinuousExecutions": 153 73 } 74}""", 75 ], 76 ) 77 78 self.expect( 79 "thread trace dump info 2 --json", 80 substrs=[ 81 """{ 82 "traceTechnology": "intel-pt", 83 "threadStats": { 84 "tid": 3497496, 85 "traceItemsCount": 19527,""", 86 """}, 87 "timingInSeconds": { 88 "Decoding instructions": """, 89 """ 90 }, 91 "events": { 92 "totalCount": 5, 93 "individualCounts": { 94 "software disabled tracing": 1, 95 "trace synchronization point": 1, 96 "CPU core changed": 1, 97 "HW clock tick": 2 98 } 99 }, 100 "errors": { 101 "totalCount": 1, 102 "libiptErrors": {}, 103 "fatalErrors": 0, 104 "otherErrors": 1 105 }, 106 "continuousExecutions": 1, 107 "PSBBlocks": 1 108 }, 109 "globalStats": { 110 "timingInSeconds": { 111 "Context switch and Intel PT traces correlation": 0""", 112 """}, 113 "totalUnattributedPSBBlocks": 0, 114 "totalCountinuosExecutions": 153, 115 "totalPSBBlocks": 5, 116 "totalContinuousExecutions": 153 117 } 118}""", 119 ], 120 ) 121 122 @testSBAPIAndCommands 123 def testLoadCompactMultiCoreTrace(self): 124 src_dir = self.getSourceDir() 125 trace_description_file_path = os.path.join( 126 src_dir, "intelpt-multi-core-trace", "trace.json" 127 ) 128 self.traceLoad( 129 traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"] 130 ) 131 132 self.expect( 133 "thread trace dump info 2", 134 substrs=["Total number of continuous executions found: 153"], 135 ) 136 137 # we'll save the trace in compact format 138 compact_trace_bundle_dir = os.path.join( 139 self.getBuildDir(), "intelpt-multi-core-trace-compact" 140 ) 141 self.traceSave(compact_trace_bundle_dir, compact=True) 142 143 # we'll delete the previous target and make sure it's trace object is deleted 144 self.dbg.DeleteTarget(self.dbg.GetTargetAtIndex(0)) 145 self.expect( 146 "thread trace dump instructions 2 -t", 147 substrs=["error: invalid target"], 148 error=True, 149 ) 150 151 # we'll load the compact trace and make sure it works 152 self.traceLoad( 153 os.path.join(compact_trace_bundle_dir, "trace.json"), substrs=["intel-pt"] 154 ) 155 self.expect( 156 "thread trace dump instructions 2 -t", 157 substrs=[ 158 "19526: [19691636.212 ns] (error)", 159 "m.out`foo() + 65 at multi_thread.cpp:12:21", 160 "19524: [19691632.221 ns] 0x0000000000400ba7 jg 0x400bb3", 161 ], 162 ) 163 self.expect( 164 "thread trace dump instructions 3 -t", 165 substrs=[ 166 "61833: [19736136.079 ns] (error)", 167 "61831: [19736132.088 ns] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", 168 "m.out`bar() + 26 at multi_thread.cpp:20:6", 169 ], 170 ) 171 172 # This reduced the number of continuous executions to look at 173 self.expect( 174 "thread trace dump info 2", 175 substrs=["Total number of continuous executions found: 3"], 176 ) 177 178 # We clean up for the next run of this test 179 self.dbg.DeleteTarget(self.dbg.GetTargetAtIndex(0)) 180 181 @testSBAPIAndCommands 182 def testLoadMultiCoreTraceWithStringNumbers(self): 183 src_dir = self.getSourceDir() 184 trace_description_file_path = os.path.join( 185 src_dir, "intelpt-multi-core-trace", "trace_with_string_numbers.json" 186 ) 187 self.traceLoad( 188 traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"] 189 ) 190 self.expect( 191 "thread trace dump instructions 2 -t", 192 substrs=[ 193 "19526: [19691636.212 ns] (error)", 194 "m.out`foo() + 65 at multi_thread.cpp:12:21", 195 "19524: [19691632.221 ns] 0x0000000000400ba7 jg 0x400bb3", 196 ], 197 ) 198 self.expect( 199 "thread trace dump instructions 3 -t", 200 substrs=[ 201 "61831: [19736132.088 ns] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", 202 "m.out`bar() + 26 at multi_thread.cpp:20:6", 203 ], 204 ) 205 206 @testSBAPIAndCommands 207 def testLoadMultiCoreTraceWithMissingThreads(self): 208 src_dir = self.getSourceDir() 209 trace_description_file_path = os.path.join( 210 src_dir, "intelpt-multi-core-trace", "trace_missing_threads.json" 211 ) 212 self.traceLoad( 213 traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"] 214 ) 215 self.expect( 216 "thread trace dump instructions 3 -t", 217 substrs=[ 218 "19526: [19691636.212 ns] (error)", 219 "m.out`foo() + 65 at multi_thread.cpp:12:21", 220 "19524: [19691632.221 ns] 0x0000000000400ba7 jg 0x400bb3", 221 ], 222 ) 223 self.expect( 224 "thread trace dump instructions 2 -t", 225 substrs=[ 226 "61831: [19736132.088 ns] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", 227 "m.out`bar() + 26 at multi_thread.cpp:20:6", 228 ], 229 ) 230 231 @testSBAPIAndCommands 232 def testLoadTrace(self): 233 src_dir = self.getSourceDir() 234 trace_description_file_path = os.path.join( 235 src_dir, "intelpt-trace", "trace.json" 236 ) 237 self.traceLoad( 238 traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"] 239 ) 240 241 target = self.dbg.GetSelectedTarget() 242 process = target.GetProcess() 243 self.assertEqual(process.GetProcessID(), 1234) 244 245 self.assertEqual(process.GetNumThreads(), 1) 246 self.assertEqual(process.GetThreadAtIndex(0).GetThreadID(), 3842849) 247 248 self.assertEqual(target.GetNumModules(), 1) 249 module = target.GetModuleAtIndex(0) 250 path = module.GetFileSpec() 251 self.assertEqual(path.fullpath, os.path.join(src_dir, "intelpt-trace", "a.out")) 252 self.assertGreater(module.GetNumSections(), 0) 253 self.assertEqual(module.GetSectionAtIndex(0).GetFileAddress(), 0x400000) 254 255 self.assertEqual( 256 "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A", module.GetUUIDString() 257 ) 258 259 # check that the Process and Thread objects were created correctly 260 self.expect("thread info", substrs=["tid = 3842849"]) 261 self.expect("thread list", substrs=["Process 1234 stopped", "tid = 3842849"]) 262 self.expect( 263 "thread trace dump info", 264 substrs=[ 265 """thread #1: tid = 3842849 266 267 Trace technology: intel-pt 268 269 Total number of trace items: 28 270 271 Memory usage: 272 Raw trace size: 4 KiB""", 273 """ 274 275 Events: 276 Number of individual events: 7 277 software disabled tracing: 2 278 hardware disabled tracing: 4 279 trace synchronization point: 1""", 280 ], 281 ) 282 283 @testSBAPIAndCommands 284 def testLoadInvalidTraces(self): 285 src_dir = self.getSourceDir() 286 287 # We test first an invalid type 288 trace_description_file_path = os.path.join( 289 src_dir, "intelpt-trace", "trace_bad.json" 290 ) 291 expected_substrs = [ 292 """error: expected object at traceBundle.processes[0] 293 294Context: 295{ 296 "cpuInfo": { ... }, 297 "processes": [ 298 /* error: expected object */ 299 123 300 ], 301 "type": "intel-pt" 302} 303 304Schema: 305{ 306 "type": "intel-pt", 307 "cpuInfo": { 308 // CPU information gotten from, for example, /proc/cpuinfo. 309 310 "vendor": "GenuineIntel" | "unknown", 311 "family": integer, 312 "model": integer, 313 "stepping": integer 314 },""" 315 ] 316 self.traceLoad( 317 traceDescriptionFilePath=trace_description_file_path, 318 error=True, 319 substrs=expected_substrs, 320 ) 321 322 # Now we test a wrong cpu family field in the global bundle description file 323 trace_description_file_path = os.path.join( 324 src_dir, "intelpt-trace", "trace_bad2.json" 325 ) 326 expected_substrs = [ 327 "error: expected uint64_t at traceBundle.cpuInfo.family", 328 "Context", 329 "Schema", 330 ] 331 self.traceLoad( 332 traceDescriptionFilePath=trace_description_file_path, 333 error=True, 334 substrs=expected_substrs, 335 ) 336 337 # Now we test a missing field in the intel-pt settings 338 trace_description_file_path = os.path.join( 339 src_dir, "intelpt-trace", "trace_bad4.json" 340 ) 341 expected_substrs = [ 342 """error: missing value at traceBundle.cpuInfo.family 343 344Context: 345{ 346 "cpuInfo": /* error: missing value */ { 347 "model": 79, 348 "stepping": 1, 349 "vendor": "GenuineIntel" 350 }, 351 "processes": [], 352 "type": "intel-pt" 353}""", 354 "Schema", 355 ] 356 self.traceLoad( 357 traceDescriptionFilePath=trace_description_file_path, 358 error=True, 359 substrs=expected_substrs, 360 ) 361 362 # Now we test an incorrect load address in the intel-pt settings 363 trace_description_file_path = os.path.join( 364 src_dir, "intelpt-trace", "trace_bad5.json" 365 ) 366 expected_substrs = [ 367 "error: missing value at traceBundle.processes[1].pid", 368 "Schema", 369 ] 370 self.traceLoad( 371 traceDescriptionFilePath=trace_description_file_path, 372 error=True, 373 substrs=expected_substrs, 374 ) 375 376 # The following wrong schema will have a valid target and an invalid one. In the case of failure, 377 # no targets should be created. 378 self.assertEqual(self.dbg.GetNumTargets(), 0) 379 trace_description_file_path = os.path.join( 380 src_dir, "intelpt-trace", "trace_bad3.json" 381 ) 382 expected_substrs = ["error: missing value at traceBundle.processes[1].pid"] 383 self.traceLoad( 384 traceDescriptionFilePath=trace_description_file_path, 385 error=True, 386 substrs=expected_substrs, 387 ) 388 self.assertEqual(self.dbg.GetNumTargets(), 0) 389 390 def testLoadTraceCursor(self): 391 src_dir = self.getSourceDir() 392 trace_description_file_path = os.path.join( 393 src_dir, "intelpt-multi-core-trace", "trace.json" 394 ) 395 traceDescriptionFile = lldb.SBFileSpec(trace_description_file_path, True) 396 397 error = lldb.SBError() 398 trace = self.dbg.LoadTraceFromFile(error, traceDescriptionFile) 399 self.assertSBError(error) 400 401 target = self.dbg.GetSelectedTarget() 402 process = target.process 403 404 # 1. Test some expected items of thread 1's trace cursor. 405 thread1 = process.threads[1] 406 cursor = trace.CreateNewCursor(error, thread1) 407 self.assertTrue(cursor) 408 self.assertTrue(cursor.HasValue()) 409 cursor.Seek(0, lldb.eTraceCursorSeekTypeBeginning) 410 cursor.SetForwards(True) 411 412 self.assertTrue(cursor.IsEvent()) 413 self.assertEqual(cursor.GetEventTypeAsString(), "HW clock tick") 414 self.assertEqual(cursor.GetCPU(), lldb.LLDB_INVALID_CPU_ID) 415 416 cursor.Next() 417 418 self.assertTrue(cursor.IsEvent()) 419 self.assertEqual(cursor.GetEventTypeAsString(), "CPU core changed") 420 self.assertEqual(cursor.GetCPU(), 51) 421 422 cursor.GoToId(19526) 423 424 self.assertTrue(cursor.IsError()) 425 self.assertEqual( 426 cursor.GetError(), 427 "decoding truncated: TSC 40450075478109270 exceeds maximum TSC value 40450075477704372, will skip decoding the remaining data of the PSB (skipping 774 of 825 bytes)", 428 ) 429 430 cursor.GoToId(19524) 431 432 self.assertTrue(cursor.IsInstruction()) 433 self.assertEqual(cursor.GetLoadAddress(), 0x400BA7) 434 435 # Helper function to check equality of the current item of two trace cursors. 436 def assertCurrentTraceCursorItemEqual(lhs, rhs): 437 self.assertTrue(lhs.HasValue() and rhs.HasValue()) 438 439 self.assertEqual(lhs.GetId(), rhs.GetId()) 440 self.assertEqual(lhs.GetItemKind(), rhs.GetItemKind()) 441 if lhs.IsError(): 442 self.assertEqual(lhs.GetError(), rhs.GetError()) 443 elif lhs.IsEvent(): 444 self.assertEqual(lhs.GetEventType(), rhs.GetEventType()) 445 self.assertEqual(lhs.GetEventTypeAsString(), rhs.GetEventTypeAsString()) 446 elif lhs.IsInstruction(): 447 self.assertEqual(lhs.GetLoadAddress(), rhs.GetLoadAddress()) 448 else: 449 self.fail("Unknown trace item kind") 450 451 for thread in process.threads: 452 sequentialTraversalCursor = trace.CreateNewCursor(error, thread) 453 self.assertSBError(error) 454 # Skip threads with no trace items 455 if not sequentialTraversalCursor.HasValue(): 456 continue 457 458 # 2. Test "End" boundary of the trace by advancing past the trace's last item. 459 sequentialTraversalCursor.Seek(0, lldb.eTraceCursorSeekTypeEnd) 460 self.assertTrue(sequentialTraversalCursor.HasValue()) 461 sequentialTraversalCursor.SetForwards(True) 462 sequentialTraversalCursor.Next() 463 self.assertFalse(sequentialTraversalCursor.HasValue()) 464 465 # 3. Test sequential traversal using sequential access API (ie Next()) 466 # and random access API (ie GoToId()) simultaneously. 467 randomAccessCursor = trace.CreateNewCursor(error, thread) 468 self.assertSBError(error) 469 # Reset the sequential cursor 470 sequentialTraversalCursor.Seek(0, lldb.eTraceCursorSeekTypeBeginning) 471 sequentialTraversalCursor.SetForwards(True) 472 self.assertTrue(sequentialTraversalCursor.IsForwards()) 473 474 while sequentialTraversalCursor.HasValue(): 475 itemId = sequentialTraversalCursor.GetId() 476 randomAccessCursor.GoToId(itemId) 477 assertCurrentTraceCursorItemEqual( 478 sequentialTraversalCursor, randomAccessCursor 479 ) 480 sequentialTraversalCursor.Next() 481 482 # 4. Test a random access with random access API (ie Seek()) and 483 # sequential access API (ie consecutive calls to Next()). 484 TEST_SEEK_ID = 3 485 randomAccessCursor.GoToId(TEST_SEEK_ID) 486 # Reset the sequential cursor 487 sequentialTraversalCursor.Seek(0, lldb.eTraceCursorSeekTypeBeginning) 488 sequentialTraversalCursor.SetForwards(True) 489 for _ in range(TEST_SEEK_ID): 490 sequentialTraversalCursor.Next() 491 assertCurrentTraceCursorItemEqual( 492 sequentialTraversalCursor, randomAccessCursor 493 ) 494 495 @testSBAPIAndCommands 496 def testLoadKernelTrace(self): 497 # kernel section without loadAddress (using default loadAddress). 498 src_dir = self.getSourceDir() 499 trace_description_file_path = os.path.join( 500 src_dir, "intelpt-kernel-trace", "trace.json" 501 ) 502 self.traceLoad( 503 traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"] 504 ) 505 506 self.expect("image list", substrs=["0xffffffff81000000", "modules/m.out"]) 507 508 self.expect( 509 "thread list", 510 substrs=[ 511 "Process 1 stopped", 512 "* thread #1: tid = 0x002d", 513 " thread #2: tid = 0x0033", 514 ], 515 ) 516 517 # kernel section with custom loadAddress. 518 trace_description_file_path = os.path.join( 519 src_dir, "intelpt-kernel-trace", "trace_with_loadAddress.json" 520 ) 521 self.traceLoad( 522 traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"] 523 ) 524 525 self.expect("image list", substrs=["0x400000", "modules/m.out"]) 526 527 @testSBAPIAndCommands 528 def testLoadInvalidKernelTrace(self): 529 src_dir = self.getSourceDir() 530 531 # Test kernel section with non-empty processeses section. 532 trace_description_file_path = os.path.join( 533 src_dir, "intelpt-kernel-trace", "trace_kernel_with_process.json" 534 ) 535 expected_substrs = [ 536 'error: "processes" must be empty when "kernel" is provided when parsing traceBundle' 537 ] 538 self.traceLoad( 539 traceDescriptionFilePath=trace_description_file_path, 540 error=True, 541 substrs=expected_substrs, 542 ) 543 544 # Test kernel section without cpus section. 545 trace_description_file_path = os.path.join( 546 src_dir, "intelpt-kernel-trace", "trace_kernel_wo_cpus.json" 547 ) 548 expected_substrs = [ 549 'error: "cpus" is required when "kernel" is provided when parsing traceBundle' 550 ] 551 self.traceLoad( 552 traceDescriptionFilePath=trace_description_file_path, 553 error=True, 554 substrs=expected_substrs, 555 ) 556