OLD | NEW |
---|---|
(Empty) | |
1 # Copyright (c) 2012 The Native Client Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 import copy | |
6 import re | |
7 | |
8 import asm | |
9 import utils | |
10 import val_runner | |
11 | |
12 | |
13 class Instruction(object): | |
14 __slots__ = [ | |
15 'offset', | |
16 'asm', | |
17 'hex', | |
18 'data', | |
19 'outs', # {validator: [message, ...], ...} | |
20 ] | |
21 | |
22 def __init__(self): | |
23 self.offset = None | |
24 self.hex = None | |
25 self.asm = None | |
26 self.outs = dict((v, []) for v in val_runner.VALIDATORS) | |
27 | |
28 @property | |
29 def size(self): | |
30 return len(self.data) | |
31 | |
32 @property | |
33 def end_offset(self): | |
34 return self.offset + self.size | |
35 | |
36 def CheckAsm(self, bits): | |
37 if self.asm is None: | |
38 return | |
39 asm_data = asm.Assemble(bits, self.asm) | |
40 assert self.data == asm_data, (utils.DataToReadableHex(asm_data), | |
41 utils.DataToReadableHex(self.data)) | |
42 | |
43 def __repr__(self): | |
44 return 'Instr(%s)@0x%x' % (self.asm or self.hex, self.offset) | |
45 | |
46 _out_suffix = '_out' | |
47 | |
48 @staticmethod | |
49 def Parse(bits, lines): | |
50 instr = Instruction() | |
51 for line in lines: | |
52 assert ':' in line, "can't parse line '%s" % line | |
Mark Seaborn
2012/09/10 19:38:06
Nit: missing closing '
Vlad Shcherbina
2012/09/11 14:26:00
Done.
| |
53 field, value = line.split(':', 1) | |
54 field = field.strip() | |
55 | |
56 if field.endswith(Instruction._out_suffix): | |
57 validator = field[:-len(Instruction._out_suffix)] | |
58 instr.outs[validator].append(value.strip()) | |
59 else: | |
60 assert getattr(instr, field) is None, 'field %s is already set' % field | |
61 setattr(instr, field, value.strip()) | |
62 | |
63 if instr.hex is not None: | |
64 instr.data = utils.ReadableHexToData(instr.hex) | |
65 else: | |
66 instr.data = asm.Assemble(bits, instr.asm) | |
67 instr.hex = utils.DataToReadableHex(instr.data) | |
68 return instr | |
69 | |
70 def ToLines(self, offset=None): | |
71 lines = [] | |
72 if self.offset != offset: | |
73 lines.append('offset: %s' % self.offset) | |
74 if self.asm is not None: | |
75 lines.append('asm: %s' % self.asm) | |
76 if self.hex is not None: | |
77 lines.append('hex: %s' % self.hex) | |
78 | |
79 for validator in val_runner.VALIDATORS: | |
80 for msg in self.outs[validator]: | |
81 lines.append('%s%s: %s' % (validator, Instruction._out_suffix, msg)) | |
82 | |
83 return lines | |
84 | |
85 | |
86 class Test(object): | |
87 __slots__ = [ | |
88 'bits', | |
89 'separators', | |
90 'sections', | |
91 'instructions', | |
92 'safe', | |
93 ] | |
94 | |
95 # Each separator/section is a list of strings. | |
96 # Number of separators always exceeds number of sections by one, because | |
97 # they interleave, starting and finishing with (possibly empty) separator. | |
98 | |
99 @staticmethod | |
100 def Parse(lines): | |
101 test = Test() | |
102 test.safe = None | |
103 test.separators = [[]] | |
104 test.sections = [] | |
105 | |
106 # Parser state: whether we are currently parsing | |
107 # separator (comments/whitelines) or section. | |
108 in_section = False | |
109 | |
110 for line in lines: | |
111 line = line.strip() | |
112 is_sep = line == '' or line.startswith('#') | |
113 | |
114 if is_sep: | |
115 if in_section: | |
116 test.separators.append([line]) | |
117 else: | |
118 test.separators[-1].append(line) | |
119 else: | |
120 if in_section: | |
121 test.sections[-1].append(line) | |
122 else: | |
123 test.sections.append([line]) | |
124 | |
125 in_section = not is_sep | |
126 | |
127 if in_section: | |
128 test.separators.append([]) | |
129 | |
130 assert len(test.separators) == len(test.sections) + 1 | |
131 | |
132 # header section is required; it specifies BITS and OUTCOME | |
133 assert len(test.sections) >= 1 | |
134 | |
135 for line in test.sections[0]: | |
136 m = re.match(r'(.*):\s*(.*)$', line) | |
137 field, value = m.groups() | |
138 if field == 'BITS': | |
139 test.bits = int(value) | |
140 elif field == 'OUTCOME': | |
141 assert value in ['valid', 'invalid'] | |
142 test.safe = value == 'valid' | |
143 else: | |
144 assert False, 'Unrecognized field %s in special section' % field | |
145 | |
146 test.instructions = [] | |
147 offset = 0 | |
148 for section in test.sections[1:]: | |
149 instr = Instruction.Parse(test.bits, section) | |
150 | |
151 if instr.hex is None: | |
152 code = asm.Assemble(test.bits, instr.asm) | |
153 instr.hex = utils.DataToReadableHex(code) | |
154 | |
155 if instr.offset is not None: | |
156 instr.offset = int(instr.offset) | |
157 else: | |
158 instr.offset = offset | |
159 test.instructions.append(instr) | |
160 offset = instr.end_offset | |
161 | |
162 return test | |
163 | |
164 def Print(self, fout): | |
165 self.sections[0] = ['BITS: %s' % self.bits] | |
166 if self.safe is not None: | |
167 self.sections[0].append( | |
168 'OUTCOME: valid' if self.safe else 'OUTCOME: invalid') | |
169 | |
170 offset = 0 | |
171 for i, instr in enumerate(self.instructions): | |
172 self.sections[i+1] = instr.ToLines(offset) | |
173 offset = instr.end_offset | |
174 | |
175 assert len(self.separators) == len(self.sections) + 1 | |
176 groups = [] | |
177 for sep, sec in zip(self.separators, self.sections): | |
178 groups.append(sep) | |
179 groups.append(sec) | |
180 groups.append(self.separators[-1]) | |
181 | |
182 for group in groups: | |
183 for line in group: | |
184 fout.write('%s\n' % line) | |
185 | |
186 def PrepareCode(self): | |
187 code_size = max(i.end_offset for i in self.instructions) | |
188 code_size = ((code_size - 1) // 32 + 1) * 32 | |
189 code = ['\x90'] * code_size | |
190 | |
191 for i in self.instructions: | |
192 code[i.offset : i.end_offset] = list(i.data) | |
193 | |
194 return ''.join(code) | |
195 | |
196 def RunValidator(self, validator): | |
197 assert validator in val_runner.VALIDATORS | |
198 if validator == 'nc': | |
199 return val_runner.RunValidator(validator, self.bits, self.PrepareCode()) | |
200 | |
201 test2 = copy.deepcopy(self) | |
202 | |
203 errors = set() | |
204 safe = True | |
205 | |
206 while True: | |
207 res = val_runner.RunValidator(validator, test2.bits, test2.PrepareCode()) | |
208 safe = safe and res.safe | |
209 errors.update(res.errors) | |
210 | |
211 patched = False | |
212 for loc, msg in res.errors: | |
213 if msg == 'DFA error in validator': | |
214 for i in test2.instructions: | |
215 if i.offset == loc: | |
216 nops = '\x90'*i.size | |
Mark Seaborn
2012/09/10 19:38:06
Nit: put spaces around '*'. Also comment that you
Vlad Shcherbina
2012/09/11 14:26:00
I think the name of the variable suggests it by it
| |
217 if i.data != nops: | |
218 i.data = nops | |
219 patched = True | |
220 if not patched: | |
221 break | |
222 | |
223 return val_runner.ValidationResults(safe=safe, errors=sorted(errors)) | |
224 | |
225 def ExpectedErrors(self, validator): | |
226 errors = [] | |
227 for i in self.instructions: | |
228 for msg in i.outs[validator]: | |
229 # Check if message has the form | |
230 # [at +<delta>] <error message> | |
231 m = re.match(r'\[at \+(\d+)\]\s(.*)$', msg) | |
232 if m is not None: | |
233 delta = int(m.group(1)) | |
234 msg = m.group(2) | |
235 else: | |
236 delta = 0 | |
237 errors.append((i.offset + delta, msg)) | |
238 return errors | |
OLD | NEW |