| 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] | |
| <tracing errors> [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] | |
| <tracing errors> [61833, 61833]""" | |
| ], | |
| ) | |
| self.expect( | |
| "thread trace dump function-calls 3 -J", | |
| substrs=[ | |
| """[ | |
| { | |
| "tracedSegments": [ | |
| { | |
| "firstInstructionId": "5", | |
| "lastInstructionId": "61831" | |
| } | |
| ] | |
| }, | |
| { | |
| "tracedSegments": [ | |
| { | |
| "firstInstructionId": "61833", | |
| "lastInstructionId": "61833" | |
| } | |
| ] | |
| } | |
| ]""" | |
| ], | |
| ) | |
| @skipIfNoIntelPT | |
| 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" | |
| } | |
| ] | |
| } | |
| ]""" | |
| ], | |
| ) | |
| @skipIfNoIntelPT | |
| 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] | |
| <tracing errors> [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" | |
| } | |
| ] | |
| } | |
| ]""" | |
| ], | |
| ) |