about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorTheofilos Augoustis <theofilos.augoustis@gmail.com>2025-10-29 10:08:34 +0000
committerTheofilos Augoustis <theofilos.augoustis@gmail.com>2025-11-06 17:23:54 +0000
commita5f1d04e97091efce462717f484bca02b7fe8af7 (patch)
treec4cad027812f22316bae601477270b0d0e74679e /src
parentcdf397ecd85921eb38b673dae4dbbdf15b5a3e56 (diff)
downloadfocaccia-a5f1d04e97091efce462717f484bca02b7fe8af7.tar.gz
focaccia-a5f1d04e97091efce462717f484bca02b7fe8af7.zip
Add deterministic log as part of trace environment
Diffstat (limited to 'src')
-rw-r--r--src/focaccia/deterministic.py131
-rw-r--r--src/focaccia/symbolic.py5
-rwxr-xr-xsrc/focaccia/tools/capture_transforms.py10
-rw-r--r--src/focaccia/trace.py3
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 270de55..45bbc22 100644
--- a/src/focaccia/symbolic.py
+++ b/src/focaccia/symbolic.py
@@ -764,6 +764,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."""