1
0
mirror of https://github.com/angr/pyvex synced 2026-06-21 13:47:01 +00:00
Files
Kevin Phoenix 5f952aff37 Use pre-commit
2023-01-17 14:47:17 -07:00

299 lines
12 KiB
Python

from collections import defaultdict
import logging
import archinfo
from .. import const, ffi
from ..expr import Const
from ..block import IRSB
from ..errors import PyVEXError, NeedStatementsNotification, LiftingException, SkipStatementsError
from .lifter import Lifter
from .post_processor import Postprocessor
log = logging.getLogger("pyvex.lifting")
lifters = defaultdict(list)
postprocessors = defaultdict(list)
def lift(
data,
addr,
arch,
max_bytes=None,
max_inst=None,
bytes_offset=0,
opt_level=1,
traceflags=0,
strict_block_end=True,
inner=False,
skip_stmts=False,
collect_data_refs=False,
cross_insn_opt=True,
load_from_ro_regions=False,
):
"""
Recursively lifts blocks using the registered lifters and postprocessors. Tries each lifter in the order in
which they are registered on the data to lift.
If a lifter raises a LiftingException on the data, it is skipped.
If it succeeds and returns a block with a jumpkind of Ijk_NoDecode, all of the lifters are tried on the rest
of the data and if they work, their output is appended to the first block.
:param arch: The arch to lift the data as.
:type arch: :class:`archinfo.Arch`
:param addr: The starting address of the block. Effects the IMarks.
:param data: The bytes to lift as either a python string of bytes or a cffi buffer object.
:param max_bytes: The maximum number of bytes to lift. If set to None, no byte limit is used.
:param max_inst: The maximum number of instructions to lift. If set to None, no instruction limit is used.
:param bytes_offset: The offset into `data` to start lifting at.
:param opt_level: The level of optimization to apply to the IR, -1 through 2. -1 is the strictest
unoptimized level, 0 is unoptimized but will perform some lookahead/lookbehind
optimizations, 1 performs constant propogation, and 2 performs loop unrolling,
which honestly doesn't make much sense in the context of pyvex. The default is 1.
:param traceflags: The libVEX traceflags, controlling VEX debug prints.
.. note:: Explicitly specifying the number of instructions to lift (`max_inst`) may not always work
exactly as expected. For example, on MIPS, it is meaningless to lift a branch or jump
instruction without its delay slot. VEX attempts to Do The Right Thing by possibly decoding
fewer instructions than requested. Specifically, this means that lifting a branch or jump
on MIPS as a single instruction (`max_inst=1`) will result in an empty IRSB, and subsequent
attempts to run this block will raise `SimIRSBError('Empty IRSB passed to SimIRSB.')`.
.. note:: If no instruction and byte limit is used, pyvex will continue lifting the block until the block
ends properly or until it runs out of data to lift.
"""
if max_bytes is not None and max_bytes <= 0:
raise PyVEXError("Cannot lift block with no data (max_bytes <= 0)")
if not data:
raise PyVEXError("Cannot lift block with no data (data is empty)")
if isinstance(data, str):
raise TypeError("Cannot pass unicode string as data to lifter")
if isinstance(data, (bytes, bytearray, memoryview)):
py_data = data
c_data = None
allow_arch_optimizations = False
else:
if max_bytes is None:
raise PyVEXError("Cannot lift block with ffi pointer and no size (max_bytes is None)")
c_data = data
py_data = None
allow_arch_optimizations = True
# In order to attempt to preserve the property that
# VEX lifts the same bytes to the same IR at all times when optimizations are disabled
# we hack off all of VEX's non-IROpt optimizations when opt_level == -1.
# This is intended to enable comparisons of the lifted IR between code that happens to be
# found in different contexts.
if opt_level < 0:
allow_arch_optimizations = False
opt_level = 0
for lifter in lifters[arch.name]:
try:
u_data = data
if lifter.REQUIRE_DATA_C:
if c_data is None:
u_data = ffi.from_buffer(ffi.BVoidP, py_data + b"\0" * 8 if type(py_data) is bytes else py_data)
max_bytes = min(len(py_data), max_bytes) if max_bytes is not None else len(py_data)
else:
u_data = c_data
skip = 0
elif lifter.REQUIRE_DATA_PY:
if bytes_offset and archinfo.arch_arm.is_arm_arch(arch) and (addr & 1) == 1:
skip = bytes_offset - 1
else:
skip = bytes_offset
if py_data is None:
if max_bytes is None:
log.debug("Cannot create py_data from c_data when no max length is given")
continue
u_data = ffi.buffer(c_data + skip, max_bytes)[:]
else:
if max_bytes is None:
u_data = py_data[skip:]
else:
u_data = py_data[skip : skip + max_bytes]
else:
raise RuntimeError(
"Incorrect lifter configuration. What type of data does %s expect?" % lifter.__class__
)
try:
final_irsb = lifter(arch, addr)._lift(
u_data,
bytes_offset - skip,
max_bytes,
max_inst,
opt_level,
traceflags,
allow_arch_optimizations,
strict_block_end,
skip_stmts,
collect_data_refs=collect_data_refs,
cross_insn_opt=cross_insn_opt,
load_from_ro_regions=load_from_ro_regions,
)
except SkipStatementsError:
assert skip_stmts is True
final_irsb = lifter(arch, addr)._lift(
u_data,
bytes_offset - skip,
max_bytes,
max_inst,
opt_level,
traceflags,
allow_arch_optimizations,
strict_block_end,
skip_stmts=False,
collect_data_refs=collect_data_refs,
cross_insn_opt=cross_insn_opt,
load_from_ro_regions=load_from_ro_regions,
)
break
except LiftingException as ex:
log.debug("Lifting Exception: %s", str(ex))
continue
else:
final_irsb = IRSB.empty_block(
arch,
addr,
size=0,
nxt=Const(const.vex_int_class(arch.bits)(addr)),
jumpkind="Ijk_NoDecode",
)
final_irsb.invalidate_direct_next()
return final_irsb
if final_irsb.size > 0 and final_irsb.jumpkind == "Ijk_NoDecode":
# We have decoded a few bytes before we hit an undecodeable instruction.
# Determine if this is an intentional NoDecode, like the ud2 instruction on AMD64
nodecode_addr_expr = final_irsb.next
if type(nodecode_addr_expr) is Const:
nodecode_addr = nodecode_addr_expr.con.value
next_irsb_start_addr = addr + final_irsb.size
if nodecode_addr != next_irsb_start_addr:
# The last instruction of the IRSB has a non-zero length. This is an intentional NoDecode.
# The very last instruction has been decoded
final_irsb.jumpkind = "Ijk_NoDecode"
final_irsb.next = final_irsb.next
final_irsb.invalidate_direct_next()
return final_irsb
# Decode more bytes
if skip_stmts:
# When gymrat will be invoked, we will merge future basic blocks to the current basic block. In this case,
# statements are usually required.
# TODO: In the future, we may further optimize it to handle cases where getting statements in gymrat is not
# TODO: required.
return lift(
data,
addr,
arch,
max_bytes=max_bytes,
max_inst=max_inst,
bytes_offset=bytes_offset,
opt_level=opt_level,
traceflags=traceflags,
strict_block_end=strict_block_end,
skip_stmts=False,
collect_data_refs=collect_data_refs,
)
next_addr = addr + final_irsb.size
if max_bytes is not None:
max_bytes -= final_irsb.size
if isinstance(data, (bytes, bytearray, memoryview)):
data_left = data[final_irsb.size :]
else:
data_left = data + final_irsb.size
if max_inst is not None:
max_inst -= final_irsb.instructions
if (max_bytes is None or max_bytes > 0) and (max_inst is None or max_inst > 0) and data_left:
more_irsb = lift(
data_left,
next_addr,
arch,
max_bytes=max_bytes,
max_inst=max_inst,
bytes_offset=bytes_offset,
opt_level=opt_level,
traceflags=traceflags,
strict_block_end=strict_block_end,
inner=True,
skip_stmts=False,
collect_data_refs=collect_data_refs,
)
if more_irsb.size:
# Successfully decoded more bytes
final_irsb.extend(more_irsb)
elif max_bytes == 0:
# We have no more bytes left. Mark the jumpkind of the IRSB as Ijk_Boring
if final_irsb.size > 0 and final_irsb.jumpkind == "Ijk_NoDecode":
final_irsb.jumpkind = "Ijk_Boring"
final_irsb.next = Const(vex_int_class(arch.bits)(final_irsb.addr + final_irsb.size))
if not inner:
for postprocessor in postprocessors[arch.name]:
try:
postprocessor(final_irsb).postprocess()
except NeedStatementsNotification:
# The post-processor cannot work without statements. Re-lift the current block with skip_stmts=False
if not skip_stmts:
# sanity check
# Why does the post-processor raise NeedStatementsNotification when skip_stmts is False?
raise TypeError(
"Bad post-processor %s: "
"NeedStatementsNotification is raised when statements are available." % postprocessor.__class__
)
# Re-lift the current IRSB
return lift(
data,
addr,
arch,
max_bytes=max_bytes,
max_inst=max_inst,
bytes_offset=bytes_offset,
opt_level=opt_level,
traceflags=traceflags,
strict_block_end=strict_block_end,
inner=inner,
skip_stmts=False,
collect_data_refs=collect_data_refs,
)
except LiftingException:
continue
return final_irsb
def register(lifter, arch_name):
"""
Registers a Lifter or Postprocessor to be used by pyvex. Lifters are are given priority based on the order
in which they are registered. Postprocessors will be run in registration order.
:param lifter: The Lifter or Postprocessor to register
:vartype lifter: :class:`Lifter` or :class:`Postprocessor`
"""
if issubclass(lifter, Lifter):
log.debug("Registering lifter %s for architecture %s.", lifter.__name__, arch_name)
lifters[arch_name].append(lifter)
if issubclass(lifter, Postprocessor):
log.debug("Registering postprocessor %s for architecture %s.", lifter.__name__, arch_name)
postprocessors[arch_name].append(lifter)
from ..const import vex_int_class
from .libvex import LibVEXLifter
from .zerodivision import ZeroDivisionPostProcessor
from . import gym
__all__ = ["lift", "register", "Lifter", "Postprocessor", "LibVEXLifter", "ZeroDivisionPostProcessor", "gym"]