summary refs log tree commit diff
diff options
context:
space:
mode:
authorNguyễn Gia Phong <cnx@loang.net>2024-10-31 14:59:17 +0900
committerNguyễn Gia Phong <cnx@loang.net>2024-10-31 14:59:17 +0900
commit95f3fe2b800940f75949b069f50a2da4712435fd (patch)
tree5d666af0ce716adcbb740f3cb4fd5a4688604c68
parent11ab3cc687bfeb64e1bb223e4e690fe423e6a15c (diff)
downloadtaosc-95f3fe2b800940f75949b069f50a2da4712435fd.tar.gz
Glue things together into a pipeline
-rw-r--r--Makefile34
-rw-r--r--collect.c47
-rw-r--r--fix.m446
-rw-r--r--patch.c102
-rw-r--r--scout.cc256
-rw-r--r--synth55
-rw-r--r--taosc.cc279
7 files changed, 535 insertions, 284 deletions
diff --git a/Makefile b/Makefile
index d768151..4ee0a88 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,32 @@
-CXXFLAGS += -std=c++20
-LDFLAGS += -lcommon -linstructionAPI -lparseAPI # dyninst
+CXXFLAGS += -g -std=c++23 -Wextra -Werror
+LDFLAGS += -lcommon -ldyninstAPI -linstructionAPI -lparseAPI # dyninst
+PREFIX ?= /usr/local
 
-.PHONY: all clean
-all: taosc
+BIN := fix scout synth
+BIN_PREFIX := $(DESTDIR)$(PREFIX)/bin/taosc-
+DATA := collect patch
+DATA_DIR := $(DESTDIR)$(PREFIX)/share/taosc
+
+.PHONY: all clean install
+
+all: $(BIN) $(DATA)
 
 clean:
-	rm taosc
+	rm -f $(BIN) $(DATA)
+
+fix: fix.m4
+	m4 -D DATA_DIR=${DATA_DIR} $< > $@
+
+collect: collect.c
+	e9compile $<
+
+patch: patch.c
+	e9compile $<
+
+install: $(addprefix $(BIN_PREFIX),$(BIN)) $(addprefix $(DATA_DIR)/,$(DATA))
+
+$(BIN_PREFIX)%: %
+	install -Dm 755 $< $@
+
+$(DATA_DIR)/%: %
+	install -Dm 644 $< $@
diff --git a/collect.c b/collect.c
new file mode 100644
index 0000000..875213a
--- /dev/null
+++ b/collect.c
@@ -0,0 +1,47 @@
+/*
+ * Live variable collector
+ * Copyright (C) 2021, 2023  Gregory James Duck
+ * Copyright (C) 2024  Nguyễn Gia Phong
+ *
+ * This file is part of taosc.
+ *
+ * Taosc is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Taosc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with taosc.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "stdlib.c"
+
+char *path;
+
+void init(int argc, const char *const *argv, const char **envp)
+{
+	environ = envp;
+	path = getenv("TAOSC_COLLECT"); /* TODO: fopen */
+	setvbuf(stderr, NULL, _IOFBF, 0);
+}
+
+
+void log(const struct STATE *state)
+{
+	static mutex_t mutex = MUTEX_INITIALIZER;
+	if (mutex_lock(&mutex) < 0)
+		return;
+	clearerr(stderr);
+	fprintf(stderr, "[begin]\n");
+	const int64_t *const env = (const int64_t *) state;
+	for (unsigned char i = 1; i < sizeof(state) / sizeof(int64_t); ++i)
+		fprintf(stderr, "%hhu %lld\n", i, env[i]);
+	fprintf(stderr, "[end]\n");
+	fflush(stderr);
+	mutex_unlock(&mutex);
+}
diff --git a/fix.m4 b/fix.m4
new file mode 100644
index 0000000..cbf3725
--- /dev/null
+++ b/fix.m4
@@ -0,0 +1,46 @@
+#!/bin/sh
+# Patcher
+# Copyright (C) 2024  Nguy?n Gia Phong
+#
+# This file is part of taosc.
+#
+# Taosc is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Taosc is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with taosc.  If not, see <https://www.gnu.org/licenses/>.
+
+set -e
+if test $# -ne 3
+then
+  echo Usage: taosc-fix binary instruction-address working-directory
+  exit 1
+fi
+binary="$(realpath $1)"
+address="$2"
+wd="$(realpath $3)"
+
+pushd DATA_DIR > /dev/null
+trap 'popd > /dev/null' EXIT
+collect="$wd/$(basename $binary).collect"
+e9tool -M addr=$address -P 'log(state)@collect' -o "$collect.orig" "$binary"
+afl-dyninst -i "$collect.orig" -o "$collect"
+patched="$wd/$(basename $binary).patched"
+e9tool -M addr=$address -P 'if dest(state)@patch goto' -o "$patched" "$binary"
+
+taosc-scout "$binary" "$address" > "$wd/destinations"
+#for dest in $(taosc-slice "$binary" "$address")
+#do
+#  for dest in $(taosc-slice "$binary" "$address")
+#  do
+#    TAOSC_PREDICATE="<v15p0" TAOSC_DESTINATION=$dest $patched\
+#      -d /home/cnx/Sauces/apr/vulnfix/data/binutils/cve_2017_14745/exploit
+#  done
+#done
diff --git a/patch.c b/patch.c
new file mode 100644
index 0000000..2aa697b
--- /dev/null
+++ b/patch.c
@@ -0,0 +1,102 @@
+/*
+ * Dynamic patch
+ * Copyright (C) 2024  Nguyễn Gia Phong
+ *
+ * This file is part of taosc.
+ *
+ * Taosc is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Taosc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with taosc.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "stdlib.c"
+
+const char *taosc_predicate;
+const void *taosc_destination;
+
+/*
+ * Get an environment variable and parse as a number.
+ * Return 0 on error.
+ */
+uint64_t getenvull(const char *name)
+{
+	const char *const s = getenv(name);
+	if (s == NULL)
+		return 0ULL;
+	errno = 0;
+	const uint64_t u = strtoull(s, NULL, 0);
+	if (errno)
+		return 0ULL;
+	return u;
+}
+
+void init(int argc, const char *const *argv, const char **envp)
+{
+	environ = envp;
+	taosc_predicate = getenv("TAOSC_PREDICATE");
+	if (taosc_predicate == NULL)
+		taosc_predicate = "p0"; /* false */
+	taosc_destination = (void *) getenvull("TAOSC_DESTINATION");
+}
+
+/* Parse *p as an integer. */
+int64_t scani(const char **p)
+{
+	int64_t i = 0;
+	for (; **p >= '0' && **p <= '9'; ++*p)
+		i = i * 10 + **p - '0';
+	return i;
+}
+
+/* Parse and evaluate *ptr in a prefix Polish notation, recursively. */
+int64_t eval(const char **ptr, const int64_t *env)
+{
+	const char op = *(*ptr)++;
+	switch (op) {
+	case 'n': /* negative integer */
+		return -scani(ptr);
+	case 'p': /* positive integer */
+		return scani(ptr);
+	case 'v': /* variable look up */
+		return env[scani(ptr)];
+	}
+	const bool eq = **ptr == '=';
+	*ptr += eq;
+	const int64_t a = eval(ptr, env);
+	const int64_t b = eval(ptr, env);
+	switch (op) {
+	case '=':
+		return a == b;
+	case '!':
+		return a != b;
+	case '>':
+		return eq ? (a >= b) : (a > b);
+	case '<':
+		return eq ? (a <= b) : (a < b);
+	case '+':
+		return a + b;
+	case '-':
+		return a - b;
+	case '*':
+		return a * b;
+	case '/':
+		return a / b;
+	default:
+		__builtin_unreachable();
+	}
+}
+
+const void *dest(const struct STATE *state)
+{
+	const char *tmp = taosc_predicate;
+	return eval(&tmp, (const int64_t *) state) ? NULL : taosc_destination;
+}
diff --git a/scout.cc b/scout.cc
new file mode 100644
index 0000000..6c3ddb5
--- /dev/null
+++ b/scout.cc
@@ -0,0 +1,256 @@
+// Patch's jump destinations searcher
+// Copyright (C) 2024  Nguyễn Gia Phong
+//
+// This file is part of taosc.
+//
+// Taosc is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Taosc is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with taosc.  If not, see <https://www.gnu.org/licenses/>.
+
+// Dyninst headers
+#include <CFG.h>
+#include <CodeObject.h>
+#include <Graph.h>
+#include <Instruction.h>
+#include <InstructionDecoder.h>
+#include <slicing.h>
+
+using AbstractRegion = Dyninst::AbsRegion;
+using Address = Dyninst::Address;
+using AssignmentConverter = Dyninst::AssignmentConverter;
+using AssignmentPtr = Dyninst::Assignment::Ptr;
+using Block = Dyninst::ParseAPI::Block;
+using CodeObject = Dyninst::ParseAPI::CodeObject;
+using CodeRegion = Dyninst::ParseAPI::CodeRegion;
+using CodeSource = Dyninst::ParseAPI::SymtabCodeSource;
+using Edge = Dyninst::Edge;
+using EdgeIterator = Dyninst::EdgeIterator;
+using Function = Dyninst::ParseAPI::Function;
+using Graph = Dyninst::Graph;
+using Instruction = Dyninst::InstructionAPI::Instruction;
+using InstructionCategory = Dyninst::InstructionAPI::InsnCategory;
+using InstructionDecoder = Dyninst::InstructionAPI::InstructionDecoder;
+using NodeIterator = Dyninst::NodeIterator;
+using SliceNode = Dyninst::SliceNode;
+using Slicer = Dyninst::Slicer;
+
+#include <cassert>
+#include <filesystem>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <queue>
+
+/// Collect elements from given iterator into a vector
+template <class Element, class Iterator>
+  std::vector <Element*>
+  range (auto iter)
+  {
+    Iterator begin, end;
+    std::vector <Element*> result;
+    for (iter (begin, end); begin != end; ++begin)
+      result.push_back (static_cast <Element*> ((*begin).get ()));
+    return result;
+  }
+
+class SlicerHelper
+  {
+    CodeSource& cs;
+    InstructionDecoder decoder;
+    Slicer::Predicates predicates;
+    AssignmentConverter ac {true, true};
+    std::set <Address> seen;
+    std::vector <std::vector <Address>> bfs_slices;
+
+  public:
+    SlicerHelper (CodeSource& cs)
+    : cs {cs},
+      decoder {(const void*) nullptr, 1, cs.getArch ()},
+      ac {true, true} // enable caching and stack analysis
+    {
+      this->predicates.setSearchForControlFlowDep(true);
+    }
+
+    /// Decode instruction at given address
+    Instruction
+    decode (Address addr)
+    {
+      auto const& insn = this->decoder.decode ((const unsigned char*)
+        this->cs.getPtrToInstruction (addr));
+      assert (insn.size () > 0);
+      return insn;
+    }
+
+    /// Collect the interprocedure backward slice at addr in BFS order
+    void
+    slice (Instruction const& insn, Address addr, Function* fun, Block* blk)
+    {
+      std::vector <AssignmentPtr> assignments;
+      this->ac.convert (insn, addr, fun, blk, assignments);
+      if (assignments.empty ())
+        return;
+      for (auto const& asgn : assignments)
+        {
+          this->bfs_slices.emplace_back ();
+          Slicer s {asgn, blk, fun};
+          auto const& slice = s.backwardSlice (this->predicates);
+#define ITER(i, E, I, x, f) \
+  for (auto const& i : range <E, I> ([x] (auto& b, auto& e) { x->f (b, e); }))
+          ITER (node, SliceNode, NodeIterator, slice, exitNodes)
+            {
+              this->seen.insert (node->addr ());
+              std::queue <SliceNode*> q; // breadth-first traversal
+              q.push (node);
+              while (!q.empty ())
+                {
+                  auto const& parent = q.front ();
+                  q.pop ();
+                  this->bfs_slices.back ().push_back (parent->addr ());
+                  ITER (edge, Edge, EdgeIterator, parent, ins)
+#undef ITER
+                    {
+                      auto const& child = edge->source ();
+                      if (this->seen.count (child->addr ()) > 0)
+                        continue;
+                      this->seen.insert (child->addr ());
+                      q.push (static_cast <SliceNode*> (child.get ()));
+                    }
+                }
+            }
+        }
+    }
+
+    /// Flatten stored BFS slices in round-robin order
+    std::vector <Address> const
+    slice_zip ()
+    {
+      std::vector <Address> result;
+      size_t n = 0;
+      for (auto const& v : this->bfs_slices)
+        {
+          if (result.empty () || v[0] != result.back ())
+            result.push_back (v[0]);
+          n = std::max (n, v.size ());
+        }
+      for (size_t i = 1; i < n; ++i)
+        for (auto const& v : this->bfs_slices)
+          if (i < v.size ())
+            result.push_back (v[i]);
+      return result;
+    }
+  };
+
+/// Find next basic block's entry after given address, reparsing if necessary
+Block*
+next_block (CodeObject& co, CodeRegion* region, Address address)
+{
+  auto blk = co.findBlockByEntry (region, address);
+  if (blk != nullptr)
+    return blk;
+  co.parse (address, true);
+  blk = co.findBlockByEntry (region, address);
+  return (blk != nullptr) ? blk : co.findNextBlock (region, address);
+}
+
+/// Find block containing given address
+Block*
+find_block (CodeSource& cs, CodeObject& co, Address target_addr)
+{
+  if (!cs.isCode (target_addr))
+    {
+      std::cerr << std::hex << target_addr
+        << " does not point to an instruction\n";
+      return nullptr;
+    }
+  std::set <CodeRegion*> regions;
+  if (cs.findRegions (target_addr, regions) != 1)
+    {
+      std::cerr << "expected 1 region containing instruction, found "
+        << regions.size () << '\n';
+      return nullptr;
+    }
+  for (auto const& region : regions)
+    {
+      std::set <Block*> blocks;
+      if (co.findBlocks (region, target_addr, blocks) > 0)
+        for (auto const& blk : blocks) // TODO: choose the best block
+          return blk;
+
+      auto* blk = next_block (co, region, region->low ());
+      while (blk != nullptr && target_addr > blk->last ())
+        blk = next_block (co, region, blk->end ());
+      if (blk == nullptr)
+        return nullptr;
+      assert (target_addr >= blk->start () && target_addr < blk->end ());
+      return blk;
+    }
+#if defined(__cpp_lib_unreachable) && (__cpp_lib_unreachable >= 202202L)
+  std::unreachable ();
+#else
+  __builtin_unreachable(); // GCC or Clang
+#endif
+}
+
+/// Slice backward from return instructions
+std::vector <Address> const
+returns_slice (CodeSource& cs, Function* fun, Address target_addr)
+{
+  SlicerHelper helper {cs};
+  for (auto const& blk : fun->blocks ())
+    for (auto [addr, step] = std::tuple {blk->start (), 0ul};
+         addr < blk->end ();
+         addr += step)
+      {
+        auto const& insn = helper.decode (addr);
+        step = insn.size ();
+        if (insn.getCategory () == InstructionCategory::c_ReturnInsn)
+          helper.slice (insn, addr, fun, blk);
+      }
+  return helper.slice_zip ();
+}
+
+int
+main (int argc, char** argv)
+{
+  if (argc != 3)
+    {
+      std::cerr << "Usage: " << std::filesystem::path (argv[0]).filename ()
+        << " binary instruction-address\n";
+      return -1;
+    }
+  CodeSource cs {argv[1]};
+  Address target_addr;
+  {
+    std::stringstream ss;
+    ss << std::hex << argv[2];
+    ss >> target_addr;
+  }
+  CodeObject co {&cs};
+  co.parse (); // parsed functions have same lifetime as co
+  auto const& block = find_block (cs, co, target_addr);
+  if (block == nullptr)
+    {
+      std::cerr << "block containing instruction not found\n";
+      return -1;
+    }
+  std::vector<Function*> functions;
+  block->getFuncs (functions);
+  if (functions.size () != 1)
+    {
+      std::cerr << "0 or multiple functions containing instruction found\n";
+      return -1;
+    }
+  for (auto* fun : functions)
+    for (auto const& addr : returns_slice (cs, fun, target_addr))
+      std::cout << std::hex << addr << '\n';
+  return 0;
+}
diff --git a/synth b/synth
new file mode 100644
index 0000000..4594d0a
--- /dev/null
+++ b/synth
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# Patch's predicate synthesizer
+# Copyright (C) 2024  Nguyễn Gia Phong
+#
+# This file is part of taosc.
+#
+# Taosc is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Taosc is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with taosc.  If not, see <https://www.gnu.org/licenses/>.
+
+from contextlib import redirect_stderr
+from os import devnull
+from os.path import basename
+from pathlib import Path
+from sys import argv, stdout
+
+from pacfix import learn
+from pacfix.invariant import INVARIANT_MAP, InvariantType
+from pacfix.utils import get_live_vars, get_valuations, parse_valuation
+
+
+def encode(inv, file):
+    if inv.inv_type == InvariantType.VAR:
+        file.write(f"v{inv.data}")
+    elif inv.inv_type == InvariantType.CONST:
+        file.write(f"{'n' if inv.data < 0 else 'p'}{abs(inv.data)}")
+    else:
+        file.write(INVARIANT_MAP[inv.inv_type])
+        encode(inv.left, file)
+        encode(inv.right, file)
+
+
+if len(argv) != 3:
+    print(f'usage: taosc-synth input-dir delta')
+    exit(1)
+input_dir = Path(argv[1])
+delta = float(argv[2])
+
+with open(input_dir / 'vars') as f: live_vars = get_live_vars(f)
+vals_neg, vals_pos = parse_valuation(get_valuations(input_dir / "neg"),
+                                     get_valuations(input_dir / "pos"))
+with open(devnull, 'w') as f, redirect_stderr(f):
+    result = learn(live_vars, vals_neg, vals_pos, delta)
+for i in result.inv_mgr.invs:
+    encode(i, stdout)
+    print()
diff --git a/taosc.cc b/taosc.cc
deleted file mode 100644
index 857fcf5..0000000
--- a/taosc.cc
+++ /dev/null
@@ -1,279 +0,0 @@
-// Slicing-based binary fault localizer and patch searcher
-// Copyright (C) 2024  Nguyễn Gia Phong
-//
-// This file is part of taosc.
-//
-// Taosc is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Taosc is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with taosc.  If not, see <https://www.gnu.org/licenses/>.
-
-// Dyninst headers
-#include <CFG.h>
-#include <CodeObject.h>
-#include <Graph.h>
-#include <Instruction.h>
-#include <InstructionDecoder.h>
-#include <slicing.h>
-
-using AbstractRegion = Dyninst::AbsRegion;
-using Address = Dyninst::Address;
-using AssignmentConverter = Dyninst::AssignmentConverter;
-using AssignmentPtr = Dyninst::Assignment::Ptr;
-using Block = Dyninst::ParseAPI::Block;
-using CodeObject = Dyninst::ParseAPI::CodeObject;
-using CodeRegion = Dyninst::ParseAPI::CodeRegion;
-using CodeSource = Dyninst::ParseAPI::SymtabCodeSource;
-using Edge = Dyninst::Edge;
-using EdgeIterator = Dyninst::EdgeIterator;
-using Function = Dyninst::ParseAPI::Function;
-using Graph = Dyninst::Graph;
-using Instruction = Dyninst::InstructionAPI::Instruction;
-using InstructionDecoder = Dyninst::InstructionAPI::InstructionDecoder;
-using InstructionCategory = Dyninst::InstructionAPI::InsnCategory;
-using NodeIterator = Dyninst::NodeIterator;
-using SliceNode = Dyninst::SliceNode;
-using Slicer = Dyninst::Slicer;
-
-#include <cassert>
-#include <functional>
-#include <iostream>
-#include <map>
-#include <queue>
-
-class SearchingAll : public Slicer::Predicates
-  {
-  public:
-    std::vector <Function*>
-    followCallBackward (Block* caller,
-                        CallStack_t& call_stack,
-                        AbstractRegion argument) override
-    {
-      std::vector <Function*> callers;
-      caller->getFuncs (callers);
-      return callers;
-    }
-  };
-
-template <class Element, class Iterator>
-  std::vector <Element*>
-  sorted (auto iter)
-  {
-    Iterator begin, end;
-    std::vector <Element*> result;
-    for (iter (begin, end); begin != end; ++begin)
-      result.push_back (static_cast <Element*> ((*begin).get ()));
-    return result;
-  }
-
-template <class SlicerPredicates>
-  class SlicerHelper
-    {
-      CodeSource& cs;
-      InstructionDecoder decoder;
-      SlicerPredicates predicates;
-      AssignmentConverter ac {true, true};
-      std::set <Address> seen;
-      std::vector <std::vector <Address>> bfs_slices;
-
-    public:
-      SlicerHelper (CodeSource& cs)
-      : cs {cs},
-        decoder {(const void*) nullptr, 1, cs.getArch ()},
-        ac {true, true} // enable caching and stack analysis
-      {
-        this->predicates.setSearchForControlFlowDep(true);
-      }
-
-      Instruction
-      decode (Address addr)
-      {
-        auto const& insn = this->decoder.decode ((const unsigned char *)
-          this->cs.getPtrToInstruction (addr));
-        assert (insn.size () > 0);
-        return insn;
-      }
-
-#define ITER(i, E, I, x, f) \
-  for (auto const& i : sorted <E, I> ([x] (auto& b, auto& e) { x->f (b, e); }))
-
-      void
-      slice (Instruction const& insn, Address addr, Function* fun, Block* blk)
-      {
-        std::vector <AssignmentPtr> assignments;
-        this->ac.convert (insn, addr, fun, blk, assignments);
-        if (assignments.empty ())
-          return;
-        for (auto const& asgn : assignments)
-          {
-            this->bfs_slices.emplace_back ();
-            Slicer s {asgn, blk, fun};
-            auto const& slice = s.backwardSlice (this->predicates);
-            ITER (node, SliceNode, NodeIterator, slice, exitNodes)
-              {
-                this->seen.insert (node->addr ());
-                std::queue <SliceNode*> q; // breadth-first traversal
-                q.push (node);
-                while (!q.empty ())
-                  {
-                    auto const& parent = q.front ();
-                    q.pop ();
-                    this->bfs_slices.back ().push_back (parent->addr ());
-                    ITER (edge, Edge, EdgeIterator, parent, ins)
-                      {
-                        auto const& child = edge->source ();
-                        if (this->seen.count (child->addr ()) > 0)
-                          continue;
-                        this->seen.insert (child->addr ());
-                        q.push (static_cast <SliceNode*> (child.get ()));
-                      }
-                  }
-              }
-          }
-      }
-
-#undef ITER
-
-      std::vector <Address>
-      slice_zip ()
-      {
-        std::vector <Address> result;
-        size_t n = 0;
-        for (auto const& v : this->bfs_slices)
-          {
-            if (result.empty () || v[0] != result.back ())
-              result.push_back (v[0]);
-            n = std::max (n, v.size ());
-          }
-        for (size_t i = 1; i < n; ++i)
-          for (auto const& v : this->bfs_slices)
-            if (i < v.size ())
-              result.push_back (v[i]);
-        return result;
-      }
-    };
-
-Block*
-next_block (CodeObject& co, CodeRegion* region, Address address)
-{
-  auto blk = co.findBlockByEntry (region, address);
-  if (blk != nullptr)
-    return blk;
-  co.parse (address, true);
-  blk = co.findBlockByEntry (region, address);
-  return (blk != nullptr) ? blk : co.findNextBlock (region, address);
-}
-
-Block*
-find_block (CodeSource& cs, CodeObject& co, Address target_addr)
-{
-  if (!cs.isCode (target_addr))
-    {
-      std::cerr << std::hex << target_addr
-        << " does not point to an instruction\n";
-      return nullptr;
-    }
-  std::set <CodeRegion*> regions;
-  cs.findRegions (target_addr, regions);
-  if (regions.size () != 1)
-    {
-      std::cerr << "expected 1 region containing instruction, found "
-        << regions.size () << '\n';
-      return nullptr;
-    }
-  for (auto const& region : regions)
-    {
-      std::set <Block*> blocks;
-      co.findBlocks (region, target_addr, blocks);
-      if (blocks.size () == 1)
-        for (auto const& blk : blocks)
-          return blk;
-
-      auto blk = next_block (co, region, region->low ());
-      while (blk != nullptr && target_addr > blk->last ())
-        blk = next_block (co, region, blk->end ());
-      if (blk == nullptr)
-        return nullptr;
-      assert (target_addr >= blk->start () && target_addr < blk->end ());
-      return blk;
-    }
-  // std::unreachable();
-  return nullptr;
-}
-
-std::vector <Address> const
-patch_locations (CodeSource& cs,
-                 std::vector<Function*> const& functions,
-                 Block* block,
-                 Address target_addr)
-{
-  SlicerHelper <SearchingAll> helper {cs};
-  std::map <Address, std::string> locations;
-  for (auto const& fun : functions)
-    helper.slice (helper.decode (target_addr), target_addr, fun, block);
-  return helper.slice_zip ();
-}
-
-std::vector <Address> const
-return_values (CodeSource& cs,
-               std::vector<Function*> const& functions,
-               Block* block,
-               Address target_addr)
-{
-  SlicerHelper <Slicer::Predicates> helper {cs};
-  for (auto const& fun : functions)
-    for (auto const& blk : fun->blocks ())
-      for (auto [addr, step] = std::tuple {blk->start (), 0ul};
-           addr < blk->end ();
-           addr += step)
-        {
-          auto const& insn = helper.decode (addr);
-          step = insn.size ();
-          if (insn.getCategory () == InstructionCategory::c_ReturnInsn)
-            helper.slice (insn, addr, fun, blk);
-        }
-  return helper.slice_zip ();
-}
-
-int
-main (int argc, char** argv)
-{
-  if (argc != 3)
-    {
-      std::cerr << "Usage: " << argv[0]
-        << " <binary path> <instruction address>\n";
-      return -1;
-    }
-  CodeSource cs {argv[1]};
-  Address target_addr;
-  {
-    std::stringstream ss;
-    ss << std::hex << argv[2];
-    ss >> target_addr;
-  }
-  CodeObject co {&cs};
-  co.parse (); // parsed functions have same lifetime as co
-  auto const& block = find_block (cs, co, target_addr);
-  if (block == nullptr)
-    {
-      std::cerr << "block containing instruction not found\n";
-      return -1;
-    }
-  std::vector<Function*> functions;
-  block->getFuncs (functions);
-
-  for (auto const& addr : patch_locations (cs, functions, block, target_addr))
-    std::cout << std::hex << addr << '\n';
-  std::cout << "------\n";
-  for (auto const& addr : return_values (cs, functions, block, target_addr))
-    std::cout << std::hex << addr << '\n';
-  return 0;
-}