import logging from ScEpTIC import tools from ScEpTIC.AST.elements.instructions.other_operations import CompareOperation, PhiOperation, SelectOperation, CallOperation, VaArgOperation from ScEpTIC.AST.elements.value import Value from ScEpTIC.emulator.intermittent_executor import profiling from ScEpTIC.exceptions import ParsingException, LLVMSyntaxErrorException from ScEpTIC.llvmir_parser.instructions_parser import binary_operations from ScEpTIC.llvmir_parser.sections_parser import function from ScEpTIC.llvmir_parser.sections_parser import global_vars def parse_other_operation(text): """ Parses and return the other operations, which are: - icmp, fcmp - phi - select - va_arg - call """ logging.debug('Calling parse_other_operation({})'.format(text)) if not is_other_operation(text): raise ParsingException('Other operation (icmp, fcmp, phi) expected.\n{} given'.format(text)) instruction_type = None if is_other_operation(text, 'cmp'): instruction_type = 'cmp' instruction_value = parse_cmp_operation(text) elif is_other_operation(text, 'phi'): instruction_type = 'phi' instruction_value = parse_phi_operation(text) elif is_other_operation(text, 'select'): instruction_type = 'select' instruction_value = parse_select_operation(text) elif is_other_operation(text, 'va_arg'): instruction_type = 'va_arg' instruction_value = parse_va_arg_instruction(text) elif is_call_instruction(text): instruction_type = 'call' instruction_value = parse_call_instruction(text) if logging.getLogger().isEnabledFor(logging.DEBUG): logging.debug('Parsed {} instruction:\n{}'.format(instruction_type, instruction_value)) return instruction_value def is_other_operation(text, operation_code = None): """ Returns if a given instruction is is an icmp or fcmp operation. """ logging.debug('Calling is_other_operation({}, {})'.format(text, operation_code)) if len(text) < 4: return False not_supported_ops = ['landingpad', 'catchpad', 'cleanuppad'] op_code = text[3] if text[0] in not_supported_ops or op_code in not_supported_ops: raise NotImplementedError('Operation {} not implemented!'.format(op_code)) if operation_code is None: valid_op = op_code in ['icmp', 'fcmp', 'phi', 'select', 'call', 'va_arg'] is_call = is_call_instruction(text) elif operation_code == 'cmp': valid_op = op_code in ['icmp', 'fcmp'] is_call = False else: valid_op = op_code == operation_code is_call = False return (valid_op and text[0] == '%' and text[2] == '=') or is_call def is_call_instruction(text): """ Returns if a given instruction is is a call one. """ logging.debug('Calling is_call_instruction({})'.format(text)) if len(text) < 5: return False token = 'call' # can be: # call ... # tail call # % x = call # % x = tail call return token in text[:5] def parse_cmp_operation(text): """ Parses and returns an icmp/fcmp operation. = icmp , = fcmp [fast-math flags]* , """ logging.debug('Calling parse_cmp_operation({})'.format(text)) if not is_other_operation(text, 'cmp'): raise ParsingException('Cmp operation (icmp, fcmp) expected.\n{} given.'.format(text)) # retrieve where the result must be stored target, offset = binary_operations.parse_result_target(text) operation_code = text[offset] text = text[offset+1:] # fcmp can have fast-math flags # but can be ignored. just parse them if operation_code == 'fcmp': specific_attr, text = binary_operations.parse_fp_specific_attr(text, 'fcmp') # parse condition, type and operands parsed = parse_cond_type_operands(text, operation_code) comparation_type = 'int' if operation_code == 'icmp' else 'float' return CompareOperation(target, parsed['op1'], parsed['op2'], parsed['condition'], comparation_type, operation_code) def parse_cond_type_operands(text, operation_code): """ Parses and returns the condition, the type and the operands of an operation as a dict with keys {'op1', 'op2', 'condition'} If the condition is not a valid one, it raises an exception. """ logging.debug('Calling parse_cond_type_operands({}, {})'.format(text, operation_code)) # condition is always at offset 0 cond = text[0] if not is_valid_cond(cond, operation_code): raise ParsingException('Condition {} not valid for {} operation.'.format(cond, operation_code)) # parse type and operands. retval = binary_operations.parse_type_and_operands(text[1:]) retval['condition'] = cond return retval def is_valid_cond(cond, operation_code): """ Returns if a given condition is a valid one for a given operation. """ logging.debug('Calling is_valid_cond({}, {})'.format(cond, operation_code)) if operation_code == 'icmp': valid_conds = ['eq', 'ne', 'ugt', 'uge', 'ult', 'ule', 'sgt', 'sge', 'slt', 'sle'] elif operation_code == 'fcmp': valid_conds = ['false', 'oeq', 'ogt', 'oge', 'olt', 'ole', 'one', 'ord', 'ueq', 'ugt', 'uge', 'ult', 'ule', 'une', 'uno', 'true'] return cond in valid_conds def parse_phi_operation(text): """ Parses and returns a phi operation. = phi [ , ], ... """ logging.debug('Calling parse_phi_operation({})'.format(text)) if not is_other_operation(text, 'phi'): raise ParsingException('phi operation expected.\n{} given.'.format(text)) # retrieve where the result must be stored target, offset = binary_operations.parse_result_target(text) text = text[offset+1:] # get type. it is after 'phi' and before the first [ index = text.index('[') return_type = global_vars.parse_type(text[:index]) # split values text = tools.split_context_into_sublist(text[index:], ',') values = {} # pair: value, %label for pair in text: # 1 to remove '[' # -4 to remove [',', '%', 'label', ']'] value = binary_operations.parse_operand(pair[1:-4]) # -3 to start from '%' # -1 to end before ']' label = tools.list_to_string(pair[-3:-1]) values[label] = value return PhiOperation(target, return_type, values) def parse_select_operation(text): """ Parses a select operation. = select selty , , """ logging.debug('Calling parse_select_operation({})'.format(text)) if not is_other_operation(text, 'select'): raise ParsingException('select operation expected.\n{} given.'.format(text)) # retrieve where the result must be stored target, offset = binary_operations.parse_result_target(text) text = text[offset+1:] # get a list of 3 sublists: # 0: selty # 1: # 2: elements = tools.split_context_into_sublist(text, ',') # parse selection type and conditional cond = binary_operations.parse_operand(elements[0], True) # parse value list 1 el1 = binary_operations.parse_operand(elements[1], True) # parse value list 2 el2 = binary_operations.parse_operand(elements[2], True) return SelectOperation(target, cond, el1, el2) def parse_call_instruction(text): """ Parses and returns a call instruction. = [tail | musttail | notail ] call [fast-math flags] [cconv] [ret attrs] | () [fn attrs] [ operand bundles ] """ logging.debug('Calling parse_call_instruction({})'.format(text)) if not is_call_instruction(text): raise ParsingException('call instruction expected.\n{} given.'.format(text)) text = profiling.pre_parse_profiling_log(text, '@') target = None # has target if text[2] == '=': target, offset = binary_operations.parse_result_target(text) text = text[offset:] # parse and remove fast-math flags and tail attributes fp_attr, text = binary_operations.parse_fp_specific_attr(text, 'call') tail_attr, text = parse_tail_specific_attr(text) # first element is call, remove it. text = text[1:] # parse and remove calling convention cc_attr, text = parse_calling_convention(text) # parse and remove ret attrs ret_attr, text = parse_return_parameter_attr(text) # parse return_type, offset = binary_operations.extract_type(text, True) text = text[offset:] function_signature = [] # parse for var_args if text[0] == '(': index = tools.get_index_of_closed_par(text, '(', ')') function_signature = function.parse_function_arguments(text[1:index]) text = text[index+1:] # bistcast ( ... to ... ) (args) if text[0] == 'bitcast': index_name = text.index('@') # index of bitcast closed ) bitcast_close_index = tools.get_index_of_closed_par(text, '(', ')') # get index of first opened ( after bitcast closed ) index_arg_open = text[bitcast_close_index:].index('(') + bitcast_close_index text = text[index_name:index_name+2] + text[index_arg_open:] if text[0] == '@': function_name = text[0] + text[1] elif text[0] == '%': function_name = Value('virtual_reg', text[0] + text[1], 'void') else: raise LLVMSyntaxErrorException('Invalid function identifier "{}".\n{}'.format(text[0], text)) # remove ['@', 'function_name'] text = text[2:] index = tools.get_index_of_closed_par(text, '(', ')') # parse arguments passed to the function function_args_lst = tools.split_context_into_sublist(text[1:index], ',') function_args = [] for arg in function_args_lst: # improper usage of function extract_type_and_attributes: in this case divides type and value from attributes arg, attributes = function.extract_type_and_attributes(arg) arg_val = binary_operations.parse_operand(arg, True) arg_val.attributes = attributes function_args.append(arg_val) text = text[index+1:] # TO BE PARSED HERE (for future updates): fn attrs, operand bundles attrs = {'fp_math': fp_attr, 'tail': tail_attr, 'cc': cc_attr, 'return': ret_attr} return CallOperation(target, function_name, return_type, function_signature, function_args, attrs) def parse_tail_specific_attr(text): """ Parses and removes tail attributes of call instruction. They are not useful in my analysis (used just for optimization) """ logging.debug('Calling parse_tail_specific_attr({})'.format(text)) tail_tokens = ['tail', 'musttail', 'notail'] ret = [] while text[0] in tail_tokens: ret.append(text[0]) text = text[1:] return ret, text def parse_calling_convention(text): """ Parses and removes calling conventions attributes of call instruction. """ logging.debug('Calling parse_calling_convention({})'.format(text)) ccs_tokens = ['ccc', 'fastcc', 'coldcc', 'webkit_jscc', 'anyregcc', 'preserve_mostcc', 'preserve_allcc', 'cxx_fast_tlscc', 'swiftcc'] calling_conventions = [] while text[0] in ccs_tokens: calling_conventions.append(text[0]) text = text[1:] # cc if text[0] == 'cc' and text[1].isdigit(): calling_conventions.append(text[0]+text[1]) text = text[2:] return calling_conventions, text def parse_return_parameter_attr(text): """ Parses and removes return attributes of call instruction. """ logging.debug('Calling parse_return_parameter_attr({})'.format(text)) param_attr = ['zeroext', 'signext', 'inreg', 'byval', 'inalloca', 'sret', 'noalias', 'nocapture', 'nest', 'returned', 'nonnull', 'swiftself', 'swifterror'] ret_param_attr = [] while text[0] in param_attr: ret_param_attr.append(text[0]) text = text[1:] return ret_param_attr, text def parse_va_arg_instruction(text): """ Parses a va_arg instruction and returns it. Note: THIS IS NOT FULLY SUPPORTED BY LLVM CODE GENERATOR ON MANY TARGETS = va_arg , %tmp = va_arg i8* %ap2, i32 It returns a value of the specified argument type and increments the va_list to point to the next argument. The actual type of va_list is target specific. """ logging.debug('Calling parse_va_arg_instruction({})'.format(text)) if not is_other_operation(text, 'va_arg'): raise ParsingException('va_arg instruction expected.\n{} given.'.format(text)) # parse target and remove = va_arg target, offset = binary_operations.parse_result_target(text) text = text[offset+1:] # split variable argument list and argument type text = tools.split_context_into_sublist(text, ',') # first group is the list virtual register list_element = binary_operations.parse_operand(text[0], True) # second group is the type of each element in the list arg_type = global_vars.parse_type(text[1]) return VaArgOperation(target, list_element, arg_type)