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