about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--flake.nix3
-rw-r--r--pyproject.toml1
-rw-r--r--src/focaccia/deterministic.py48
-rwxr-xr-xsrc/focaccia/tools/capture_transforms.py10
-rw-r--r--src/focaccia/trace.py7
-rw-r--r--uv.lock26
6 files changed, 92 insertions, 3 deletions
diff --git a/flake.nix b/flake.nix
index a523c21..5aebf84 100644
--- a/flake.nix
+++ b/flake.nix
@@ -331,7 +331,6 @@
 
 			validate-qemu = {
 				type = "app";
-				# program = "${packages.focaccia}/bin/validate-qemu";
 				program = let
 					wrapper = pkgs.writeShellScriptBin "validate-qemu" ''
 						exec ${packages.focaccia}/bin/validate-qemu --gdb "${gdbInternal}/bin/gdb" "$@"
@@ -400,6 +399,7 @@
 					packages.dev
 					rr
 					musl-pkgs.gcc
+					pkgs.capnproto
 					musl-pkgs.pkg-config
 				];
 
@@ -416,6 +416,7 @@
 					musl-minimal-pkgs.pkgsStatic.gzip
 					musl-minimal-pkgs.pkgsStatic.file
 					musl-pkgs.gcc
+					pkgs.capnproto
 					musl-pkgs.pkg-config
 					musl-minimal-redis-nocheck
 				];
diff --git a/pyproject.toml b/pyproject.toml
index c92cf14..a6c4460 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,6 +14,7 @@ authors = [
 dependencies = [
 	"cffi",
 	"miasm",
+	"jinja2",
 	"brotli",
 	"pycapnp",
 	"setuptools",
diff --git a/src/focaccia/deterministic.py b/src/focaccia/deterministic.py
new file mode 100644
index 0000000..d05ac7f
--- /dev/null
+++ b/src/focaccia/deterministic.py
@@ -0,0 +1,48 @@
+"""Parsing of JSON files containing snapshot data."""
+
+import os
+from typing import Union
+
+import brotli
+
+try:
+    import capnp
+    rr_trace = capnp.load(file_name='./rr/src/rr_trace.capnp',
+                          imports=[os.path.dirname(p) for p in capnp.__path__])
+except Exception as e:
+    print(f'Cannot load RR trace loader: {e}')
+    exit(2)
+
+Frame = rr_trace.Frame
+TaskEvent = rr_trace.TaskEvent
+MMap = rr_trace.MMap
+SerializedObject = Union[Frame, TaskEvent, MMap]
+
+class DeterministicLog:
+    def __init__(self, log_dir: str):
+        self.base_directory = log_dir
+
+    def events_file(self) -> str:
+        return os.path.join(self.base_directory, 'events')
+
+    def tasks_file(self) -> str:
+        return os.path.join(self.base_directory, 'tasks')
+
+    def mmaps_file(self) -> str:
+        return os.path.join(self.base_directory, 'mmaps')
+
+    def _read(self, file, obj: SerializedObject) -> list[SerializedObject]:
+        with open(file, 'rb') as f:
+            f.read(8)
+            data = brotli.decompress(f.read())
+            return obj.read_multiple_bytes_packed(data)
+
+    def events(self):
+        return self._read(self.events_file(), Frame)
+
+    def tasks(self):
+        return self._read(self.tasks_file(), TaskEvent)
+
+    def mmaps(self):
+        return self._read(self.mmaps_file(), MMap)
+
diff --git a/src/focaccia/tools/capture_transforms.py b/src/focaccia/tools/capture_transforms.py
index 0adba43..f41d6f4 100755
--- a/src/focaccia/tools/capture_transforms.py
+++ b/src/focaccia/tools/capture_transforms.py
@@ -1,5 +1,6 @@
 #!/usr/bin/env python3
 
+import sys
 import argparse
 import logging
 
@@ -24,6 +25,8 @@ def main():
     prog.add_argument('-r', '--remote',
                       default=False,
                       help='Remote target to trace (e.g. 127.0.0.1:12345)')
+    prog.add_argument('-l', '--deterministic-log',
+                      help='Path of the directory storing the deterministic log produced by RR')
     prog.add_argument('--log-level',
                       help='Set the logging level')
     prog.add_argument('--force',
@@ -46,7 +49,12 @@ def main():
     else:
         logging.basicConfig(level=logging.INFO)
 
-    env = TraceEnvironment(args.binary, args.args, utils.get_envp())
+    detlog = None
+    if args.deterministic_log:
+        from focaccia.deterministic import DeterministicLog
+        detlog = DeterministicLog(args.deterministic_log)
+
+    env = TraceEnvironment(args.binary, args.args, utils.get_envp(), nondeterminism_log=detlog)
     tracer = SymbolicTracer(env, remote=args.remote, cross_validate=args.cross_validate,
                             force=args.force)
     trace = tracer.trace()
diff --git a/src/focaccia/trace.py b/src/focaccia/trace.py
index 829b03f..f274418 100644
--- a/src/focaccia/trace.py
+++ b/src/focaccia/trace.py
@@ -11,15 +11,20 @@ class TraceEnvironment:
                  binary: str,
                  argv: list[str],
                  envp: list[str],
-                 binary_hash: str | None = None):
+                 binary_hash: str | None = None,
+                 nondeterminism_log = None):
         self.argv = argv
         self.envp = envp
         self.binary_name = binary
+        self.detlog = nondeterminism_log
         if binary_hash is None and self.binary_name is not None:
             self.binary_hash = file_hash(binary)
         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."""
diff --git a/uv.lock b/uv.lock
index a9ea1d1..315d51c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -87,6 +87,7 @@ dependencies = [
     { name = "brotli", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
     { name = "cffi", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
     { name = "cpuid", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
+    { name = "jinja2", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
     { name = "miasm", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
     { name = "pycapnp", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
     { name = "setuptools", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
@@ -106,6 +107,7 @@ requires-dist = [
     { name = "brotli" },
     { name = "cffi" },
     { name = "cpuid", git = "https://github.com/taugoust/cpuid.py.git?rev=master" },
+    { name = "jinja2" },
     { name = "miasm", directory = "miasm" },
     { name = "pycapnp" },
     { name = "pyright", marker = "extra == 'dev'" },
@@ -134,6 +136,30 @@ wheels = [
 ]
 
 [[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markupsafe", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
+    { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
+    { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
+    { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
+]
+
+[[package]]
 name = "miasm"
 version = "0.1.5"
 source = { directory = "miasm" }