diff options
-rw-r--r-- | lib/Core/Differentiator.cpp | 54 | ||||
-rw-r--r-- | lib/Core/Differentiator.h | 10 | ||||
-rw-r--r-- | lib/Core/Executor.cpp | 2 | ||||
-rw-r--r-- | runtime/POSIX/fd_init.c | 2 | ||||
-rwxr-xr-x | tools/klee-psychic/klee-psychic | 171 |
5 files changed, 81 insertions, 158 deletions
diff --git a/lib/Core/Differentiator.cpp b/lib/Core/Differentiator.cpp index 95c08bda..553df337 100644 --- a/lib/Core/Differentiator.cpp +++ b/lib/Core/Differentiator.cpp @@ -109,35 +109,41 @@ Differentiator::Differentiator(std::unique_ptr<TimingSolver>* s, Differentiator::Bytes run(const std::uint64_t rev, const char* prog, - const Differentiator::TestArgs& argv) + const Differentiator::TestInps& inputs) { - int fildes[2]; + int ind[2], outd[2]; { - const auto err = pipe(fildes); + auto err = pipe(ind); + assert(!err); + err = pipe(outd); assert(!err); } posix_spawn_file_actions_t action; posix_spawn_file_actions_init(&action); - posix_spawn_file_actions_addclose(&action, fildes[0]); - posix_spawn_file_actions_adddup2(&action, fildes[1], 1); + posix_spawn_file_actions_adddup2(&action, ind[0], 0); + posix_spawn_file_actions_addclose(&action, ind[1]); + posix_spawn_file_actions_addclose(&action, outd[0]); + posix_spawn_file_actions_adddup2(&action, outd[1], 1); { pid_t pid; - std::vector<const char*> argp {prog}; - for (const auto& v : argv) - argp.push_back((const char *) v.data()); - argp.push_back(NULL); + std::vector<const char*> argv {prog}; + for (const auto& v : inputs.first) + argv.push_back((const char *) v.data()); + argv.push_back(NULL); std::ostringstream env; env << "KLEE_META=" << rev; char *const envp[] = {const_cast<char* const>(env.str().c_str()), NULL}; const auto err = posix_spawn(&pid, prog, &action, NULL, - const_cast<char* const *>(argp.data()), envp); + const_cast<char* const *>(argv.data()), envp); assert(!err); } - close(fildes[1]); + write(ind[0], inputs.second.data(), inputs.second.size()); + close(ind[0]); + close(outd[1]); char buffer[128]; // output buffer for concrete execution Differentiator::Bytes result; - for (unsigned char n; n = read(fildes[0], buffer, sizeof(buffer));) { + for (unsigned char n; n = read(outd[0], buffer, sizeof(buffer));) { assert(n >= 0); for (unsigned char i = 0; i < n; ++i) result.push_back(buffer[i]); @@ -156,14 +162,14 @@ logBytes(const Differentiator::Bytes& buffer) } void -logArgs(const Differentiator::TestArgs& args) +logInputs(const Differentiator::TestInps& inputs) { llvm::errs() << "Args:"; - for (const auto& s : args) { + for (const auto& s : inputs.first) { llvm::errs() << ' '; logBytes(s); } - llvm::errs() << '\n'; + llvm::errs() << "\nStdin: " << inputs.second.data() << '\n'; } void @@ -188,7 +194,7 @@ Differentiator::extract(ExecutionState* a, ExecutionState* b, const std::vector<const Array*>& objects, const std::vector<Bytes>& values) { - TestArgs argv; + TestInps inputs; TestOuts outputs; { std::map<std::uint8_t, Bytes> args; @@ -200,18 +206,20 @@ Differentiator::extract(ExecutionState* a, ExecutionState* b, } else if (isSymOut(name.substr(0, name.size() - 2))) { const auto rev = ((name[name.size() - 1] == 'a') ? a : b)->patchNo; outputs[rev].first[name.substr(4, name.size() - 6)] = values[i]; + } else if (name == "stdin") { + inputs.second = values[i]; } } uint8_t last = 0; for (const auto& [rev, arg] : args) { assert(rev == last); - argv.push_back(arg); + inputs.first.push_back(arg); last++; } } for (const auto& rev : this->revisions) - outputs[rev].second = run(rev, this->prog, argv); - this->tests[argv] = outputs; + outputs[rev].second = run(rev, this->prog, inputs); + this->tests[inputs] = outputs; std::map<std::string, Clusters> revOut; for (const auto& [rev, out] : outputs) { @@ -228,7 +236,7 @@ Differentiator::extract(ExecutionState* a, ExecutionState* b, for (std::uint64_t i : p.second) for (std::uint64_t j : q.second) if (i < j) - this->done.emplace(std::make_pair(i, j), &this->tests[argv]); + this->done.emplace(std::make_pair(i, j), &this->tests[inputs]); } } @@ -314,7 +322,7 @@ Differentiator::search(ExecutionState* latest) void Differentiator::log() { - std::vector<std::pair<const TestArgs, const Clusters>> clusterings; + std::vector<std::pair<const TestInps, const Clusters>> clusterings; for (auto& t : this->tests) { Clusters clusters; for (const auto& rev : this->revisions) { @@ -327,8 +335,8 @@ Differentiator::log() if (clusters.size() > 1) clusterings.push_back({std::move(t.first), std::move(clusters)}); } - for (const auto& [args, clusters] : clusterings) { - logArgs(args); + for (const auto& [inputs, clusters] : clusterings) { + logInputs(inputs); logClusters(clusters); llvm::errs() << '\n'; } diff --git a/lib/Core/Differentiator.h b/lib/Core/Differentiator.h index 11225ac7..98c15e92 100644 --- a/lib/Core/Differentiator.h +++ b/lib/Core/Differentiator.h @@ -35,8 +35,8 @@ private: struct Differentiator { /// Buffer of bytes typedef std::vector<unsigned char> Bytes; - /// CLI arguments - typedef std::vector<Bytes> TestArgs; + /// argv stdin + typedef std::pair<std::vector<Bytes>, Bytes> TestInps; /// :rev (:var val) stdout typedef std::map<std::uint64_t, std::pair<std::map<std::string, Bytes>, @@ -59,6 +59,8 @@ struct Differentiator { /// Program revision numbers std::set<std::uint64_t> revisions; + /// Differentiated pairs + std::map<std::pair<std::uint64_t, std::uint64_t>, TestOuts*> done; private: /// Program path for concrete execution. const char* prog; @@ -67,7 +69,7 @@ private: std::set<ExecutionState*, ExecutionStatePathCondCompare>> exits; /// Differential tests - std::map<TestArgs, TestOuts> tests; + std::map<TestInps, TestOuts> tests; /// SMT solver "borrowed" from Executor std::unique_ptr<TimingSolver>* solver; /// SMT solving timeout @@ -76,8 +78,6 @@ private: ArrayCache& arrayCache; /// Symbolic output renamers ExprCmbnVisitor visitorA, visitorB; - /// Differentiated pairs - std::map<std::pair<std::uint64_t, std::uint64_t>, TestOuts*> done; }; } // namespace klee diff --git a/lib/Core/Executor.cpp b/lib/Core/Executor.cpp index 465100f4..34b3d7b8 100644 --- a/lib/Core/Executor.cpp +++ b/lib/Core/Executor.cpp @@ -1104,7 +1104,7 @@ Executor::StatePair Executor::fork(ExecutionState ¤t, ref<Expr> condition, // Fix branch in only-replay-seed mode, if we don't have both true // and false seeds. - if (isSeeding && + if (isSeeding && !Differentiator::extractPatchNumber(condition).first && (current.forkDisabled || OnlyReplaySeeds) && res == Solver::Unknown) { bool trueSeed=false, falseSeed=false; diff --git a/runtime/POSIX/fd_init.c b/runtime/POSIX/fd_init.c index b93148ec..90c04a73 100644 --- a/runtime/POSIX/fd_init.c +++ b/runtime/POSIX/fd_init.c @@ -153,7 +153,7 @@ void klee_init_fds(unsigned n_files, unsigned file_length, __exe_fs.sym_stdout = malloc(sizeof(*__exe_fs.sym_stdout)); if (!__exe_fs.sym_stdout) klee_report_error(__FILE__, __LINE__, "out of memory in klee_init_env", "user.err"); - __create_new_dfile(__exe_fs.sym_stdout, stdout_length, "stdout", &s); + __create_new_dfile(__exe_fs.sym_stdout, stdout_length, "out!stdout!0", &s); __exe_env.fds[1].dfile = __exe_fs.sym_stdout; __exe_fs.stdout_writes = 0; } diff --git a/tools/klee-psychic/klee-psychic b/tools/klee-psychic/klee-psychic index 6f1c1bcf..271c7496 100755 --- a/tools/klee-psychic/klee-psychic +++ b/tools/klee-psychic/klee-psychic @@ -1,128 +1,43 @@ -#!/usr/bin/env python3 -import sys -import os -import subprocess -import tempfile -import select -import shutil -from itertools import dropwhile, takewhile -from pathlib import Path - -HELP="""OVERVIEW: ZESTI like wrapper of KLEE/Psychic - -USAGE: klee-psychic [klee-options] <input bytecode> <concrete program> - <concrete program arguments> - -WARNING this script is not equivalent to ZESTI in ICSE 2012. It just provides a similar interface to KLEE. Namely it first explores the path of <concrete program arguments> and then continues symbolic execution from that point. Most importantly it does not implement the ZESTI searcher. -""" - - -KLEE="klee" -KTEST_GEN="ktest-gen" - -def find_klee_bin_dir(): - global KLEE - global KTEST_GEN - bin_dir = os.path.dirname(os.path.realpath(__file__)) - KLEE = bin_dir + "/klee" - KTEST_GEN = bin_dir + "/ktest-gen" - if not os.path.isfile(KLEE): - print("WARNING can't find klee at " + KLEE) - KLEE= shutil.which("klee") - print("Using klee in PATH", KLEE) - if not os.path.isfile(KTEST_GEN): - print("WARNING can't find ktest-gen at " + KTEST_GEN) - KTEST_GEN= shutil.which("ktest-gen") - print("Using ktest-gen in PATH", KTEST_GEN) - if KTEST_GEN is None or KLEE is None: - print("Failed to find KLEE at this script location or in PATH. Quitting ...") - sys.exit(1) - print("Using", KLEE) - - -def is_option(arg): - """Check if argument is optional.""" - return arg.startswith('-') - - -def split_args(): - """Return KLEE options, bitcode, executable and its arguments.""" - klee_args = tuple(takewhile(is_option, sys.argv[1:])) - rest = dropwhile(is_option, sys.argv[1:]) - return klee_args, next(rest), next(rest), tuple(rest) - - -def maybe_file_size(name): - try: - return os.path.getsize(name) - except: - return None - -def get_stdin_file(tmpdir): - stdin = "" - stdin_size = 0 - if sys.stdin in select.select([sys.stdin], [], [], 0)[0]: - stdin += sys.stdin.readline() - if stdin == "": - return None, stdin_size - stdin_file_name = tmpdir.name + "/stdin.file" - with open(stdin_file_name, 'w') as f: - stdin_size = f.write(stdin) - return stdin_file_name, stdin_size - - - -def prog_args_to_posix(prog_args): - posix_args = [] - sym_file = 'A' - sym_file_sizes = [] - gen_out_args = [] - for parg in prog_args: - file_size = maybe_file_size(parg) - if file_size is None: - posix_args += ['--sym-arg', str(len(parg))] - gen_out_args += [parg] - else: - sym_file_sizes += [file_size] - posix_args += [sym_file] - sym_file = chr(ord(sym_file) + 1) - gen_out_args += ['--sym-file', parg] - - if ord(sym_file) - ord('A') > 0: - posix_args += ['--sym-files', str(ord(sym_file) - ord('A')), str(max(sym_file_sizes))] - return posix_args, gen_out_args - -def create_ktest_file(gen_out_args, tmpdir): - out_file=tmpdir + "/test.ktest" - subprocess.run([KTEST_GEN, "--bout-file", out_file] + gen_out_args, check=True) - return out_file - - -def main(): - try: - klee_args, bc, prog, prog_args = split_args() - except StopIteration: - print(HELP) - return - find_klee_bin_dir() - tmpdir = tempfile.TemporaryDirectory() - stdin_file, stdin_size = get_stdin_file(tmpdir) - posix_args, gen_out_args = prog_args_to_posix(prog_args) - if stdin_file is not None: - gen_out_args += ["--sym-stdin", stdin_file] - posix_args += ["--sym-stdin", str(stdin_size)] - ktest_file = create_ktest_file(gen_out_args,tmpdir.name) - Path('test.ktest').write_bytes(Path(ktest_file).read_bytes()) - proc = subprocess.Popen([KLEE, *klee_args, f'-seed-file={ktest_file}', - bc, prog, *posix_args], - stdout=sys.stdout, stderr=sys.stderr) - while proc.returncode is None: - try: - proc.wait() - except KeyboardInterrupt: - pass # This is expected when stopping KLEE, so we wait for KLEE to finish - sys.exit(proc.returncode) - - -if __name__ == "__main__": - main() +#!/bin/sh +set -eux + +ktest_file=$(mktemp -t ktest.XXXXXXXXXX) +trap 'rm "$ktest_file"' EXIT +klee_args="--allow-seed-extension --only-replay-seeds --seed-file=$ktest_file" +while test "$1" != "${1#-}" # hyphen-prefixed +do + klee_args+=" $1" + shift +done +program_bc="$1" +shift +program_bin="$1" +shift + +stdin=$(mktemp -t in.XXXXXXXXXX) +trap 'rm "$stdin"' EXIT +cat > $stdin +stdout=$(mktemp -t out.XXXXXXXXXX) +trap 'rm "$stdout"' EXIT +"$program_bin" "$@" < $stdin > $stdout + +ktest_args="--sym-stdin $stdin" +sym_args= +for arg in $@ +do + if test -r $arg + then + ktest_args+=" --sym-file $arg" + # TODO: --sym-files + else + ktest_args+=" $arg" + sym_args+=" --sym-arg=o{#arg}" + fi +done + +ktest_gen=$KLEE_PREFIX/bin/ktest-gen +test -x "$ktest_gen" +"$ktest_gen" ${ktest_args[@]} --bout-file "$ktest_file" +klee_bin=$KLEE_PREFIX/bin/klee +test -x "$klee_bin" +"$klee_bin" ${klee_args[@]} "$program_bc" "$program_bin" ${sym_args[@]} |