| 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.assertIsNotNone(layer_id) | |
| 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]) |