from intelpt_testcase import * from lldbsuite.test.lldbtest import * from lldbsuite.test.decorators import * class TestTraceDumpInfo(TraceIntelPTTestCaseBase): def testDumpSimpleFunctionCalls(self): self.expect( "trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json") ) self.expect( "thread trace dump function-calls 2", error=True, substrs=['error: no thread with index: "2"'], ) self.expect( "thread trace dump function-calls 1 -j", substrs=[ '[{"tracedSegments":[{"firstInstructionId":"3","lastInstructionId":"26"}]}]' ], ) self.expect( "thread trace dump function-calls 1 -J", substrs=[ """[ { "tracedSegments": [ { "firstInstructionId": "3", "lastInstructionId": "26" } ] } ]""" ], ) # We test first some code without function call self.expect( "thread trace dump function-calls 1", substrs=[ """thread #1: tid = 3842849 [call tree #0] a.out`main + 4 at main.cpp:2 to 4:0 [3, 26]""" ], ) def testFunctionCallsWithErrors(self): self.expect( "trace load -v " + os.path.join( self.getSourceDir(), "intelpt-multi-core-trace", "trace.json" ) ) # We expect that tracing errors appear as a different tree self.expect( "thread trace dump function-calls 2", substrs=[ """thread #2: tid = 3497496 [call tree #0] m.out`foo() + 65 at multi_thread.cpp:12:21 to 12:21 [4, 19524] [call tree #1] [19526, 19526]""" ], ) self.expect( "thread trace dump function-calls 2 -J", substrs=[ """[ { "tracedSegments": [ { "firstInstructionId": "4", "lastInstructionId": "19524" } ] }, { "tracedSegments": [ { "firstInstructionId": "19526", "lastInstructionId": "19526" } ] } ]""" ], ) self.expect( "thread trace dump function-calls 3", substrs=[ """thread #3: tid = 3497497 [call tree #0] m.out`bar() + 30 at multi_thread.cpp:19:3 to 20:6 [5, 61831] [call tree #1] [61833, 61833]""" ], ) self.expect( "thread trace dump function-calls 3 -J", substrs=[ """[ { "tracedSegments": [ { "firstInstructionId": "5", "lastInstructionId": "61831" } ] }, { "tracedSegments": [ { "firstInstructionId": "61833", "lastInstructionId": "61833" } ] } ]""" ], ) def testInlineFunctionCalls(self): self.expect( "file " + os.path.join(self.getSourceDir(), "inline-function", "a.out") ) self.expect("b main") # we'll trace from the beginning of main self.expect("b 17") self.expect("r") self.expect("thread trace start") self.expect("c") self.expect( "thread trace dump function-calls", substrs=[ """[call tree #0] a.out`main + 8 at inline.cpp:15:7 to 16:14 [2, 6] a.out`foo(int) at inline.cpp:8:16 to 9:15 [7, 14] a.out`foo(int) + 22 [inlined] mult(int, int) at inline.cpp:2:7 to 5:10 [15, 22] a.out`foo(int) + 49 at inline.cpp:9:15 to 12:1 [23, 27] a.out`main + 25 at inline.cpp:16:14 to 16:14 [28, 28]""" ], ) self.expect( "thread trace dump function-calls -J", substrs=[ """[ { "tracedSegments": [ { "firstInstructionId": "2", "lastInstructionId": "6", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "7", "lastInstructionId": "14", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "15", "lastInstructionId": "22" } ] } }, { "firstInstructionId": "23", "lastInstructionId": "27" } ] } }, { "firstInstructionId": "28", "lastInstructionId": "28" } ] } ]""" ], ) def testIncompleteInlineFunctionCalls(self): self.expect( "file " + os.path.join(self.getSourceDir(), "inline-function", "a.out") ) self.expect("b 4") # we'll trace from the middle of the inline method self.expect("b 17") self.expect("r") self.expect("thread trace start") self.expect("c") self.expect( "thread trace dump function-calls", substrs=[ """[call tree #0] a.out`main a.out`foo(int) a.out`foo(int) + 36 [inlined] mult(int, int) + 14 at inline.cpp:4:5 to 5:10 [2, 6] a.out`foo(int) + 49 at inline.cpp:9:15 to 12:1 [7, 11] a.out`main + 25 at inline.cpp:16:14 to 16:14 [12, 12]""" ], ) self.expect( "thread trace dump function-calls -J", substrs=[ """[ { "untracedPrefixSegment": { "nestedCall": { "untracedPrefixSegment": { "nestedCall": { "tracedSegments": [ { "firstInstructionId": "2", "lastInstructionId": "6" } ] } }, "tracedSegments": [ { "firstInstructionId": "7", "lastInstructionId": "11" } ] } }, "tracedSegments": [ { "firstInstructionId": "12", "lastInstructionId": "12" } ] } ]""" ], ) def testMultifileFunctionCalls(self): # This test is extremely important because it first calls the method foo() which requires going through the dynamic linking. # You'll see the entry "a.out`symbol stub for: foo()" which will invoke the ld linker, which will in turn find the actual foo # function and eventually invoke it. However, we don't have the image of the linker in the trace bundle, so we'll see errors # because the decoder couldn't find the linker binary! After those failures, the linker will resume right where we return to # main after foo() finished. # Then, we call foo() again, but because it has already been loaded by the linker, we don't invoke the linker anymore! And # we'll see a nice tree without errors in this second invocation. Something interesting happens here. We still have an # invocation to the symbol stub for foo(), but it modifies the stack so that when we return from foo() we don't stop again # at the symbol stub, but instead we return directly to main. This is an example of returning several levels up in the # call stack. # Not only that, we also have an inline method in between. self.expect( "trace load " + os.path.join( self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json" ) ) self.expect( "thread trace dump function-calls", substrs=[ """thread #1: tid = 815455 [call tree #0] a.out`main + 15 at main.cpp:10 to 10:0 [3, 3] a.out`symbol stub for: foo() to <+11> [7, 9] a.out`a.out[0x0000000000400510] to a.out[0x0000000000400516] [10, 11] [call tree #1] [12, 12] [call tree #2] a.out`main + 20 at main.cpp:10 to 12:0 [16, 22] a.out`main + 34 [inlined] inline_function() at main.cpp:4 to 6:0 [26, 30] a.out`main + 55 at main.cpp:14 to 16:0 [31, 37] a.out`symbol stub for: foo() to <+0> [38, 38] libfoo.so`foo() at foo.cpp:3 to 4:0 [39, 42] libfoo.so`symbol stub for: bar() to <+0> [43, 43] libbar.so`bar() at bar.cpp:1 to 4:0 [44, 52] libfoo.so`foo() + 13 at foo.cpp:4 to 6:0 [53, 60] a.out`main + 68 at main.cpp:16 to 16:0 [61, 63]""" ], ) self.expect( "thread trace dump function-calls -J", substrs=[ """[ { "tracedSegments": [ { "firstInstructionId": "3", "lastInstructionId": "3", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "7", "lastInstructionId": "9", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "10", "lastInstructionId": "11" } ] } } ] } } ] }, { "tracedSegments": [ { "firstInstructionId": "12", "lastInstructionId": "12" } ] }, { "tracedSegments": [ { "firstInstructionId": "16", "lastInstructionId": "22", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "26", "lastInstructionId": "30" } ] } }, { "firstInstructionId": "31", "lastInstructionId": "37", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "38", "lastInstructionId": "38", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "39", "lastInstructionId": "42", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "43", "lastInstructionId": "43", "nestedCall": { "tracedSegments": [ { "firstInstructionId": "44", "lastInstructionId": "52" } ] } } ] } }, { "firstInstructionId": "53", "lastInstructionId": "60" } ] } } ] } }, { "firstInstructionId": "61", "lastInstructionId": "63" } ] } ]""" ], )