xref: /llvm-project/lldb/examples/python/disasm-stress-test.py (revision fd35a92300a00edaf56ae94176317390677569a4)
1#!/usr/bin/env python
2
3import argparse
4import datetime
5import re
6import subprocess
7import sys
8import time
9
10parser = argparse.ArgumentParser(
11    description="Run an exhaustive test of the LLDB disassembler for a specific architecture."
12)
13
14parser.add_argument(
15    "--arch",
16    required=True,
17    action="store",
18    help="The architecture whose disassembler is to be tested",
19)
20parser.add_argument(
21    "--bytes",
22    required=True,
23    action="store",
24    type=int,
25    help="The byte width of instructions for that architecture",
26)
27parser.add_argument(
28    "--random",
29    required=False,
30    action="store_true",
31    help="Enables non-sequential testing",
32)
33parser.add_argument(
34    "--start",
35    required=False,
36    action="store",
37    type=int,
38    help="The first instruction value to test",
39)
40parser.add_argument(
41    "--skip",
42    required=False,
43    action="store",
44    type=int,
45    help="The interval between instructions to test",
46)
47parser.add_argument(
48    "--log",
49    required=False,
50    action="store",
51    help="A log file to write the most recent instruction being tested",
52)
53parser.add_argument(
54    "--time",
55    required=False,
56    action="store_true",
57    help="Every 100,000 instructions, print an ETA to standard out",
58)
59parser.add_argument(
60    "--lldb",
61    required=False,
62    action="store",
63    help="The path to LLDB.framework, if LLDB should be overridden",
64)
65
66arguments = sys.argv[1:]
67
68arg_ns = parser.parse_args(arguments)
69
70
71def AddLLDBToSysPathOnMacOSX():
72    def GetLLDBFrameworkPath():
73        lldb_path = subprocess.check_output(["xcrun", "-find", "lldb"])
74        re_result = re.match("(.*)/Developer/usr/bin/lldb", lldb_path)
75        if re_result is None:
76            return None
77        xcode_contents_path = re_result.group(1)
78        return xcode_contents_path + "/SharedFrameworks/LLDB.framework"
79
80    lldb_framework_path = GetLLDBFrameworkPath()
81
82    if lldb_framework_path is None:
83        print("Couldn't find LLDB.framework")
84        sys.exit(-1)
85
86    sys.path.append(lldb_framework_path + "/Resources/Python")
87
88
89if arg_ns.lldb is None:
90    AddLLDBToSysPathOnMacOSX()
91else:
92    sys.path.append(arg_ns.lldb + "/Resources/Python")
93
94import lldb
95
96debugger = lldb.SBDebugger.Create()
97
98if not debugger.IsValid():
99    print("Couldn't create an SBDebugger")
100    sys.exit(-1)
101
102target = debugger.CreateTargetWithFileAndArch(None, arg_ns.arch)
103
104if not target.IsValid():
105    print("Couldn't create an SBTarget for architecture " + arg_ns.arch)
106    sys.exit(-1)
107
108
109def ResetLogFile(log_file):
110    if log_file != sys.stdout:
111        log_file.seek(0)
112
113
114def PrintByteArray(log_file, byte_array):
115    for byte in byte_array:
116        print(hex(byte) + " ", end=" ", file=log_file)
117    print(file=log_file)
118
119
120class SequentialInstructionProvider:
121    def __init__(self, byte_width, log_file, start=0, skip=1):
122        self.m_byte_width = byte_width
123        self.m_log_file = log_file
124        self.m_start = start
125        self.m_skip = skip
126        self.m_value = start
127        self.m_last = (1 << (byte_width * 8)) - 1
128
129    def PrintCurrentState(self, ret):
130        ResetLogFile(self.m_log_file)
131        print(self.m_value, file=self.m_log_file)
132        PrintByteArray(self.m_log_file, ret)
133
134    def GetNextInstruction(self):
135        if self.m_value > self.m_last:
136            return None
137        ret = bytearray(self.m_byte_width)
138        for i in range(self.m_byte_width):
139            ret[self.m_byte_width - (i + 1)] = (self.m_value >> (i * 8)) & 255
140        self.PrintCurrentState(ret)
141        self.m_value += self.m_skip
142        return ret
143
144    def GetNumInstructions(self):
145        return (self.m_last - self.m_start) / self.m_skip
146
147    def __iter__(self):
148        return self
149
150    def next(self):
151        ret = self.GetNextInstruction()
152        if ret is None:
153            raise StopIteration
154        return ret
155
156
157class RandomInstructionProvider:
158    def __init__(self, byte_width, log_file):
159        self.m_byte_width = byte_width
160        self.m_log_file = log_file
161        self.m_random_file = open("/dev/random", "r")
162
163    def PrintCurrentState(self, ret):
164        ResetLogFile(self.m_log_file)
165        PrintByteArray(self.m_log_file, ret)
166
167    def GetNextInstruction(self):
168        ret = bytearray(self.m_byte_width)
169        for i in range(self.m_byte_width):
170            ret[i] = self.m_random_file.read(1)
171        self.PrintCurrentState(ret)
172        return ret
173
174    def __iter__(self):
175        return self
176
177    def next(self):
178        ret = self.GetNextInstruction()
179        if ret is None:
180            raise StopIteration
181        return ret
182
183
184log_file = None
185
186
187def GetProviderWithArguments(args):
188    global log_file
189    if args.log is not None:
190        log_file = open(args.log, "w")
191    else:
192        log_file = sys.stdout
193    instruction_provider = None
194    if args.random:
195        instruction_provider = RandomInstructionProvider(args.bytes, log_file)
196    else:
197        start = 0
198        skip = 1
199        if args.start is not None:
200            start = args.start
201        if args.skip is not None:
202            skip = args.skip
203        instruction_provider = SequentialInstructionProvider(
204            args.bytes, log_file, start, skip
205        )
206    return instruction_provider
207
208
209instruction_provider = GetProviderWithArguments(arg_ns)
210
211fake_address = lldb.SBAddress()
212
213actually_time = arg_ns.time and not arg_ns.random
214
215if actually_time:
216    num_instructions_logged = 0
217    total_num_instructions = instruction_provider.GetNumInstructions()
218    start_time = time.time()
219
220for inst_bytes in instruction_provider:
221    if actually_time:
222        if (num_instructions_logged != 0) and (num_instructions_logged % 100000 == 0):
223            curr_time = time.time()
224            elapsed_time = curr_time - start_time
225            remaining_time = float(total_num_instructions - num_instructions_logged) * (
226                float(elapsed_time) / float(num_instructions_logged)
227            )
228            print(str(datetime.timedelta(seconds=remaining_time)))
229        num_instructions_logged = num_instructions_logged + 1
230    inst_list = target.GetInstructions(fake_address, inst_bytes)
231    if not inst_list.IsValid():
232        print("Invalid instruction list", file=log_file)
233        continue
234    inst = inst_list.GetInstructionAtIndex(0)
235    if not inst.IsValid():
236        print("Invalid instruction", file=log_file)
237        continue
238    instr_output_stream = lldb.SBStream()
239    inst.GetDescription(instr_output_stream)
240    print(instr_output_stream.GetData(), file=log_file)
241