258 lines
9.8 KiB
Python
258 lines
9.8 KiB
Python
from collections import defaultdict
|
|
import lldb
|
|
import json
|
|
from intelpt_testcase import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
from lldbsuite.test.decorators import *
|
|
import os
|
|
|
|
|
|
class TestTraceExport(TraceIntelPTTestCaseBase):
|
|
def testErrorMessages(self):
|
|
ctf_test_file = self.getBuildArtifact("ctf-test.json")
|
|
# We first check the output when there are no targets
|
|
self.expect(
|
|
f"thread trace export ctf --file {ctf_test_file}",
|
|
substrs=[
|
|
"error: invalid target, create a target using the 'target create' command"
|
|
],
|
|
error=True,
|
|
)
|
|
|
|
# We now check the output when there's a non-running target
|
|
self.expect(
|
|
"target create "
|
|
+ os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
|
|
)
|
|
|
|
self.expect(
|
|
f"thread trace export ctf --file {ctf_test_file}",
|
|
substrs=["error: Command requires a current process."],
|
|
error=True,
|
|
)
|
|
|
|
# Now we check the output when there's a running target without a trace
|
|
self.expect("b main")
|
|
self.expect("run")
|
|
|
|
self.expect(
|
|
f"thread trace export ctf --file {ctf_test_file}",
|
|
substrs=["error: Process is not being traced"],
|
|
error=True,
|
|
)
|
|
|
|
def _testHtrBasicSuperBlockPassFullCheck(self):
|
|
"""
|
|
Test the BasicSuperBlock pass of HTR.
|
|
|
|
This test uses a very small trace so that the expected output is digestible and
|
|
it's possible to manually verify the behavior of the algorithm.
|
|
|
|
This test exhaustively checks that each entry
|
|
in the output JSON is equal to the expected value.
|
|
|
|
"""
|
|
|
|
self.expect(
|
|
"trace load -v "
|
|
+ os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
|
|
substrs=["intel-pt"],
|
|
)
|
|
|
|
ctf_test_file = self.getBuildArtifact("ctf-test.json")
|
|
|
|
self.expect(f"thread trace export ctf --file {ctf_test_file}")
|
|
self.assertTrue(os.path.exists(ctf_test_file))
|
|
|
|
with open(ctf_test_file) as f:
|
|
data = json.load(f)
|
|
|
|
"""
|
|
The expected JSON contained by "ctf-test.json"
|
|
|
|
dur: number of instructions in the block
|
|
|
|
name: load address of the first instruction of the block and the
|
|
name of the most frequently called function from the block (if applicable)
|
|
|
|
ph: 'X' for Complete events (see link to documentation below)
|
|
|
|
pid: the ID of the HTR layer the blocks belong to
|
|
|
|
ts: offset from the beginning of the trace for the first instruction in the block
|
|
|
|
See https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.j75x71ritcoy
|
|
for documentation on the Trace Event Format
|
|
"""
|
|
# Comments on the right indicate if a block is a "head" and/or "tail"
|
|
# See BasicSuperBlockMerge in TraceHTR.h for a description of the algorithm
|
|
expected = [
|
|
{"dur": 1, "name": "0x400511", "ph": "X", "pid": 0, "ts": 0},
|
|
{"dur": 1, "name": "0x400518", "ph": "X", "pid": 0, "ts": 1},
|
|
{"dur": 1, "name": "0x40051f", "ph": "X", "pid": 0, "ts": 2},
|
|
{"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 3}, # head
|
|
{"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 4}, # tail
|
|
{"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 5},
|
|
{"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 6},
|
|
{"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 7}, # head
|
|
{"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 8}, # tail
|
|
{"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 9},
|
|
{"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 10},
|
|
{"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 11}, # head
|
|
{"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 12}, # tail
|
|
{"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 13},
|
|
{"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 14},
|
|
{"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 15}, # head
|
|
{"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 16}, # tail
|
|
{"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 17},
|
|
{"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 18},
|
|
{"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 19}, # head
|
|
{"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 20}, # tail
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 3}},
|
|
"dur": 3,
|
|
"name": "0x400511",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 0,
|
|
},
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400529",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 3,
|
|
}, # head, tail
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400521",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 5,
|
|
},
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400529",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 7,
|
|
}, # head, tail
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400521",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 9,
|
|
},
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400529",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 11,
|
|
}, # head, tail
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400521",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 13,
|
|
},
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400529",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 15,
|
|
}, # head, tail
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400521",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 17,
|
|
},
|
|
{
|
|
"args": {"Metadata": {"Functions": [], "Number of Instructions": 2}},
|
|
"dur": 2,
|
|
"name": "0x400529",
|
|
"ph": "X",
|
|
"pid": 1,
|
|
"ts": 19,
|
|
}, # head, tail
|
|
]
|
|
|
|
# Check that the length of the expected JSON array is equal to the actual
|
|
self.assertEqual(len(data), len(expected))
|
|
for i in range(len(data)):
|
|
# Check each individual JSON object in "ctf-test.json" against the expected value above
|
|
self.assertEqual(data[i], expected[i])
|
|
|
|
def _testHtrBasicSuperBlockPassSequenceCheck(self):
|
|
"""
|
|
Test the BasicSuperBlock pass of HTR.
|
|
|
|
This test exports a modest sized trace and only checks that a particular sequence of blocks are
|
|
expected, see `testHtrBasicSuperBlockPassFullCheck` for a more "exhaustive" test.
|
|
|
|
TODO: Once the "trace save" command is implemented, gather Intel PT
|
|
trace of this program and load it like the other tests instead of
|
|
manually executing the commands to trace the program.
|
|
"""
|
|
self.expect(
|
|
f"target create {os.path.join(self.getSourceDir(), 'intelpt-trace', 'export_ctf_test_program.out')}"
|
|
)
|
|
self.expect("b main")
|
|
self.expect("r")
|
|
self.expect("b exit")
|
|
self.expect("thread trace start")
|
|
self.expect("c")
|
|
|
|
ctf_test_file = self.getBuildArtifact("ctf-test.json")
|
|
|
|
self.expect(f"thread trace export ctf --file {ctf_test_file}")
|
|
self.assertTrue(os.path.exists(ctf_test_file))
|
|
|
|
with open(ctf_test_file) as f:
|
|
data = json.load(f)
|
|
|
|
num_units_by_layer = defaultdict(int)
|
|
index_of_first_layer_1_block = None
|
|
for i, event in enumerate(data):
|
|
layer_id = event.get("pid")
|
|
self.assertTrue(layer_id is not None)
|
|
if layer_id == 1 and index_of_first_layer_1_block is None:
|
|
index_of_first_layer_1_block = i
|
|
num_units_by_layer[layer_id] += 1
|
|
|
|
# Check that there are only two layers and that the layer IDs are correct
|
|
# Check that layer IDs are correct
|
|
self.assertTrue(
|
|
len(num_units_by_layer) == 2
|
|
and 0 in num_units_by_layer
|
|
and 1 in num_units_by_layer
|
|
)
|
|
|
|
# The expected block names for the first 7 blocks of layer 1
|
|
expected_block_names = [
|
|
"0x4005f0",
|
|
"0x4005fe",
|
|
"0x400606: iterative_handle_request_by_id(int, int)",
|
|
"0x4005a7",
|
|
"0x4005af",
|
|
"0x4005b9: fast_handle_request(int)",
|
|
"0x4005d5: log_response(int)",
|
|
]
|
|
|
|
data_index = index_of_first_layer_1_block
|
|
for i in range(len(expected_block_names)):
|
|
self.assertEqual(data[data_index + i]["name"], expected_block_names[i])
|