about summary refs log tree commit diff
path: root/llvm_mode/afl-ld.c
diff options
context:
space:
mode:
Diffstat (limited to 'llvm_mode/afl-ld.c')
-rw-r--r--llvm_mode/afl-ld.c750
1 files changed, 750 insertions, 0 deletions
diff --git a/llvm_mode/afl-ld.c b/llvm_mode/afl-ld.c
new file mode 100644
index 00000000..65a75879
--- /dev/null
+++ b/llvm_mode/afl-ld.c
@@ -0,0 +1,750 @@
+/*
+  american fuzzy lop++ - wrapper for GNU ld
+  -----------------------------------------
+
+  Written by Marc Heuse <mh@mh-sec.de> for afl++
+
+  Maintained by Marc Heuse <mh@mh-sec.de>,
+                Heiko Eißfeldt <heiko.eissfeldt@hexco.de>
+                Andrea Fioraldi <andreafioraldi@gmail.com>
+                Dominik Maier <domenukk@gmail.com>
+
+  Copyright 2019-2020 AFLplusplus Project. All rights reserved.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at:
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  The sole purpose of this wrapper is to preprocess clang LTO files before
+  linking by ld and perform the instrumentation on the whole program.
+
+*/
+
+#define AFL_MAIN
+
+#include "config.h"
+#include "types.h"
+#include "debug.h"
+#include "alloc-inl.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+
+#include <dirent.h>
+
+static u8 **ld_params,              /* Parameters passed to the real 'ld'   */
+    **link_params,                  /* Parameters passed to 'llvm-link'     */
+    **opt_params,                   /* Parameters passed to 'opt' opt       */
+    **inst_params;                  /* Parameters passed to 'opt' inst      */
+
+static u8* input_file;              /* Originally specified input file      */
+static u8 *final_file,              /* Instrumented file for the real 'ld'  */
+    *linked_file,                   /* file where we link all files         */
+    *modified_file;                 /* file that was optimized before instr */
+static u8* afl_path = AFL_PATH;
+static u8* real_ld = AFL_REAL_LD;
+static u8  cwd[4096];
+static u8* tmp_dir;
+static u8* ar_dir;
+static u8  ar_dir_cnt;
+
+static u8 be_quiet,                 /* Quiet mode (no stderr output)        */
+    debug,                          /* AFL_DEBUG                            */
+    passthrough,                    /* AFL_LD_PASSTHROUGH - no link+optimize*/
+    we_link,                        /* we have bc/ll -> link + optimize     */
+    just_version;                   /* Just show version?                   */
+
+static u32 ld_param_cnt = 1,        /* Number of params to 'ld'             */
+    link_param_cnt = 1,             /* Number of params to 'llvm-link'      */
+    opt_param_cnt = 1,              /* Number of params to 'opt' opt        */
+    inst_param_cnt = 1;             /* Number of params to 'opt' instr      */
+
+/* This function wipes a directory - our AR unpack directory in this case */
+static u8 wipe_directory(u8* path) {
+
+  DIR*           d;
+  struct dirent* d_ent;
+
+  d = opendir(path);
+
+  if (!d) return 0;
+
+  while ((d_ent = readdir(d))) {
+
+    if (strcmp(d_ent->d_name, ".") != 0 && strcmp(d_ent->d_name, "..") != 0) {
+
+      u8* fname = alloc_printf("%s/%s", path, d_ent->d_name);
+      if (unlink(fname)) PFATAL("Unable to delete '%s'", fname);
+      ck_free(fname);
+
+    }
+
+  }
+
+  closedir(d);
+
+  return !!rmdir(path);
+
+}
+
+/* remove temporary files on fatal errors */
+static void at_exit_handler(void) {
+
+  if (!getenv("AFL_KEEP_ASSEMBLY")) {
+
+    if (linked_file) {
+
+      unlink(linked_file);
+      linked_file = NULL;
+
+    }
+
+    if (modified_file) {
+
+      unlink(modified_file);
+      modified_file = NULL;
+
+    }
+
+    if (final_file) {
+
+      unlink(final_file);
+      final_file = NULL;
+
+    }
+
+    if (ar_dir != NULL) {
+
+      wipe_directory(ar_dir);
+      ar_dir = NULL;
+
+    }
+
+  }
+
+}
+
+/* This function checks if the parameter is a) an existing file and b)
+   if it is a BC or LL file, if both are true it returns 1 and 0 otherwise */
+int is_llvm_file(const char* file) {
+
+  int fd;
+  u8  buf[5];
+
+  if ((fd = open(file, O_RDONLY)) < 0) return 0;
+
+  if (read(fd, buf, sizeof(buf)) != sizeof(buf)) return 0;
+  buf[sizeof(buf) - 1] = 0;
+
+  close(fd);
+
+  if (strncmp(buf, "; Mo", 4) == 0) return 1;
+  if (buf[0] == 'B' && buf[1] == 'C' && buf[2] == 0xC0 && buf[3] == 0xDE)
+    return 1;
+
+  return 0;
+
+}
+
+/* Return the current working directory, not thread safe ;-) */
+u8* getthecwd() {
+
+  static u8 fail[] = "";
+  if (getcwd(cwd, sizeof(cwd)) == NULL) return fail;
+  return cwd;
+
+}
+
+/* Check if an ar extracted file is already in the parameter list */
+int is_duplicate(u8** params, u32 ld_param_cnt, u8* ar_file) {
+
+  for (uint32_t i = 0; i < ld_param_cnt; i++)
+    if (params[i] != NULL)
+      if (strcmp(params[i], ar_file) == 0) return 1;
+
+  return 0;
+
+}
+
+/* Examine and modify parameters to pass to 'ld', 'llvm-link' and 'llmv-ar'.
+   Note that the file name is always the last parameter passed by GCC,
+   so we exploit this property to keep the code "simple". */
+static void edit_params(int argc, char** argv) {
+
+  u32 i, have_lto = 0;
+
+  if (tmp_dir == NULL) {
+
+    tmp_dir = getenv("TMPDIR");
+    if (!tmp_dir) tmp_dir = getenv("TEMP");
+    if (!tmp_dir) tmp_dir = getenv("TMP");
+    if (!tmp_dir) tmp_dir = "/tmp";
+
+  }
+
+  linked_file =
+      alloc_printf("%s/.afl-%u-%u-1.ll", tmp_dir, getpid(), (u32)time(NULL));
+  modified_file =
+      alloc_printf("%s/.afl-%u-%u-2.bc", tmp_dir, getpid(), (u32)time(NULL));
+  final_file =
+      alloc_printf("%s/.afl-%u-%u-3.bc", tmp_dir, getpid(), (u32)time(NULL));
+
+  ld_params = ck_alloc((argc + 128) * sizeof(u8*));
+  link_params = ck_alloc((argc + 256) * sizeof(u8*));
+  inst_params = ck_alloc(12 * sizeof(u8*));
+  opt_params = ck_alloc(12 * sizeof(u8*));
+
+  ld_params[0] = (u8*)real_ld;
+  ld_params[argc] = 0;
+
+  link_params[0] = alloc_printf("%s/%s", LLVM_BINDIR, "llvm-link");
+  link_params[link_param_cnt++] = "-S";  // we create the linked file as .ll
+  link_params[link_param_cnt++] = "-o";
+  link_params[link_param_cnt++] = linked_file;
+
+  opt_params[0] = alloc_printf("%s/%s", LLVM_BINDIR, "opt");
+  if (getenv("AFL_DONT_OPTIMIZE") == NULL)
+    opt_params[opt_param_cnt++] = "-O3";
+  else
+    opt_params[opt_param_cnt++] = "-O0";
+  // opt_params[opt_param_cnt++] = "-S"; // only when debugging
+  opt_params[opt_param_cnt++] = linked_file;  // input: .ll file
+  opt_params[opt_param_cnt++] = "-o";
+  opt_params[opt_param_cnt++] = modified_file;  // output: .bc file
+
+  inst_params[0] = alloc_printf("%s/%s", LLVM_BINDIR, "opt");
+  inst_params[inst_param_cnt++] =
+      alloc_printf("--load=%s/afl-llvm-lto-instrumentation.so", afl_path);
+  // inst_params[inst_param_cnt++] = "-S"; // only when debugging
+  inst_params[inst_param_cnt++] = "--disable-opt";
+  inst_params[inst_param_cnt++] = "--afl-lto";
+  inst_params[inst_param_cnt++] = modified_file;  // input: .bc file
+  inst_params[inst_param_cnt++] = "-o";
+  inst_params[inst_param_cnt++] = final_file;  // output: .bc file
+
+  for (i = 1; i < argc; i++) {
+
+    if (strncmp(argv[i], "-flto", 5) == 0) have_lto = 1;
+
+    if (!strcmp(argv[i], "-version")) {
+
+      just_version = 1;
+      ld_params[1] = argv[i];
+      ld_params[2] = NULL;
+      final_file = input_file;
+      return;
+
+    }
+
+    if (strcmp(argv[i], "--afl") == 0) {
+
+      if (!be_quiet) OKF("afl++ test command line flag detected, exiting.");
+      exit(0);
+
+    }
+
+    // is the parameter an .a AR archive? If so, unpack and check its files
+    if (argv[i][0] != '-' && strlen(argv[i]) > 2 &&
+        argv[i][strlen(argv[i]) - 1] == 'a' &&
+        argv[i][strlen(argv[i]) - 2] == '.') {
+
+      // This gets a bit odd. I encountered several .a files being linked and
+      // where the same "foo.o" was in both .a archives. llvm-link does not
+      // like this so we have to work around that ...
+
+      u8             this_wd[4096], *this_ar;
+      u8             ar_params_cnt = 4;
+      u8*            ar_params[ar_params_cnt];
+      s32            pid, status;
+      DIR*           arx;
+      struct dirent* dir_ent;
+
+      if (ar_dir_cnt == 0) {  // first archive, we setup up the basics
+
+        ar_dir = alloc_printf("%s/.afl-%u-%u.dir", tmp_dir, getpid(),
+                              (u32)time(NULL));
+        if (mkdir(ar_dir, 0700) != 0)
+          FATAL("can not create temporary directory %s", ar_dir);
+
+      }
+
+      if (getcwd(this_wd, sizeof(this_wd)) == NULL)
+        FATAL("can not get the current working directory");
+      if (chdir(ar_dir) != 0)
+        FATAL("can not chdir to temporary directory %s", ar_dir);
+      if (argv[i][0] == '/')
+        this_ar = argv[i];
+      else
+        this_ar = alloc_printf("%s/%s", this_wd, argv[i]);
+      ar_params[0] = alloc_printf("%s/%s", LLVM_BINDIR, "llvm-ar");
+      ar_params[1] = "x";
+      ar_params[2] = this_ar;
+      ar_params[3] = NULL;
+
+      if (!be_quiet) OKF("Running ar unpacker on %s into %s", this_ar, ar_dir);
+
+      if (debug) {
+
+        SAYF(cMGN "[D]" cRST " cd \"%s\";", getthecwd());
+        for (uint32_t j = 0; j < ar_params_cnt; j++)
+          SAYF(" \"%s\"", ar_params[j]);
+        SAYF("\n");
+
+      }
+
+      if (!(pid = fork())) {
+
+        execvp(ar_params[0], (char**)ar_params);
+        FATAL("Oops, failed to execute '%s'", ar_params[0]);
+
+      }
+
+      if (pid < 0) FATAL("fork() failed");
+      if (waitpid(pid, &status, 0) <= 0) FATAL("waitpid() failed");
+      if (WEXITSTATUS(status) != 0) exit(WEXITSTATUS(status));
+
+      if (chdir(this_wd) != 0)
+        FATAL("can not chdir back to our working directory %s", this_wd);
+
+      if (!(arx = opendir(ar_dir))) FATAL("can not open directory %s", ar_dir);
+
+      while ((dir_ent = readdir(arx)) != NULL) {
+
+        u8* ar_file = alloc_printf("%s/%s", ar_dir, dir_ent->d_name);
+
+        if (dir_ent->d_name[strlen(dir_ent->d_name) - 1] == 'o' &&
+            dir_ent->d_name[strlen(dir_ent->d_name) - 2] == '.') {
+
+          if (passthrough || argv[i][0] == '-' || is_llvm_file(ar_file) == 0) {
+
+            if (is_duplicate(ld_params, ld_param_cnt, ar_file) == 0) {
+
+              ld_params[ld_param_cnt++] = ar_file;
+              if (debug)
+                SAYF(cMGN "[D] " cRST "not a LTO link file: %s\n", ar_file);
+
+            }
+
+          } else {
+
+            if (is_duplicate(link_params, link_param_cnt, ar_file) == 0) {
+
+              if (we_link == 0) {  // we have to honor order ...
+
+                ld_params[ld_param_cnt++] = final_file;
+                we_link = 1;
+
+              }
+
+              link_params[link_param_cnt++] = ar_file;
+              if (debug) SAYF(cMGN "[D] " cRST "is a link file: %s\n", ar_file);
+
+            }
+
+          }
+
+        } else
+
+            if (dir_ent->d_name[0] != '.')
+          WARNF("Unusual file found in ar archive %s: %s", argv[i], ar_file);
+
+      }
+
+      closedir(arx);
+      ar_dir_cnt++;
+
+      continue;
+
+    }
+
+    if (passthrough || argv[i][0] == '-' || is_llvm_file(argv[i]) == 0) {
+
+      // -O3 fucks up the CFG and instrumentation, so we downgrade to O2
+      // which is as we want things. Lets hope this is not too different
+      // in the various llvm versions!
+      if (strncmp(argv[i], "-plugin-opt=O", 13) == 0 &&
+          !getenv("AFL_DONT_OPTIMIZE"))
+        ld_params[ld_param_cnt++] = "-plugin-opt=O2";
+      else
+        ld_params[ld_param_cnt++] = argv[i];
+
+    } else {
+
+      if (we_link == 0) {  // we have to honor order ...
+        ld_params[ld_param_cnt++] = final_file;
+        we_link = 1;
+
+      }
+
+      link_params[link_param_cnt++] = argv[i];
+
+    }
+
+  }
+
+  // if (have_lto == 0) ld_params[ld_param_cnt++] = AFL_CLANG_FLTO; // maybe we
+  // should not ...
+  ld_params[ld_param_cnt] = NULL;
+  link_params[link_param_cnt] = NULL;
+  opt_params[opt_param_cnt] = NULL;
+  inst_params[inst_param_cnt] = NULL;
+
+}
+
+/* clean AFL_PATH from PATH */
+
+void clean_path() {
+
+  char *tmp, *newpath = NULL, *path = getenv("PATH");
+  u8    done = 0;
+
+  if (debug)
+    SAYF(cMGN "[D]" cRST " old PATH=%s, AFL_PATH=%s\n", path, AFL_PATH);
+
+  // wipe AFL paths from PATH that we set
+  // we added two paths so we remove the two paths
+  while (!done) {
+
+    if (*path == 0)
+      done = 1;
+    else if (*path++ == ':')
+      done = 1;
+
+  }
+
+  while (*path == ':')
+    path++;
+
+  // AFL_PATH could be additionally in PATH so check and remove to not call our
+  // 'ld'
+  const size_t pathlen = strlen(path);
+  const size_t afl_pathlen = strlen(AFL_PATH);
+  newpath = malloc(pathlen + 1);
+  if (strcmp(AFL_PATH, "/bin") != 0 && strcmp(AFL_PATH, "/usr/bin") != 0 &&
+      afl_pathlen > 1 && (tmp = strstr(path, AFL_PATH)) != NULL &&  // it exists
+      (tmp == path ||
+       (tmp > path &&
+        tmp[-1] == ':')) &&  // either starts with it or has a colon before
+      (tmp + afl_pathlen == path + pathlen ||
+       (tmp + afl_pathlen <
+        path + (pathlen && tmp[afl_pathlen] ==
+                               ':'))  // end with it or has a colon at the end
+       )) {
+
+    int one_colon = 1;
+
+    if (tmp > path) {
+
+      memcpy(newpath, path, tmp - path);
+      newpath[tmp - path - 1] = 0;  // remove ':'
+      one_colon = 0;
+
+    }
+
+    if (tmp + afl_pathlen < path + pathlen) tmp += afl_pathlen + one_colon;
+
+    setenv("PATH", newpath, 1);
+
+  } else
+
+    setenv("PATH", path, 1);
+
+  if (debug) SAYF(cMGN "[D]" cRST " new PATH=%s\n", getenv("PATH"));
+  free(newpath);
+
+}
+
+/* Main entry point */
+
+int main(int argc, char** argv) {
+
+  s32 pid, i;
+  int status;
+  u8 *ptr, exe[4096], exe2[4096], proc[32], val[2] = " ";
+  int have_afl_ld_caller = 0;
+
+  if (isatty(2) && !getenv("AFL_QUIET") && !getenv("AFL_DEBUG")) {
+
+    if (getenv("AFL_LD") != NULL)
+      SAYF(cCYA "afl-ld" VERSION cRST
+                " by Marc \"vanHauser\" Heuse <mh@mh-sec.de> (level %d)\n",
+           have_afl_ld_caller);
+
+  } else
+
+    be_quiet = 1;
+
+  if (getenv("AFL_DEBUG") != NULL) debug = 1;
+  if (getenv("AFL_PATH") != NULL) afl_path = getenv("AFL_PATH");
+  if (getenv("AFL_LD_PASSTHROUGH") != NULL) passthrough = 1;
+  if (getenv("AFL_REAL_LD") != NULL) real_ld = getenv("AFL_REAL_LD");
+  if (real_ld == NULL || strlen(real_ld) < 2) real_ld = "/bin/ld";
+  if (real_ld != NULL && real_ld[0] != '/')
+    real_ld = alloc_printf("/bin/%s", real_ld);
+
+  if ((ptr = getenv("AFL_LD_CALLER")) != NULL) have_afl_ld_caller = atoi(ptr);
+  val[0] = 0x31 + have_afl_ld_caller;
+  setenv("AFL_LD_CALLER", val, 1);
+
+  if (debug) {
+
+    SAYF(cMGN "[D] " cRST
+              "AFL_LD=%s, set AFL_LD_CALLER=%s, have_afl_ld_caller=%d, "
+              "real_ld=%s\n",
+         getenv("AFL_LD"), val, have_afl_ld_caller, real_ld);
+    SAYF(cMGN "[D]" cRST " cd \"%s\";", getthecwd());
+    for (i = 0; i < argc; i++)
+      SAYF(" \"%s\"", argv[i]);
+    SAYF("\n");
+
+  }
+
+  sprintf(proc, "/proc/%d/exe", getpid());
+  if (readlink(proc, exe, sizeof(exe) - 1) > 0) {
+
+    if (readlink(real_ld, exe2, sizeof(exe2) - 1) < 1) exe2[0] = 0;
+    exe[sizeof(exe) - 1] = 0;
+    exe[sizeof(exe2) - 1] = 0;
+    if (strcmp(exe, real_ld) == 0 || strcmp(exe, exe2) == 0)
+      PFATAL(cLRD "[!] " cRST
+                  "Error: real 'ld' path points to afl-ld, set AFL_REAL_LD to "
+                  "the real 'ld' program!");
+
+  }
+
+  if (have_afl_ld_caller > 1)
+    PFATAL(cLRD "[!] " cRST
+                "Error: afl-ld calls itself in a loop, set AFL_REAL_LD to the "
+                "real 'ld' program!");
+
+  if (argc < 2) {
+
+    SAYF(
+        "\n"
+        "This is a helper application for afl-fuzz. It is a wrapper around GNU "
+        "'ld',\n"
+        "executed by the toolchain whenever using "
+        "afl-clang-lto/afl-clang-lto++.\n"
+        "You probably don't want to run this program directly.\n\n"
+
+        "Environment variables:\n"
+        "  AFL_LD_PASSTHROUGH   do not link+optimize == no instrumentation\n"
+        "  AFL_REAL_LD          point to the real ld if necessary\n"
+
+        "\nafl-ld was compiled with the fixed real 'ld' path of %s and the "
+        "clang "
+        "bin path of %s\n\n",
+        real_ld, LLVM_BINDIR);
+
+    exit(1);
+
+  }
+
+  if (getenv("AFL_LD") == NULL) {
+
+    /* if someone install clang/ld into the same directory as afl++ then
+       they are out of luck ... */
+
+    if (have_afl_ld_caller == 1) { clean_path(); }
+
+    if (real_ld != NULL && strlen(real_ld) > 1) execvp(real_ld, argv);
+    execvp("ld", argv);  // fallback
+    PFATAL("Oops, failed to execute 'ld' - check your PATH");
+
+  }
+
+  atexit(at_exit_handler);  // ensure to wipe temp files if things fail
+
+  edit_params(argc, argv);  // here most of the magic happens :-)
+
+  if (!just_version) {
+
+    if (we_link == 0) {
+
+      if (!getenv("AFL_QUIET"))
+        WARNF("No LTO input file found, cannot instrument!");
+
+    } else {
+
+      /* first we link all files */
+      if (!be_quiet) OKF("Running bitcode linker, creating %s", linked_file);
+
+      if (debug) {
+
+        SAYF(cMGN "[D]" cRST " cd \"%s\";", getthecwd());
+        for (i = 0; i < link_param_cnt; i++)
+          SAYF(" \"%s\"", link_params[i]);
+        SAYF("\n");
+
+      }
+
+      if (!(pid = fork())) {
+
+        execvp(link_params[0], (char**)link_params);
+        FATAL("Oops, failed to execute '%s'", link_params[0]);
+
+      }
+
+      if (pid < 0) PFATAL("fork() failed");
+      if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");
+      if (WEXITSTATUS(status) != 0) exit(WEXITSTATUS(status));
+
+      /* then we perform an optimization on the collected objects files */
+      if (!be_quiet)
+        OKF("Performing optimization via opt, creating %s", modified_file);
+      if (debug) {
+
+        SAYF(cMGN "[D]" cRST " cd \"%s\";", getthecwd());
+        for (i = 0; i < opt_param_cnt; i++)
+          SAYF(" \"%s\"", opt_params[i]);
+        SAYF("\n");
+
+      }
+
+      if (!(pid = fork())) {
+
+        execvp(opt_params[0], (char**)opt_params);
+        FATAL("Oops, failed to execute '%s'", opt_params[0]);
+
+      }
+
+      if (pid < 0) PFATAL("fork() failed");
+      if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");
+      if (WEXITSTATUS(status) != 0) exit(WEXITSTATUS(status));
+
+      /* then we run the instrumentation through the optimizer */
+      if (!be_quiet)
+        OKF("Performing instrumentation via opt, creating %s", final_file);
+      if (debug) {
+
+        SAYF(cMGN "[D]" cRST " cd \"%s\";", getthecwd());
+        for (i = 0; i < inst_param_cnt; i++)
+          SAYF(" \"%s\"", inst_params[i]);
+        SAYF("\n");
+
+      }
+
+      if (!(pid = fork())) {
+
+        execvp(inst_params[0], (char**)inst_params);
+        FATAL("Oops, failed to execute '%s'", inst_params[0]);
+
+      }
+
+      if (pid < 0) PFATAL("fork() failed");
+      if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");
+      if (WEXITSTATUS(status) != 0) exit(WEXITSTATUS(status));
+
+    }
+
+    /* next step - run the linker! :-) */
+
+  }
+
+  if (!be_quiet) OKF("Running real linker %s", real_ld);
+  if (debug) {
+
+    SAYF(cMGN "[D]" cRST " cd \"%s\";", getthecwd());
+    for (i = 0; i < ld_param_cnt; i++)
+      SAYF(" \"%s\"", ld_params[i]);
+    SAYF("\n");
+
+  }
+
+  if (!(pid = fork())) {
+
+    clean_path();
+
+    unsetenv("AFL_LD");
+
+    if (strlen(real_ld) > 1) execvp(real_ld, (char**)ld_params);
+    execvp("ld", (char**)ld_params);  // fallback
+    FATAL("Oops, failed to execute 'ld' - check your PATH");
+
+  }
+
+  if (pid < 0) PFATAL("fork() failed");
+
+  if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");
+  if (debug) SAYF(cMGN "[D] " cRST "linker result: %d\n", status);
+
+  if (!just_version) {
+
+    if (!getenv("AFL_KEEP_ASSEMBLY")) {
+
+      if (linked_file) {
+
+        unlink(linked_file);
+        linked_file = NULL;
+
+      }
+
+      if (modified_file) {
+
+        unlink(modified_file);
+        modified_file = NULL;
+
+      }
+
+      if (final_file) {
+
+        unlink(final_file);
+        final_file = NULL;
+
+      }
+
+      if (ar_dir != NULL) {
+
+        wipe_directory(ar_dir);
+        ar_dir = NULL;
+
+      }
+
+    } else {
+
+      if (!be_quiet) {
+
+        SAYF(
+            "[!] afl-ld: keeping link file %s, optimized bitcode %s and "
+            "instrumented bitcode %s",
+            linked_file, modified_file, final_file);
+        if (ar_dir_cnt > 0 && ar_dir)
+          SAYF(" and ar archive unpack directory %s", ar_dir);
+        SAYF("\n");
+
+      }
+
+    }
+
+    if (status == 0) {
+
+      if (!be_quiet) OKF("Linker was successful");
+
+    } else {
+
+      SAYF(cLRD "[-] " cRST
+                "Linker failed, please investigate and send a bug report. Most "
+                "likely an 'ld' option is incompatible with %s. Try "
+                "AFL_KEEP_ASSEMBLY=1 and AFL_DEBUG=1 for replaying.\n",
+           AFL_CLANG_FLTO);
+
+    }
+
+  }
+
+  exit(WEXITSTATUS(status));
+
+}
+