# Copyright 2011-2021 IBM Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""":mod:`microprobe.utils.bin` definition module
This module implements the required features to interpret instructions in
binary codification and translate them into Microprobe internal represenation
of instruction, operands, labels and addreses.
The main elements of this module are the following:
- :class:`~.MicroprobeBinInstructionStream` objects to represent a binary
stream of codified instructions
- :func:`~.interpret_bin` function validates the binary codification and
translates them into internal Microprobe represenation of instructions and
operands.
"""
# Futures
from __future__ import absolute_import, division, print_function
# Built-in modules
import atexit
import collections
import itertools
import math
import string
import re
from typing import List
# Third party modules
import cachetools
# Own modules
import microprobe.code.ins
from microprobe import MICROPROBE_RC
from microprobe.exceptions import MicroprobeBinaryError, \
MicroprobeCodeGenerationError, MicroprobeValueError, \
MicroprobeCacheError
from microprobe.target.isa.instruction import instruction_type_from_bin
from microprobe.utils.cache import read_default_cache_data, \
write_default_cache_data_silent
from microprobe.utils.logger import get_logger
from microprobe.utils.misc import Progress, \
RejectingDict, int_to_twocs, twocs_to_int
# Constants
LOG = get_logger(__name__)
_BIN_CACHE_ENABLED = True
_BIN_CACHE_FILE = __file__ + ".bin"
_BIN_CACHE = None
_BIN_CACHE_USED = False
_BIN_CACHE_SAVED = False
_BIN_CACHE_SIZE = 16*1024
_BIN_CACHE_STATS = {'hit': 0, 'access': 0}
_CODE_CACHE_ENABLED = True
_CODE_CACHE_FILE = __file__ + ".code"
_CODE_CACHE = None
_CODE_CACHE_USED = False
_CODE_CACHE_SAVED = False
_CODE_CACHE_SIZE = 16*1024
_CODE_CACHE_STATS = {'hit': 0, 'access': 0}
_DATA_CACHE = RejectingDict()
_DATA_CACHE_LENGTHS: List[str] = []
_DATA_CACHE_ENABLED = False
__all__ = ["interpret_bin",
"MicroprobeBinInstructionStream", ]
# Functions
[docs]
def interpret_bin(
code, target, fmt="hex", safe=None,
single=False, little_endian=None, word_length=None
):
"""
Return the list of :class:`~.MicroprobeInstructionDefinition` objects
that results from interpreting the *code* (a binary stream).
The *target* object is used to validate the existence of the instruction
and operands in the target.
:param code: String to interpret
:type code: :class:`~.str` object
:param target: Target definition
:type target: :class:`~.Target` object
:return: A list of instructions, operands, labels, etc. resulting from
interpreting the assembly
:rtype: :class:`~.list` of :class:`~.MicroprobeInstructionDefinition`
:raise microprobe.exceptions.MicroprobeBinaryError: if something is wrong
during the interpretation
"""
global _BIN_CACHE
global _CODE_CACHE
global _BIN_CACHE_USED
global _CODE_CACHE_USED
global _BIN_CACHE_SAVED
global _CODE_CACHE_SAVED
global _BIN_CACHE_SIZE
global _CODE_CACHE_SIZE
if _BIN_CACHE is None:
try:
_BIN_CACHE = cachetools.LRUCache(_BIN_CACHE_SIZE, getsizeof=None)
if _BIN_CACHE_ENABLED:
_BIN_CACHE = read_default_cache_data(_BIN_CACHE_FILE)
except MicroprobeCacheError:
_BIN_CACHE = cachetools.LRUCache(_BIN_CACHE_SIZE, getsizeof=None)
if _CODE_CACHE is None:
try:
_CODE_CACHE = cachetools.LRUCache(_CODE_CACHE_SIZE, getsizeof=None)
if _CODE_CACHE_ENABLED:
_CODE_CACHE = read_default_cache_data(_CODE_CACHE_FILE)
except MicroprobeCacheError:
_CODE_CACHE = cachetools.LRUCache(_CODE_CACHE_SIZE, getsizeof=None)
if safe is None:
safe = MICROPROBE_RC['safe_bin']
if single:
little_endian = False
if fmt == "hex":
word_length = int(math.ceil(len(code)/2))
elif fmt == "bin":
word_length = int(math.ceil(len(code)/8))
else:
raise MicroprobeBinaryError("Unknown format '%s'" % fmt)
key = "%s-%s-%s-%s" % (code, target.name + target.isa.path, fmt, safe)
if key in _CODE_CACHE and _CODE_CACHE_ENABLED:
progress = Progress(len(code), msg="Bytes parsed:")
code = [instrdef.copy() for instrdef in _CODE_CACHE[key]]
_CODE_CACHE_STATS['access'] += 1
_CODE_CACHE_STATS['hit'] += 1
progress(increment=len(code))
del progress
elif safe:
code = _interpret_bin_code_safe(
code, target, single, fmt=fmt, little_endian=little_endian,
word_length=word_length
)
if _CODE_CACHE_ENABLED:
_CODE_CACHE_STATS['access'] += 1
_CODE_CACHE[key] = code[:]
_CODE_CACHE_USED = True
else:
code = _interpret_bin_code(
code, target, single, fmt=fmt, little_endian=little_endian,
word_length=word_length
)
if _CODE_CACHE_ENABLED:
_CODE_CACHE_STATS['access'] += 1
_CODE_CACHE[key] = code[:]
_CODE_CACHE_USED = True
if (_BIN_CACHE_ENABLED and _BIN_CACHE_USED and not _BIN_CACHE_SAVED):
atexit.register(
write_default_cache_data_silent,
_BIN_CACHE_FILE, _BIN_CACHE,
data_reload=True
)
atexit.register(
_show_cache_stats, _BIN_CACHE_FILE, _BIN_CACHE_STATS
)
_BIN_CACHE_SAVED = True
if (_CODE_CACHE_ENABLED and _CODE_CACHE_USED and not _CODE_CACHE_SAVED):
atexit.register(
write_default_cache_data_silent,
_CODE_CACHE_FILE, _CODE_CACHE,
data_reload=True
)
atexit.register(
_show_cache_stats, _CODE_CACHE_FILE, _CODE_CACHE_STATS
)
_CODE_CACHE_SAVED = True
return code
def _interpret_bin_code(
code, target, single, fmt="hex", little_endian=True, word_length=None
):
instructions_and_params = collections.deque()
binstream = MicroprobeBinInstructionStream(
code, target, fmt=fmt, little_endian=little_endian,
word_length=word_length
)
while not binstream.empty():
bins, instr_type, operands = binstream.decode_next()
instr_def = microprobe.code.ins.MicroprobeInstructionDefinition(
instr_type, operands, None, None, bins, None, None
)
instructions_and_params.append(instr_def)
return list(instructions_and_params)
def _interpret_bin_code_safe(
code, target, single, fmt="hex", little_endian=None, word_length=None
):
instructions_and_params = collections.deque()
binstream = MicroprobeBinInstructionStream(
code, target, fmt=fmt,
_data_cache=True,
little_endian=little_endian,
word_length=word_length
)
min_skip_length = min(
[ins.format.length for ins in target.instructions.values()]) * 2
fmt = "0x%%0%dx" % min_skip_length
while not binstream.empty():
try:
bins, instr_type, operands = binstream.decode_next()
instr_def = microprobe.code.ins.MicroprobeInstructionDefinition(
instr_type, operands, None, None, bins, None, None
)
instructions_and_params.append(instr_def)
except MicroprobeBinaryError:
if single:
skip = binstream.skip_all()
else:
skip = binstream.skip(characters=min_skip_length)
instr_type = instruction_type_from_bin(skip, target)
instr_def = microprobe.code.ins.MicroprobeInstructionDefinition(
instr_type, (), None, None, skip, None, None
)
instructions_and_params.append(instr_def)
if (_BIN_CACHE_ENABLED and
(instr_type.name != "raw" or len(binstream.lengths) == 1)):
key = (fmt % int(skip, 16)).lower()
_BIN_CACHE[target.name+target.isa.path+key] = (
fmt %
int(skip, 16), instr_type, ())
return list(instructions_and_params)
def _compute_target_lengths(target):
"""
:param target:
:type target:
"""
instr_formats = set(
(
instr.format for instr in target.instructions.values()
)
)
lengths = set((instr_format.length for instr_format in instr_formats))
LOG.debug("Possible lengths: %s", lengths)
return lengths
def _interpret_bin_instr(instr_type, bin_instr):
"""
:param target:
:type target:
"""
LOG.debug("Interpret bin instr: %s, %s", instr_type, hex(bin_instr))
operand_values = []
processed_bits = 0
for op_descriptor, field in zip(
list(instr_type.operands.items()), instr_type.format.fields
):
dummy_fieldname, op_descriptor = op_descriptor
operand, dummy_io_def = op_descriptor
processed_bits += field.size
LOG.debug("Process field: %s, size: %d", field, field.size)
LOG.debug("Process operand: %s", operand)
LOG.debug("Processed bits: %d", processed_bits)
if not field.default_show:
LOG.debug("Skip, not shown")
continue
field_value = (
bin_instr >> (
(
(instr_type.format.length * 8) - processed_bits
)
)
) & (
(0b1 << field.size) - 1
)
LOG.debug("All: 0x%x", bin_instr)
LOG.debug(
"Shift bits: %d", (instr_type.format.length * 8) - processed_bits
)
LOG.debug(
"Shifted value: 0x%x", (
bin_instr >> (
(
(instr_type.format.length * 8) - processed_bits
)
)
)
)
LOG.debug("Field size: %d", field.size)
LOG.debug("Mask used: 0x%x", (0b1 << field.size) - 1)
LOG.debug("Field value: %s", hex(field_value))
LOG.debug("Field value: %s", bin(field_value))
found = False
if (operand.immediate or
operand.address_relative or operand.address_absolute):
LOG.debug("Immediate/Relative operand")
oper_size_pairs = [(field_value, field.size, 0)]
if operand.shift > 0:
LOG.debug("Operand shifted by: %s", operand.shift)
pair = (
field_value << operand.shift, field.size + operand.shift,
operand.shift
)
oper_size_pairs.append(pair)
if hasattr(operand, "step"):
LOG.debug("Operand has a step of: %s", operand.step)
for idx in [0, 1, 2]:
if (int(math.log(operand.step, 2)) - idx) < 0:
continue
pair = (
field_value << (
int(math.log(operand.step, 2)) - idx
), field.size + int(math.log(operand.step, 2)) - idx,
(int(math.log(operand.step, 2)) - idx)
)
oper_size_pairs.append(pair)
if operand.shift > 0:
LOG.debug(
"Operand shifted/stepped: %s %s", operand.shift,
operand.step
)
pair = (
field_value << (
(
int(math.log(operand.step, 2)) - 1
) + operand.shift
), field.size + int(math.log(operand.step, 2)) - 1 +
operand.shift, (
(
int(math.log(operand.step, 2)) - 1
) + operand.shift
)
)
oper_size_pairs.append(pair)
if operand.address_relative or operand.address_absolute or (
not operand.address_relative and operand.min < 0
):
LOG.debug("C2 codification")
new_pairs = []
for oper, size, shift in oper_size_pairs:
new_pairs.append((twocs_to_int(oper, size), size, shift))
oper_size_pairs += new_pairs
#
# Handle special codifications
# TODO: This should be moved to the architecture
# back-ends
#
# MB field on POWERPC
special_condition = False
if field.name in ["MB6", "ME6"]:
LOG.debug("Special condition field 1")
special_condition = True
pair = (
(
(field_value >> 1) & 0b11111
) | (
(field_value & 0b1) << 5
), field.size, operand.shift
)
oper_size_pairs.append(pair)
if field.name in ["SH6_5"]:
LOG.debug("Special condition field 2")
special_condition = True
pair = (field_value + 32, 6, operand.shift)
oper_size_pairs.append(pair)
if field.name in ["Dd0", "Dd2"]:
special_condition = True
pair = (0, field.size, operand.shift)
oper_size_pairs.append(pair)
if field.name in ['Dd1']:
special_condition = True
dd2_value = bin_instr & 0b1
dd0_value = (bin_instr >> 6) & (0b1111111111)
value = (field_value << 1)
value = value | dd2_value
value = value | (dd0_value << 6)
value = twocs_to_int(value, 16)
pair = (value, 16, operand.shift)
oper_size_pairs.append(pair)
if field.name in ["dc", "dm"]:
special_condition = True
pair = (0, field.size, operand.shift)
oper_size_pairs.append(pair)
if field.name in ['dx']:
special_condition = True
dc_value = (bin_instr >> 6) & 0b1
dm_value = (bin_instr >> 2) & 0b1
value = field_value
value = value | (dm_value << 5)
value = value | (dc_value << 6)
# value = twocs_to_int(value, 16)
pair = (value, 7, operand.shift)
oper_size_pairs.append(pair)
# Z Fixes
if field.name in ["DH1", "DH2"]:
LOG.debug("Special condition field 3")
special_condition = True
pair = (0, field.size, operand.shift)
oper_size_pairs.append(pair)
if field.name in ["DL1", "DL2"]:
LOG.debug("Special condition field 4")
special_condition = True
next_field_value = (
bin_instr >> (
(
(instr_type.format.length * 8) -
(processed_bits + 8)
)
)
) & (
(0b1 << 8) - 1
)
pair = (
field_value | (next_field_value << field.size),
field.size + 8, operand.shift
)
oper_size_pairs.append(pair)
pair = (twocs_to_int(pair[0], pair[1]), pair[1], pair[2])
oper_size_pairs.append(pair)
# RISC-V fixes
# List of immediate fixes which need to be performed
riscv_fixes = {
'sb_imm7': ('sb_imm5', 13, True, '12|10:5;#13;4:1|11'),
's_imm7': ('s_imm5', 12, True, '11:5;#13;4:0'),
'uj_imm20': (None, 21, True, '20|10:1|11|19:12'),
'cw_imm3': ('c_imm2', 7, False, '#3;5:3;#3;2|6'),
'cd_imm3': ('c_imm2', 8, False, '#3;5:3;#3;7|6'),
'cb_imm5': ('c_imm3', 9, True, '#3;8|4:3;#3;7:6|2:1|5'),
'cw_imm5': ('c_imm1', 8, False, '#3;5;#5;4:2|7:6'),
'cd_imm5': ('c_imm1', 9, False, '#3;5;#5;4:3|8:6'),
'ci_imm5': ('c_imm1', 6, True, '#3;5;#5;4:0'),
'cu_imm5': ('c_imm1', 20, False, '#3;5;#5;4:0', 5),
'cs_imm5': ('c_imm1', 10, True, '#3;9;#5;4|6|8:7|5'),
'cls_imm5': ('c_imm1', 6, False, '#3;5;#5;4:0'),
'cw_imm6': (None, 8, False, '#3;5:2|7:6'),
'cd_imm6': (None, 9, False, '#3;5:3|8:6'),
'c_imm11': (None, 12, True, '#3;11|4|9:8|10|6|7|3:1|5'),
'cs_imm8': (None, 10, False, '#3;5:4|9:6|2|3')
}
zero_fields = [fix[0] for _, (_, fix) in
enumerate(riscv_fixes.items())]
def _parse_riscv_fix_field(fix):
value = 0
src_pos = (instr_type.format.length * 8) - 1
segments = fix[3].split(';')
for segment in segments:
if segment.startswith('#'):
src_pos -= int(segment[1:])
else:
tokens = segment.split('|')
for token in tokens:
if ':' in token:
start, end = map(int, token.split(':'))
for dst_pos in range(start, end - 1, -1):
bit = (bin_instr >> src_pos) & 1
value |= (bit << dst_pos)
src_pos -= 1
else:
bit = (bin_instr >> src_pos) & 1
dst_pos = int(token)
value |= (bit << dst_pos)
src_pos -= 1
if fix[2]:
value = twocs_to_int(value, fix[1])
if len(fix) == 5 and (value >> fix[4]) > 0:
value = (1 << fix[1]) + twocs_to_int(value, fix[4]+1)
return value
# Fields which will contain the actual value
if field.name in riscv_fixes:
special_condition = True
fix = riscv_fixes[field.name]
value = _parse_riscv_fix_field(fix)
pair = (value, fix[1], operand.shift)
oper_size_pairs.append(pair)
# Fields which will contain a 0
if field.name in zero_fields:
special_condition = True
pair = (0, field.size, operand.shift)
oper_size_pairs.append(pair)
oper_size_pairs = list(set(oper_size_pairs))
LOG.debug("Possible operand values: %s", oper_size_pairs)
valid_values = []
for operand_value, size, shift in oper_size_pairs:
# Regular no shift
valid = (
int(operand.codification(operand_value)) == field_value
)
# C2 no shift
valid = valid or (
int_to_twocs(operand_value, size + shift) == field_value
)
# Regular shifted
valid = valid or (
(
int(operand.codification(operand_value)) >> shift
) == field_value
)
# C2 shifted
valid = valid or (
(
int_to_twocs(operand_value, size + shift) >> shift
) == field_value
)
# C2 shifted 2
valid = valid or (
(
int_to_twocs(operand_value >> shift, size - shift)
) == field_value
)
valid = valid or special_condition
if valid:
LOG.debug("Codification matches")
try:
LOG.debug("Checking: %s", operand_value)
LOG.debug("Operand: %s", operand)
LOG.debug("Instruction type: %s", instr_type)
operand.check(operand_value)
valid_values.append(operand_value)
found = True
LOG.debug("Codification matches and valid value!")
except MicroprobeValueError as exc:
LOG.debug(exc)
LOG.debug("Codification matches but not valid value")
operand_values.append(set(valid_values))
else:
LOG.debug("Non immediate operand")
registers = []
for value in operand.values():
LOG.debug("Testing value: %s", value)
if operand.codification(value) == "N/A":
continue
int_val = int(operand.codification(value))
valid = int_val == field_value
if int_val > (2**field.size) - 1:
mask = (2**field.size) - 1
valid = valid or (int_val & mask) == field_value
valid = valid or ((int_val >> 1) & mask) == field_value
# Handle POWERPC specific codification
if (value.type.name).startswith("SPR"):
valid = (
((int_val >> 5) & 0b11111) | (
(int_val & 0b11111) << 5
)
) == field_value
if field.name in ['TP']:
tmp_value = field_value << 0b1
tx_value = ((bin_instr >> 21) & 0b1) * 32
tmp_value = tx_value + tmp_value
if operand.codification(value) == str(tmp_value):
valid = True
# Handle RISCV specific codification
if field.name in ['crs1', 'crs2', 'crd',
'fcrs1', 'fcrs2', 'fcrd']:
valid = (int_val & 0b1000
and (int_val & 0b111) == field_value)
if valid:
try:
operand.check(value)
registers.append(value)
LOG.debug("Adding value: %s", value)
except MicroprobeValueError:
LOG.debug("Codification matches but not valid value")
registers = list(set(registers))
if len(registers) > 0:
found = True
operand_values.append(registers)
if not found:
return None
LOG.debug("Operand values: %s", operand_values)
return operand_values
def _swap_bytes(original, little_endian=False):
result = ""
if not little_endian:
return original
# Pad with 0s on the left until having an even number of digits
if (len(original) % 2 != 0):
original = "0" + original
while (len(original) > 0):
original, result = original[:-2], result + original[-2:]
return result
def _normalize_code(
code, fmt="hex", endianess_missmatch=False, word_length=None
):
"""
:param code:
:type code:
:param iformat:
:type iformat:
"""
skip_character = " \n"
skip_regex = '|'.join([c for c in skip_character])
if fmt == "hex":
digit_list = string.hexdigits + skip_character
elif fmt == "bin":
digit_list = "01" + skip_character
else:
raise MicroprobeBinaryError(
"Unknown format '%s'" % fmt
)
if not isinstance(code, str):
raise MicroprobeBinaryError(
"Invalid input provided. Code is not a string"
)
for character in code:
if not isinstance(character, str):
raise MicroprobeBinaryError(
"Invalid input provided. Not character provided"
)
if character not in digit_list:
raise MicroprobeBinaryError(
"Invalid input provided. Not hexadecimal number provided"
)
code = "".join([char for char in code if char not in skip_character])
if endianess_missmatch:
LOG.debug("Fixing missmatch between input data and target's endianess")
assert word_length is not None
n = word_length * 2
words = [(code[i:i+n]) for i in range(0, len(code), n)]
code = ""
for word in words:
code += _swap_bytes(word, True)
if fmt == "hex":
return code
elif fmt == "bin":
return "".join(
[
hex(int(code[elem:elem + 4], 2))[
2:
] for elem in range(0, len(code), 4)
]
)
# Classes
[docs]
class MicroprobeBinInstructionStream(object):
"""
"""
[docs]
def __init__(
self, code, target, fmt="hex",
_data_cache=False, little_endian=None, word_length=None
):
"""
:param code:
:type code:
:param target:
:type target:
"""
self._little_endian = target.little_endian
if little_endian is not None:
self._stream_little_endian = little_endian
else:
self._stream_little_endian = target.little_endian
endianess_missmatch = self._little_endian != self._stream_little_endian
self._code = _normalize_code(
code,
fmt=fmt,
endianess_missmatch=endianess_missmatch,
word_length=word_length
)
self._fmt = fmt
self._target = target
self._index = 0
self._data_cache = _data_cache
self._lengths = list(sorted(_compute_target_lengths(target),
reverse=True))
self._progress = Progress(len(self._code), msg="Bytes parsed:")
[docs]
def empty(self):
"""
"""
return self._index >= len(self._code)
@property
def lengths(self):
return self._lengths
[docs]
def skip(self, characters=2):
self._index += characters
self._progress(increment=characters)
assert self._index % 2 == 0
bin_str = self._code[self._index - characters:self._index]
if self._little_endian:
bin_str = _swap_bytes(bin_str, self._little_endian)
return bin_str
[docs]
def skip_all(self):
pindex = self._index
self._index += len(self._code)
self._progress(increment=len(self._code)-pindex)
assert self._index % 2 == 0
bin_str = self._code[pindex:self._index]
if self._little_endian:
bin_str = _swap_bytes(bin_str, self._little_endian)
return bin_str
[docs]
def decode_next(self):
"""
"""
global _BIN_CACHE
global _BIN_CACHE_USED
if self.empty():
raise MicroprobeBinaryError("Binary stream finished")
LOG.debug("Index %s : value: %s",
self._index, self._code[self._index:self._index + 16])
bin_str = None
if self._data_cache and _DATA_CACHE_ENABLED:
for length in sorted(_DATA_CACHE_LENGTHS, reverse=True):
bin_str = self._code[self._index:(self._index + length)]
if bin_str in _DATA_CACHE:
self._index += length
self._progress(increment=length)
LOG.debug("%s in data cache" % bin_str)
return bin_str, None, None
LOG.debug("Not found in data cache")
matches = []
max_size = max(self._lengths) * 2
for length in self._lengths:
LOG.debug("Trying length: %d", length)
if self._index == 0 and len(self._code) < (length * 2):
LOG.debug("Short bin string without leading zeros, skipping.")
continue
elif self._index + length * 2 > len(self._code):
LOG.debug("Skipping length %s ...", length)
continue
bin_str = self._code[self._index:self._index + max_size]
bin_str = bin_str[0:length * 2]
if self._little_endian:
bin_str = _swap_bytes(bin_str, self._little_endian)
bin_int = int(bin_str, 16)
fmt = "0x%%0%dx" % (length * 2)
bin_str = fmt % bin_int
if (self._target.name + self._target.isa.path+bin_str in
_BIN_CACHE and _BIN_CACHE_ENABLED):
add = _BIN_CACHE[self._target.name + self._target.isa.path +
bin_str][1].format.length * 2
self._index += add
self._progress(increment=add)
LOG.debug("%s in code cache" % bin_str)
_BIN_CACHE_STATS['access'] += 1
_BIN_CACHE_STATS['hit'] += 1
#
# Return always the largest match in cache
#
return _BIN_CACHE[
self._target.name + self._target.isa.path + bin_str
]
LOG.debug("Looking for length: %d", length)
matches += [
(instr_type, bin_int)
for instr_type in self._target.instructions.values()
if instr_type.format.length == length and instr_type.match(
bin_int
)
]
LOG.debug("Current matches: %s", matches)
if len(matches) == 0:
raise MicroprobeBinaryError(
"Unable to interpret the binary provided: %s" % bin_str
)
if len(set([match[0].format.length for match in matches])) > 1:
raise MicroprobeBinaryError(
"Multiple instructions with multiple lengths found"
)
possible_match_operands = []
for instr_type, bin_int in matches:
LOG.debug("Instruction type: %s", instr_type)
operands = _interpret_bin_instr(instr_type, bin_int)
LOG.debug("Operands: %s", operands)
if operands is not None:
for elem in itertools.product(*operands):
possible_match_operands.append((bin_int, instr_type, elem))
if len(possible_match_operands) == 0:
raise MicroprobeBinaryError(
"No variants found when processing: %s" %
[hex(elem2) for dummy, elem2 in matches]
)
for bin_int, instr_type, operand_values in possible_match_operands:
instruction = microprobe.code.ins.Instruction()
instruction.set_arch_type(instr_type)
LOG.debug(
"Check match: %s : Values: %s",
instr_type.name,
operand_values)
try:
for operand, value in zip(
instruction.operands(), operand_values
):
operand.set_value(value)
codification = int(instruction.binary(), 2)
if bin_int == codification:
LOG.debug("Match found")
add = instr_type.format.length * 2
self._index += add
fmt = "0x%%0%dx" % (add)
self._progress(increment=add)
assert self._index % 2 == 0
assert int(
fmt %
bin_int, 16) == bin_int, (hex(bin_int),
fmt)
if _BIN_CACHE_ENABLED and instr_type.name != "raw":
key = (fmt % bin_int).lower()
_BIN_CACHE[
self._target.name + self._target.isa.path + key
] = (
fmt % bin_int, instr_type, operand_values
)
_BIN_CACHE_STATS['access'] += 1
_BIN_CACHE_USED = True
return (fmt % bin_int, instr_type, operand_values)
except MicroprobeValueError:
LOG.debug("Skip match")
continue
except MicroprobeCodeGenerationError:
LOG.debug("Skip match")
continue
raise MicroprobeBinaryError("No candidates found for %s", hex(bin_int))
def _show_cache_stats(cache_file, cache_stats):
ratio = (1.0 * cache_stats['hit'] / cache_stats['access']) * 100
LOG.debug(
"Cache %s stats: %d / %d (%2.2f%%)",
cache_file, cache_stats['hit'], cache_stats['access'], ratio
)