diff options
| -rw-r--r-- | src/focaccia/deterministic.py | 131 | ||||
| -rw-r--r-- | src/focaccia/symbolic.py | 5 | ||||
| -rwxr-xr-x | src/focaccia/tools/capture_transforms.py | 10 | ||||
| -rw-r--r-- | src/focaccia/trace.py | 3 |
4 files changed, 143 insertions, 6 deletions
diff --git a/src/focaccia/deterministic.py b/src/focaccia/deterministic.py index d05ac7f..f920382 100644 --- a/src/focaccia/deterministic.py +++ b/src/focaccia/deterministic.py @@ -5,6 +5,8 @@ from typing import Union import brotli +from .arch import Arch + try: import capnp rr_trace = capnp.load(file_name='./rr/src/rr_trace.capnp', @@ -18,6 +20,99 @@ TaskEvent = rr_trace.TaskEvent MMap = rr_trace.MMap SerializedObject = Union[Frame, TaskEvent, MMap] +def parse_x64_registers(enc_regs: bytes, signed: bool=False) -> dict[str, int]: + idx = 0 + def parse_reg(): + nonlocal idx + enc_reg = enc_regs[idx:(idx := idx + 8)] + return int.from_bytes(enc_reg, byteorder='little', signed=signed) + + regs = {} + + regs['r15'] = parse_reg() + regs['r14'] = parse_reg() + regs['r13'] = parse_reg() + regs['r12'] = parse_reg() + regs['rbp'] = parse_reg() + regs['rbx'] = parse_reg() + regs['r11'] = parse_reg() + regs['r10'] = parse_reg() + regs['r9'] = parse_reg() + regs['r8'] = parse_reg() + + regs['rax'] = parse_reg() + regs['rcx'] = parse_reg() + regs['rdx'] = parse_reg() + regs['rsi'] = parse_reg() + regs['rdi'] = parse_reg() + regs['orig_rax'] = parse_reg() + regs['rip'] = parse_reg() + regs['cs'] = parse_reg() + regs['eflags'] = parse_reg() + regs['rsp'] = parse_reg() + regs['ss'] = parse_reg() + regs['fs_base'] = parse_reg() + regs['ds'] = parse_reg() + regs['es'] = parse_reg() + regs['fs'] = parse_reg() + regs['gs'] = parse_reg() + regs['gs_base'] = parse_reg() + + return regs + +def parse_aarch64_registers(enc_regs: bytes, order: str='little', signed: bool=False) -> dict[str, int]: + idx = 0 + def parse_reg(): + nonlocal idx + enc_reg = enc_regs[idx:(idx := idx + 8)] + return int.from_bytes(enc_reg, byteorder=order, signed=signed) + + regnames = [] + for i in range(32): + regnames.append(f'x{i}') + regnames.append('sp') + regnames.append('pc') + regnames.append('cpsr') + + regs = {} + for i in range(len(regnames)): + regs[regnames[i]] = parse_reg() + + return regs + +class Event: + def __init__(self, + pc: int, + tid: int, + arch: Arch, + event_type: str, + registers: dict[str, int], + memory_writes: dict[int, int]): + self.pc = pc + self.tid = tid + self.arch = arch + self.event_type = event_type + + self.registers = registers + self.mem_writes = memory_writes + + def __repr__(self) -> str: + reg_repr = '' + for reg, value in self.registers.items(): + reg_repr += f'{reg} = {hex(value)}\n' + + mem_write_repr = '' + for addr, size in self.mem_writes.items(): + mem_write_repr += f'{hex(addr)}:{hex(addr+size)}\n' + + repr_str = f'Thread {hex(self.tid)} executed event {self.event_type} at {hex(self.pc)}\n' + repr_str += f'Register set:\n{reg_repr}' + + if len(self.mem_writes): + repr_str += f'\nMemory writes:\n{mem_write_repr}' + + return repr_str + class DeterministicLog: def __init__(self, log_dir: str): self.base_directory = log_dir @@ -37,12 +132,42 @@ class DeterministicLog: data = brotli.decompress(f.read()) return obj.read_multiple_bytes_packed(data) - def events(self): + def raw_events(self) -> list[SerializedObject]: return self._read(self.events_file(), Frame) - def tasks(self): + def raw_tasks(self) -> list[SerializedObject]: return self._read(self.tasks_file(), TaskEvent) - def mmaps(self): + def raw_mmaps(self) -> list[SerializedObject]: return self._read(self.mmaps_file(), MMap) + def events(self) -> list[Event]: + def parse_registers(event: Frame) -> Union[int, dict[str, int]]: + arch = event.arch + if arch == rr_trace.Arch.x8664: + regs = parse_x64_registers(event.registers.raw) + return regs['rip'], regs + if arch == rr_trace.Arch.aarch64: + regs = parse_aarch64_registers(event.registers.raw) + return regs['pc'], regs + raise NotImplementedError(f'Unable to parse registers for architecture {arch}') + + def parse_memory_writes(event: Frame) -> dict[int, int]: + writes = {} + for raw_write in event.memWrites: + writes[int(raw_write.addr)] = int(raw_write.size) + return writes + + events = [] + raw_events = self.raw_events() + for raw_event in raw_events: + pc, registers = parse_registers(raw_event) + mem_writes = parse_memory_writes(raw_event) + event = Event(pc, + raw_event.tid, + raw_event.arch, + raw_event.event.which(), + registers, mem_writes) + events.append(event) + return events + diff --git a/src/focaccia/symbolic.py b/src/focaccia/symbolic.py index 19fa5c1..44f8cb7 100644 --- a/src/focaccia/symbolic.py +++ b/src/focaccia/symbolic.py @@ -762,6 +762,11 @@ class SymbolicTracer: ctx = DisassemblyContext(self.target) arch = ctx.arch + debug('Non-deterministic events handled:') + nondet_events = self.env.detlog.events() + for event in nondet_events: + debug(event) + # Trace concolically strace: list[SymbolicTransform] = [] while not self.target.is_exited(): diff --git a/src/focaccia/tools/capture_transforms.py b/src/focaccia/tools/capture_transforms.py index f41d6f4..7e28ed3 100755 --- a/src/focaccia/tools/capture_transforms.py +++ b/src/focaccia/tools/capture_transforms.py @@ -53,6 +53,16 @@ def main(): if args.deterministic_log: from focaccia.deterministic import DeterministicLog detlog = DeterministicLog(args.deterministic_log) + else: + class NullDeterministicLog: + def __init__(self): pass + def events_file(self): return None + def tasks_file(self): return None + def mmaps_file(self): return None + def events(self): return [] + def tasks(self): return [] + def mmaps(self): return [] + detlog = NullDeterministicLog() env = TraceEnvironment(args.binary, args.args, utils.get_envp(), nondeterminism_log=detlog) tracer = SymbolicTracer(env, remote=args.remote, cross_validate=args.cross_validate, diff --git a/src/focaccia/trace.py b/src/focaccia/trace.py index f274418..4a4509d 100644 --- a/src/focaccia/trace.py +++ b/src/focaccia/trace.py @@ -22,9 +22,6 @@ class TraceEnvironment: else: self.binary_hash = binary_hash - def is_deterministic(self) -> bool: - return self.detlog is not None - @classmethod def from_json(cls, json: dict) -> TraceEnvironment: """Parse a JSON object into a TraceEnvironment.""" |