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"
}
]
}
]"""
],
)
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]
<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"
}
]
}
]"""
],
)