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