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 10 11class TestTraceExport(TraceIntelPTTestCaseBase): 12 def testErrorMessages(self): 13 ctf_test_file = self.getBuildArtifact("ctf-test.json") 14 # We first check the output when there are no targets 15 self.expect( 16 f"thread trace export ctf --file {ctf_test_file}", 17 substrs=[ 18 "error: invalid target, create a target using the 'target create' command" 19 ], 20 error=True, 21 ) 22 23 # We now check the output when there's a non-running target 24 self.expect( 25 "target create " 26 + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out") 27 ) 28 29 self.expect( 30 f"thread trace export ctf --file {ctf_test_file}", 31 substrs=["error: Command requires a current process."], 32 error=True, 33 ) 34 35 # Now we check the output when there's a running target without a trace 36 self.expect("b main") 37 self.expect("run") 38 39 self.expect( 40 f"thread trace export ctf --file {ctf_test_file}", 41 substrs=["error: Process is not being traced"], 42 error=True, 43 ) 44 45 def _testHtrBasicSuperBlockPassFullCheck(self): 46 """ 47 Test the BasicSuperBlock pass of HTR. 48 49 This test uses a very small trace so that the expected output is digestible and 50 it's possible to manually verify the behavior of the algorithm. 51 52 This test exhaustively checks that each entry 53 in the output JSON is equal to the expected value. 54 55 """ 56 57 self.expect( 58 "trace load -v " 59 + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), 60 substrs=["intel-pt"], 61 ) 62 63 ctf_test_file = self.getBuildArtifact("ctf-test.json") 64 65 self.expect(f"thread trace export ctf --file {ctf_test_file}") 66 self.assertTrue(os.path.exists(ctf_test_file)) 67 68 with open(ctf_test_file) as f: 69 data = json.load(f) 70 71 """ 72 The expected JSON contained by "ctf-test.json" 73 74 dur: number of instructions in the block 75 76 name: load address of the first instruction of the block and the 77 name of the most frequently called function from the block (if applicable) 78 79 ph: 'X' for Complete events (see link to documentation below) 80 81 pid: the ID of the HTR layer the blocks belong to 82 83 ts: offset from the beginning of the trace for the first instruction in the block 84 85 See https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.j75x71ritcoy 86 for documentation on the Trace Event Format 87 """ 88 # Comments on the right indicate if a block is a "head" and/or "tail" 89 # See BasicSuperBlockMerge in TraceHTR.h for a description of the algorithm 90 expected = [ 91 {"dur": 1, "name": "0x400511", "ph": "X", "pid": 0, "ts": 0}, 92 {"dur": 1, "name": "0x400518", "ph": "X", "pid": 0, "ts": 1}, 93 {"dur": 1, "name": "0x40051f", "ph": "X", "pid": 0, "ts": 2}, 94 {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 3}, # head 95 {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 4}, # tail 96 {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 5}, 97 {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 6}, 98 {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 7}, # head 99 {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 8}, # tail 100 {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 9}, 101 {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 10}, 102 {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 11}, # head 103 {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 12}, # tail 104 {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 13}, 105 {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 14}, 106 {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 15}, # head 107 {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 16}, # tail 108 {"dur": 1, "name": "0x400521", "ph": "X", "pid": 0, "ts": 17}, 109 {"dur": 1, "name": "0x400525", "ph": "X", "pid": 0, "ts": 18}, 110 {"dur": 1, "name": "0x400529", "ph": "X", "pid": 0, "ts": 19}, # head 111 {"dur": 1, "name": "0x40052d", "ph": "X", "pid": 0, "ts": 20}, # tail 112 { 113 "args": {"Metadata": {"Functions": [], "Number of Instructions": 3}}, 114 "dur": 3, 115 "name": "0x400511", 116 "ph": "X", 117 "pid": 1, 118 "ts": 0, 119 }, 120 { 121 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 122 "dur": 2, 123 "name": "0x400529", 124 "ph": "X", 125 "pid": 1, 126 "ts": 3, 127 }, # head, tail 128 { 129 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 130 "dur": 2, 131 "name": "0x400521", 132 "ph": "X", 133 "pid": 1, 134 "ts": 5, 135 }, 136 { 137 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 138 "dur": 2, 139 "name": "0x400529", 140 "ph": "X", 141 "pid": 1, 142 "ts": 7, 143 }, # head, tail 144 { 145 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 146 "dur": 2, 147 "name": "0x400521", 148 "ph": "X", 149 "pid": 1, 150 "ts": 9, 151 }, 152 { 153 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 154 "dur": 2, 155 "name": "0x400529", 156 "ph": "X", 157 "pid": 1, 158 "ts": 11, 159 }, # head, tail 160 { 161 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 162 "dur": 2, 163 "name": "0x400521", 164 "ph": "X", 165 "pid": 1, 166 "ts": 13, 167 }, 168 { 169 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 170 "dur": 2, 171 "name": "0x400529", 172 "ph": "X", 173 "pid": 1, 174 "ts": 15, 175 }, # head, tail 176 { 177 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 178 "dur": 2, 179 "name": "0x400521", 180 "ph": "X", 181 "pid": 1, 182 "ts": 17, 183 }, 184 { 185 "args": {"Metadata": {"Functions": [], "Number of Instructions": 2}}, 186 "dur": 2, 187 "name": "0x400529", 188 "ph": "X", 189 "pid": 1, 190 "ts": 19, 191 }, # head, tail 192 ] 193 194 # Check that the length of the expected JSON array is equal to the actual 195 self.assertEqual(len(data), len(expected)) 196 for i in range(len(data)): 197 # Check each individual JSON object in "ctf-test.json" against the expected value above 198 self.assertEqual(data[i], expected[i]) 199 200 def _testHtrBasicSuperBlockPassSequenceCheck(self): 201 """ 202 Test the BasicSuperBlock pass of HTR. 203 204 This test exports a modest sized trace and only checks that a particular sequence of blocks are 205 expected, see `testHtrBasicSuperBlockPassFullCheck` for a more "exhaustive" test. 206 207 TODO: Once the "trace save" command is implemented, gather Intel PT 208 trace of this program and load it like the other tests instead of 209 manually executing the commands to trace the program. 210 """ 211 self.expect( 212 f"target create {os.path.join(self.getSourceDir(), 'intelpt-trace', 'export_ctf_test_program.out')}" 213 ) 214 self.expect("b main") 215 self.expect("r") 216 self.expect("b exit") 217 self.expect("thread trace start") 218 self.expect("c") 219 220 ctf_test_file = self.getBuildArtifact("ctf-test.json") 221 222 self.expect(f"thread trace export ctf --file {ctf_test_file}") 223 self.assertTrue(os.path.exists(ctf_test_file)) 224 225 with open(ctf_test_file) as f: 226 data = json.load(f) 227 228 num_units_by_layer = defaultdict(int) 229 index_of_first_layer_1_block = None 230 for i, event in enumerate(data): 231 layer_id = event.get("pid") 232 self.assertIsNotNone(layer_id) 233 if layer_id == 1 and index_of_first_layer_1_block is None: 234 index_of_first_layer_1_block = i 235 num_units_by_layer[layer_id] += 1 236 237 # Check that there are only two layers and that the layer IDs are correct 238 # Check that layer IDs are correct 239 self.assertTrue( 240 len(num_units_by_layer) == 2 241 and 0 in num_units_by_layer 242 and 1 in num_units_by_layer 243 ) 244 245 # The expected block names for the first 7 blocks of layer 1 246 expected_block_names = [ 247 "0x4005f0", 248 "0x4005fe", 249 "0x400606: iterative_handle_request_by_id(int, int)", 250 "0x4005a7", 251 "0x4005af", 252 "0x4005b9: fast_handle_request(int)", 253 "0x4005d5: log_response(int)", 254 ] 255 256 data_index = index_of_first_layer_1_block 257 for i in range(len(expected_block_names)): 258 self.assertEqual(data[data_index + i]["name"], expected_block_names[i]) 259