1from collections import defaultdict 2import lldb 3import json 4from intelpt_testcase import * 5from lldbsuite.test.lldbtest import * 6from lldbsuite.test import lldbutil 7from lldbsuite.test.decorators import * 8import os 9 10class TestTraceExport(TraceIntelPTTestCaseBase): 11 12 mydir = TestBase.compute_mydir(__file__) 13 14 def testErrorMessages(self): 15 ctf_test_file = self.getBuildArtifact("ctf-test.json") 16 # We first check the output when there are no targets 17 self.expect(f"thread trace export ctf --file {ctf_test_file}", 18 substrs=["error: invalid target, create a target using the 'target create' command"], 19 error=True) 20 21 # We now check the output when there's a non-running target 22 self.expect("target create " + 23 os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) 24 25 self.expect(f"thread trace export ctf --file {ctf_test_file}", 26 substrs=["error: invalid process"], 27 error=True) 28 29 # Now we check the output when there's a running target without a trace 30 self.expect("b main") 31 self.expect("run") 32 33 self.expect(f"thread trace export ctf --file {ctf_test_file}", 34 substrs=["error: Process is not being traced"], 35 error=True) 36 37 38 def testHtrBasicSuperBlockPassFullCheck(self): 39 ''' 40 Test the BasicSuperBlock pass of HTR. 41 42 This test uses a very small trace so that the expected output is digestible and 43 it's possible to manually verify the behavior of the algorithm. 44 45 This test exhaustively checks that each entry 46 in the output JSON is equal to the expected value. 47 48 ''' 49 50 self.expect("trace load -v " + 51 os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), 52 substrs=["intel-pt"]) 53 54 ctf_test_file = self.getBuildArtifact("ctf-test.json") 55 56 self.expect(f"thread trace export ctf --file {ctf_test_file}") 57 self.assertTrue(os.path.exists(ctf_test_file)) 58 59 with open(ctf_test_file) as f: 60 data = json.load(f) 61 62 ''' 63 The expected JSON contained by "ctf-test.json" 64 65 dur: number of instructions in the block 66 67 name: load address of the first instruction of the block and the 68 name of the most frequently called function from the block (if applicable) 69 70 ph: 'X' for Complete events (see link to documentation below) 71 72 pid: the ID of the HTR layer the blocks belong to 73 74 ts: offset from the beginning of the trace for the first instruction in the block 75 76 See https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.j75x71ritcoy 77 for documentation on the Trace Event Format 78 ''' 79 # Comments on the right indicate if a block is a "head" and/or "tail" 80 # See BasicSuperBlockMerge in TraceHTR.h for a description of the algorithm 81 expected = [ 82 {"dur":1,"name":"0x400511","ph":"X","pid":0,"ts":0}, 83 {"dur":1,"name":"0x400518","ph":"X","pid":0,"ts":1}, 84 {"dur":1,"name":"0x40051f","ph":"X","pid":0,"ts":2}, 85 {"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":3}, # head 86 {"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":4}, # tail 87 {"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":5}, 88 {"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":6}, 89 {"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":7}, # head 90 {"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":8}, # tail 91 {"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":9}, 92 {"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":10}, 93 {"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":11}, # head 94 {"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":12}, # tail 95 {"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":13}, 96 {"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":14}, 97 {"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":15}, # head 98 {"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":16}, # tail 99 {"dur":1,"name":"0x400521","ph":"X","pid":0,"ts":17}, 100 {"dur":1,"name":"0x400525","ph":"X","pid":0,"ts":18}, 101 {"dur":1,"name":"0x400529","ph":"X","pid":0,"ts":19}, # head 102 {"dur":1,"name":"0x40052d","ph":"X","pid":0,"ts":20}, # tail 103 {"args":{"Metadata":{"Functions":[],"Number of Instructions":3}},"dur":3,"name":"0x400511","ph":"X","pid":1,"ts":0}, 104 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":3}, # head, tail 105 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":5}, 106 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":7}, # head, tail 107 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":9}, 108 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":11}, # head, tail 109 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":13}, 110 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":15}, # head, tail 111 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400521","ph":"X","pid":1,"ts":17}, 112 {"args":{"Metadata":{"Functions":[],"Number of Instructions":2}},"dur":2,"name":"0x400529","ph":"X","pid":1,"ts":19} # head, tail 113 ] 114 115 # Check that the length of the expected JSON array is equal to the actual 116 self.assertTrue(len(data) == len(expected)) 117 for i in range(len(data)): 118 # Check each individual JSON object in "ctf-test.json" against the expected value above 119 self.assertTrue(data[i] == expected[i]) 120 121 def testHtrBasicSuperBlockPassSequenceCheck(self): 122 ''' 123 Test the BasicSuperBlock pass of HTR. 124 125 This test exports a modest sized trace and only checks that a particular sequence of blocks are 126 expected, see `testHtrBasicSuperBlockPassFullCheck` for a more "exhaustive" test. 127 128 TODO: Once the "trace save" command is implemented, gather Intel PT 129 trace of this program and load it like the other tests instead of 130 manually executing the commands to trace the program. 131 ''' 132 self.expect(f"target create {os.path.join(self.getSourceDir(), 'intelpt-trace', 'export_ctf_test_program.out')}") 133 self.expect("b main") 134 self.expect("r") 135 self.expect("b exit") 136 self.expect("thread trace start") 137 self.expect("c") 138 139 ctf_test_file = self.getBuildArtifact("ctf-test.json") 140 141 self.expect(f"thread trace export ctf --file {ctf_test_file}") 142 self.assertTrue(os.path.exists(ctf_test_file)) 143 144 145 with open(ctf_test_file) as f: 146 data = json.load(f) 147 148 num_units_by_layer = defaultdict(int) 149 index_of_first_layer_1_block = None 150 for i, event in enumerate(data): 151 layer_id = event.get('pid') 152 self.assertTrue(layer_id is not None) 153 if layer_id == 1 and index_of_first_layer_1_block is None: 154 index_of_first_layer_1_block = i 155 num_units_by_layer[layer_id] += 1 156 157 # Check that there are only two layers and that the layer IDs are correct 158 # Check that layer IDs are correct 159 self.assertTrue(len(num_units_by_layer) == 2 and 0 in num_units_by_layer and 1 in num_units_by_layer) 160 161 # The expected block names for the first 7 blocks of layer 1 162 expected_block_names = [ 163 '0x4005f0', 164 '0x4005fe', 165 '0x400606: iterative_handle_request_by_id(int, int)', 166 '0x4005a7', 167 '0x4005af', 168 '0x4005b9: fast_handle_request(int)', 169 '0x4005d5: log_response(int)', 170 ] 171 172 data_index = index_of_first_layer_1_block 173 for i in range(len(expected_block_names)): 174 self.assertTrue(data[data_index + i]['name'] == expected_block_names[i]) 175 176