Chromium Code Reviews| 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 |