2 # Copyright (c) 2013 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
7 Generate all acceptable regular instructions by traversing validator DFA
8 and run check them against RDFA decoder and text-based specification.
12 import multiprocessing
32 def IsRexPrefix(byte):
33 return 0x40 <= byte < 0x50
36 def PadToBundleSize(bytes):
37 assert len(bytes) <= validator.BUNDLE_SIZE
38 return bytes + [NOP] * (validator.BUNDLE_SIZE - len(bytes))
41 def ConditionToRestrictedRegisterNumber(condition):
42 restricted, restricted_instead_of_sandboxed = condition.GetAlteredRegisters()
43 if restricted is not None:
44 assert restricted_instead_of_sandboxed is None
45 return validator.REGISTER_BY_NAME[restricted]
46 elif restricted_instead_of_sandboxed is not None:
47 return validator.REGISTER_BY_NAME[restricted_instead_of_sandboxed]
52 def RestrictedRegisterNumberToCondition(rr):
53 assert rr is None or rr in validator.ALL_REGISTERS, rr
55 return spec.Condition()
56 elif rr == validator.NC_REG_RBP:
57 return spec.Condition(restricted_instead_of_sandboxed='%rbp')
58 elif rr == validator.NC_REG_RSP:
59 return spec.Condition(restricted_instead_of_sandboxed='%rsp')
61 return spec.Condition(validator.REGISTER_NAMES[rr])
64 def ValidateAndGetPostcondition(
65 bundle, actual_size, precondition, validator_inst):
66 """Validate single x86-64 instruction and get postcondition for it.
69 bundle: sequence of bytes (as python string)
70 actual_size: size of the instruction in the beginning of the bundle
71 (remaining bytes are expected to be NOPS)
72 precondition: instance of spec.Condition representing precondition validator
74 validator_inst: implementation of validator.
76 Pair (validation_result, postcondition).
77 Validation_result is True or False. When validation_result is True,
78 postcondition is spec.Condition instance representing postcondition
79 for this instruction established by validator.
82 valid, final_rr = validator_inst.ValidateAndGetFinalRestrictedRegister(
85 initial_rr=ConditionToRestrictedRegisterNumber(precondition))
88 return True, RestrictedRegisterNumberToCondition(final_rr)
93 def CheckValid64bitInstruction(
99 bundle = ''.join(map(chr, PadToBundleSize(instruction)))
100 for conflicting_precondition in spec.Condition.All():
101 if conflicting_precondition.Implies(precondition):
102 continue # not conflicting
103 result, _ = ValidateAndGetPostcondition(
104 bundle, len(instruction), conflicting_precondition, validator_inst)
106 'validator incorrectly accepted %s with precondition %s, '
107 'while specification requires precondition %s'
108 % (dis, conflicting_precondition, precondition))
110 result, actual_postcondition = ValidateAndGetPostcondition(
111 bundle, len(instruction), precondition, validator_inst)
113 print 'warning: validator rejected %s with precondition %s' % (
116 # We are checking for implication, not for equality, because in some cases
117 # specification computes postcondition with better precision.
118 # For example, xchg with memory is not treated as zero-extending
119 # instruction, so validator thinks that postcondition of
121 # is Condition(default), while in fact it's Condition(%rax is restricted).
122 # (https://code.google.com/p/nativeclient/issues/detail?id=3071)
123 # TODO(shcherbina): change it to equality test when such cases
125 assert postcondition.Implies(actual_postcondition), (
126 'validator incorrectly determined postcondition %s '
127 'for %s where specification predicted %s'
128 % (actual_postcondition, dis, postcondition))
129 if postcondition != actual_postcondition:
131 'warning: validator reported too broad postcondition %s for %s, '
132 'where specification predicted %s'
133 % (actual_postcondition, dis, postcondition))
136 def CheckInvalid64bitInstruction(
141 bundle = ''.join(map(chr, PadToBundleSize(instruction)))
142 for precondition in spec.Condition.All():
143 result, _ = ValidateAndGetPostcondition(
144 bundle, len(instruction), precondition, validator_inst)
146 'validator incorrectly accepted %s with precondition %s, '
147 'while specification rejected because %s'
148 % (dis, precondition, sandboxing_error))
151 def ValidateInstruction(instruction, validator_inst):
152 dis = validator_inst.DisassembleChunk(
153 ''.join(map(chr, instruction)),
154 bitness=options.bitness)
156 # Objdump 2.24 (and consequently our decoder) displays fwait with rex prefix
157 # in the following way (note the rex byte is extraneous here):
160 # We manually convert it to
162 # for the purpose of validation.
163 # TODO(shyamsundarr): investigate whether we can get rid of this special
164 # handling. Also https://code.google.com/p/nativeclient/issues/detail?id=3496.
165 if (len(instruction) == 2 and
166 IsRexPrefix(instruction[0]) and
167 instruction[1] == FWAIT):
168 assert len(dis) == 2, (instruction, dis)
169 assert dis[1].disasm == 'fwait', (instruction, dis)
170 dis[0] = objdump_parser.Instruction(
171 address=dis[0].address, bytes=(dis[0].bytes + dis[1].bytes),
172 disasm=dis[1].disasm)
175 assert len(dis) == 1, (instruction, dis)
177 assert dis.bytes == instruction, (instruction, dis)
179 if options.bitness == 32:
180 result = validator_inst.ValidateChunk(
181 ''.join(map(chr, PadToBundleSize(instruction))),
185 spec.ValidateDirectJumpOrRegularInstruction(dis, bitness=32)
187 print 'warning: validator rejected', dis
189 except spec.SandboxingError as e:
191 print 'validator accepted instruction disallowed by specification'
193 except spec.DoNotMatchError:
195 print 'validator accepted instruction not recognized by specification'
202 _, precondition, postcondition = (
203 spec.ValidateDirectJumpOrRegularInstruction(dis, bitness=64))
205 CheckValid64bitInstruction(instruction,
211 except spec.SandboxingError as e:
212 CheckInvalid64bitInstruction(instruction,
216 except spec.DoNotMatchError as e:
217 CheckInvalid64bitInstruction(
221 sandboxing_error=spec.SandboxingError(
222 'unrecognized instruction %s' % e))
227 class WorkerState(object):
228 def __init__(self, prefix, validator_inst):
229 self.total_instructions = 0
231 self.validator_inst = validator_inst
233 def ReceiveInstruction(self, bytes):
234 self.total_instructions += 1
235 self.num_valid += ValidateInstruction(bytes, self.validator_inst)
238 def Worker((prefix, state_index)):
239 worker_state = WorkerState(prefix, worker_validator)
242 dfa_traversal.TraverseTree(
243 dfa.states[state_index],
244 final_callback=worker_state.ReceiveInstruction,
247 except Exception as e:
248 traceback.print_exc() # because multiprocessing imap swallows traceback
253 worker_state.total_instructions,
254 worker_state.num_valid)
258 parser = optparse.OptionParser(usage='%prog [options] xmlfile')
260 parser.add_option('--bitness',
262 help='The subarchitecture: 32 or 64')
263 parser.add_option('--validator_dll',
264 help='Path to librdfa_validator_dll')
265 parser.add_option('--decoder_dll',
266 help='Path to librdfa_decoder_dll')
268 options, args = parser.parse_args()
270 if options.bitness not in [32, 64]:
271 parser.error('specify -b 32 or -b 64')
274 parser.error('specify one xml file')
278 return options, xml_file
281 options, xml_file = ParseOptions()
282 # We are doing it here to share state graph between workers spawned by
283 # multiprocess. Passing it every time is slow.
284 dfa = dfa_parser.ParseXml(xml_file)
285 worker_validator = validator.Validator(
286 validator_dll=options.validator_dll,
287 decoder_dll=options.decoder_dll)
291 assert dfa.initial_state.is_accepting
292 assert not dfa.initial_state.any_byte
294 print len(dfa.states), 'states'
296 num_suffixes = dfa_traversal.GetNumSuffixes(dfa.initial_state)
298 # We can't just write 'num_suffixes[dfa.initial_state]' because
299 # initial state is accepting.
300 total_instructions = sum(
301 num_suffixes[t.to_state]
302 for t in dfa.initial_state.forward_transitions.values())
303 print total_instructions, 'regular instructions total'
305 tasks = dfa_traversal.CreateTraversalTasks(dfa.states, dfa.initial_state)
306 print len(tasks), 'tasks'
308 pool = multiprocessing.Pool()
310 results = pool.imap(Worker, tasks)
314 for prefix, count, valid_count in results:
315 print ', '.join(map(hex, prefix))
317 num_valid += valid_count
319 print total, 'instructions were processed'
320 print num_valid, 'valid instructions'
323 if __name__ == '__main__':