from typing import List, TYPE_CHECKING
import logging
import claripy
from cle.backends.elf.compilation_unit import CompilationUnit
from cle.backends.elf.variable import Variable
from cle.backends.elf.elf import ELF
from .plugin import KnowledgeBasePlugin
if TYPE_CHECKING:
from ..knowledge_base import KnowledgeBase
l = logging.getLogger(name=__name__)
[docs]class DebugVariableContainer:
"""
Variable tree for variables with same name to lock up which variable is visible at a given program counter address.
"""
def __init__(self):
"""
It is recommended to use DebugVariableManager.add_variable() instead
"""
self.less_visible_vars = []
def _insertvar(self, var: "DebugVariable"):
for i, v in enumerate(self.less_visible_vars):
if var.test_unsupported_overlap(v):
if var.cle_variable.declaration_only:
# ignore var
return
elif v.cle_variable.declaration_only:
# ignore v
self.less_visible_vars[i] = var
var.less_visible_vars = v.less_visible_vars
return
else:
l.warning(
'Unsupported variable with overlapping scopes. Have "%s" with %d-%d and ignore %d-%d.',
v.cle_variable.name,
v.low_pc,
v.high_pc,
var.low_pc,
var.high_pc,
)
return
if var.contains(v):
self.less_visible_vars[i] = var
var.less_visible_vars.append(v)
return
if v.contains(var):
v._insertvar(var)
return
self.less_visible_vars.append(var)
def __setitem__(self, index, value):
assert isinstance(index, slice) and isinstance(value, Variable)
low_pc = index.start
high_pc = index.stop
dvar = DebugVariable(low_pc, high_pc, value)
return self._insertvar(dvar)
[docs] def from_pc(self, pc) -> Variable:
"""
Returns the visible variable (if any) for a given pc address.
"""
for var in self.less_visible_vars:
if claripy.is_true(var.low_pc <= pc) and claripy.is_true(pc < var.high_pc):
return var.from_pc(pc)
return None
def __getitem__(self, index):
return self.from_pc(index)
[docs]class DebugVariable(DebugVariableContainer):
"""
:ivar low_pc: Start of the visibility scope of the variable as program counter address (rebased)
:ivar high_pc: End of the visibility scope of the variable as program counter address (rebased)
:ivar cle_variable: Original variable from cle
"""
def __init__(self, low_pc: int, high_pc: int, cle_variable: Variable):
"""
It is recommended to use DebugVariableManager.add_variable() instead
"""
super().__init__()
self.low_pc = low_pc
self.high_pc = high_pc
self.cle_variable = cle_variable
# overwrites the method of DebugVariableContainer
[docs] def from_pc(self, pc) -> Variable:
if claripy.is_true(pc < self.low_pc) or claripy.is_true(self.high_pc < pc):
# not within range
return None
for var in self.less_visible_vars:
if claripy.is_true(var.low_pc <= pc) and claripy.is_true(pc < var.high_pc):
return var.from_pc(pc)
return self.cle_variable
[docs] def contains(self, dvar: "DebugVariable") -> bool:
return self.low_pc <= dvar.low_pc and dvar.high_pc <= self.high_pc
[docs] def test_unsupported_overlap(self, dvar: "DebugVariable") -> bool:
"""
Test for an unsupported overlapping
:param dvar: Second DebugVariable to compare with
:return: True if there is an unsupported overlapping
"""
l1 = self.low_pc
l2 = dvar.low_pc
h1 = self.high_pc
h2 = dvar.high_pc
if l1 == l2 and h1 == h2:
return True
if l2 < l1 < h2 < h1:
return True
if l1 < l2 < h1 < h2:
return True
return False
[docs]class DebugVariableManager(KnowledgeBasePlugin):
"""
Structure to manage and access variables with different visibility scopes.
"""
def __init__(self, kb: "KnowledgeBase"):
super().__init__()
self._kb: "KnowledgeBase" = kb
self._dvar_containers = {}
[docs] def from_name_and_pc(self, var_name: str, pc_addr: int) -> Variable:
"""
Get a variable from its string in the scope of pc.
"""
dvar = self._dvar_containers[var_name]
return dvar.from_pc(pc_addr)
[docs] def from_name(self, var_name: str) -> DebugVariableContainer:
"""
Get the variable container for all variables named var_name
:param var_name: name for a variable
"""
if var_name not in self._dvar_containers:
self._dvar_containers[var_name] = DebugVariableContainer()
return self._dvar_containers[var_name]
def __getitem__(self, var_name):
assert type(var_name) == str
return self.from_name(var_name)
[docs] def add_variable(self, cle_var: Variable, low_pc: int, high_pc: int):
"""
Add/load a variable
:param cle_variable: The variable to add
:param low_pc: Start of the visibility scope of the variable as program counter address (rebased)
:param high_pc: End of the visibility scope of the variable as program counter address (rebased)
"""
name = cle_var.name
if name not in self._dvar_containers:
self._dvar_containers[name] = DebugVariableContainer()
container = self._dvar_containers[name]
container[low_pc:high_pc] = cle_var
def __setitem__(self, index, cle_var):
assert isinstance(index, slice) and isinstance(cle_var, Variable)
return self.add_variable(cle_var, index.start, index.stop)
# Methods similar to the once in VariableManager
[docs] def add_variable_list(self, vlist: List[Variable], low_pc: int, high_pc: int):
"""
Add all variables in a list with the same visibility range
:param vlist: A list of cle varibles to add
:param low_pc: Start of the visibility scope as program counter address (rebased)
:param high_pc: End of the visibility scope as program counter address (rebased)
"""
for v in vlist:
self.add_variable(v, low_pc, high_pc)
[docs] def load_from_dwarf(self, elf_object: ELF = None, cu: CompilationUnit = None):
"""
Automatically load all variables (global/local) from the DWARF debugging info
:param elf_object: Optional, when only one elf object should be considered (e.g. p.loader.main_object)
:param cu: Optional, when only one compilation unit should be considered
"""
if elf_object:
objs = [elf_object]
else:
objs = self._kb._project.loader.all_elf_objects
for obj in objs:
if cu:
if obj not in obj.compilation_units:
break
cu_list = [cu]
else:
cu_list = obj.compilation_units
for cu_curr in cu_list:
for cle_var in cu_curr.global_variables:
if cle_var.external:
self.add_variable(cle_var, obj.min_addr, obj.max_addr)
else:
# static variable
self.add_variable(cle_var, cu_curr.min_addr, cu_curr.max_addr)
for subp in cu_curr.functions.values():
for cle_var in subp.local_variables:
low_pc = cle_var.lexical_block.low_pc + obj.mapped_base
high_pc = cle_var.lexical_block.high_pc + obj.mapped_base
self.add_variable(cle_var, low_pc, high_pc)
KnowledgeBasePlugin.register_default("dvars", DebugVariableManager)