| 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): |
| |
| mydir = TestBase.compute_mydir(__file__) |
| |
| 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: invalid 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.assertTrue(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.assertTrue(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.assertTrue(data[data_index + i]['name'] == expected_block_names[i]) |
| |