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