AFL注释
2022-04-27 22:16:35

AFL注释

1、afl-gcc.c:

概述:

afl-gcc.c是一个gcc的封装,能够实现对一些关键节点进行插桩,从而记录一些程序执行路径之类的信息,方便对程序的一些运行情况进行反馈

main函数:

主要实现三个函数的调用:

详细注释:

/*
  Copyright 2013 Google LLC 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

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

/*
   american fuzzy lop - wrapper for GCC and clang
   ----------------------------------------------

   Written and maintained by Michal Zalewski <lcamtuf@google.com>

   This program is a drop-in replacement for GCC or clang. The most common way
   of using it is to pass the path to afl-gcc or afl-clang via CC when invoking
   ./configure.

   (Of course, use CXX and point it to afl-g++ / afl-clang++ for C++ code.)

   The wrapper needs to know the path to afl-as (renamed to 'as'). The default
   is /usr/local/lib/afl/. A convenient way to specify alternative directories
   would be to set AFL_PATH.

   If AFL_HARDEN is set, the wrapper will compile the target app with various
   hardening options that may help detect memory management issues more
   reliably. You can also specify AFL_USE_ASAN to enable ASAN.

   If you want to call a non-default compiler as a next step of the chain,
   specify its location via AFL_CC or AFL_CXX.

*/

/*
alf是一个gcc的一个封装,能够实现对于一些关键节点的插桩==》记录一些程序执行路径之类的信息
方便程序对于一些运行情况进行反馈
*/

#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>

static u8*  as_path;                /* Path to the AFL 'as' wrapper :AFL as包装器的路径      */
static u8** cc_params;              /* Parameters passed to the real CC :CC实际使用的编译器参数 */
static u32  cc_par_cnt = 1;         /* Param count, including argv0 :参数计数包括argv0     */
static u8   be_quiet,               /* Quiet mode     :静默模式                   */
            clang_mode;             /* Invoked as afl-clang*?   :是否使用afl-clang*模式 */


/* Try to find our "fake" GNU assembler in AFL_PATH or at the location derived
   from argv[0]. If that fails, abort. */

/*该函数通过argv0(当前文件路径)寻找对应的汇编器as
as:Linux上很常用的一个汇编器,负责把生成的汇编代码编译到二进制
*/
static void find_as(u8* argv0) {

  u8 *afl_path = getenv("AFL_PATH");//获取环境中的AFL_PATH变量
  u8 *slash, *tmp;

  if (afl_path) {
//获取成功
    tmp = alloc_printf("%s/as", afl_path);
//通过alloc_printf动态分配一段空间存储对应的路径

    if (!access(tmp, X_OK)) { //检查这段路径是否可访问
      as_path = afl_path;//可访问赋值给as_path
      ck_free(tmp);//释放alloc_printf分配的内存
      return;
    }

    ck_free(tmp);//不可访问直接释放alloc_printf分配的内存

  }

  slash = strrchr(argv0, '/');//获取AFL_PATH变量失败,提取当前路径dir

  if (slash) {//如果获取到当前路径的dir

    u8 *dir;

    *slash = 0;
    dir = ck_strdup(argv0);//提取当前路径dir
    *slash = '/';

    tmp = alloc_printf("%s/afl-as", dir);//alloc_printf为dir开辟空间存放路径

    if (!access(tmp, X_OK)) {
      //如果路径可达
      as_path = dir;//将路径赋值给as_path
      ck_free(tmp);//释放alloc_printf创建的空间
      return;
    }

    ck_free(tmp);//如果路径不能访问,释放alloc_printf为dir开辟的空间
    ck_free(dir);

  }

  if (!access(AFL_PATH "/as", X_OK)) {
    //以上两种情况都没有成功==》直接找as
    as_path = AFL_PATH;//找到了且可以访问就直接赋值
    return;
  }

  FATAL("Unable to find AFL wrapper binary for 'as'. Please set AFL_PATH");
 //找不到通过FATAL输出错误信息,然后exit(1)
}


/* Copy argv to cc_params, making the necessary edits. */

/*该函数主要用来设置CC的参数
*/

static void edit_params(u32 argc, char** argv) {

  u8 fortify_set = 0, asan_set = 0;
  u8 *name;

#if defined(__FreeBSD__) && defined(__x86_64__)
  u8 m32_set = 0;
#endif

  cc_params = ck_alloc((argc + 128) * sizeof(u8*));//为cc_params开辟内存空间

  name = strrchr(argv[0], '/');//通过找到最后一个/取出此时对应什么编译器,例:afl-gcc,将这个名字赋给name
  if (!name) name = argv[0]; else name++;

  if (!strncmp(name, "afl-clang", 9)) {//如果以afl-clang开通

    clang_mode = 1;//设置clang模式参数为1

    setenv(CLANG_ENV_VAR, "1", 1);

    if (!strcmp(name, "afl-clang++")) {//如果以afl-clang开头++
      u8* alt_cxx = getenv("AFL_CXX");//获取环境变量AFL_CXX
      cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++";//如果获取到环境变量值,将环境变量赋值给cc_params[],如果没有获取则直接给字符串“clang++”
    } else {
      u8* alt_cc = getenv("AFL_CC");//如果name变量中不是afl-clang++,就获取环境变量AFL-CC
      cc_params[0] = alt_cc ? alt_cc : (u8*)"clang";//如果获取到环境变量值,将环境变量赋值给cc_params[],如果没有获取到则直接给字符串“clang”
    }

  } else {

    /* With GCJ and Eclipse installed, you can actually compile Java! The
       instrumentation will work (amazingly). Alas, unhandled exceptions do
       not call abort(), so afl-fuzz would need to be modified to equate
       non-zero exit codes with crash conditions when working with Java
       binaries. Meh. */
//如果不是以afl-clang开头,并且是apple平台,进入该分支
#ifdef __APPLE__

    if (!strcmp(name, "afl-g++")) cc_params[0] = getenv("AFL_CXX");//如果对比值是afl-g++,则获取AFL-CXX环境变量赋给cc_params[0]
    else if (!strcmp(name, "afl-gcj")) cc_params[0] = getenv("AFL_GCJ");//如果对比值是afl-gcj,则获取AFL-GCJ环境变量赋给cc_params[0]
    else cc_params[0] = getenv("AFL_CC");//如果name值不是以上两个,则获取AFL-CC环境变量赋给CC_params[0]

    if (!cc_params[0]) {//如果cc_params[0]没有值,则提示Mac下要使用afl-clang,如果要使用afl-gcc则需要配置路径

      SAYF("\n" cLRD "[-] " cRST
           "On Apple systems, 'gcc' is usually just a wrapper for clang. Please use the\n"
           "    'afl-clang' utility instead of 'afl-gcc'. If you really have GCC installed,\n"
           "    set AFL_CC or AFL_CXX to specify the correct path to that compiler.\n");

      FATAL("AFL_CC or AFL_CXX required on MacOS X");

    }

#else//不是apple平台

    if (!strcmp(name, "afl-g++")) {//对比值如果是afl-gcc++
      u8* alt_cxx = getenv("AFL_CXX");//获取AFL_CXX环境变量
      cc_params[0] = alt_cxx ? alt_cxx : (u8*)"g++";//如果获取到值则直接将环境变量赋给cc_params[0],如果没有则直接将字符串“g++”赋给cc_params[0]
    } else if (!strcmp(name, "afl-gcj")) {//对比值如果是afl-gcj
      u8* alt_cc = getenv("AFL_GCJ");//获取AFL_GCJ环境变量
      cc_params[0] = alt_cc ? alt_cc : (u8*)"gcj";//如果获取到值则直接将环境变量赋给cc_params[0],如果没有则直接将字符串“gcj”赋给cc_params[0]
    } else {
      u8* alt_cc = getenv("AFL_CC");//获取AFL_CC环境变量
      cc_params[0] = alt_cc ? alt_cc : (u8*)"gcc";//如果获取到值则直接将环境变量赋给cc_params[0],如果没有则直接将字符串“gcc”赋给cc_params[0]
    }

#endif /* __APPLE__ */

  }

  while (--argc) {//循环遍历参数
    u8* cur = *(++argv);//获取参数

    if (!strncmp(cur, "-B", 2)) {//如果当前参数是“-B”

      if (!be_quiet) WARNF("-B is already set, overriding");//判断静默模式是否关闭,如果关闭提示“-B”参数已经设置
      //-B选项用于设置编译器的搜索路径,在find_as函数中处理

      if (!cur[2] && argc > 1) { argc--; argv++; }
      continue;

    }

    if (!strcmp(cur, "-integrated-as")) continue;//当前参数如果是"-integrated-as"时跳出本次循环

    if (!strcmp(cur, "-pipe")) continue;//当前参数如果是"-pipe"则跳出本次循环

#if defined(__FreeBSD__) && defined(__x86_64__)//判断如果是FreeBSD系统或者是64位系统
    if (!strcmp(cur, "-m32")) m32_set = 1;//判断当前参数为"-m32"时,设置m32_set标志参数为1
#endif

    if (!strcmp(cur, "-fsanitize=address") ||
        !strcmp(cur, "-fsanitize=memory")) asan_set = 1;
//判断当前参数为"-fsanitize=address"或者"-fsanitize=memory"时,设置asan_set参数为1==》这两个参数告诉gcc要检查内存访问错误
    if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;
//判断当前参数为"FORTIFY_SOURCE",设置fortify_set参数为1==》此参数为fortify保护是否开启
    cc_params[cc_par_cnt++] = cur;//给cc_params赋值,cc_par_cnt全局变量初始值为1

  }

  cc_params[cc_par_cnt++] = "-B";
  cc_params[cc_par_cnt++] = as_path;//取出find_as()函数中的as_path,组成"-B as_path"

  if (clang_mode)//判断clang模式为1==>此标志参数在获取参数时进行第一次设置
    cc_params[cc_par_cnt++] = "-no-integrated-as";//赋值cc_params追加参数"-no-integrated-as"

  if (getenv("AFL_HARDEN")) {//获取环境变量AFL_HAREEN,如果成功获取,进入分支

    cc_params[cc_par_cnt++] = "-fstack-protector-all";//赋值cc_params追加参数"-fstack-protector-all"

    if (!fortify_set)//检查是否设置fortify_set参数,如果没有,进入分支
      cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";////赋值cc_params追加参数"-D_FORTIFY_SOURCE=2"

  }

  if (asan_set) {//判断是否检查检查内存,如果已经在224中设置为1

    /* Pass this on to afl-as to adjust map density. */

    setenv("AFL_USE_ASAN", "1", 1);//设置"AFL_USE_ASAN"环境变量为1

  } else if (getenv("AFL_USE_ASAN")) {//如果AFL_USE_ASAN已经被设置为1,进入分支

    if (getenv("AFL_USE_MSAN"))//判断获取AFL_USE_MSAN环境变量是否成功
      FATAL("ASAN and MSAN are mutually exclusive");//提示ASAN和MSAN是互斥的

    if (getenv("AFL_HARDEN"))//判断获取AFL_HADREN环境变量是否获取成功,成功则进入分支
      FATAL("ASAN and AFL_HARDEN are mutually exclusive");//提示ASAN与HARDEN是互斥的

    cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
    cc_params[cc_par_cnt++] = "-fsanitize=address";
//如果上述两个环境变量都没有被设置,则在cc_params追加"-U_FORTIFY_SOURCE"和"-fsanitize=address"两个参数
  } else if (getenv("AFL_USE_MSAN")) {//获取AFL_USE_MSAN环境变量,成功则进入分支
//
    if (getenv("AFL_USE_ASAN"))//获取AFL_USE_ASAN环境变量
      FATAL("ASAN and MSAN are mutually exclusive");//提示MSAN与ASAN互斥

    if (getenv("AFL_HARDEN"))//获取AFL_HARDEN环境变量
      FATAL("MSAN and AFL_HARDEN are mutually exclusive");//提示MSAN与HARDEN互斥

    cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
    cc_params[cc_par_cnt++] = "-fsanitize=memory";
//如果上述两个环境变量都没有被设置,则在cc_params追加"-U_FORTIFY_SOURCE"和"-fsanitize=memory"参数

  }

  if (!getenv("AFL_DONT_OPTIMIZE")) {//获取AFL_DONT_OPTIMIZE环境变量,失败进入分支

#if defined(__FreeBSD__) && defined(__x86_64__)

    /* On 64-bit FreeBSD systems, clang -g -m32 is broken, but -m32 itself
       works OK. This has nothing to do with us, but let's avoid triggering
       that bug. */
//在64位FreeBSD系统上,clang -g -m32不能用,但-m32本身工作正常。这与我们无关,但我们得避免触发那个漏洞
    if (!clang_mode || !m32_set)//如果没有设置clang模式或者没有设置-m32参数则进入分支
      cc_params[cc_par_cnt++] = "-g";//cc_params中追加-g参数

#else//如果不是以上两种系统则进入分支

      cc_params[cc_par_cnt++] = "-g";//cc_params中追加-g参数

#endif

    cc_params[cc_par_cnt++] = "-O3";//cc_params中追加-O3参数
    cc_params[cc_par_cnt++] = "-funroll-loops";//cc_params中追加-funroll-loops参数

    /* Two indicators that you're building for fuzzing; one of them is
       AFL-specific, the other is shared with libfuzzer. */
//你为模糊建立的两个指标,其中一个是afl特定的,另一个是libfuzzer共享的
    cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
    cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";
//在cc_params中追加"-D__AFL_COMPILER=1"和"-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"两个参数
  }

  if (getenv("AFL_NO_BUILTIN")) {//如果设置了AFL_NO_BUILTIN环境变量则进入循环

    cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strstr";
    cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr";
//在cc_params中追加参数
  }

  cc_params[cc_par_cnt] = NULL;//cc_params最后追加NULL,表示参数数组结束

}


/* Main entry point */

int main(int argc, char** argv) {

  if (isatty(2) && !getenv("AFL_QUIET")) {

    SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n");

  } else be_quiet = 1;

  if (argc < 2) {

    SAYF("\n"
         "This is a helper application for afl-fuzz. It serves as a drop-in replacement\n"
         "for gcc or clang, letting you recompile third-party code with the required\n"
         "runtime instrumentation. A common use pattern would be one of the following:\n\n"

         "  CC=%s/afl-gcc ./configure\n"
         "  CXX=%s/afl-g++ ./configure\n\n"

         "You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.\n"
         "Setting AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
         BIN_PATH, BIN_PATH);

    exit(1);

  }

  find_as(argv[0]);//主要来查找汇编器

  edit_params(argc, argv);//通过我们传入编译的参数进行参数处理,将确定的参数放入cc_params[]数组

  execvp(cc_params[0], (char**)cc_params);//执行afl-gcc
  /*参数解析:
  cc_params[0]:编译器
  (char**)cc_params:编译器参数
  */

  FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);

  return 0;

}

2、afl-as.c:

详细注释:

/*
  Copyright 2013 Google LLC 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

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

/*
   american fuzzy lop - wrapper for GNU as
   ---------------------------------------

   Written and maintained by Michal Zalewski <lcamtuf@google.com>

   The sole purpose of this wrapper is to preprocess assembly files generated
   by GCC / clang and inject the instrumentation bits included from afl-as.h. It
   is automatically invoked by the toolchain when compiling programs using
   afl-gcc / afl-clang.

   Note that it's an explicit non-goal to instrument hand-written assembly,
   be it in separate .s files or in __asm__ blocks. The only aspiration this
   utility has right now is to be able to skip them gracefully and allow the
   compilation process to continue.

   That said, see experimental/clang_asm_normalize/ for a solution that may
   allow clang users to make things work even with hand-crafted assembly. Just
   note that there is no equivalent for GCC.

*/

#define AFL_MAIN

#include "config.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"

#include "afl-as.h"

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <fcntl.h>

#include <sys/wait.h>
#include <sys/time.h>

static u8** as_params;          /* Parameters passed to the real 'as'   */

static u8*  input_file;         /* Originally specified input file      */
static u8*  modified_file;      /* Instrumented file for the real 'as'  */

static u8   be_quiet,           /* Quiet mode (no stderr output)        */
            clang_mode,         /* Running in clang mode?               */
            pass_thru,          /* Just pass data through?              */
            just_version,       /* Just show version?                   */
            sanitizer;          /* Using ASAN / MSAN                    */

static u32  inst_ratio = 100,   /* Instrumentation probability (%)      */
            as_par_cnt = 1;     /* Number of params to 'as'             */

/* If we don't find --32 or --64 in the command line, default to 
   instrumentation for whichever mode we were compiled with. This is not
   perfect, but should do the trick for almost all use cases. */

#ifdef WORD_SIZE_64

static u8   use_64bit = 1;  //64位标志位

#else

static u8   use_64bit = 0;    //32位标志位

#ifdef __APPLE__
#  error "Sorry, 32-bit Apple platforms are not supported."//苹果平台不支持32位
#endif /* __APPLE__ */

#endif /* ^WORD_SIZE_64 */


/* Examine and modify parameters to pass to 'as'. Note that the file name
   is always the last parameter passed by GCC, so we exploit this property
   to keep the code simple. */
/*检查并修改要传递给as的参数,注意文件名总是GCC传递的最后一个参数
因此我们利用这个属性来保持代码简单
*/


static void edit_params(int argc, char** argv) {

  u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS");//获取环境变量TMPDIR和AFL_AS
  u32 i;

#ifdef __APPLE__ //如果是APPLE平台

  u8 use_clang_as = 0;

  /* On MacOS X, the Xcode cctool 'as' driver is a bit stale and does not work
     with the code generated by newer versions of clang that are hand-built
     by the user. See the thread here: http://goo.gl/HBWDtn.

     To work around this, when using clang and running without AFL_AS
     specified, we will actually call 'clang -c' instead of 'as -q' to
     compile the assembly file.

     The tools aren't cmdline-compatible, but at least for now, we can
     seemingly get away with this by making only very minor tweaks. Thanks
     to Nico Weber for the idea. */

  if (clang_mode && !afl_as) {
    //如果使用clang模式并且没有获得AFL_AS环境变量

    use_clang_as = 1; //设指use_clang_as变量为1

    afl_as = getenv("AFL_CC");
    if (!afl_as) afl_as = getenv("AFL_CXX");
    if (!afl_as) afl_as = "clang";  //将afl_as赋值为AFL_CC,AFL_CXX环境变量或clang中的一种

  }

#endif /* __APPLE__ */

  /* Although this is not documented, GCC also uses TEMP and TMP when TMPDIR
     is not set. We need to check these non-standard variables to properly
     handle the pass_thru logic later on. */

  if (!tmp_dir) tmp_dir = getenv("TEMP");
  if (!tmp_dir) tmp_dir = getenv("TMP");
  if (!tmp_dir) tmp_dir = "/tmp";  //为tmp_dir赋值为TMPDIR,TEMP,TMP环境变量或者/tmp中的一种

  as_params = ck_alloc((argc + 32) * sizeof(u8*)); //为as_params开辟空间

  as_params[0] = afl_as ? afl_as : (u8*)"as";
  //afl_as是否已经获取到AFL_AS环境变量,如果获取到就将环境变量的值赋给as_params[0]
  //如果没有获取到,就将as字符串赋给afl_as

  as_params[argc] = 0;   //设置最后一个参数为0

  for (i = 1; i < argc - 1; i++) {  //从第一个参数开始遍历到最后一个参部

    if (!strcmp(argv[i], "--64")) use_64bit = 1; //如果遍历到--64参数,就将use_64bit设置为1
    else if (!strcmp(argv[i], "--32")) use_64bit = 0;//如果遍历到--32参数,就将use_64bit设置为0

#ifdef __APPLE__  //如果时APPLE平台

    /* The Apple case is a bit different... */

    if (!strcmp(argv[i], "-arch") && i + 1 < argc) {  //如果遍历到-arch参数

      if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1;  //-arch x86_64,将use_bit设置为1
      else if (!strcmp(argv[i + 1], "i386")) //是-arch i386就报错
        FATAL("Sorry, 32-bit Apple platforms are not supported.");

    }

    /* Strip options that set the preference for a particular upstream
       assembler in Xcode. */

    if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q")))
      continue;//如果是clang模式,并且遍历的是-q或者-Q参数,跳出循环

#endif /* __APPLE__ */

    as_params[as_par_cnt++] = argv[i];

  }

#ifdef __APPLE__  //APPLE平台

  /* When calling clang as the upstream assembler, append -c -x assembler
     and hope for the best. */

  if (use_clang_as) {

    as_params[as_par_cnt++] = "-c";
    as_params[as_par_cnt++] = "-x";
    as_params[as_par_cnt++] = "assembler";
    //如果使用的是clang模式,追加参数-c -x assembler

  }

#endif /* __APPLE__ */

  input_file = argv[argc - 1];  //将最后一个参数的值赋给input_file

  if (input_file[0] == '-') {  //如果input_file的首字母为-

    if (!strcmp(input_file + 1, "-version")) {//如果是-version
      just_version = 1;  //设置just_version为1
      modified_file = input_file;  //modified_file设置为-version
      goto wrap_things_up;  //跳转至参数组合结尾
    }

    if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)");
    //如果-后面的不是version,抛出异常
      else input_file = NULL;

  } else {

    /* Check if this looks like a standard invocation as a part of an attempt
       to compile a program, rather than using gcc on an ad-hoc .s file in
       a format we may not understand. This works around an issue compiling
       NSS. */

    if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) &&
        strncmp(input_file, "/var/tmp/", 9) &&
        strncmp(input_file, "/tmp/", 5)) pass_thru = 1;
        /*如果首字母不是-,则对比input_file的前strlen(tmp_dir)9~5个字节
        是否与tmp_dir,/var/tmp/,/tmp/相同,如果不相同就将pass_thru设置为1*/
  }

  modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(),(u32)time(NULL));
  //设置midified_file为类似tmp_dir/.afl-pid-time.s这样的字符串

wrap_things_up:

  as_params[as_par_cnt++] = modified_file;  //接收参数为modified_file
  as_params[as_par_cnt]   = NULL;   //参数接收结束

}


/* Process input file, generate modified_file. Insert instrumentation in all
   the appropriate places. */
/*处理输入文件,生成modified_file,将桩插入所有释放的位置*/
static void add_instrumentation(void) {

  static u8 line[MAX_LINE];

  FILE* inf;
  FILE* outf;
  s32 outfd;
  u32 ins_lines = 0;

  u8  instr_ok = 0, skip_csect = 0, skip_next_label = 0,
      skip_intel = 0, skip_app = 0, instrument_next = 0;

#ifdef __APPLE__

  u8* colon_pos;

#endif /* __APPLE__ */

  if (input_file) {  //如果存在输入文件名

    inf = fopen(input_file, "r"); //尝试获取input_file句柄,将fd赋值给inf
    if (!inf) PFATAL("Unable to read '%s'", input_file); //如果获取不到,抛异常

  } else inf = stdin; //如果不存在文件名则赋值标准输入

  outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600);
  //以写的方式打开modified_file,如果文件已存在就直接打开吗,如果没有就创建一个

  if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file);

  outf = fdopen(outfd, "w"); //尝试打开

  if (!outf) PFATAL("fdopen() failed");  //如果文件没有写入权限则抛出异常  

  while (fgets(line, MAX_LINE, inf)) {
    /*循环读取inf指向的文件的每一行到line数组,每行最多MAX_LINE(8192)个字符,含末尾\0*/

    /* In some cases, we want to defer writing the instrumentation trampoline
       until after all the labels, macros, comments, etc. If we're in this
       mode, and if the line starts with a tab followed by a character, dump
       the trampoline now. */

    if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&
        instrument_next && line[0] == '\t' && isalpha(line[1])) {
          //判断是否为defered mode模式

      fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
              R(MAP_SIZE));  //插桩

      instrument_next = 0;  //instrument_next重新设置为0
      ins_lines++;  //插桩计数器+1

    }

    /* Output the actual line, call it a day in pass-thru mode. */

    fputs(line, outf);

    if (pass_thru) continue;

    /* All right, this is where the actual fun begins. For one, we only want to
       instrument the .text section. So, let's keep track of that in processed
       files - and let's set instr_ok accordingly. */
    /*首先我们只想检测.text部分,让我们在已处理的文件中跟踪它,并相应地设置instr_ok
    */

    if (line[0] == '\t' && line[1] == '.') {
      //判断读入的行是否以\t开头,并且line[1]是否为.

      /* OpenBSD puts jump tables directly inline with the code, which is
         a bit annoying. They use a specific format of p2align directives
         around them, so we use that as a signal. */
      /*OpenBSD将跳转表直接内联到代码中,这有点烦人,他们使用特定格式篇p2align指令,我们将其用作信号
      */

      if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&
          isdigit(line[10]) && line[11] == '\n') skip_next_label = 1;
      //检查是否为p2align指令,如果是则设置skip_next_label为1

      if (!strncmp(line + 2, "text\n", 5) ||
          !strncmp(line + 2, "section\t.text", 13) ||
          !strncmp(line + 2, "section\t__TEXT,__text", 21) ||
          !strncmp(line + 2, "section __TEXT,__text", 21)) {
        instr_ok = 1;
        continue; 
        /*匹配"text\n","section\t.text","section\t__TEXT,__text","section __TEXT,__text"
        如果匹配成功就设置instr_ok为1,跳出本次循环
        */
      }

      if (!strncmp(line + 2, "section\t", 8) ||
          !strncmp(line + 2, "section ", 8) ||
          !strncmp(line + 2, "bss\n", 4) ||
          !strncmp(line + 2, "data\n", 5)) {
        instr_ok = 0;
        continue;
        /*匹配"section\t","section ","bss\n","data\n"
        如果匹配成功就设置instr_ok为0
        */
      }

    }

    /* Detect off-flavor assembly (rare, happens in gdb). When this is
       encountered, we set skip_csect until the opposite directive is
       seen, and we do not instrument. */

    if (strstr(line, ".code")) {  //判断架构

      if (strstr(line, ".code32")) skip_csect = use_64bit;
      if (strstr(line, ".code64")) skip_csect = !use_64bit;

    }

    /* Detect syntax changes, as could happen with hand-written assembly.
       Skip Intel blocks, resume instrumentation when back to AT&T. */

    if (strstr(line, ".intel_syntax")) skip_intel = 1;  //判断是否为Intel汇编语法
    if (strstr(line, ".att_syntax")) skip_intel = 0;  //判断是否为att汇编语法

    /* Detect and skip ad-hoc __asm__ blocks, likewise skipping them. */

    if (line[0] == '#' || line[1] == '#') {   //ad-hoc__asm__快是否跳过

      if (strstr(line, "#APP")) skip_app = 1;
      if (strstr(line, "#NO_APP")) skip_app = 0;

    }

    /* If we're in the right mood for instrumenting, check for function
       names or conditional labels. This is a bit messy, but in essence,
       we want to catch:  插桩时终端关注对象

         ^main:      - function entry point (always instrumented)  main函数
         ^.L0:       - GCC branch label    gcc下地分支标记
         ^.LBB0_0:   - clang branch label (but only in clang mode)   clang下地分支标记
         ^\tjnz foo  - conditional branches    条件跳转分支标记

       ...but not:

         ^# BB#0:    - clang comments
         ^ # BB#0:   - ditto
         ^.Ltmp0:    - clang non-branch labels
         ^.LC0       - GCC non-branch labels
         ^.LBB0_0:   - ditto (when in GCC mode)
         ^\tjmp foo  - non-conditional jumps

       Additionally, clang and GCC on MacOS X follow a different convention
       with no leading dots on labels, hence the weird maze of #ifdefs
       later on.

     */

    if (skip_intel || skip_app || skip_csect || !instr_ok ||
        line[0] == '#' || line[0] == ' ') continue;

    /* Conditional branch instruction (jnz, etc). We append the instrumentation
       right after the branch (to instrument the not-taken path) and at the
       branch destination label (handled later on). */
    /*跳转指令jnz等,我们将检测附加在分支之后(以检测未使用地路径)和分支目标标签(稍后处理)*/

    if (line[0] == '\t') {

      if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) {

        fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
                R(MAP_SIZE));
        //判断是否为64位程序,使用fprintf函数将桩插在outf指向地文件的\tj[^m].跳转指令位置
        //插入长度为R函数创建的小于MAP_SIZE的随机数

        ins_lines++;  //插桩计数器+1,跳出循环进行下一次遍历

      }

      continue;

    }

    /* Label of some sort. This may be a branch destination, but we need to
       tread carefully and account for several different formatting
       conventions. */

#ifdef __APPLE__

    /* Apple: L<whatever><digit>: */

    if ((colon_pos = strstr(line, ":"))) {  //检查line中是否存在":"

      if (line[0] == 'L' && isdigit(*(colon_pos - 1))) {  //检查line是否以L开始

#else

    /* Everybody else: .L<whatever>: */

    if (strstr(line, ":")) {  //检查line中是否存在":"

      if (line[0] == '.') {  //检查是否以"."开始

#endif /* __APPLE__ */

        /* .L0: or LBB0_0: style jump destination */

#ifdef __APPLE__

        /* Apple: L<num> / LBB<num> */

        if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3)))
            && R(100) < inst_ratio) {

#else

        /* Apple: .L<num> / .LBB<num> */

        if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3)))
            && R(100) < inst_ratio) {
          //检查line[2]是否为数字,或者clang模式下从line[1]开始的三个字节是否为LBB,并且随机数小于插桩密度

#endif /* __APPLE__ */

          /* An optimization is possible here by adding the code only if the
             label is mentioned in the code in contexts other than call / jmp.
             That said, this complicates the code by requiring two-pass
             processing (messy with stdin), and results in a speed gain
             typically under 10%, because compilers are generally pretty good
             about not generating spurious intra-function jumps.

             We use deferred output chiefly to avoid disrupting
             .Lfunc_begin0-style exception handling calculations (a problem on
             MacOS X). */

          if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;
          //设置instrument_next为1
        }

      } else {

        /* Function label (always instrumented, deferred mode). */
        //否则代表这是一个Function label 插桩^func 设置instrument_next为1
        instrument_next = 1;
    
      }

    }

  }

  if (ins_lines)   //如果插桩计数器不为0
    fputs(use_64bit ? main_payload_64 : main_payload_32, outf);
    //向outf中写入main_payload_64或者mian_payload_32

  if (input_file) fclose(inf);  //关闭文件
  fclose(outf);  //关闭文件

  if (!be_quiet) {  //如果使用的不是静默模式

    if (!ins_lines) WARNF("No instrumentation targets found%s.",
                          pass_thru ? " (pass-thru mode)" : "");
    //如果插桩计数器为空,抛异常
    else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).",
             ins_lines, use_64bit ? "64" : "32",
             getenv("AFL_HARDEN") ? "hardened" : 
             (sanitizer ? "ASAN/MSAN" : "non-hardened"),
             inst_ratio);
    //插桩成功输出
  }

}


/* Main entry point */

int main(int argc, char** argv) {

  s32 pid;
  u32 rand_seed;
  int status;
  u8* inst_ratio_str = getenv("AFL_INST_RATIO");
  //该环境变量主要是控制每个分支的概率,取值0到1

  struct timeval tv;
  struct timezone tz;

  clang_mode = !!getenv(CLANG_ENV_VAR);

  if (isatty(2) && !getenv("AFL_QUIET")) {

    SAYF(cCYA "afl-as " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
 
  } else be_quiet = 1;

  if (argc < 2) {

    SAYF("\n"
         "This is a helper application for afl-fuzz. It is a wrapper around GNU 'as',\n"
         "executed by the toolchain whenever using afl-gcc or afl-clang. You probably\n"
         "don't want to run this program directly.\n\n"

         "Rarely, when dealing with extremely complex projects, it may be advisable to\n"
         "set AFL_INST_RATIO to a value less than 100 in order to reduce the odds of\n"
         "instrumenting every discovered branch.\n\n");

    exit(1);

  }

  gettimeofday(&tv, &tz);  //获取当前精确时间

  rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid();  //通过当前时间与进程pid进行异或处理

  srandom(rand_seed);   //获取随机化种子

  edit_params(argc, argv);     //检查并修改参数以传递给as,文件名是以GCC传递的最后一个参数

  if (inst_ratio_str) {  //如果获取到AFL_INST_RATIO环境变量则进入分支

    if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100) 
      FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)");
    //如果没有将覆盖率写入inst_ratio变量或者inst_ratio中的值查过100,抛异常
  }

  if (getenv(AS_LOOP_ENV_VAR)) //如果获取到"__AFL_AS_LOOPCHECK"环境变量
    FATAL("Endless loop when calling 'as' (remove '.' from your PATH)");//抛出异常

  setenv(AS_LOOP_ENV_VAR, "1", 1);  //设置"__AFL_AS_LOOPCHECK"环境变量为1

  /* When compiling with ASAN, we don't have a particularly elegant way to skip
     ASAN-specific branches. But we can probabilistically compensate for
     that... */

  if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) {
    //获取到AFL_USE_ASAN和AFL_USE_MASN环境变量,如果其中一个为1则进入分支
    sanitizer = 1;  //sanitizer设置为1
    inst_ratio /= 3;  //insr_ratio除以3
  }

  if (!just_version) add_instrumentation();
  //如果不是只查询version,就会进入add_instrumentation函数
  //该函数主要处理输入文件,生成modified_file,将桩插入释放的位置


  if (!(pid = fork())) {  //调用fork创建一个子进程

    execvp(as_params[0], (char**)as_params);  //调用execvp函数执行命令和参数
    FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]);
    //不成功则抛异常
  }

  if (pid < 0) PFATAL("fork() failed");  //创建子进程失败则抛出异常

  if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");  //等待子进程结束

  if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file);  
  //读取环境变量"AFL_KEEP_ASSEMBLY"失败则unlink掉modified_file
  //设置该环境变量是为了防止afl-as删掉插桩后的汇编文件,设置为1会保留插桩后的汇编文件
  exit(WEXITSTATUS(status));

}

3、afl-fuzz.c:

初始配置函数

setup_signal_handlers

main函数对输入参数进行配置的第一个初始配置函数

作用:注册必要的信号处理函数

/* Set up signal handlers. More complicated that needs to be, because libc on
   Solaris doesn't resume interrupted reads(), sets SA_RESETHAND when you call
   siginterrupt(), and does other unnecessary things. */
/*设置信号处理程序,这里需要更加复杂===》libc Solaris不会中断reads()
在调用设置SA_RESETHAND siginterrupt(),并执行其他不必要的操作*/

EXP_ST void setup_signal_handlers(void) {
 //注册必要的信号处理函数
  struct sigaction sa;  //创建sigaction结构体变量sa

  sa.sa_handler   = NULL;
  sa.sa_flags     = SA_RESTART;
  sa.sa_sigaction = NULL;

  sigemptyset(&sa.sa_mask);  //初始化mask为空

  /* Various ways of saying "stop".
  主要是stop函数的处理 */

  sa.sa_handler = handle_stop_sig;
  //handle_stop_sig设置为top_soon为1,如果child_pid存在
  //向其发送SIGKILL终止信号,从而被系统杀死,如果forksrv_pid存在
  //向其发送SIGKILL终止信号
  sigaction(SIGHUP, &sa, NULL);
  sigaction(SIGINT, &sa, NULL);
  sigaction(SIGTERM, &sa, NULL);
  //SIGHUP,SIGINT,SIGTERM信号关联执行handle_stop_sig

  /* Exec timeout notifications. 处理超时情况*/

  sa.sa_handler = handle_timeout;
  /*handle_timeout判断如果child_pid>0,则设置child_time_out为1,并kill掉child_pid
  如果child_pid=-1,设置child_time_out为1,并kill掉child_pid*/
  sigaction(SIGALRM, &sa, NULL);//SIGALRM信号关联handle_resize

  /* Window resize  处理窗口大小的变化信号 */

  sa.sa_handler = handle_resize;
  //handle_resize设置为clear_screen=1
  sigaction(SIGWINCH, &sa, NULL);//SIGWINCH信号关联handle_resize

  /* SIGUSR1: skip entry */

  sa.sa_handler = handle_skipreq;  //handle_skipreq设置skip_requested=1
  sigaction(SIGUSR1, &sa, NULL);  //用户自定义信号,定义成skip_request(SIGUSRI),信号关联执行handle_skipreq

  /* Things we don't care about.  不关心的信号种类 */

  sa.sa_handler = SIG_IGN;
  sigaction(SIGTSTP, &sa, NULL);
  sigaction(SIGPIPE, &sa, NULL);

}

check_asan_opts

检查ASAN选项,获取ASAN_OPTIONS和MSAN_OPTIONS环境变量,依据获取值抛出异常

/* Check ASAN options. 检查asan选项*/

static void check_asan_opts(void) {
  u8* x = getenv("ASAN_OPTIONS");  //获取ASAN_OPTIONS环境变量

  if (x) {   //如果获取了环境变量

    if (!strstr(x, "abort_on_error=1"))  //检查是否同时设置了about_on_error=1,如果没有==》抛出异常
      FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!");

    if (!strstr(x, "symbolize=0"))  //检查是否同时设置了symboliz=0,如果没有==》抛出异常
      FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!");

  }

  x = getenv("MSAN_OPTIONS");  //获取环境变量MSAN_OPTIONS

  if (x) {  //如果获取了环境变量

    if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR)))
    //检查是否同时设置了exit_code对应的状态码,如果没有==》抛出异常
      FATAL("Custom MSAN_OPTIONS set without exit_code="
            STRINGIFY(MSAN_ERROR) " - please fix!");

    if (!strstr(x, "symbolize=0"))
    //检查是否同时设置了symbolize=0,如果没有==》抛出异常
      FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!");

  }

}

fix_up_sync

如果使用了-M或者-S指定了fuzzer,检查互斥,参数语法,fuzzer ID,sync_id是否过长

更新sync_dir和out_dir

/* Validate and fix up out_dir and sync_dir when using -S. */
/*在使用-S时,验证并修复out_dir和sync_dir。*/
static void fix_up_sync(void) {

  u8* x = sync_id;

  if (dumb_mode)   //如果设置了dump_mode模式,提示-S / -M 参数与 -n 参数互斥
    FATAL("-S / -M and -n are mutually exclusive");

  if (skip_deterministic) {

    if (force_deterministic)   //-d参数纠正
      FATAL("use -S instead of -M -d");
    else
      FATAL("-S already implies -d");

  }

  while (*x) {

    if (!isalnum(*x) && *x != '_' && *x != '-')   //fuzzer ID纠正
      FATAL("Non-alphanumeric fuzzer ID specified via -S or -M");

    x++;

  }

  if (strlen(sync_id) > 32) FATAL("Fuzzer ID too long");
  //sync_id过长抛出异常

  x = alloc_printf("%s/%s", out_dir, sync_id);

  sync_dir = out_dir;   //更新sync_dir的值
  out_dir  = x;       //更新out_dir的值,设置为out_dir或者sync_id

  if (!force_deterministic) {  //如果没有设置force_deterministic
    skip_deterministic = 1;  //设置skip_deterministic为1
    use_splicing = 1;  //设置use_splicing为1
  }

}

save_cmdline

将当前输入参数拷贝进buf空间中

/* Make a copy of the current command line.复制当前命令行 */

static void save_cmdline(u32 argc, char** argv) {

  u32 len = 1, i;
  u8* buf;

  for (i = 0; i < argc; i++)  //计算参数长度
    len += strlen(argv[i]) + 1;
  
  buf = orig_cmdline = ck_alloc(len);  //开辟存储参数的空间

  for (i = 0; i < argc; i++) {  //遍历参数

    u32 l = strlen(argv[i]);    //计算当前argv[i]的长度

    memcpy(buf, argv[i], l);    //将argv[i]中的内容放到buf空间
    buf += l;

    if (i != argc - 1) *(buf++) = ' ';
    //检查后面是否还有参数

  }

  *buf = 0;

}

fix_up_banner

获取目标程序名称或程序路径或路径+省略号

/* Trim and possibly create a banner for the run. */

static void fix_up_banner(u8* name) {

  if (!use_banner) {  //如果没有设置use_banner

    if (sync_id) {  //如果设置了sync_id

      use_banner = sync_id;  //将sync_id赋值给use_banner

    } else {//如果没有设置sync_id

      u8* trim = strrchr(name, '/');  //获取最后一个参数中最后一个/后面的内容(目标文件名称)
      if (!trim) use_banner = name; else use_banner = trim + 1;
      //如果没有获取到,将目标文件路径赋值给use_banner
      //如果获取到了,将去掉trim中的/
      //例:trim=/closure,则use_banner=closure
    }

  }

  if (strlen(use_banner) > 40) {  //use_banner长度超过40

    u8* tmp = ck_alloc(44);  //创建一个44字节长度变量tmp
    sprintf(tmp, "%.40s...", use_banner);  //取use_banary的前40个字节后面加上省略号
    use_banner = tmp;  //将tmp赋值给use_banner

  }

}

check_if_tty

检测是否在tty终端上运行

/* Check if we're on TTY. */

static void check_if_tty(void) {

  struct winsize ws;

  if (getenv("AFL_NO_UI")) {    //如果设置了AFL_NO_UI环境变量
    OKF("Disabling the UI because AFL_NO_UI is set.");  //提示禁用UI
    not_on_tty = 1;  //设置not_on_tty为1
    return;
  }

  if (ioctl(1, TIOCGWINSZ, &ws)) {   //通过ioctl函数获取窗口大小

    if (errno == ENOTTY) {  //如果报错为ENOTTY
      OKF("Looks like we're not running on a tty, so I'll be a bit less verbose.");
      //提示不是在tty终端上运行的
      not_on_tty = 1;//设置not_no_tty为1
    }

    return;
  }

}

CPU相关管理函数

get_core_count

获取CPU核心数

/* Count the number of logical CPU cores. */
//获取CPU核心数

static void get_core_count(void) {

  u32 cur_runnable = 0;

#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__)

  size_t s = sizeof(cpu_core_count);

  /* On *BSD systems, we can just use a sysctl to get the number of CPUs. */

#ifdef __APPLE__

  if (sysctlbyname("hw.logicalcpu", &cpu_core_count, &s, NULL, 0) < 0)
    return;

#else

  int s_name[2] = { CTL_HW, HW_NCPU };

  if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0) return;

#endif /* ^__APPLE__ */

#else

#ifdef HAVE_AFFINITY

  cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN);

#else

  FILE* f = fopen("/proc/stat", "r");
  u8 tmp[1024];

  if (!f) return;

  while (fgets(tmp, sizeof(tmp), f))
    if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3])) cpu_core_count++;

  fclose(f);

#endif /* ^HAVE_AFFINITY */

#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */

  if (cpu_core_count > 0) {

    cur_runnable = (u32)get_runnable_processes();

#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__)

    /* Add ourselves, since the 1-minute average doesn't include that yet. */

    cur_runnable++;

#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */

    OKF("You have %u CPU core%s and %u runnable tasks (utilization: %0.0f%%).",
        cpu_core_count, cpu_core_count > 1 ? "s" : "",
        cur_runnable, cur_runnable * 100.0 / cpu_core_count);

    if (cpu_core_count > 1) {

      if (cur_runnable > cpu_core_count * 1.5) {

        WARNF("System under apparent load, performance may be spotty.");

      } else if (cur_runnable + 1 <= cpu_core_count) {

        OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path);
  
      }

    }

  } else {

    cpu_core_count = 0;
    WARNF("Unable to figure out the number of CPU cores.");

  }

}
check_crash_handling

确保core dump不进入程序

/* Make sure that core dumps don't go to a program. */
//确保核心转储不进入程序。
static void check_crash_handling(void) {

#ifdef __APPLE__

  /* Yuck! There appears to be no simple C API to query for the state of 
     loaded daemons on MacOS X, and I'm a bit hesitant to do something
     more sophisticated, such as disabling crash reporting via Mach ports,
     until I get a box to test the code. So, for now, we check for crash
     reporting the awful way. */
  
  if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash$'")) return;

  SAYF("\n" cLRD "[-] " cRST
       "Whoops, your system is configured to forward crash notifications to an\n"
       "    external crash reporting utility. This will cause issues due to the\n"
       "    extended delay between the fuzzed binary malfunctioning and this fact\n"
       "    being relayed to the fuzzer via the standard waitpid() API.\n\n"
       "    To avoid having crashes misinterpreted as timeouts, please run the\n" 
       "    following commands:\n\n"

       "    SL=/System/Library; PL=com.apple.ReportCrash\n"
       "    launchctl unload -w ${SL}/LaunchAgents/${PL}.plist\n"
       "    sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist\n");

  if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES"))
    FATAL("Crash reporter detected");

#else

  /* This is Linux specific, but I don't think there's anything equivalent on
     *BSD, so we can just let it slide for now. */

  s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY);
  u8  fchar;

  if (fd < 0) return;

  ACTF("Checking core_pattern...");

  if (read(fd, &fchar, 1) == 1 && fchar == '|') {

    SAYF("\n" cLRD "[-] " cRST
         "Hmm, your system is configured to send core dump notifications to an\n"
         "    external utility. This will cause issues: there will be an extended delay\n"
         "    between stumbling upon a crash and having this information relayed to the\n"
         "    fuzzer via the standard waitpid() API.\n\n"

         "    To avoid having crashes misinterpreted as timeouts, please log in as root\n" 
         "    and temporarily modify /proc/sys/kernel/core_pattern, like so:\n\n"

         "    echo core >/proc/sys/kernel/core_pattern\n");

    if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES"))
      FATAL("Pipe at the beginning of 'core_pattern'");

  }
 
  close(fd);

#endif /* ^__APPLE__ */

}
check_cpu_governor

检查CPU管理者

/* Check CPU governor. */
//检查CPU管理者
static void check_cpu_governor(void) {

  FILE* f;
  u8 tmp[128];
  u64 min = 0, max = 0;

  if (getenv("AFL_SKIP_CPUFREQ")) return;

  f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r");
  if (!f) return;

  ACTF("Checking CPU scaling governor...");

  if (!fgets(tmp, 128, f)) PFATAL("fgets() failed");

  fclose(f);

  if (!strncmp(tmp, "perf", 4)) return;

  f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r");

  if (f) {
    if (fscanf(f, "%llu", &min) != 1) min = 0;
    fclose(f);
  }

  f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r");

  if (f) {
    if (fscanf(f, "%llu", &max) != 1) max = 0;
    fclose(f);
  }

  if (min == max) return;

  SAYF("\n" cLRD "[-] " cRST
       "Whoops, your system uses on-demand CPU frequency scaling, adjusted\n"
       "    between %llu and %llu MHz. Unfortunately, the scaling algorithm in the\n"
       "    kernel is imperfect and can miss the short-lived processes spawned by\n"
       "    afl-fuzz. To keep things moving, run these commands as root:\n\n"

       "    cd /sys/devices/system/cpu\n"
       "    echo performance | tee cpu*/cpufreq/scaling_governor\n\n"

       "    You can later go back to the original state by replacing 'performance' with\n"
       "    'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ\n"
       "    to make afl-fuzz skip this check - but expect some performance drop.\n",
       min / 1024, max / 1024);

  FATAL("Suboptimal CPU scaling governor");

}

setup_post

如果设置了AFL_POST_LIBRARY环境变量,则加载afl_portprocess函数

/* Load postprocessor, if available. */

static void setup_post(void) {

  void* dh;
  u8* fn = getenv("AFL_POST_LIBRARY");
  //获取环境变量AFL_POST_LIBRARY
  u32 tlen = 6;

  if (!fn) return;  
  //如果没有设置AFL_POST_LIBRARY,直接返回

  ACTF("Loading postprocessor from '%s'...", fn);
  //输出环境变量AFL_POST_LIBRARY

  dh = dlopen(fn, RTLD_NOW);
  //以RTLD_NOW模式打开AFL_POST_LIBRARY环境变量指向的动态链接库
  //在返回前解析处所有未定义的符号
  if (!dh) FATAL("%s", dlerror());
  //如果dlopen没有解析出未定义的符号,报错

  post_handler = dlsym(dh, "afl_postprocess");
  //post_handler赋值为动态链接库中afl_postprocess()函数地址
  if (!post_handler) FATAL("Symbol 'afl_postprocess' not found.");
  //如果没有获取到afl_postprocess函数地址,抛出异常

  /* Do a quick test. It's better to segfault now than later =) */

  post_handler("hello", &tlen);//afl_portprocess函数测试

  OKF("Postprocessor installed successfully.");

}

setup_shm

设置共享内存以及virgin_bitis

/* Configure shared memory and virgin_bits. This is called at startup. */
//设置共享内存以及virgin_bitis
/*virgin_bits:记录当前tuple信息,通过virgin_tmout和virgin_crash记录fuzz过程
中出现的所有目标程序超时及崩溃的tuple信息*/
//trace_bits:记录当前的tuple信息,位于共享内存上,便于进程间通信
EXP_ST void setup_shm(void) {

  u8* shm_str;

  if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE);
  //如果in_bitmap为空,则初始化virgin_bits[MAP_SIZE]数组,每个元素为'255'(\xff)

  memset(virgin_tmout, 255, MAP_SIZE);
  //初始化virgin_tmout[MAP_SIZE]数组,每个元素值为'255'(\xff)
  memset(virgin_crash, 255, MAP_SIZE);
  //初始化virgin_crash[MAP_SIZE]数组,每个元素值为'255'(\xff)

  shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);
  //shm_id为shmget函数返回的共享内存标识符
  /*
  shmget函数创建共享内存,IPC_PRIVATE为0所以创建一块新的共享内存,内存空间为MAP_SIZE字节
  权限:
  IPC_CREAT如果共享内存不存在,则创建一个共享内存
  IPC_EXEC只有在共享内存不存在的时候,新的共享内存才会创建,否则产生错误
  0600代表拥有者具有读写权限
  */


  if (shm_id < 0) PFATAL("shmget() failed");
  //如果没有获得共享内存标识符,即共享内存创建失败,抛出异常

  atexit(remove_shm);
  //注册程序正常终止时删除共享内存
  //remove_shm()函数对共享内存进行IPC_RMID操作(删除操作)

  shm_str = alloc_printf("%d", shm_id); 
  //创建shm_id字符串变量,里面存放着shm_id

  /* If somebody is asking us to fuzz instrumented binaries in dumb mode,
     we don't want them to detect instrumentation, since we won't be sending
     fork server commands. This should be replaced with better auto-detection
     later on, perhaps? */

  if (!dumb_mode) setenv(SHM_ENV_VAR, shm_str, 1);
  //如果没有设置dumb模式,设置"SHM_ENV_VAR"环境变量为shm_str

  ck_free(shm_str);  //释放shm_str指针

  trace_bits = shmat(shm_id, NULL, 0);
  //启动shmat对共享内存的访问,调用成功将trace_bits变量赋值为指向共享内存第一个字节的指针
  
  if (trace_bits == (void *)-1) PFATAL("shmat() failed");
  //如果shmat访问共享内存失败返回-1,并抛出异常

}

init_count_class16

统计遍历路径的数量

tcace_bits是用一个字节来记录是否到达某一个路径,和这个路径被命中多少次

static const u8 count_class_lookup8[256] = {

  [0]           = 0,
  [1]           = 1,
  [2]           = 2,
  [3]           = 4,
  [4 ... 7]     = 8,   //遍历4次到7次记作8次
  [8 ... 15]    = 16,  //遍历8次到15次记作16次
  [16 ... 31]   = 32,   //遍历16次到31次记作32次
  [32 ... 127]  = 64,   //遍历32次到127次记作64次
  [128 ... 255] = 128   //遍历128次到255次记作128次

};

static u16 count_class_lookup16[65536];


EXP_ST void init_count_class16(void) {

  u32 b1, b2;

  for (b1 = 0; b1 < 256; b1++) 
    for (b2 = 0; b2 < 256; b2++)
    /*将count_class_lookup16[65536]拆分成两个嵌套的256次循环
    256*256=65536
    */
      count_class_lookup16[(b1 << 8) + b2] = 
        (count_class_lookup8[b1] << 8) |
        count_class_lookup8[b2];
        /*count_class_lookup8[b1]左移8的结果同count_class_lookup8[b2]
        进行按位或操作,结果放在b1左移8加上b2下标的count_class_lookup16数组成员中
        */

}

通过对路径判断是否有什么利用价值,减小该路径的循环次数==》减小循环造成的影响

setup_dirs_fds

准备输出文件目录(out_dir或sync_dir),创建plot_file

/* Prepare output directories and fds. */

EXP_ST void setup_dirs_fds(void) {

  u8* tmp;
  s32 fd;

  ACTF("Setting up output directories...");

  if (sync_id && mkdir(sync_dir, 0700) && errno != EEXIST)
  //如果sync_id存在,则创建sync_dir目录,赋予读写执行权限
      PFATAL("Unable to create '%s'", sync_dir);
      //创建失败error != EEXIST时抛出异常

  if (mkdir(out_dir, 0700)) {  //创建out_dir,赋予读写执行权限

    if (errno != EEXIST) PFATAL("Unable to create '%s'", out_dir);
    //如果error!=EEXIST,抛出异常

    maybe_delete_out_dir();//调用函数

  } else {
    //如果out_dir没有被创建成功

    if (in_place_resume)//如果设置了in_place_resume,抛出异常
      FATAL("Resume attempted but old output directory not found");

    out_dir_fd = open(out_dir, O_RDONLY);
    //以只读的方式打开out_dir,返回out_put句柄

#ifndef __sun  //如果没有定义宏__sun

    if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB))
    //如果打开打开out_dir失败,或者为out_dir通过flock()函数创建互斥锁定失败,抛出异常
      PFATAL("Unable to flock() output directory.");

#endif /* !__sun */

  }

  /* Queue directory for any starting & discovered paths. */

  tmp = alloc_printf("%s/queue", out_dir);
  //创建out_dir/queue目录,权限读写执行
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* Top-level directory for queue metadata used for session
     resume and related tasks. */

  tmp = alloc_printf("%s/queue/.state/", out_dir);
  //创建out_dir/queue/.state/目录,权限读写执行
  //该目录用于保存session resume和tasks的队列元元素
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* Directory for flagging queue entries that went through
     deterministic fuzzing in the past. */

  tmp = alloc_printf("%s/queue/.state/deterministic_done/", out_dir);
  //创建out_dir/queue/.state/deterministic_done/目录,权限读写执行
  //该目录标记过去经历过deterministic fuzzing的队列条目
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* Directory with the auto-selected dictionary entries. */

  tmp = alloc_printf("%s/queue/.state/auto_extras/", out_dir);
  //创建out_dir/queue/.state/auto_extras/目录,权限读写执行
  //该目录用于保存带有自动选择的字典条目
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* The set of paths currently deemed redundant. */

  tmp = alloc_printf("%s/queue/.state/redundant_edges/", out_dir);
  //创建out_dir/queue/.state/redundant_edges/,权限读写执行
  //该目录保存被认为是多余的路径集
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* The set of paths showing variable behavior. */

  tmp = alloc_printf("%s/queue/.state/variable_behavior/", out_dir);
  //创建out_dir/queue/.state/variable_behavior/,权限读写执行
  //该目录保存不同行为的路径集
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* Sync directory for keeping track of cooperating fuzzers. */

  if (sync_id) {

    tmp = alloc_printf("%s/.synced/", out_dir);
    //创建out_dir/.synced/目录,权限读写执行
    //该目录用于跟踪cooperating fuzzers
    if (mkdir(tmp, 0700) && (!in_place_resume || errno != EEXIST))
      PFATAL("Unable to create '%s'", tmp);

    ck_free(tmp);

  }

  /* All recorded crashes. */

  tmp = alloc_printf("%s/crashes", out_dir);
  //创建out_dir/crashes,权限读写执行
  //该目录用于记录crash
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* All recorded hangs. */

  tmp = alloc_printf("%s/hangs", out_dir);
  //创建out_dir/hangs,权限读写执行
  //该目录用于记录hangs
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  /* Generally useful file descriptors. */

  dev_null_fd = open("/dev/null", O_RDWR);
  //尝试以读写方式打开/dev/null
  if (dev_null_fd < 0) PFATAL("Unable to open /dev/null");

  dev_urandom_fd = open("/dev/urandom", O_RDONLY);
  //尝试以读写方式打开/dev/urandom
  if (dev_urandom_fd < 0) PFATAL("Unable to open /dev/urandom");

  /* Gnuplot output file. 建立Gnuplot目录 */

  tmp = alloc_printf("%s/plot_data", out_dir);
  //以只写方式打开out_dir/plot_data文件
  fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600);
  //如果文件不存在就创建,返回句柄fd
  if (fd < 0) PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  plot_file = fdopen(fd, "w");
  //根据句柄获得FILE结构体指针plot_file
  if (!plot_file) PFATAL("fdopen() failed");

  fprintf(plot_file, "# unix_time, cycles_done, cur_path, paths_total, "
                     "pending_total, pending_favs, map_size, unique_crashes, "
                     "unique_hangs, max_depth, execs_per_sec\n");
  //向plot_file写入unix_time, cycles_done, cur_path, paths_total,
  //pending_total, pending_favs, map_size, unique_crashes,
  //unique_hangs, max_depth, execs_per_sec
                     /* ignore errors */

}

read_testcases

从输入读取测试用例入列,在启动时调用

struct dirent{
	long d_ino; 				//索引节点号
	off_t d_off; 				//在目录文件中的偏移
	unsigned short d_reclen; 	//文件名长
	unsigned char d_type; 		//文件类型
	char d_name [NAME_MAX+1]; 	//文件名,最长255字节
}


/* Read all testcases from the input directory, then queue them for testing.
   Called at startup. */
/*从输入(input)读取测试用例并入队,在启动时调用*/
static void read_testcases(void) {

  struct dirent **nl;
  s32 nl_cnt;
  u32 i;
  u8* fn;

  /* Auto-detect non-in-place resumption attempts.
  自动检测非原位恢复尝试 */

  fn = alloc_printf("%s/queue", in_dir);
  //fn:"in_dir/queue"
  if (!access(fn, F_OK)) in_dir = fn; else ck_free(fn);
  //access()函数检查in_dir/queue是否存在(F_OK表示文件或目录是否存在)
  //如果满足要检查的权限==》路径存在,返回0  /   不存在返回-1
  //如果存在in_dir/queue目录==》设置in_dir为"in_dir/queue"

  ACTF("Scanning '%s'...", in_dir);

  /* We use scandir() + alphasort() rather than readdir() because otherwise,
     the ordering  of test cases would vary somewhat randomly and would be
     difficult to control. */
  /*调用scandir扫描in_dir,并将扫描结果排序后放在dirnet结构体变量nl中
  匹配到的文件个数存储在nl_cnt中
  不使用readdir==》测试用例的顺序会随机变化
  */
  nl_cnt = scandir(in_dir, &nl, NULL, alphasort);

  if (nl_cnt < 0) {   //如果没有扫描到匹配文件

    if (errno == ENOENT || errno == ENOTDIR)

      SAYF("\n" cLRD "[-] " cRST
           "The input directory does not seem to be valid - try again. The fuzzer needs\n"
           "    one or more test case to start with - ideally, a small file under 1 kB\n"
           "    or so. The cases must be stored as regular files directly in the input\n"
           "    directory.\n");

    PFATAL("Unable to open '%s'", in_dir);

  }

  if (shuffle_queue && nl_cnt > 1) {  //如果设置了shuffle_queue并且扫描有结果

    ACTF("Shuffling queue...");
    shuffle_ptrs((void**)nl, nl_cnt);   //使用shuffle_ptrs函数将nl指针数组进行跟跌

  }

  for (i = 0; i < nl_cnt; i++) {

    struct stat st;

    u8* fn = alloc_printf("%s/%s", in_dir, nl[i]->d_name);
    u8* dfn = alloc_printf("%s/.state/deterministic_done/%s", in_dir, nl[i]->d_name);

    u8  passed_det = 0;

    free(nl[i]); /* not tracked */
 
    if (lstat(fn, &st) || access(fn, R_OK))  //从fn获取的文件信息保存在st中,检测目录是否可以访问,不能访问抛异常
      PFATAL("Unable to access '%s'", fn);

    /* This also takes care of . and .. */

    if (!S_ISREG(st.st_mode) || !st.st_size || strstr(fn, "/README.testcases")) {
      //S_ISREG查看st.st_mode确定文件属性,st.st_size确定文件大小
      //strstr(fn, "/README.testcases")匹配是否是"/README.testcases"
      //排除干扰文件,如.或..或README

      ck_free(fn);
      ck_free(dfn);
      continue;

    }

    if (st.st_size > MAX_FILE) 
    //如果是有效文件,检查文件大小是否超多规定值界限默认1024*1024,即1M
      FATAL("Test case '%s' is too big (%s, limit is %s)", fn,
            DMS(st.st_size), DMS(MAX_FILE));

    /* Check for metadata that indicates that deterministic fuzzing
       is complete for this entry. We don't want to repeat deterministic
       fuzzing when resuming aborted scans, because it would be pointless
       and probably very time-consuming. */

    if (!access(dfn, F_OK)) passed_det = 1;
    //检查dfn是否被创建成功,此检查用来判断是否这个入口已经完成了deterministic fuzzing
    //在恢复异常终止时的扫描时不想重新进行deterministic fuzzing
    //创建成功设置passwd_det为1
    ck_free(dfn);

    add_to_queue(fn, st.st_size, passed_det);
    //调用add_to_queue将这个文件入队

  }

  free(nl); /* not tracked */

  if (!queued_paths) {   //如果没有入队的输入文件,抛异常

    SAYF("\n" cLRD "[-] " cRST
         "Looks like there are no valid test cases in the input directory! The fuzzer\n"
         "    needs one or more test case to start with - ideally, a small file under\n"
         "    1 kB or so. The cases must be stored as regular files directly in the\n"
         "    input directory.\n");

    FATAL("No usable test cases in '%s'", in_dir);

  }

  last_path_time = 0;     //设置last_path_time为0
  queued_at_start = queued_paths;    //设置queue_at_start 为 queued_paths

}

scandir()函数原型:

int scandir(const char *dir, struct dirent ***namelist, int (*select)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **));

一参:dir代表要扫描的路径

二参:扫描结果放在namelist列表中,每一项都是一个dirent

三参:select适用于过滤的函数,对于一参进行过滤

四参:compar将过滤后的结果进行排序

load_auto

load自动生成的提取出来的词典token

/* Load automatically generated extras. 加载自动生成的额外文件 */

static void load_auto(void) {

  u32 i;

  for (i = 0; i < USE_AUTO_EXTRAS; i++) {
    //循环

    u8  tmp[MAX_AUTO_EXTRA + 1];
    u8* fn = alloc_printf("%s/.state/auto_extras/auto_%06u", in_dir, i);
    s32 fd, len;

    fd = open(fn, O_RDONLY, 0600);
    //以只读模式打开fn句柄获得句柄fd

    if (fd < 0) {   //如果打不开,抛异常

      if (errno != ENOENT) PFATAL("Unable to open '%s'", fn);
      ck_free(fn);
      break;

    }

    /* We read one byte more to cheaply detect tokens that are too
       long (and skip them). */

    len = read(fd, tmp, MAX_AUTO_EXTRA + 1);
    //如果能打开,从fd指针读取MAX_AUTO_EXTRA+1个字节到tmp数组中
    //MAX_AUTO_EXTRA默认为32,+1字节判断是否读取的token过长,len为read读取长度

    if (len < 0) PFATAL("Unable to read from '%s'", fn);
    //未读取成功,抛出异常

    if (len >= MIN_AUTO_EXTRA && len <= MAX_AUTO_EXTRA)
    //如果读取长度在MIN_AUTO_EXTRA(默认3)和MAX_AUTO_EXTRA(默认32)之间
    //调用maybe_add_auto
      maybe_add_auto(tmp, len);

    close(fd);
    ck_free(fn);

  }

  if (i) OKF("Loaded %u auto-discovered dictionary tokens.", i);
  else OKF("No auto-generated dictionary tokens to reuse.");

}

maybe_add_auto

处理extras数组,判断是否添加新的a_extras[]数组项

/* Maybe add automatic extra. */

static void maybe_add_auto(u8* mem, u32 len) {
  //men为读取的auto extro文件,len为对应的长度

  u32 i;

  /* Allow users to specify that they don't want auto dictionaries. 
  允许用户指定他们不想要生成字典*/

  if (!MAX_AUTO_EXTRAS || !USE_AUTO_EXTRAS) return;
  //如果用户设置了MAX_AUTO_EXTRAS和USE_AUTO_EXTRAS为0,直接返回

  /* Skip runs of identical bytes. 跳过相同字节的运行*/

  for (i = 1; i < len; i++) //从mem[1]开始遍历
    if (mem[0] ^ mem[i]) break; 
    //跳过与mem[0]相同的字节,索引i停止在第一个与mem[0]不同的位置

  if (i == len) return;  //如果mem中所有的字节都相同,直接跳出函数

  /* Reject builtin interesting values. */

  if (len == 2) {  //如果len长度为2

    i = sizeof(interesting_16) >> 1;

    while (i--) //让mem与interesting_16[]数组中的元素进行比较
    //如果有相同,直接返回
      if (*((u16*)mem) == interesting_16[i] ||
          *((u16*)mem) == SWAP16(interesting_16[i])) return;

  }

  if (len == 4) {  //如果len长度为4

    i = sizeof(interesting_32) >> 2;

    while (i--)  //让mem与interesting_32进行比较
    //如果有相同,直接返回
      if (*((u32*)mem) == interesting_32[i] ||
          *((u32*)mem) == SWAP32(interesting_32[i])) return;

  }

  /* Reject anything that matches existing extras. Do a case-insensitive
     match. We optimize by exploiting the fact that extras[] are sorted
     by size. */

  for (i = 0; i < extras_cnt; i++)
  //由于extras数组保存元素的顺序时从小到大==》依次遍历比较长度==》
  //直接在相同的位置进行比较
    if (extras[i].len >= len) break;

  for (; i < extras_cnt && extras[i].len == len; i++)
    if (!memcmp_nocase(extras[i].data, mem, len)) return;
    //与extras[]数组中已经存放extras相比,相等就直接返回

  /* Last but not least, check a_extras[] for matches. There are no
     guarantees of a particular sort order. */

  auto_changed = 1;   //设置auto_changed为1

  for (i = 0; i < a_extras_cnt; i++) {  //遍历a_extras数组

    if (a_extras[i].len == len && !memcmp_nocase(a_extras[i].data, mem, len)) {
      //比较a_extras[i].data与mem是否相同

      a_extras[i].hit_cnt++;
      //如果相同a_extras[i].hit.cut命中次数+1
      //==》代表预料中被使用的次数
      goto sort_a_extras; //跳转至sort_a_extras

    }

  }

  /* At this point, looks like we're dealing with a new entry. So, let's
     append it if we have room. Otherwise, let's randomly evict some other
     entry from the bottom half of the list. */

  if (a_extras_cnt < MAX_AUTO_EXTRAS) {
    //如果a_extras_cut小于MAX_AUTO_EXTRAS(默认USE_AUTO_EXTRAS * 10)
    //说明a_extras_cut未满,需要构造一个新项放进a_extras数组中

    a_extras = ck_realloc_block(a_extras, (a_extras_cnt + 1) *
                                sizeof(struct extra_data));
                                //调整a_extras_cut未a_extras_cut+1

    a_extras[a_extras_cnt].data = ck_memdup(mem, len);
    //a_extras[a_extras_cnt].data设置为mem
    a_extras[a_extras_cnt].len  = len;
    //a_extras[a_extras_cnt].len设置为len
    a_extras_cnt++;  //a_extras_cut计数+1

  } else {
    //如果a_extras已经填满了

    i = MAX_AUTO_EXTRAS / 2 +
        UR((MAX_AUTO_EXTRAS + 1) / 2);
    //获取一个随机数i

    ck_free(a_extras[i].data);  //随机替换掉一个a_extras[i].data

    a_extras[i].data    = ck_memdup(mem, len);  //替换成mem
    a_extras[i].len     = len;   //长度替换成len
    a_extras[i].hit_cnt = 0;    //hit_cnt替换成0

  }

sort_a_extras:

  /* First, sort all auto extras by use count, descending order. */

  qsort(a_extras, a_extras_cnt, sizeof(struct extra_data),
        compare_extras_use_d);
        //按照使用次数,降序对所有a_extras[i]进行排序

  /* Then, sort the top USE_AUTO_EXTRAS entries by size. */

  qsort(a_extras, MIN(USE_AUTO_EXTRAS, a_extras_cnt),
        sizeof(struct extra_data), compare_extras_len);
        //按size对a_extras中前USE_AUTO_EXTRAS个进行排序(默认50)
}

pivot_inputs

为input中的测试用例创建硬链接

/* Create hard links for input test cases in the output directory, choosing
   good names and pivoting accordingly. */
//为input中的测试用例创建硬链接
static void pivot_inputs(void) {

  struct queue_entry* q = queue; //创建fuzzing队列结构体queue
  u32 id = 0;

  ACTF("Creating hard links for all input files...");

  while (q) {

    u8  *nfn, *rsl = strrchr(q->fname, '/');
    //将测试用例文件名称赋给rsl
    u32 orig_id;

    if (!rsl) rsl = q->fname; else rsl++;
    //如果没有获取文件名称
    //将用例文件路径赋给rsl

    /* If the original file name conforms to the syntax and the recorded
       ID matches the one we'd assign, just use the original file name.
       This is valuable for resuming fuzzing runs. */

#ifndef SIMPLE_FILES  //如果没有定义SIMPLE_FILES(单文件)
#  define CASE_PREFIX "id:"
#else
#  define CASE_PREFIX "id_"
#endif /* ^!SIMPLE_FILES */

    if (!strncmp(rsl, CASE_PREFIX, 3) &&
        sscanf(rsl + 3, "%06u", &orig_id) == 1 && orig_id == id) {
          /*比较rsl前三个字节如果是CASE_OREFIX,并且可以将CASE_PREFIX之后的数组(id)以格式化
          "%06u"的形式写入orig_id,并且orig_id == id*/

      u8* src_str;
      u32 src_id;

      resuming_fuzz = 1;  //设置resuming_fuzz为1
      nfn = alloc_printf("%s/queue/%s", out_dir, rsl);


      /* Since we're at it, let's also try to find parent and figure out the
         appropriate depth for this entry. */

      src_str = strchr(rsl + 3, ':');  //搜索第一次出现":"之后的字符串
      //保存在src_str中

      if (src_str && sscanf(src_str + 1, "%06u", &src_id) == 1) {
        //如果获取到src_str,并且能够以格式化"%06u"的形式写入src_id中

        struct queue_entry* s = queue;  //创建queue_entry结构体指针s
        while (src_id-- && s) s = s->next;  //从指针s从头开始扫描,没扫描一个元素,src_id--
        //s后移到下一个元素
        if (s) q->depth = s->depth + 1;  //src_id--至0,并且s队列中还有数值
        //队列深度+1

        if (max_depth < q->depth) max_depth = q->depth;
        //判断队列深度是否超过最大深度
        //如果超过,则指定最大深度

      }

    } else {
      //如果不是以CASE_PREFIX开头

      /* No dice - invent a new name, capturing the original one as a
         substring. */

#ifndef SIMPLE_FILES   //如果没有定义SIMPLE_FILES(单文件)

      u8* use_name = strstr(rsl, ",orig:");  //检测srl是否存在",orig:"为前缀的字符串

      if (use_name) use_name += 6; else use_name = rsl;
      //存在前缀,取后面的字符串赋值给use_name
      //如果没有则use_name被赋值为rsl
      nfn = alloc_printf("%s/queue/id:%06u,orig:%s", out_dir, id, use_name);
      //nfn:out_dir/queue/id:000000,orig:a.txt(id为0为例)

#else//如果定义了SIMPLE_FILES(单文件)

      nfn = alloc_printf("%s/queue/id_%06u", out_dir, id); //拼接

#endif /* ^!SIMPLE_FILES */

    }

    /* Pivot to the new queue entry. */

    link_or_copy(q->fname, nfn);
    //调用link_or_copy创建硬链接   q->fname到nfn
    ck_free(q->fname);
    q->fname = nfn;   //重新对队列中这一元素的fname进行赋值为nfn

    /* Make sure that the passed_det value carries over, too. */

    if (q->passed_det) mark_as_det_done(q);
    //如果设置了q->passed_det为1
    //调用mark_as_det_done函数标记queue这一项已经fuzz过了
    //保持q->passed_det为1

    q = q->next;   //指向下一个队列元素
    id++;    //id增加

  }

  if (in_place_resume) nuke_resume_dir();  //遍历结束后增加检查in_place_resume
  //如果设置了,调用nuke_resume_dir删除output/_resume/*临时目录,该目录主要用于本地临时恢复

}

load_extras

如果定义了extras_dir,从extras_dir读取extras到extras数组里,并按size排序

/* Read extras from the extras directory and sort them by size. */
//从extras_dir读取extras到extras数组里,并按size排序
static void load_extras(u8* dir) {

  DIR* d;
  struct dirent* de;
  u32 min_len = MAX_DICT_FILE, max_len = 0, dict_level = 0;
  u8* x;

  /* If the name ends with @, extract level and continue. */

  if ((x = strchr(dir, '@'))) {  //检测dir目录中是否存在@字符

    *x = 0;   //如果有的话替换为空字节
    dict_level = atoi(x + 1);   //将@后面的字符以int形式赋值给dict_level变量

  }

  ACTF("Loading extra dictionary from '%s' (level %u)...", dir, dict_level);

  d = opendir(dir);   //打开dir路径

  if (!d) {   //打开失败

    if (errno == ENOTDIR) {
      //如果报错为ENOTDIR,表示这不是一个目录文件
      load_extras_file(dir, &min_len, &max_len, dict_level);
      goto check_and_sort;
    }

    PFATAL("Unable to open '%s'", dir);
    //报错不为ENOTDIR,直接抛异常

  }

  if (x) FATAL("Dictionary levels not supported for directories.");

  while ((de = readdir(d))) {
    //循环扫描并读取目录下的文件到extras[]中,按照size大小排序

    struct stat st;
    u8* fn = alloc_printf("%s/%s", dir, de->d_name);
    s32 fd;

    if (lstat(fn, &st) || access(fn, R_OK))
      PFATAL("Unable to access '%s'", fn);

    /* This also takes care of . and .. */
    if (!S_ISREG(st.st_mode) || !st.st_size) {

      ck_free(fn);
      continue;

    }

    if (st.st_size > MAX_DICT_FILE)
      FATAL("Extra '%s' is too big (%s, limit is %s)", fn,
            DMS(st.st_size), DMS(MAX_DICT_FILE));

    if (min_len > st.st_size) min_len = st.st_size;
    if (max_len < st.st_size) max_len = st.st_size;

    extras = ck_realloc_block(extras, (extras_cnt + 1) *
               sizeof(struct extra_data));

    extras[extras_cnt].data = ck_alloc(st.st_size);
    extras[extras_cnt].len  = st.st_size;

    fd = open(fn, O_RDONLY);

    if (fd < 0) PFATAL("Unable to open '%s'", fn);

    ck_read(fd, extras[extras_cnt].data, st.st_size, fn);

    close(fd);
    ck_free(fn);

    extras_cnt++;

  }

  closedir(d);

check_and_sort:   //检查token的size

  if (!extras_cnt) FATAL("No usable files in '%s'", dir);

  qsort(extras, extras_cnt, sizeof(struct extra_data), compare_extras_len);

  OKF("Loaded %u extra tokens, size range %s to %s.", extras_cnt,
      DMS(min_len), DMS(max_len));

  if (max_len > 32)
    WARNF("Some tokens are relatively large (%s) - consider trimming.",
          DMS(max_len));

  if (extras_cnt > MAX_DET_EXTRAS)
    WARNF("More than %u tokens - will use them probabilistically.",
          MAX_DET_EXTRAS);

}

find_timeout

当恢复本地会话时没有使用参数-t参数进行设置,防止不停调整超过时间

调用此函数需判断timeout_given变量是否进行设置,如果没有设置,调用本函数

/* The same, but for timeouts. The idea is that when resuming sessions without
   -t given, we don't want to keep auto-scaling the timeout over and over
   again to prevent it from growing due to random flukes. */

static void find_timeout(void) {

  static u8 tmp[4096]; /* Ought to be enough for anybody. */

  u8  *fn, *off;
  s32 fd, i;
  u32 ret;

  if (!resuming_fuzz) return;  //如果没有设置resuming_fuzz,直接return

  if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir);
  //如果设置了in_place_resume为1
  //fn:out_dir/fuzzer_stats
  else fn = alloc_printf("%s/../fuzzer_stats", in_dir);
  //如果没有设置
  //fn:in_dir/../fuzzer_stats

  fd = open(fn, O_RDONLY);//以只读的模式打开fn
  ck_free(fn);

  if (fd < 0) return;   //如果没打开直接返回

  i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */
  //读取文件内容到tmp[4096]中
  close(fd);

  off = strstr(tmp, "exec_timeout      : ");
  //在tmp中查找字符串"exec_timeout      : "
  if (!off) return;   //没找到直接返回

  ret = atoi(off + 20);    //读取这个timeout值给ret
  if (ret <= 4) return;   //如果timeout值小于等于4,直接退出

  exec_tmout = ret;   //如果大于4,赋值给exec_tmout变量
  timeout_given = 3;   //timeout_given设置为3

}

detect_file_args

检查输入argv中是否存在@@,替换成out_dir/.cur_input

/* Detect @@ in args. */

EXP_ST void detect_file_args(char** argv) {

  u32 i = 0;
  u8* cwd = getcwd(NULL, 0);    //获取当前工作目录的绝对路径

  if (!cwd) PFATAL("getcwd() failed");
  //如果没有获取到,抛出异常

  while (argv[i]) {   //遍历输入的参数

    u8* aa_loc = strstr(argv[i], "@@");
    //查找本次循环中参数是否存在@@,找到了就将其位置放在aa_loc中

    if (aa_loc) {   //如果存在

      u8 *aa_subst, *n_arg;

      /* If we don't have a file name chosen yet, use a safe default. */

      if (!out_file)    //如果没有设置out_file
        out_file = alloc_printf("%s/.cur_input", out_dir);
        //赋值out_file为路径:out_dir/.cur_input

      /* Be sure that we're always using fully-qualified paths. */

      if (out_file[0] == '/') aa_subst = out_file;
      //判断路径是否以/开始
      //如果是,则直接赋值给aa_subst变量
      else aa_subst = alloc_printf("%s/%s", cwd, out_file);
      //如果不是以/开始,则直接拼接绝对路径

      /* Construct a replacement argv value. */

      *aa_loc = 0;   //将当前@@替换成空字符
      n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2);
      //跳过aal_loc两个字节进行拼接
      argv[i] = n_arg;   //n_arg:out_dir/.cur_input
      *aa_loc = '@';   //将当前位置替换为@

      if (out_file[0] != '/') ck_free(aa_subst);

    }

    i++;

  }

  free(cwd); /* not tracked */

}

setup_stdio_file

如果没有设置out_file,调用该函数,如果没有使用-f,删除原本的out_dir/.cur_input

创建一个新的out_dir/.cur_input,保存其文件描述符在out_fd中

/* Setup the output file for fuzzed data, if not using -f. */

EXP_ST void setup_stdio_file(void) {

  u8* fn = alloc_printf("%s/.cur_input", out_dir);
  //fn: out_dir/.cur_input

  unlink(fn); /* Ignore errors */

  out_fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600);
  //删除原本的out_dir/.cur_input,创建新的,将文件描述符交给out_fd

  if (out_fd < 0) PFATAL("Unable to create '%s'", fn);
  //open失败,抛出异常

  ck_free(fn);

}

check_binary

检查指定路径要执行的程序是否存在,是否为shell脚本,同时检查elf文件头是否合法及程序是否被插桩

/* Do a PATH search and find target binary to see that it exists and
   isn't a shell script - a common and painful mistake. We also check for
   a valid ELF header and for evidence of AFL instrumentation. */

EXP_ST void check_binary(u8* fname) {

  u8* env_path = 0;
  struct stat st;

  s32 fd;
  u8* f_data;
  u32 f_len = 0;

  ACTF("Validating target binary...");

  if (strchr(fname, '/') || !(env_path = getenv("PATH"))) {

    target_path = ck_strdup(fname);
    if (stat(target_path, &st) || !S_ISREG(st.st_mode) ||
        !(st.st_mode & 0111) || (f_len = st.st_size) < 4)
      FATAL("Program '%s' not found or not executable", fname);

  } else {

    while (env_path) {

      u8 *cur_elem, *delim = strchr(env_path, ':');

      if (delim) {

        cur_elem = ck_alloc(delim - env_path + 1);
        memcpy(cur_elem, env_path, delim - env_path);
        delim++;

      } else cur_elem = ck_strdup(env_path);

      env_path = delim;

      if (cur_elem[0])
        target_path = alloc_printf("%s/%s", cur_elem, fname);
      else
        target_path = ck_strdup(fname);

      ck_free(cur_elem);

      if (!stat(target_path, &st) && S_ISREG(st.st_mode) &&
          (st.st_mode & 0111) && (f_len = st.st_size) >= 4) break;

      ck_free(target_path);
      target_path = 0;

    }

    if (!target_path) FATAL("Program '%s' not found or not executable", fname);

  }

  if (getenv("AFL_SKIP_BIN_CHECK")) return;

  /* Check for blatant user errors. */

  if ((!strncmp(target_path, "/tmp/", 5) && !strchr(target_path + 5, '/')) ||
      (!strncmp(target_path, "/var/tmp/", 9) && !strchr(target_path + 9, '/')))
     FATAL("Please don't keep binaries in /tmp or /var/tmp");

  fd = open(target_path, O_RDONLY);

  if (fd < 0) PFATAL("Unable to open '%s'", target_path);

  f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0);

  if (f_data == MAP_FAILED) PFATAL("Unable to mmap file '%s'", target_path);

  close(fd);

  if (f_data[0] == '#' && f_data[1] == '!') {

    SAYF("\n" cLRD "[-] " cRST
         "Oops, the target binary looks like a shell script. Some build systems will\n"
         "    sometimes generate shell stubs for dynamically linked programs; try static\n"
         "    library mode (./configure --disable-shared) if that's the case.\n\n"

         "    Another possible cause is that you are actually trying to use a shell\n" 
         "    wrapper around the fuzzed component. Invoking shell can slow down the\n" 
         "    fuzzing process by a factor of 20x or more; it's best to write the wrapper\n"
         "    in a compiled language instead.\n");

    FATAL("Program '%s' is a shell script", target_path);

  }

#ifndef __APPLE__

  if (f_data[0] != 0x7f || memcmp(f_data + 1, "ELF", 3))
    FATAL("Program '%s' is not an ELF binary", target_path);

#else

  if (f_data[0] != 0xCF || f_data[1] != 0xFA || f_data[2] != 0xED)
    FATAL("Program '%s' is not a 64-bit Mach-O binary", target_path);

#endif /* ^!__APPLE__ */

  if (!qemu_mode && !dumb_mode &&
      !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) {

    SAYF("\n" cLRD "[-] " cRST
         "Looks like the target binary is not instrumented! The fuzzer depends on\n"
         "    compile-time instrumentation to isolate interesting test cases while\n"
         "    mutating the input data. For more information, and for tips on how to\n"
         "    instrument binaries, please see %s/README.\n\n"

         "    When source code is not available, you may be able to leverage QEMU\n"
         "    mode support. Consult the README for tips on how to enable this.\n"

         "    (It is also possible to use afl-fuzz as a traditional, \"dumb\" fuzzer.\n"
         "    For that, you can use the -n option - but expect much worse results.)\n",
         doc_path);

    FATAL("No instrumentation detected");

  }

  if (qemu_mode &&
      memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) {

    SAYF("\n" cLRD "[-] " cRST
         "This program appears to be instrumented with afl-gcc, but is being run in\n"
         "    QEMU mode (-Q). This is probably not what you want - this setup will be\n"
         "    slow and offer no practical benefits.\n");

    FATAL("Instrumentation found in -Q mode");

  }

  if (memmem(f_data, f_len, "libasan.so", 10) ||
      memmem(f_data, f_len, "__msan_init", 11)) uses_asan = 1;

  /* Detect persistent & deferred init signatures in the binary. */

  if (memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) {

    OKF(cPIN "Persistent mode binary detected.");
    setenv(PERSIST_ENV_VAR, "1", 1);
    persistent_mode = 1;

  } else if (getenv("AFL_PERSISTENT")) {

    WARNF("AFL_PERSISTENT is no longer supported and may misbehave!");

  }

  if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) {

    OKF(cPIN "Deferred forkserver binary detected.");
    setenv(DEFER_ENV_VAR, "1", 1);
    deferred_mode = 1;

  } else if (getenv("AFL_DEFER_FORKSRV")) {

    WARNF("AFL_DEFER_FORKSRV is no longer supported and may misbehave!");

  }

  if (munmap(f_data, f_len)) PFATAL("unmap() failed");

}

get_cur_time

以毫秒为单位获取unix时间

/* Get unix time in milliseconds */

static u64 get_cur_time(void) {

  struct timeval tv;
  struct timezone tz;

  gettimeofday(&tv, &tz);

  return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000);

}

perform_dry_run

执行所有的测试用例,以检查是否按照预期工作

/* Perform dry run of all test cases to confirm that the app is working as
   expected. This is done only for the initial inputs, and only once. */

static void perform_dry_run(char** argv) {

  struct queue_entry* q = queue;
  u32 cal_failures = 0;
  u8* skip_crashes = getenv("AFL_SKIP_CRASHES");

  while (q) {   //循环队列

    u8* use_mem;
    u8  res;
    s32 fd;

    u8* fn = strrchr(q->fname, '/') + 1;
    //获取测试用例名称,以a.txt为例
    //fn:id:000000,orig:a.txt

    ACTF("Attempting dry run with '%s'...", fn);

    fd = open(q->fname, O_RDONLY);  //以只读模式尝试打开测试用例
    if (fd < 0) PFATAL("Unable to open '%s'", q->fname);
    //打开失败,抛出异常

    use_mem = ck_alloc_nozero(q->len);
    //创建q->len大小的内存空间,内存指针赋给use_mem变量

    if (read(fd, use_mem, q->len) != q->len)  //将测试用例文件中的内容读进use_mem中
      FATAL("Short read from '%s'", q->fname);  //没有成功,抛出异常

    close(fd);

    res = calibrate_case(argv, q, use_mem, 0, 1);    //校准测试用例
    ck_free(use_mem);

    if (stop_soon) return;   //如果设置了stop_soon,直接返回

    if (res == crash_mode || res == FAULT_NOBITS)  //如果res结果为crash_mode或者FAULT_NOBITS
      SAYF(cGRA "    len = %u, map size = %u, exec speed = %llu us\n" cRST, 
           q->len, q->bitmap_size, q->exec_us);
           //打印q->len, q->bitmap_size, q->exec_us

    switch (res) {    //进入switch大循环判断res错误类型

      case FAULT_NONE:

        if (q == queue) check_map_coverage();
        //如果q是第一个测试用例,调用check_map_coverage函数==》评估覆盖率
        //计数trace_bits发现的路径数,如果小于100,直接返回
        //在ttrace_bits的数组后半段,如果有值就直接返回

        if (crash_mode) FATAL("Test case '%s' does *NOT* crash", fn);
        //如果设置了crash_mode,抛出异常

        break;

      case FAULT_TMOUT:

        if (timeout_given) {    //如果指定了-t参数

          /* The -t nn+ syntax in the command line sets timeout_given to '2' and
             instructs afl-fuzz to tolerate but skip queue entries that time
             out. */

          if (timeout_given > 1) {
            WARNF("Test case results in a timeout (skipping)");
            q->cal_failed = CAL_CHANCES;   //设置q->cal_failed为CAL_CHANCES
            cal_failures++;    //cal_failures计数+1
            break;
          }

          SAYF("\n" cLRD "[-] " cRST
               "The program took more than %u ms to process one of the initial test cases.\n"
               "    Usually, the right thing to do is to relax the -t option - or to delete it\n"
               "    altogether and allow the fuzzer to auto-calibrate. That said, if you know\n"
               "    what you are doing and want to simply skip the unruly test cases, append\n"
               "    '+' at the end of the value passed to -t ('-t %u+').\n", exec_tmout,
               exec_tmout);

          FATAL("Test case '%s' results in a timeout", fn);

        } else {

          SAYF("\n" cLRD "[-] " cRST
               "The program took more than %u ms to process one of the initial test cases.\n"
               "    This is bad news; raising the limit with the -t option is possible, but\n"
               "    will probably make the fuzzing process extremely slow.\n\n"

               "    If this test case is just a fluke, the other option is to just avoid it\n"
               "    altogether, and find one that is less of a CPU hog.\n", exec_tmout);

          FATAL("Test case '%s' results in a timeout", fn);

        }

      case FAULT_CRASH:  

        if (crash_mode) break;  //如果设置了crash_mod,直接break

        if (skip_crashes) {
          //如果设置了skip_crash
          WARNF("Test case results in a crash (skipping)");
          q->cal_failed = CAL_CHANCES;  //设置q->cal_failed为CAL_CHANCES
          cal_failures++;   //cal_failures计数+1
          break;
        }

        if (mem_limit) {   //如果设置了mem_limit,提示内存不足,抛出异常

          SAYF("\n" cLRD "[-] " cRST
               "Oops, the program crashed with one of the test cases provided. There are\n"
               "    several possible explanations:\n\n"

               "    - The test case causes known crashes under normal working conditions. If\n"
               "      so, please remove it. The fuzzer should be seeded with interesting\n"
               "      inputs - but not ones that cause an outright crash.\n\n"

               "    - The current memory limit (%s) is too low for this program, causing\n"
               "      it to die due to OOM when parsing valid files. To fix this, try\n"
               "      bumping it up with the -m setting in the command line. If in doubt,\n"
               "      try something along the lines of:\n\n"

#ifdef RLIMIT_AS
               "      ( ulimit -Sv $[%llu << 10]; /path/to/binary [...] <testcase )\n\n"
#else
               "      ( ulimit -Sd $[%llu << 10]; /path/to/binary [...] <testcase )\n\n"
#endif /* ^RLIMIT_AS */

               "      Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
               "      estimate the required amount of virtual memory for the binary. Also,\n"
               "      if you are using ASAN, see %s/notes_for_asan.txt.\n\n"

#ifdef __APPLE__
  
               "    - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
               "      break afl-fuzz performance optimizations when running platform-specific\n"
               "      binaries. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

               "    - Least likely, there is a horrible bug in the fuzzer. If other options\n"
               "      fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
               DMS(mem_limit << 20), mem_limit - 1, doc_path);

        } else {

          SAYF("\n" cLRD "[-] " cRST
               "Oops, the program crashed with one of the test cases provided. There are\n"
               "    several possible explanations:\n\n"

               "    - The test case causes known crashes under normal working conditions. If\n"
               "      so, please remove it. The fuzzer should be seeded with interesting\n"
               "      inputs - but not ones that cause an outright crash.\n\n"

#ifdef __APPLE__
  
               "    - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
               "      break afl-fuzz performance optimizations when running platform-specific\n"
               "      binaries. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

               "    - Least likely, there is a horrible bug in the fuzzer. If other options\n"
               "      fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");

        }

        FATAL("Test case '%s' results in a crash", fn);

      case FAULT_ERROR:
      //无法执行目标应用程序

        FATAL("Unable to execute target application ('%s')", argv[0]);

      case FAULT_NOINST:
      //样例没有出现任何路径信息

        FATAL("No instrumentation detected");

      case FAULT_NOBITS: 

        useless_at_start++;   //useless_at_start计数器+1

        if (!in_bitmap && !shuffle_queue)
        //如果这个案例有出现路径信息,但是没有出现新的路径,会被认为是一条无效的路径
          WARNF("No new instrumentation output, test case may be useless.");

        break;

    }

    if (q->var_behavior) WARNF("Instrumentation output varies across runs.");
    //如果这个样例的var_behavior为真==》说明他多次运行,同样的输入条件下,却出现不同的覆盖信息

    q = q->next;    //指向下一条用例

  }

  if (cal_failures) {    //如果设置了cal_failures

    if (cal_failures == queued_paths)   //代表所有用例均超时
      FATAL("All test cases time out%s, giving up!",
            skip_crashes ? " or crash" : "");

    WARNF("Skipped %u test cases (%0.02f%%) due to timeouts%s.", cal_failures,
          ((double)cal_failures) * 100 / queued_paths,
          skip_crashes ? " or crashes" : "");

    if (cal_failures * 5 > queued_paths)
    //计算cal_failures * 5是否大于queued_paths
      WARNF(cLRD "High percentage of rejected test cases, check settings!");
      //测试用例的问题比例很高,可能需要重新检查设置

  }

  OKF("All test cases processed.");

}

calibrate_case

评估inputs路径下的case是否存在异常行为,在新发现路径时评估testcase的行为是否为可变

static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem,
                         u32 handicap, u8 from_queue) {

  static u8 first_trace[MAP_SIZE];  //创建first_trace数组

  u8  fault = 0, new_bits = 0, var_detected = 0, hnb = 0,
      first_run = (q->exec_cksum == 0);
      //q->exec_cksum为0,说明这个case是第一次运行
      //即来自input目录下,将first_run变量设置为1

  u64 start_us, stop_us;

  s32 old_sc = stage_cur, old_sm = stage_max;
  u32 use_tmout = exec_tmout;
  u8* old_sn = stage_name;

  /* Be a bit more generous about timeouts when resuming sessions, or when
     trying to calibrate already-added finds. This helps avoid trouble due
     to intermittent latency. */

  if (!from_queue || resuming_fuzz)
  //如果from_queue为0或者resuming_fuzz为1==》
  //代表不来自于queue中或者resuming sessions的时候
    use_tmout = MAX(exec_tmout + CAL_TMOUT_ADD,
                    exec_tmout * CAL_TMOUT_PERC / 100);
                    //则设置use_tmout的值被设置的很大

  q->cal_failed++;   //q->cal_failed记录+1

  stage_name = "calibration";   //stage_name设置为"calibration"
  stage_max  = fast_cal ? 3 : CAL_CYCLES;
  //根据判断fast_cal是否为1.来设置stage_max的值为3或者CAL_CYCLES(默认8)

  //每个新测试用例以及显示出来可变行为的测试用例的校准周期数,以及本次stage要执行几次

  /* Make sure the forkserver is up before we do anything, and let's not
     count its spin-up time toward binary calibration. */

  if (dumb_mode != 1 && !no_forkserver && !forksrv_pid)
  //如果不是dumb_mode模式或者no_forkserver为0(禁用forkserver),并且forksrv_pid为0
    init_forkserver(argv);  //调用init_forkserver函数启动fork server

  if (q->exec_cksum) {
    //如果q->exec_cksum不为空,说明queue不是来自input文件夹,而是评估新的case

    memcpy(first_trace, trace_bits, MAP_SIZE);
    //将trace_bits拷贝至first_trace中
    hnb = has_new_bits(virgin_bits);
    //调用has_new_bits函数计算,将结果赋值给hnb
    if (hnb > new_bits) new_bits = hnb;
    //如果新计算结果大于new_bits
    //给new_bits重新赋值

  }

  start_us = get_cur_time_us();   //获取当前系统时间

  for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {  //循环stage_max次

    u32 cksum;

    if (!first_run && !(stage_cur % stats_update_freq)) show_stats();
    //如果这个queue不是来自input文件夹,而是评估case,并且第一轮calibration stage执行结束
    //调用show_stats函数画出界面,用来展示本次执行结果

    write_to_testcase(use_mem, q->len);  //从q->fname中读取内容写到.cur_input中

    fault = run_target(argv, use_tmout);  //run结果保存在fault中

    /* stop_soon is set by the handler for Ctrl+C. When it's pressed,
       we want to bail out quickly. */

    if (stop_soon || fault != crash_mode) goto abort_calibration;
    //如果出现终止或者fault结果不为crash_mode,跳转到abort_calibration

    if (!dumb_mode && !stage_cur && !count_bytes(trace_bits)) {
      //如果不是dumb_mode模式,并且这是calibration stage为第一次运行
      //并且共享内存中没有任何路径
      fault = FAULT_NOINST;   //fault被设置为FAULT_NOINST
      goto abort_calibration;    //跳转至abort_calibration
    }

    cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
    //hash32计算的32位uint值保存在cksum中

    if (q->exec_cksum != cksum) {
      //如果不相等,代表这是第一次运行
      //或者在相同参数下,每次运行,cksum却不同
      //是一个路径可变的queue

      hnb = has_new_bits(virgin_bits);
      if (hnb > new_bits) new_bits = hnb;
      //若条件成立,将new_bits赋值为计算结果

      if (q->exec_cksum) {
        //判断是否可变queue,如果为1,则说明不是第一次执行queue

        u32 i;

        for (i = 0; i < MAP_SIZE; i++) {

          if (!var_bytes[i] && first_trace[i] != trace_bits[i]) {
            //如果first_byts[i]为1,并且不等于trace_bits[i],说明不可变的queue

            var_bytes[i] = 1;   //如果var_bytes[i]为空则赋值为1
            stage_max    = CAL_CYCLES_LONG;    //设置stage_max为CAL_CYCLES_LONG

          }

        }

        var_detected = 1;   //将var_detected为1

      } else {
        //如果q->exec_cksum为0,代表第一次执行queue

        q->exec_cksum = cksum;   //设置q->exec_cksum的值为计算出来的本次执行的cksum
        memcpy(first_trace, trace_bits, MAP_SIZE);
        //将trace_bits拷贝至first_trace中

      }

    }

  }

  stop_us = get_cur_time_us();  //保存停止时间

  total_cal_us     += stop_us - start_us;  //总轮次执行时间存放在total_cal_us中
  total_cal_cycles += stage_max;    //总执行轮次放在stage_max中

  /* OK, let's collect some stats about the performance of this test case.
     This is used for fuzzing air time calculations in calculate_score(). */

  q->exec_us     = (stop_us - start_us) / stage_max;  //计算单词执行时间的平均值赋值给q->exec_us
  q->bitmap_size = count_bytes(trace_bits);   //最后一次执行所覆盖的路径数赋值给q->bitmap_size
  q->handicap    = handicap;
  q->cal_failed  = 0;

  total_bitmap_size += q->bitmap_size;   //加上这个queue所覆盖到的路径数
  total_bitmap_entries++;

  update_bitmap_score(q);

  /* If this case didn't result in new output from the instrumentation, tell
     parent. This is a non-critical problem, but something to warn the user
     about. */
 /*如果fault为FAULT_NONE,并且不是dumb_mode模式,并且是第一次运行,
 并且new_bits为0,代表在这个样例所有轮次的执行里,都没有发现新的路径和异常*/
  if (!dumb_mode && first_run && !fault && !new_bits) fault = FAULT_NOBITS;
  //设置fault为FAULT_NOBITS

abort_calibration:

  if (new_bits == 2 && !q->has_new_cov) {
    //new_bits为2并且q->has_new_cov为空
    q->has_new_cov = 1;  //设置q->has_new_cov为1
    queued_with_cov++;  //+1==》代表queue发现一条新的路径
  }

  /* Mark variable paths. */

  if (var_detected) {  //var_detected为1==》这个queue为一个可变的路径

    var_byte_count = count_bytes(var_bytes);
    //计算var_bytes里被置位的tuple个数,保存到var_byte_count中,代表tuple具有可变行为

    if (!q->var_behavior) {
      mark_as_variable(q);
      //创建符号链接out_dir/queue/.state/variable_behavior/fname
      //设置queue的var_behavior为1
      queued_variable++;   //queued_variable计数器+1
    }

  }

  stage_name = old_sn;
  stage_cur  = old_sc;
  stage_max  = old_sm;   //恢复之前的stage

  if (!first_run) show_stats();  //如果不是第一次运行这个queue,展示状态

  return fault;  //返回fault值

}

init_forkserver

为目标程序创建通信通道,并且通过fork server fork出子进程,判断启动失败的原因

/* Spin up fork server (instrumented mode only). The idea is explained here:

   http://lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html

   In essence, the instrumentation allows us to skip execve(), and just keep
   cloning a stopped child. So, we just execute once, and then send commands
   through a pipe. The other part of this logic is in afl-as.h. */

EXP_ST void init_forkserver(char** argv) {

  static struct itimerval it;
  int st_pipe[2], ctl_pipe[2];
  int status;
  s32 rlen;

  ACTF("Spinning up the fork server...");

  if (pipe(st_pipe) || pipe(ctl_pipe)) PFATAL("pipe() failed");
  //如果无法创建管道,抛出异常

  forksrv_pid = fork();
  //fork出一个子进程
  /*如果fork成功,会出现一个子进程和父进程
  在子进程中fork函数的返回值为0
  在父进程中,fork返回创建的子进程的进程ID*/

  if (forksrv_pid < 0) PFATAL("fork() failed");
  //判断是否fork成功

  if (!forksrv_pid) {
    //判断当前为父进程或者子进程

    /*          子进程开始                */

    struct rlimit r;

    /* Umpf. On OpenBSD, the default fd limit for root users is set to
       soft 128. Let's try to fix that... */

    if (!getrlimit(RLIMIT_NOFILE, &r) && r.rlim_cur < FORKSRV_FD + 2) {

      r.rlim_cur = FORKSRV_FD + 2;
      setrlimit(RLIMIT_NOFILE, &r); /* Ignore errors */

    }

    if (mem_limit) {

      r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;

#ifdef RLIMIT_AS

      setrlimit(RLIMIT_AS, &r); /* Ignore errors */

#else

      /* This takes care of OpenBSD, which doesn't have RLIMIT_AS, but
         according to reliable sources, RLIMIT_DATA covers anonymous
         maps - so we should be getting good protection against OOM bugs. */

      setrlimit(RLIMIT_DATA, &r); /* Ignore errors */

#endif /* ^RLIMIT_AS */


    }

    /* Dumping cores is slow and can lead to anomalies if SIGKILL is delivered
       before the dump is complete. */

    r.rlim_max = r.rlim_cur = 0;

    setrlimit(RLIMIT_CORE, &r); /* Ignore errors */

    /* Isolate the process and configure standard descriptors. If out_file is
       specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */

    setsid();    //创建一个新的session 使子进程完全独立,脱离控制

    dup2(dev_null_fd, 1);
    dup2(dev_null_fd, 2);
    //重新配置fd 关闭子进程的stdout和stderr
    //将其重定位至/dev/null中
    //相当于关闭了子进程的全部输出

    if (out_file) {   //设置out_file

      dup2(dev_null_fd, 0);   //关闭子进程的stdin,重定向到/dev/null

    } else {

      dup2(out_fd, 0);  //关闭stdin,重定向至out_fd
      close(out_fd);   //关闭out_td

    }

    /* Set up control and status pipes, close the unneeded original fds. */

    if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed");
    //将FORKSRV_FD重定向至ctl_pipe[0],子进程只能读取命令
    if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed");
    //将FORKSRV_FD + 1重定向至st_pipe[1],子进程只能发送"写出"状态

    close(ctl_pipe[0]);
    close(ctl_pipe[1]);
    close(st_pipe[0]);
    close(st_pipe[1]);

    close(out_dir_fd);
    close(dev_null_fd);
    close(dev_urandom_fd);
    close(fileno(plot_file));  
    //关闭一些进程中的文件描述符

    /* This should improve performance a bit, since it stops the linker from
       doing extra work post-fork(). */

    if (!getenv("LD_BIND_LAZY")) setenv("LD_BIND_NOW", "1", 0);
    //如果没有设置LD_BIND_LAZY环境变量
    //设置LD_BIND_NOW为1,防止linker在fork之后做额外的操作

    /* Set sane defaults for ASAN if nothing else specified. */

    setenv("ASAN_OPTIONS", "abort_on_error=1:"
                           "detect_leaks=0:"
                           "symbolize=0:"
                           "allocator_may_return_null=1", 0);
                           //设置ASAN选项

    /* MSAN is tricky, because it doesn't support abort_on_error=1 at this
       point. So, we do this in a very hacky way. */

    setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":"
                           "symbolize=0:"
                           "abort_on_error=1:"
                           "allocator_may_return_null=1:"
                           "msan_track_origins=0", 0);
                           //设置MSAN选项

    execv(target_path, argv);
    //带参数执行target,该参数除非出错,否则不会返回
    //execv会替换原有的进程空间为target_path代表的程序,这个进程结束相当于子进程结束
    //第一个target会进入__afl_maybe_log里的__afl_fork_wait_loop,并充当fork server
    //在整个fuzz过程中都不会结束
    //每次要fuzz一次target都会从这个fork server中fork出一个子进程去fuzz

    /* Use a distinctive bitmap signature to tell the parent about execv()
       falling through. */

    *(u32*)trace_bits = EXEC_FAIL_SIG;
    exit(0);

  }
  //              子进程结束

  /* Close the unneeded endpoints. */

  close(ctl_pipe[0]);
  close(st_pipe[1]);    //关闭ctl_pipe[0]和st_pipe[1],控制管道读与状态管道写(父进程不需要)

  fsrv_ctl_fd = ctl_pipe[1];
  fsrv_st_fd  = st_pipe[0];

  /* Wait for the fork server to come up, but don't wait too long. */

  it.it_value.tv_sec = ((exec_tmout * FORK_WAIT_MULT) / 1000);
  it.it_value.tv_usec = ((exec_tmout * FORK_WAIT_MULT) % 1000) * 1000;
  //等待fork server 启动(不能等太久)

  setitimer(ITIMER_REAL, &it, NULL);

  rlen = read(fsrv_st_fd, &status, 4);  //从管道中读取4个字节放在status中

  it.it_value.tv_sec = 0;
  it.it_value.tv_usec = 0;

  setitimer(ITIMER_REAL, &it, NULL);   //超时报错

  /* If we have a four-byte "hello" message from the server, we're all set.
     Otherwise, try to figure out what went wrong. */

  if (rlen == 4) {   //读取了4个字节
    OKF("All right - fork server is up.");   //准备就绪,直接返回
    return;
  }

  if (child_timed_out)    //如果设置了child_time_out
    FATAL("Timeout while initializing fork server (adjusting -t may help)");
    //提醒调整-t参数

  if (waitpid(forksrv_pid, &status, 0) <= 0)//否则等待forksrv返回status
    PFATAL("waitpid() failed");

  if (WIFSIGNALED(status)) {  //等待forksrv是否返回的异常退出信号
  //如果是异常退出信号

    if (mem_limit && mem_limit < 500 && uses_asan) {
      //判断是否由于设置的mem_limit过小导致的

      SAYF("\n" cLRD "[-] " cRST
           "Whoops, the target binary crashed suddenly, before receiving any input\n"
           "    from the fuzzer! Since it seems to be built with ASAN and you have a\n"
           "    restrictive memory limit configured, this is expected; please read\n"
           "    %s/notes_for_asan.txt for help.\n", doc_path);
           //由于未收到任何input就crash,可能是由于asan和mem_limit的原因

    } else if (!mem_limit) {
      //如果没有设置mem_limit

      SAYF("\n" cLRD "[-] " cRST
           "Whoops, the target binary crashed suddenly, before receiving any input\n"
           "    from the fuzzer! There are several probable explanations:\n\n"

           "    - The binary is just buggy and explodes entirely on its own. If so, you\n"
           "      need to fix the underlying problem or find a better replacement.\n\n"

#ifdef __APPLE__

           "    - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
           "      break afl-fuzz performance optimizations when running platform-specific\n"
           "      targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

           "    - Less likely, there is a horrible bug in the fuzzer. If other options\n"
           "      fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");  //告知原因

    } else {
      //否则告知原因

      SAYF("\n" cLRD "[-] " cRST
           "Whoops, the target binary crashed suddenly, before receiving any input\n"
           "    from the fuzzer! There are several probable explanations:\n\n"

           "    - The current memory limit (%s) is too restrictive, causing the\n"
           "      target to hit an OOM condition in the dynamic linker. Try bumping up\n"
           "      the limit with the -m setting in the command line. A simple way confirm\n"
           "      this diagnosis would be:\n\n"

#ifdef RLIMIT_AS
           "      ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#else
           "      ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#endif /* ^RLIMIT_AS */

           "      Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
           "      estimate the required amount of virtual memory for the binary.\n\n"

           "    - The binary is just buggy and explodes entirely on its own. If so, you\n"
           "      need to fix the underlying problem or find a better replacement.\n\n"

#ifdef __APPLE__

           "    - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
           "      break afl-fuzz performance optimizations when running platform-specific\n"
           "      targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

           "    - Less likely, there is a horrible bug in the fuzzer. If other options\n"
           "      fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
           DMS(mem_limit << 20), mem_limit - 1);

    }

    FATAL("Fork server crashed with signal %d", WTERMSIG(status));
    //通过WIERMSIG(status)获取信号告知用户single原因

  }

  if (*(u32*)trace_bits == EXEC_FAIL_SIG)
  //检查trace_bits == EXEC_FAIL_SIG,如果设置了说明没有正常执行
    FATAL("Unable to execute target application ('%s')", argv[0]);

  if (mem_limit && mem_limit < 500 && uses_asan) {
    //判断条件,可能是由于配置的内存限制导致的

    SAYF("\n" cLRD "[-] " cRST
           "Hmm, looks like the target binary terminated before we could complete a\n"
           "    handshake with the injected code. Since it seems to be built with ASAN and\n"
           "    you have a restrictive memory limit configured, this is expected; please\n"
           "    read %s/notes_for_asan.txt for help.\n", doc_path);

  } else if (!mem_limit) {
    //如果没有设置mem_limit,可能是fuzzer的bug

    SAYF("\n" cLRD "[-] " cRST
         "Hmm, looks like the target binary terminated before we could complete a\n"
         "    handshake with the injected code. Perhaps there is a horrible bug in the\n"
         "    fuzzer. Poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");

  } else {
    //告知用户可能是其他原因

    SAYF("\n" cLRD "[-] " cRST
         "Hmm, looks like the target binary terminated before we could complete a\n"
         "    handshake with the injected code. There are %s probable explanations:\n\n"

         "%s"
         "    - The current memory limit (%s) is too restrictive, causing an OOM\n"
         "      fault in the dynamic linker. This can be fixed with the -m option. A\n"
         "      simple way to confirm the diagnosis may be:\n\n"

#ifdef RLIMIT_AS
         "      ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#else
         "      ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#endif /* ^RLIMIT_AS */

         "      Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
         "      estimate the required amount of virtual memory for the binary.\n\n"

         "    - Less likely, there is a horrible bug in the fuzzer. If other options\n"
         "      fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
         getenv(DEFER_ENV_VAR) ? "three" : "two",
         getenv(DEFER_ENV_VAR) ?
         "    - You are using deferred forkserver, but __AFL_INIT() is never\n"
         "      reached before the program terminates.\n\n" : "",
         DMS(mem_limit << 20), mem_limit - 1);

  }

  FATAL("Fork server handshake failed");

}

has_new_bits

检查有没有新路径或者某个路径的执行次数有所不同

/* Check if the current execution path brings anything new to the table.
   Update virgin bits to reflect the finds. Returns 1 if the only change is
   the hit-count for a particular tuple; 2 if there are new tuples seen. 
   Updates the map, so subsequent calls will always return 0.

   This function is called after every exec() on a fairly large buffer, so
   it needs to be fast. We do this in 32-bit and 64-bit flavors. */

static inline u8 has_new_bits(u8* virgin_map) {

#ifdef WORD_SIZE_64   //64位实现

  u64* current = (u64*)trace_bits;   //current指向trace_bits的首地址
  u64* virgin  = (u64*)virgin_map;    //virgin指向virgin_map的首地址

  u32  i = (MAP_SIZE >> 3);
  //代表MAP_SIZE除以2的3次方,即MAP_SIZE/8,按照8个字节一组,一共分了i组

#else
//32位实现

  u32* current = (u32*)trace_bits;
  u32* virgin  = (u32*)virgin_map;

  u32  i = (MAP_SIZE >> 2);
  //代表MAP_SIZE除以2的2次方,即MAP_SIZE/4,按照4个字节一组,一共分了i组

#endif /* ^WORD_SIZE_64 */

  u8   ret = 0;

  while (i--) {   //循环遍历分组

    /* Optimize for (*current & *virgin) == 0 - i.e., no bits in current bitmap
       that have not been already cleared from the virgin map - since this will
       almost always be the case. */

    if (unlikely(*current) && unlikely(*current & *virgin)) {
      //如果*current不为0且*current & *virgin
      //代表current发现了新的路径或者某条路径的执行次数和之前有所不同

      if (likely(ret < 2)) {

        u8* cur = (u8*)current;   //cur指向current第一个字节
        u8* vir = (u8*)virgin;    //vir指向virgin第一个字节

        /* Looks like we have not found any new bytes yet; see if any non-zero
           bytes in current[] are pristine in virgin[]. */

#ifdef WORD_SIZE_64

        if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) ||
            (cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff) ||
            (cur[4] && vir[4] == 0xff) || (cur[5] && vir[5] == 0xff) ||
            (cur[6] && vir[6] == 0xff) || (cur[7] && vir[7] == 0xff)) ret = 2;
            //判断cur[i] && vir[i] == 0xff
            //有一个为真
            //设置ret为2,代表发现了之前没有出现的tuple
        else ret = 1;
        //否则设置ret为1,代表只是命中次数更新

#else

        if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) ||
            (cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff)) ret = 2;

        else ret = 1;

#endif /* ^WORD_SIZE_64 */

      }

      *virgin &= ~*current;

    }

    current++;
    virgin++;
    //current和virgin移动到下一个8字节,直到MAP_SIZE全部被遍历完

  }

  if (ret && virgin_map == virgin_bits) bitmap_changed = 1;
  //如果传入的参数virgin_map值为virgin_bits,并且ret不为0
  //设置bitmap_changed为1

  return ret;  //返回ret值

}

run_target

通过子进程的方式执行目标应用,这个函数部分代码和前面的init_forkserver很像

但是这个函数处理了在设置了no_forkserver的情况的下的处理过程

/* Execute target application, monitoring for timeouts. Return status
   information. The called program will update trace_bits[]. */

static u8 run_target(char** argv, u32 timeout) {

  static struct itimerval it;
  static u32 prev_timed_out = 0;
  static u64 exec_ms = 0;

  int status = 0;
  u32 tb4;

  child_timed_out = 0;

  /* After this memset, trace_bits[] are effectively volatile, so we
     must prevent any earlier operations from venturing into that
     territory. */

  memset(trace_bits, 0, MAP_SIZE);   //清空trace_bits[MAP_SIZE]为0
  MEM_BARRIER();

  /* If we're running in "dumb" mode, we can't rely on the fork server
     logic compiled into the target program, so we will just keep calling
     execve(). There is a bit of code duplication between here and 
     init_forkserver(), but c'est la vie. */

  if (dumb_mode == 1 || no_forkserver) {
    //如果dumb_mode等于1或设置no_forkserver

    child_pid = fork();
    //fork出一个子进程
    //调用execv执行子进程中的target_path
    //如果失败了就向trace_bits中写入EXEC_FAIL_SIG

    if (child_pid < 0) PFATAL("fork() failed");

    if (!child_pid) {

      struct rlimit r;

      if (mem_limit) {

        r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;

#ifdef RLIMIT_AS

        setrlimit(RLIMIT_AS, &r); /* Ignore errors */

#else

        setrlimit(RLIMIT_DATA, &r); /* Ignore errors */

#endif /* ^RLIMIT_AS */

      }

      r.rlim_max = r.rlim_cur = 0;

      setrlimit(RLIMIT_CORE, &r); /* Ignore errors */

      /* Isolate the process and configure standard descriptors. If out_file is
         specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */

      setsid();

      dup2(dev_null_fd, 1);
      dup2(dev_null_fd, 2);

      if (out_file) {

        dup2(dev_null_fd, 0);

      } else {

        dup2(out_fd, 0);
        close(out_fd);

      }

      /* On Linux, would be faster to use O_CLOEXEC. Maybe TODO. */

      close(dev_null_fd);
      close(out_dir_fd);
      close(dev_urandom_fd);
      close(fileno(plot_file));

      /* Set sane defaults for ASAN if nothing else specified. */

      setenv("ASAN_OPTIONS", "abort_on_error=1:"
                             "detect_leaks=0:"
                             "symbolize=0:"
                             "allocator_may_return_null=1", 0);

      setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":"
                             "symbolize=0:"
                             "msan_track_origins=0", 0);

      execv(target_path, argv);

      /* Use a distinctive bitmap value to tell the parent about execv()
         falling through. */

      *(u32*)trace_bits = EXEC_FAIL_SIG;
      exit(0);

    }

  } else {
    //代表此时已经有一个fork server正在进行

    s32 res;

    /* In non-dumb mode, we have the fork server up and running, so simply
       tell it to have at it, and then read back PID. */

    if ((res = write(fsrv_ctl_fd, &prev_timed_out, 4)) != 4) {
      //向控制管道中写入4个字节的prev_timed_out
      //命令fork server开始fork出一个子进程进行fuzz

      if (stop_soon) return 0;
      RPFATAL(res, "Unable to request new process from fork server (OOM?)");

    }

    if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) {
      //从状态管道中读取fork server返回的fork出的子进程的ID到child_pid中

      if (stop_soon) return 0;
      RPFATAL(res, "Unable to request new process from fork server (OOM?)");

    }

    if (child_pid <= 0) FATAL("Fork server is misbehaving (OOM?)");

  }

  /* Configure timeout, as requested by user, then wait for child to terminate. */

  it.it_value.tv_sec = (timeout / 1000);
  it.it_value.tv_usec = (timeout % 1000) * 1000;

  setitimer(ITIMER_REAL, &it, NULL);   //超时报错

  /* The SIGALRM handler simply kills the child_pid and sets child_timed_out. */

  if (dumb_mode == 1 || no_forkserver) {
    //如果满足说明无fork server,以子进程的方式execv

    if (waitpid(child_pid, &status, 0) <= 0) PFATAL("waitpid() failed");
    //waitpid等待子进程的status

  } else {

    s32 res;

    if ((res = read(fsrv_st_fd, &status, 4)) != 4) {
      //否则从状态管道中读取status

      if (stop_soon) return 0;
      RPFATAL(res, "Unable to communicate with fork server (OOM?)");

    }

  }

  if (!WIFSTOPPED(status)) child_pid = 0;

  getitimer(ITIMER_REAL, &it);
  exec_ms = (u64) timeout - (it.it_value.tv_sec * 1000 +
                             it.it_value.tv_usec / 1000);
                             //计算执行时间

  it.it_value.tv_sec = 0;
  it.it_value.tv_usec = 0;

  setitimer(ITIMER_REAL, &it, NULL);

  total_execs++;   //执行次数计数+1

  /* Any subsequent operations on trace_bits must not be moved by the
     compiler below this point. Past this location, trace_bits[] behave
     very normally and do not have to be treated as volatile. */

  MEM_BARRIER();

  tb4 = *(u32*)trace_bits;

#ifdef WORD_SIZE_64
  classify_counts((u64*)trace_bits);
#else
  classify_counts((u32*)trace_bits);
#endif /* ^WORD_SIZE_64 */

  prev_timed_out = child_timed_out;

  /* Report outcome to caller. */
 //判断子进程是否是异常退出,如果是,判断异常输出原因后返回
  if (WIFSIGNALED(status) && !stop_soon) {
    //WIFSIGNALED(status)若为异常结束子进程返回的状态,则为真

    kill_signal = WTERMSIG(status);
    //获取子进程终止的信号代码

    if (child_timed_out && kill_signal == SIGKILL) return FAULT_TMOUT;
    //如果child_timed_out为1且状态码为SIGKILL
    //返回FAULT_TMOUT

    return FAULT_CRASH;  //返回FAULT_CRASH

  }

  /* A somewhat nasty hack for MSAN, which doesn't support abort_on_error and
     must use a special exit code. */
  /*这是对MSAN的一种恶意攻击,它不支持abort_on_error,并且必须使用特殊的退出代码*/

  if (uses_asan && WEXITSTATUS(status) == MSAN_ERROR) {
    kill_signal = 0;
    return FAULT_CRASH;
  }

  if ((dumb_mode == 1 || no_forkserver) && tb4 == EXEC_FAIL_SIG)
  //判断是否是无forkserver的execve执行失败
    return FAULT_ERROR;

  /* It makes sense to account for the slowest units only if the testcase was run
  under the user defined timeout. */
  if (!(timeout > exec_tmout) && (slowest_exec_ms < exec_ms)) {
    //如果最慢执行时间小于当前执行时间,并且timeout < exec_ms
    slowest_exec_ms = exec_ms;  //更新slowest_exec_ms
  }

  return FAULT_NONE;

}

classify_counts

static inline void classify_counts(u64* mem) {

  u32 i = MAP_SIZE >> 3;   //每8个字节一组,一共i组 

  while (i--) {  //循环遍历每组

    /* Optimize for sparse bitmaps. */

    if (unlikely(*mem)) {    //如果对应的mem不为0

      u16* mem16 = (u16*)mem;    //每次取两个字节

      mem16[0] = count_class_lookup16[mem16[0]];
      mem16[1] = count_class_lookup16[mem16[1]];
      mem16[2] = count_class_lookup16[mem16[2]];
      mem16[3] = count_class_lookup16[mem16[3]];
      //i从0~4,计算mem[i]的值,在count_class_lookup16[mem16[i]]中找到对应的值赋给mem16[i]

    }

    mem++;

  }

}

update_bitmap_score

每当发现新的路径,就调用这个函数来比较是否为更有利的路径==》最小的路径经济和来遍历到所有bitmap中的位

/* When we bump into a new path, we call this to see if the path appears
   more "favorable" than any of the existing ones. The purpose of the
   "favorables" is to have a minimal set of paths that trigger all the bits
   seen in the bitmap so far, and focus on fuzzing them at the expense of
   the rest.

   The first step of the process is to maintain a list of top_rated[] entries
   for every byte in the bitmap. We win that slot if there is no previous
   contender, or if the contender has a more favorable speed x size factor. */

static void update_bitmap_score(struct queue_entry* q) {

  u32 i;
  u64 fav_factor = q->exec_us * q->len;
  //fav_factor=该case的执行时间乘以样例大小,以这两个指标来衡量favorable的权重,越小越优

  /* For every byte set in trace_bits[], see if there is a previous winner,
     and how it compares to us. */

  for (i = 0; i < MAP_SIZE; i++)    //遍历trace_bits数组

    if (trace_bits[i]) {   //如果当前trace_bits数组值不为0,代表这是已经覆盖到path

       if (top_rated[i]) {   //如果该path对应的top_rated存在

         /* Faster-executing or smaller test cases are favored. */

         if (fav_factor > top_rated[i]->exec_us * top_rated[i]->len) continue;
         //如果fav_factor <本次path的乘积,代表top_rared[i]的更优
         //跳出循环遍历下一个path

         /* Looks like we're going to win. Decrease ref count for the
            previous winner, discard its trace_bits[] if necessary. */

         if (!--top_rated[i]->tc_ref) {
           //如果该path长度top_rated乘积更大,则将top_rated对应的tc_ref字段-1
           ck_free(top_rated[i]->trace_mini);
           top_rated[i]->trace_mini = 0;   //释放并清空trace_mini
         }

       }

       /* Insert ourselves as the new winner. */

       top_rated[i] = q;  //设置top_rated为当前case,替换为更优
       q->tc_ref++;    //tc_ref+1

       if (!q->trace_mini) {    //如果trace_mini为空
         q->trace_mini = ck_alloc(MAP_SIZE >> 3);
         minimize_bits(q->trace_mini, trace_bits);
         //将trace_bits经过minimize_bits压缩,存放在trace_mini中
       }

       score_changed = 1;   //设置score_changed为1

     }

}

minimize_bits

将数据本身转换成位置记录下来

/* Compact trace bytes into a smaller bitmap. We effectively just drop the
   count information here. This is called only sporadically, for some
   new paths. */

static void minimize_bits(u8* dst, u8* src) {

  u32 i = 0;

  while (i < MAP_SIZE) {

    if (*(src++)) dst[i >> 3] |= 1 << (i & 7);
    /*
    *(src++)==>每次取trace_bits[]数组中一个元素的值
    i >> 3==>获得dst的index,即这个数据会被压缩到dst[index]的bit中
    i & 7==>不考虑高位的情况下只保留低三位,相当于dst[]byte中的哪一个bit
    1 << (i & 7)==> 把1放在表示对应数组的bit上*/
    i++;//遍历下一个

  }

}

cull_queue

精简队列

/* The second part of the mechanism discussed above is a routine that
   goes over top_rated[] entries, and then sequentially grabs winners for
   previously-unseen bytes (temp_v) and marks them as favored, at least
   until the next run. The favored entries are given more air time during
   all fuzzing steps. */

static void cull_queue(void) {
  //精简队列

  struct queue_entry* q;
  static u8 temp_v[MAP_SIZE >> 3];
  u32 i;

  if (dumb_mode || !score_changed) return;
  //如果设置了dumb_mode或没有设置score_changed,直接返回

  score_changed = 0;   //设置score_changed为0

  memset(temp_v, 255, MAP_SIZE >> 3);
  //初始化temp_v数组,初始值为0xff
  //如果为1代表还没有覆盖到
  //如果为0代表已经覆盖到了

  queued_favored  = 0;   //设置queued_favored为0
  pending_favored = 0;   //设置pending_favored为0

  q = queue;

  while (q) {   //循环队列
    q->favored = 0;   //设置favored都为0
    q = q->next;    //指向下一个用例
  }

  /* Let's see if anything in the bitmap isn't captured in temp_v.
     If yes, and if it has a top_rated[] contender, let's use it. 
     让我们看看位图中是否有什么东西没有在temp_v中捕获。如果是,并且它有一个top_rated[]竞争者,我们就使用它*/

  for (i = 0; i < MAP_SIZE; i++)
    if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) {
      //判断path对应的bit有没有被置位

      u32 j = MAP_SIZE >> 3;

      /* Remove all bits belonging to the current entry from temp_v.
      从temp_var中删除所有属于当前条目的位 */

      while (j--) 
        if (top_rated[i]->trace_mini[j])
        //如果top_rated[i]有值,且该path在tmp_v里被置位
          temp_v[j] &= ~top_rated[i]->trace_mini[j];
          //从temp_v中清除所有top_rated[i]覆盖到path
          //将对应的bit设置为0

      top_rated[i]->favored = 1;   //设置本次favored为1
      queued_favored++;     //queued_favored计数器+1

      if (!top_rated[i]->was_fuzzed) pending_favored++;
      //如果top_rated[i]->was_fuzzed被设置为0,证明还没有被fuzz过
      //pending_favored计数器+1

    }

  q = queue;

  while (q) {    //遍历队列
    mark_as_redundant(q, !q->favored);
    //如果不是favored的case,就标记成redundant_edges
    q = q->next;
  }

}

mark_as_redundant

标记/取消标记为冗余(仅限边缘)

不用于恢复状态,在后面处理数据集有用

/* Mark / unmark as redundant (edge-only). This is not used for restoring state,
   but may be useful for post-processing datasets. */

static void mark_as_redundant(struct queue_entry* q, u8 state) {

  u8* fn;
  s32 fd;

  if (state == q->fs_redundant) return;

  q->fs_redundant = state;  //条件不成立,赋值

  fn = strrchr(q->fname, '/');
  fn = alloc_printf("%s/queue/.state/redundant_edges/%s", out_dir, fn + 1);
  //拼接路径

  if (state) {   //如果state为1

    fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
    //尝试连接out_dir/queue/.state/redundant_edges/fname
    if (fd < 0) PFATAL("Unable to create '%s'", fn);
    close(fd);

  } else {
    //如果state为0

    if (unlink(fn)) PFATAL("Unable to remove '%s'", fn);
    //尝试删除路径out_dir/queue/.state/redundant_edges/fname

  }

  ck_free(fn);

}

show_init_stats

在处理输入目录的末尾显示快速统计信息,还有一堆警告

其中还有一些校准在此结束,还有一些硬编码的常量

/* Display quick statistics at the end of processing the input directory,
   plus a bunch of warnings. Some calibration stuff also ended up here,
   along with several hardcoded constants. Maybe clean up eventually. */

static void show_init_stats(void) {

  struct queue_entry* q = queue;
  u32 min_bits = 0, max_bits = 0;
  u64 min_us = 0, max_us = 0;
  u64 avg_us = 0;
  u32 max_len = 0;

  if (total_cal_cycles) avg_us = total_cal_us / total_cal_cycles;
  //total_cal_us  总执行时间
  //total_cal_cycles   //总执行轮次

  while (q) {
    //遍历queue,更新min_us,max_us,min_bits,max_bits,max_len

    if (!min_us || q->exec_us < min_us) min_us = q->exec_us;
    if (q->exec_us > max_us) max_us = q->exec_us;

    if (!min_bits || q->bitmap_size < min_bits) min_bits = q->bitmap_size;
    if (q->bitmap_size > max_bits) max_bits = q->bitmap_size;

    if (q->len > max_len) max_len = q->len;

    q = q->next;

  }

  SAYF("\n");

  if (avg_us > (qemu_mode ? 50000 : 10000))   //如果avg_us大于10000就警告
    WARNF(cLRD "The target binary is pretty slow! See %s/perf_tips.txt.",
          doc_path);

  /* Let's keep things moving with slow binaries. */

  if (avg_us > 50000) havoc_div = 10;     /* 0-19 execs/sec   */
  //如果大于50000,设置havoc_div为10
  else if (avg_us > 20000) havoc_div = 5; /* 20-49 execs/sec  */
  //如果大于20000,设置havoc_div为5
  else if (avg_us > 10000) havoc_div = 2; /* 50-100 execs/sec */
  //如果大于10000,设置havoc_div为2

  if (!resuming_fuzz) {   //如果不是resuming_fuzz

    if (max_len > 50 * 1024)   //对queue的大小超限发出警告
      WARNF(cLRD "Some test cases are huge (%s) - see %s/perf_tips.txt!",
            DMS(max_len), doc_path);
    else if (max_len > 10 * 1024)
      WARNF("Some test cases are big (%s) - see %s/perf_tips.txt.",
            DMS(max_len), doc_path);

    if (useless_at_start && !in_bitmap)   //如果useless_at_start不为0,就警告可以精简样本
      WARNF(cLRD "Some test cases look useless. Consider using a smaller set.");

    if (queued_paths > 100)   //对queue的个数超限发出警告
      WARNF(cLRD "You probably have far too many input files! Consider trimming down.");
    else if (queued_paths > 20)
      WARNF("You have lots of input files; try starting small.");

  }

  OKF("Here are some useful stats:\n\n"

      cGRA "    Test case count : " cRST "%u favored, %u variable, %u total\n"
      cGRA "       Bitmap range : " cRST "%u to %u bits (average: %0.02f bits)\n"
      cGRA "        Exec timing : " cRST "%s to %s us (average: %s us)\n",
      queued_favored, queued_variable, queued_paths, min_bits, max_bits, 
      ((double)total_bitmap_size) / (total_bitmap_entries ? total_bitmap_entries : 1),
      DI(min_us), DI(max_us), DI(avg_us));

  if (!timeout_given) {  //如果timeout_given为0

    /* Figure out the appropriate timeout. The basic idea is: 5x average or
       1x max, rounded up to EXEC_TM_ROUND ms and capped at 1 second.

       If the program is slow, the multiplier is lowered to 2x or 3x, because
       random scheduler jitter is less likely to have any impact, and because
       our patience is wearing thin =) */

    if (avg_us > 50000) exec_tmout = avg_us * 2 / 1000;
    else if (avg_us > 10000) exec_tmout = avg_us * 3 / 1000;
    else exec_tmout = avg_us * 5 / 1000;

    exec_tmout = MAX(exec_tmout, max_us / 1000);
    //exec_tmout和所有样例中执行时间最长的样例进行比较,取最大值给exec_tmout
    exec_tmout = (exec_tmout + EXEC_TM_ROUND) / EXEC_TM_ROUND * EXEC_TM_ROUND;

    if (exec_tmout > EXEC_TIMEOUT) exec_tmout = EXEC_TIMEOUT;
    //如果exec_tmout大于EXEC_TIMEOUT,就设置为EXEC_TIMEOUT

    ACTF("No -t option specified, so I'll use exec timeout of %u ms.", 
         exec_tmout);

    timeout_given = 1;  //设置timeout_given为1

  } else if (timeout_given == 3) {
    //代表这是resuming session

    ACTF("Applying timeout settings from resumed session (%u ms).", exec_tmout);

  }

  /* In dumb mode, re-running every timing out test case with a generous time
     limit is very expensive, so let's select a more conservative default. */

  if (dumb_mode && !getenv("AFL_HANG_TMOUT"))
  //如果是dumb_mode模式,并且没有设置AFL_HANG_TMOUT环境变量
    hang_tmout = MIN(EXEC_TIMEOUT, exec_tmout * 2 + 100);
    //设置hang_tmout为exec_tmout * 2 + 100和EXEC_TIMEOUT中的最小值

  OKF("All set and ready to roll!");

}

find_start_position

恢复时请尝试查找要从其开始的队列位置,这仅在resume时以及当我们可以找到原始的fuzzer_stats时才有意义

/* When resuming, try to find the queue position to start from. This makes sense
   only when resuming, and when we can find the original fuzzer_stats. */

static u32 find_start_position(void) {

  static u8 tmp[4096]; /* Ought to be enough for anybody. */

  u8  *fn, *off;
  s32 fd, i;
  u32 ret;

  if (!resuming_fuzz) return 0;
  //如果不是resuming_fuzz,直接返回

  if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir);
  //如果是in_place_resume
  //拼接out_dir/fuzzer_stats
  else fn = alloc_printf("%s/../fuzzer_stats", in_dir);
  //拼接in_dir/../fuzzer_stats

  fd = open(fn, O_RDONLY);  //以只读的模式打开fn指向的文件
  ck_free(fn);

  if (fd < 0) return 0;

  i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */
  //将这个文件中的内容读到tmp中
  close(fd);

  off = strstr(tmp, "cur_path          : ");  //寻找cur_path
  if (!off) return 0;

  ret = atoi(off + 20);  //将cur_path后面的值设置为ret的值
  if (ret >= queued_paths) ret = 0;  //如果大于queued_paths就设置ret为0
  return ret;

}

write_stats_file

更新统计信息文件以进入无人值守的监视

/* Update stats file for unattended monitoring. */

static void write_stats_file(double bitmap_cvg, double stability, double eps) {

  static double last_bcvg, last_stab, last_eps;
  static struct rusage usage;

  u8* fn = alloc_printf("%s/fuzzer_stats", out_dir);
  //拼接out_dir/fuzzer_stats
  s32 fd;
  FILE* f;

  fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600);  //创建文件

  if (fd < 0) PFATAL("Unable to create '%s'", fn);

  ck_free(fn);

  f = fdopen(fd, "w");

  if (!f) PFATAL("fdopen() failed");

  /* Keep last values in case we're called from another context
     where exec/sec stats and such are not readily available. */

  if (!bitmap_cvg && !stability && !eps) {
    bitmap_cvg = last_bcvg;
    stability  = last_stab;
    eps        = last_eps;
  } else {
    last_bcvg = bitmap_cvg;
    last_stab = stability;
    last_eps  = eps;
  }
  //写入统计信息
  fprintf(f, "start_time        : %llu\n"    //fuzz运行的开始时间
             "last_update       : %llu\n"    //当前时间
             "fuzzer_pid        : %u\n"      //获取当前pid
             "cycles_done       : %llu\n"    //queue_cycle在queue_cur为空,即执行到当前队列尾的时候才增加1,所以这代表queue队列被完全变异一次的次数
             "execs_done        : %llu\n"    //total_execs,target的总的执行次数,每次run_target的时候会增加1
             "execs_per_sec     : %0.02f\n"  //每秒执行的次数
             "paths_total       : %u\n"      //queued_paths在每次add_to_queue的时候会增加1,代表queue里的样例总数
             "paths_favored     : %u\n"      //queued_favored,有价值的路径总数
             "paths_found       : %u\n"      //queued_discovered在每次common_fuzz_stuff去执行一次fuzz时,发现新的interesting case的时候会增加1,代表在fuzz运行期间发现的新queue entry
             "paths_imported    : %u\n"      //queued_imported是master-slave模式下,如果sync过来的case是interesting的,就增加1
             "max_depth         : %u\n"      //最大路径深度
             "cur_path          : %u\n"      //current_entry一般情况下代表的是正在执行的queue entry的整数ID,queue首节点的ID是0  /* Must match find_start_position() */
             "pending_favs      : %u\n"      //pending_favored 等待fuzz的favored paths数
             "pending_total     : %u\n"      //pending_not_fuzzed 在queue中等待fuzz的case数
             "variable_paths    : %u\n"      //queued_variable在calibrate_case去评估一个新的test case的时候,如果发现这个case的路径是可变的,则将这个计数器加一,代表发现了一个可变case
             "stability         : %0.02f%%\n"//稳定性
             "bitmap_cvg        : %0.02f%%\n"//
             "unique_crashes    : %llu\n"    //unique_crashes这是在save_if_interesting时,如果fault是FAULT_CRASH,就将unique_crashes计数器加一
             "unique_hangs      : %llu\n"    //unique_hangs这是在save_if_interesting时,如果fault是FAULT_TMOUT,且exec_tmout小于hang_tmout,就以hang_tmout为超时时间再执行一次,如果还超时,就让hang计数器加一
             "last_path         : %llu\n"    //在add_to_queue里将一个新case加入queue时,就设置一次last_path_time为当前时间,last_path_time / 1000
             "last_crash        : %llu\n"    //同上,在unique_crashes加一的时候,last_crash也更新时间,last_crash_time / 1000
             "last_hang         : %llu\n"    //同上,在unique_hangs加一的时候,last_hang也更新时间,last_hang_time / 1000
             "execs_since_crash : %llu\n"    //total_execs - last_crash_execs,这里last_crash_execs是在上一次crash的时候的总计执行了多少次
             "exec_timeout      : %u\n"      //配置好的超时时间,有三种可能的配置方式   /* Must match find_timeout() */
             "afl_banner        : %s\n"
             "afl_version       : " VERSION "\n"
             "target_mode       : %s%s%s%s%s%s%s\n"
             "command_line      : %s\n"
             "slowest_exec_ms   : %llu\n",
             start_time / 1000, get_cur_time() / 1000, getpid(),
             queue_cycle ? (queue_cycle - 1) : 0, total_execs, eps,
             queued_paths, queued_favored, queued_discovered, queued_imported,
             max_depth, current_entry, pending_favored, pending_not_fuzzed,
             queued_variable, stability, bitmap_cvg, unique_crashes,
             unique_hangs, last_path_time / 1000, last_crash_time / 1000,
             last_hang_time / 1000, total_execs - last_crash_execs,
             exec_tmout, use_banner,
             qemu_mode ? "qemu " : "", dumb_mode ? " dumb " : "",
             no_forkserver ? "no_forksrv " : "", crash_mode ? "crash " : "",
             persistent_mode ? "persistent " : "", deferred_mode ? "deferred " : "",
             (qemu_mode || dumb_mode || no_forkserver || crash_mode ||
              persistent_mode || deferred_mode) ? "" : "default",
             orig_cmdline, slowest_exec_ms);
             /* ignore errors */

  /* Get rss value from the children
     We must have killed the forkserver process and called waitpid
     before calling getrusage */
  if (getrusage(RUSAGE_CHILDREN, &usage)) {
      WARNF("getrusage failed");
  } else if (usage.ru_maxrss == 0) {
    fprintf(f, "peak_rss_mb       : not available while afl is running\n");
  } else {
#ifdef __APPLE__
    fprintf(f, "peak_rss_mb       : %zu\n", usage.ru_maxrss >> 20);
#else
    fprintf(f, "peak_rss_mb       : %zu\n", usage.ru_maxrss >> 10);
#endif /* ^__APPLE__ */
  }

  fclose(f);

}

save_auto

保存自动生成的extras

/* Save automatically generated extras. */

static void save_auto(void) {

  u32 i;

  if (!auto_changed) return;
  //如果auto_changed为0,则直接返回
  auto_changed = 0;
  //如果不为0,就设置为0

  for (i = 0; i < MIN(USE_AUTO_EXTRAS, a_extras_cnt); i++) {

    u8* fn = alloc_printf("%s/queue/.state/auto_extras/auto_%06u", out_dir, i);
    //拼接路径out_dir/queue/.state/auto_extras/auto_00000i
    s32 fd;

    fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600);
     //创建out_dir/queue/.state/auto_extras/auto_00000i

    if (fd < 0) PFATAL("Unable to create '%s'", fn);

    ck_write(fd, a_extras[i].data, a_extras[i].len, fn);  //写入extras

    close(fd);
    ck_free(fn);

  }

}

FUZZ主要执行流程

fuzz主循环

  //fuzz主循环
  while (1) {

    u8 skipped_fuzz;

    cull_queue();//精简队列
//queue_cur指向当前队列中的元素(entry)
    if (!queue_cur) {//如果queye_cur中为空,代表queue被执行了一轮

      queue_cycle++;//+1:说明走了一个循环
      current_entry     = 0;
      cur_skipped_paths = 0;
      queue_cur         = queue;//重新指向队头

      while (seek_to) {//如果seek_to不为0
        current_entry++;
        seek_to--;
        queue_cur = queue_cur->next;
        //queue_cur顺着队列持续后移==》最终移动到seek_to的位置上
      }

      show_stats();//显示状态

      if (not_on_tty) {//如果不是终端模式
        ACTF("Entering queue cycle %llu.", queue_cycle);
        //输出当前是第几个循环
        fflush(stdout);
      }

      /* If we had a full queue cycle with no new finds, try
         recombination strategies next. 
         如果我们经历了一个完整的扫描周期后都没有新的路径发现,那么尝试调整策略
         */

      if (queued_paths == prev_queued) {

        if (use_splicing) cycles_wo_finds++; else use_splicing = 1;
//当设置了use_splicing,cycles_wo_finds计数加一
//否则use_splicing为1
      } else cycles_wo_finds = 0;

      prev_queued = queued_paths;

      if (sync_id && queue_cycle == 1 && getenv("AFL_IMPORT_FIRST"))
      //如果设置了sync_id,并且queue_cycle==1,并且环境变量中设置了AFL_IMPORT_FIRST
        sync_fuzzers(use_argv);
        //调用sync_fuzzers

    }

    skipped_fuzz = fuzz_one(use_argv);
//对样本进行变换后fuzz,返回skipped_fuzz
    if (!stop_soon && sync_id && !skipped_fuzz) {
      //若stop_soon为0,设置了sync_id,skipped_fuzz为0
      if (!(sync_interval_cnt++ % SYNC_INTERVAL))
      //若sync_interval_cnt没有到一个周期(% SYNC_INTERVAL)
        sync_fuzzers(use_argv);
        //同步其他fuzzer

    }

    if (!stop_soon && exit_1) stop_soon = 2;
//如果没有设置stop_soon,且exit_1不为0,那么设置stop_soon=2
    if (stop_soon) break;
//设置了stop_soon,退出fuzz主循环
    queue_cur = queue_cur->next;//否则准备qemu中的下一个样本
    current_entry++;

  }

fuzz_one:

首先是准备fuzz:

初始化变量,根据概率及前期轮次结果判断返回节点,映射case

/* Take the current entry from the queue, fuzz it for a while. This
   function is a tad too long... returns 0 if fuzzed successfully, 1 if
   skipped or bailed out. */
/* 从队列中取出放前的一项,然后进行fuzz,返回0则fuzz成功
*/
static u8 fuzz_one(char** argv) {

//前期准备

  s32 len, fd, temp_len, i, j;
  u8  *in_buf, *out_buf, *orig_in, *ex_tmp, *eff_map = 0;
  u64 havoc_queued,  orig_hit_cnt, new_hit_cnt;
  u32 splice_cycle = 0, perf_score = 100, orig_perf, prev_cksum, eff_cnt = 1;

  u8  ret_val = 1, doing_det = 0;

  u8  a_collect[MAX_AUTO_EXTRA];
  u32 a_len = 0;

#ifdef IGNORE_FINDS

  /* In IGNORE_FINDS mode, skip any entries that weren't in the
     initial data set. */

  if (queue_cur->depth > 1) return 1;

#else

  if (pending_favored) {//如果设置了pendling_favored

    /* If we have any favored, non-fuzzed new arrivals in the queue,
       possibly skip to them at the expense of already-fuzzed or non-favored
       cases. */

    if ((queue_cur->was_fuzzed || !queue_cur->favored) &&
        UR(100) < SKIP_TO_NEW_PROB) return 1;
//查看当前queue中的一项是否已经fuzz过了或者不是favored,并且打印一个100以内的随机数,如果小于SKIP_TO_NEW_PROB,return 1
  } else if (!dumb_mode && !queue_cur->favored && queued_paths > 10) {
//如果非dumb_mode,且当前的不是favored,并且queued_paths > 10==》
    /* Otherwise, still possibly skip non-favored cases, albeit less often.
       The odds of skipping stuff are higher for already-fuzzed inputs and
       lower for never-fuzzed entries. */

    if (queue_cycle > 1 && !queue_cur->was_fuzzed) {
//如果queue_cycle > 1 并且queue_cur还没有fuzz过
      if (UR(100) < SKIP_NFAV_NEW_PROB) return 1;
//打一个100以内的随机数,如果小于SKIP_NFAV_NEW_PROB,直接return 1
//SKIP_NFAV_NEW_PROB默认为75
//75%
    } else {
      if (UR(100) < SKIP_NFAV_OLD_PROB) return 1;
//否则打一个100以内的随机数,如果小于SKIP_NFAV_OLD_PROB,直接return 1
//SKIP_NFAV_OLD_PROB默认为95
//95%
    }

  }

#endif /* ^IGNORE_FINDS */

  if (not_on_tty) {//如果不是tty模式
    ACTF("Fuzzing test case #%u (%u total, %llu uniq crashes found)...",
         current_entry, queued_paths, unique_crashes);
         //输出current_entry, queued_paths, unique_crashes提示信息
    fflush(stdout);//刷新stdout缓冲区
  }

  /* Map the test case into memory. */

  fd = open(queue_cur->fname, O_RDONLY);//打开case文件

  if (fd < 0) PFATAL("Unable to open '%s'", queue_cur->fname);//打开失败报错

  len = queue_cur->len;//将当前case大小赋值给len

  orig_in = in_buf = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);//将当前的test case映射进入内存

  if (orig_in == MAP_FAILED) PFATAL("Unable to mmap '%s'", queue_cur->fname);//映射失败报错

  close(fd);

  /* We could mmap() out_buf as MAP_PRIVATE, but we end up clobbering every
     single byte anyway, so it wouldn't give us any performance or memory usage
     benefits. */

  out_buf = ck_alloc_nozero(len);//分配len大小的空间,初始化为0
  //将空间首地址赋值给out_buf

  subseq_tmouts = 0;//设置连续超时次数为0

  cur_depth = queue_cur->depth;//设置cur_depth为当前case路径深度
CALIBRATION

(only if failed earlier on)

  /*******************************************
   * CALIBRATION (only if failed earlier on) *
   *******************************************/

//校准:主要核实case的校验错误,如果小于3则调用calibrate_case进行重新校验,再根据返回结果做下一步判断

  if (queue_cur->cal_failed) {//如果当前queue_cur->cal_failed==》存在标准错误

    u8 res = FAULT_TMOUT;

    if (queue_cur->cal_failed < CAL_CHANCES) {//标准错误小于3次

      /* Reset exec_cksum to tell calibrate_case to re-execute the testcase
         avoiding the usage of an invalid trace_bits.
          重制exec_cksum来告诉calibrate_case重新执行testcase来避免对于无效trace_bits的使用。
         For more info: https://github.com/AFLplusplus/AFLplusplus/pull/425 */

      queue_cur->exec_cksum = 0;

      res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0);//进行重新校验

      if (res == FAULT_ERROR)//如果返回值为FAULT_ERROR,直接报错终止
        FATAL("Unable to execute target application");
    }

    if (stop_soon || res != crash_mode) {//如果设置了stop_soon或者res不等于crash_mode
      cur_skipped_paths++;//计数+1
      goto abandon_entry;//跳转到abandon_entry
    }

  }
TRIMMING
  /************
   * TRIMMING *
   ************/

//判断case是否已被修剪,没有过就调用trim_case修剪一次,更新修剪后case的大小

  if (!dumb_mode && !queue_cur->trim_done) {//如果不是dumb_mode模式,并且case没有经过修剪

    u8 res = trim_case(argv, queue_cur, in_buf);
//调用trim_case进行修剪,返回res
    if (res == FAULT_ERROR)//如果res结果为FAULT_ERROR,抛异常
      FATAL("Unable to execute target application");

    if (stop_soon) {//如果设置了stop_soon
      cur_skipped_paths++;//计数+1
      goto abandon_entry;//跳转到abandon_entry
    }

    /* Don't retry trimming, even if it failed. */

    queue_cur->trim_done = 1;//标记为已被修剪

    if (len != queue_cur->len) len = queue_cur->len;
//如果修剪前的case大小不等于修建后长度,更新为修剪后长度
  }

  memcpy(out_buf, in_buf, len);//将in_buf拷贝len个字节到out_buf中
PERFORMANCE SCORE
  /*********************
   * PERFORMANCE SCORE *
   *********************/

//判断该case是否已经经历过deterministic阶段,如果已经经历过,直接跳转至havoc阶段

  orig_perf = perf_score = calculate_score(queue_cur);//计算当前case的可取性

  /* Skip right away if -d is given, if we have done deterministic fuzzing on
     this entry ourselves (was_fuzzed), or if it has gone through deterministic
     testing in earlier, resumed runs (passed_det). 
     如果给出了-d,如果我们自己对这个条目进行了确定性模糊化(was_fuzzed),或者如果它在之前已经进行了确定性测试,则立即跳过,继续运行(passed_det)
     */

  if (skip_deterministic || queue_cur->was_fuzzed || queue_cur->passed_det)
  //如果已经设置了skip_deterministic或者case已经被fuzz过,或者queue_cur->passed_det==1
    goto havoc_stage;//跳转

  /* Skip deterministic fuzzing if exec path checksum puts this out of scope
     for this master instance. */

  if (master_max && (queue_cur->exec_cksum % master_max) != master_id - 1)
  ////如果执行路径校验和将其置于此主实例的范围之外
    goto havoc_stage;//跳转

  doing_det = 1;
SIMPLE BITFLIP

(+dictionary construction)

  /*********************************************
   * SIMPLE BITFLIP (+dictionary construction) *
   *********************************************/

//将case中内容按位取反的变异过程

//首先定义一个宏
#define FLIP_BIT(_ar, _b) do { \
    u8* _arf = (u8*)(_ar); \
    u32 _bf = (_b); \
    _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \
  } while (0)

该宏为变异的主要实现:

一参

传入FLIP_BIT的一参:_ar==》out_buf==》从case映射到内存的内容

注意点

out_buf为u8(4字节char)

假设case内容为”12345678”==》第一组取值:”1234”

在将数据加载进内存的时候是以字符串的形式

二参

在对len<<3之后的结果作为循环条件==》

从0~32依次作为二参_b传入FLIP_BIT

传入后:

_arf:out_buf (1234)
_bf: 0~31 (out_buf为4字节,字节8位,共32)
_arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7));

==>

closure1=(_bf) & 7)

closure2=128 << closure1

closure3=(_bf) >> 3

_arf[closure3]=_arf[closure3]^closure2

步骤一

closure1=(_bf) & 7)

_bf为不断循环传入的031==>031分别与7进行与运算

==》结果分为4组,每组的值为0~7

步骤二

closure2=128 << closure1

结果为按照位的位置依次变1

步骤三

closure3=(_bf) >> 3

步骤四

_arf[closure3]=_arf[closure3]^closure2

closure3作为_afl指针数组的下标==》closure变化为0,1,2,3==》

注意closure3是连续8个0,1,2,3==》_afl[0],_afl[1],_afl[2],_afl[3]分别要与closure2进行8此异或操作==》

每次的异或结果还将刷新_afl[index]的值==》

例:

_arf[0] = 00110001 ^ 10000000 
_arf[0] = 00110001 ^ 01000000 
_arf[0] = 00110001 ^ 00100000 
_arf[0] = 00110001 ^ 00010000 
_arf[0] = 00110001 ^ 00001000 
_arf[0] = 00110001 ^ 00000100 
_arf[0] = 00110001 ^ 00000010 
_arf[0] = 00110001 ^ 00000001 

==》对out_buf中的内容进行按位取反==》

对FLIP_BIT调用:

FLIP_BIT(out_buf, stage_cur);  //将out_buf中的内容进行按位取反

if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
//调用common_fuzz_stuff进行fuzz,保存interesting种子
//如果返回为1,跳转至abandon_entry

FLIP_BIT(out_buf, stage_cur);  //将out_buf恢复

bitflip 1/1 && collect tokens 按位取反 && 搜集token:

bitflip 1/1主要进行case内容的位取反工作,记录token,刷新新路径和crash次数

  /*********************************************
   * SIMPLE BITFLIP (+dictionary construction) *
   *********************************************/

//将case中内容按位取反的变异过程

//首先定义一个宏
#define FLIP_BIT(_ar, _b) do { \
    u8* _arf = (u8*)(_ar); \
    u32 _bf = (_b); \
    _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \
  } while (0)

  /* Single walking bit. */

  stage_short = "flip1";
  stage_max   = len << 3;  //定义stage_max由len决定
  stage_name  = "bitflip 1/1";  //bitflip 1/1变异策略

  stage_val_type = STAGE_VAL_NONE;

  orig_hit_cnt = queued_paths + unique_crashes;

  prev_cksum = queue_cur->exec_cksum;

  for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
    //循环给FLIP_BIT提供二参

    stage_cur_byte = stage_cur >> 3;

    FLIP_BIT(out_buf, stage_cur);  //将out_buf中的内容进行按位取反

    if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
    //调用common_fuzz_stuff进行fuzz,保存interesting种子
    //如果返回为1,跳转至abandon_entry

    FLIP_BIT(out_buf, stage_cur);  //将out_buf恢复

    /* While flipping the least significant bit in every byte, pull of an extra
       trick to detect possible syntax tokens. In essence, the idea is that if
       you have a binary blob like this:

       xxxxxxxxIHDRxxxxxxxx

       ...and changing the leading and trailing bytes causes variable or no
       changes in program flow, but touching any character in the "IHDR" string
       always produces the same, distinctive path, it's highly likely that
       "IHDR" is an atomically-checked magic value of special significance to
       the fuzzed format.

       We do this here, rather than as a separate stage, because it's a nice
       way to keep the operation approximately "free" (i.e., no extra execs).
       
       Empirically, performing the check when flipping the least significant bit
       is advantageous, compared to doing it at the time of more disruptive
       changes, where the program flow may be affected in more violent ways.

       The caveat is that we won't generate dictionaries in the -d mode or -S
       mode - but that's probably a fair trade-off.

       This won't work particularly well with paths that exhibit variable
       behavior, but fails gracefully, so we'll carry out the checks anyway.

      */

      /*经过bitflip 1/1变异时,会对每一个byte的最低有效位取反后进行识别处理,如果连续 多个bytes的最低位被取反后程序的路径都没有变化,
	     但是与原始路径不一致,就会把这一段 连续的bytes判断是一条token(分词器选中词条)

       举例,如果输入数据为“select * from table”时,原始路径应该是执行SQL语句流程路径,第一次取反的应该是“s”,但是在变异后“felect”并不符合语法,
       那么在执行SQL语句时流程路径就会走向报错,与原始路径不同,但是“s”会取反8次,每一次的结果“kelect”、“oekect”....都不符合SQL语句语法,
       因此执行路径就会和“felect”相同。也就是说破坏“select”被破坏将会与正确路径不一致,而破坏之后的路径都一样,那么AFL就会将“select”作为token记录下来*/

    if (!dumb_mode && (stage_cur & 7) == 7) {
      //如果不是dumb_mode模式并且(stage_cur & 7) == 7==》
      //意味着只有stage_cur==7、15、23...,即当翻转到每个字节最低有效位的时候进入分支

      u32 cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
      //对当前trace_bits户进行hash32运算,值记录在cksum中

      if (stage_cur == stage_max - 1 && cksum == prev_cksum) {
        //循环至stage_max - 1也就是内容结尾
        //并且校验与上次相同,即当前路径与上一次路径相比没有变化

        /* If at end of file and we are still collecting a string, grab the
           final character and force output. */

        if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3];
        //如果当前token数量小于32(MAX_AUTO_EXTRA默认为32)
        //将当前字符作为token拼接到a_collect[]数组中
        a_len++;  //token数量+1

        if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA)
        //如果token数量大于等于3(MIN_AUTO_EXTRA默认为3)且小于等于32(MAX_AUTO_EXTRA默认为32)
          maybe_add_auto(a_collect, a_len);
          //调用maybe_add_auto将累计的a_collect[]数组中的内容添加到a_extras[]数组中

      } else if (cksum != prev_cksum) {
        //如果当前校验和与上一次校验和不一样,说明与上一次执行路径不一样,那么本次编译的字节是存在问题的

        /* Otherwise, if the checksum has changed, see if we have something
           worthwhile queued up, and collect that if the answer is yes. */

        if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA)
        //如果token数量大于等于3(MIN_AUTO_EXTRA默认为3)且小于等于32(MAX_AUTO_EXTRA默认为32)
          maybe_add_auto(a_collect, a_len);
          //调用maybe_add_auto将a_collect中的token添加到a_extras数组中

        a_len = 0;  //清空a_len
        prev_cksum = cksum;  //刷新prev_cksum为当前cksum

      }

      /* Continue collecting string, but only if the bit flip actually made
         any difference - we don't want no-op tokens. */

      if (cksum != queue_cur->exec_cksum) {
        //如果当前路径与原始路径不相同,这说明可能是因为token被破坏导致与原始执行路径不符

        if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3];
        //如果token数量小于32
        //将当前循环字符添加到a_collect数组中
        a_len++;

      }

    }

  }

  new_hit_cnt = queued_paths + unique_crashes;

  stage_finds[STAGE_FLIP1]  += new_hit_cnt - orig_hit_cnt;
  //stage_finds[STAGE_FLIP1]累加整个FLIP_BIT过程中新发现的路径和crash总和
  stage_cycles[STAGE_FLIP1] += stage_max;
  //stage_cycles[STAGE_FLIP1]累加整个FLIP_BIT过程中变异的次数

bitflip 2/1 相邻两位进行取反:

/* Two walking bits. */

stage_name  = "bitflip 2/1";  //bitflip 2/1相邻两位取反变异策略
stage_short = "flip2";
stage_max   = (len << 3) - 1;

orig_hit_cnt = new_hit_cnt;

for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {

  stage_cur_byte = stage_cur >> 3;

  FLIP_BIT(out_buf, stage_cur);  //第一次按位取反
  FLIP_BIT(out_buf, stage_cur + 1);  //第二次按位取反
  //在stage_cur+1==》说明这两次取反会将out_buf中相邻两位数据进行取反

  if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
  //调用common_fuzz_stuff对变异后数据进行测试,记录interesting
  //如果返回1跳转到abandon_entry

  FLIP_BIT(out_buf, stage_cur);  //第一次取反恢复
  FLIP_BIT(out_buf, stage_cur + 1);  //相邻位取反恢复

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_FLIP2]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_FLIP2]累加出现的新路径与crash次数的和
stage_cycles[STAGE_FLIP2] += stage_max;
//stage_cycles[STAGE_FLIP2]累加bitflip 2/1变异次数

bitflip 4/1 相邻四位进行取反

/* Four walking bits. */

stage_name  = "bitflip 4/1";  //bitflip 4/1相邻四位取反变异策略
stage_short = "flip4";
stage_max   = (len << 3) - 3;

orig_hit_cnt = new_hit_cnt;

for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {

  stage_cur_byte = stage_cur >> 3;

  FLIP_BIT(out_buf, stage_cur);    //第一位取反
  FLIP_BIT(out_buf, stage_cur + 1);  //相邻第二位取反
  FLIP_BIT(out_buf, stage_cur + 2);  //相邻第三位取反
  FLIP_BIT(out_buf, stage_cur + 3);  //相邻第四位取反

  if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
  //调用common_fuzz_stuff对变异后数据进行测试,记录interesting
  //如果返回1跳转到abandon_entry

  FLIP_BIT(out_buf, stage_cur);  //第一位取反恢复
  FLIP_BIT(out_buf, stage_cur + 1);   //第二位取反恢复
  FLIP_BIT(out_buf, stage_cur + 2);   //第三位取反恢复
  FLIP_BIT(out_buf, stage_cur + 3);   //第四位取反霍夫

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_FLIP4]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_FLIP4]累加出现的新路径与crash次数的和
stage_cycles[STAGE_FLIP4] += stage_max;
//stage_cycles[STAGE_FLIP4]累加bitflip 4/1变异次数

bitflip 8/8

effector map byte取反

构建effector map

==》不使用前面定义的宏进行反转==》直接将0xFF与其进行异或实现翻转

==》

在这个进程中会生成了一个effector map[]数组,在对每个

在一个byte完全反转,如果造成执行路径与原始路径不一样==》将byte在effector map中标记为1==》有效

否则标记为0==》无效

在某些情况下并不会检测有效字符,在dumb_mode模式下或者指定fuzzer情况下,此时所有字符都有可能进行变异

  /* Effector map setup. These macros calculate:

     EFF_APOS      - position of a particular file offset in the map.
     EFF_ALEN      - length of a map with a particular number of bytes.
     EFF_SPAN_ALEN - map span for a sequence of bytes.

   */

#define EFF_APOS(_p)          ((_p) >> EFF_MAP_SCALE2)
//EFF_APOS(_p){ p >> 3 }
#define EFF_REM(_x)           ((_x) & ((1 << EFF_MAP_SCALE2) - 1))
//EFF_REM(_x){ x & ((1<<3)-1) }
#define EFF_ALEN(_l)          (EFF_APOS(_l) + !!EFF_REM(_l))
//EFF_ALEN(_l){ (l>>3) + ((l&((1<<3)-1))==0)?0:1 }
#define EFF_SPAN_ALEN(_p, _l) (EFF_APOS((_p) + (_l) - 1) - EFF_APOS(_p) + 1)
//EFF_SPAN_ALEN(_p, _l){ (p+l-1) >> 3 - (p>>3) +1 }

  /* Initialize effector map for the next step (see comments below). Always
     flag first and last byte as doing something. */

  eff_map    = ck_alloc(EFF_ALEN(len));  //为eff_map开辟case字节长度空间
  eff_map[0] = 1;   //设置eff_map[0]为1

  if (EFF_APOS(len - 1) != 0) {  //如果(len-1)>>3不等于0,即case数据长度大于等于9
    eff_map[EFF_APOS(len - 1)] = 1;  //设置eff_map[(len-1)>>3]为1
    eff_cnt++;
  }

  /* Walking byte. */

  stage_name  = "bitflip 8/8";  //bitflip 8/8 字节翻转变异策略
  stage_short = "flip8";
  stage_max   = len;

  orig_hit_cnt = new_hit_cnt;

  for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
    //stage_max为len

    stage_cur_byte = stage_cur;

    out_buf[stage_cur] ^= 0xFF;
    //对out_buf中的每一个bit做异或反转
    //0xFF=11111111

    if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
    //调用common_fuzz_stuff对变异后数据进行测试,记录interesting
    //如果返回1跳转到abandon_entry

    /* We also use this stage to pull off a simple trick: we identify
       bytes that seem to have no effect on the current execution path
       even when fully flipped - and we skip them during more expensive
       deterministic stages, such as arithmetics or known ints. */

    if (!eff_map[EFF_APOS(stage_cur)]) {
      //如果eff_map[stage_cur>>3]为0

      u32 cksum;

      /* If in dumb mode or if the file is very short, just flag everything
         without wasting time on checksums. */

      if (!dumb_mode && len >= EFF_MIN_LEN)
      //如果不是dumb_mode模式并且len大于等于128(EFF_MIN_LEN默认128)
        cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);  //计算当前校验和
      else
      //否则如果是dumb_mode模式或len小于128
        cksum = ~queue_cur->exec_cksum;
        //cksum等于queue_cur->exec_cksum按位取反结果

      if (cksum != queue_cur->exec_cksum) {
        eff_map[EFF_APOS(stage_cur)] = 1;
        //产生新的路径,发生了变化,此时直接将对应的eff_map中的项标记为1
        eff_cnt++;  //标记次数+1
      }

    }

    out_buf[stage_cur] ^= 0xFF;  //取反复位

  }

  /* If the effector map is more than EFF_MAX_PERC dense, just flag the
     whole thing as worth fuzzing, since we wouldn't be saving much time
     anyway. */

  if (eff_cnt != EFF_ALEN(len) &&
      eff_cnt * 100 / EFF_ALEN(len) > EFF_MAX_PERC) {
        //如果eff_map的密度超过EFF_MAX_PERC(默认90)

    memset(eff_map, 1, EFF_ALEN(len));  //初始化eff_map为1

    blocks_eff_select += EFF_ALEN(len);   //blocks_eff_select累加更新EFF_ALEN(len)

  } else {

    blocks_eff_select += eff_cnt;

  }

  blocks_eff_total += EFF_ALEN(len);

  new_hit_cnt = queued_paths + unique_crashes;

  stage_finds[STAGE_FLIP8]  += new_hit_cnt - orig_hit_cnt;
  //stage_finds[STAGE_FLIP8]累加出现的新路径与crash次数的和
  stage_cycles[STAGE_FLIP8] += stage_max;
  //stage_cycles[STAGE_FLIP8]累加bitflip 8/1变异次数

  /* Two walking bytes. */

  if (len < 2) goto skip_bitflip;
  //如果len<2
  //调转至skip_bitflip

bitflip 16/8

连续两个字节翻转

stage_name  = "bitflip 16/8";  //连续两字节翻转
stage_short = "flip16";
stage_cur   = 0;
stage_max   = len - 1;

orig_hit_cnt = new_hit_cnt;

for (i = 0; i < len - 1; i++) {

  /* Let's consult the effector map... */

  if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) {
    //检查eff_map对应连续两个字节是否为0
    stage_max--;   //--跳过
    continue;
  }

  stage_cur_byte = i;

  *(u16*)(out_buf + i) ^= 0xFFFF;  //连续两个字节翻转

  if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
  //调用common_fuzz_stuff测试记录
  //如果返回值为1,跳转至abandon_entry
  stage_cur++;  //stage_cur计数+1

  *(u16*)(out_buf + i) ^= 0xFFFF;  //翻转+1


}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_FLIP16]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_FLIP16]累加出现的新路径与crash次数的和
stage_cycles[STAGE_FLIP16] += stage_max;
//stage_cycles[STAGE_FLIP16]累加bitflip 16/8变异次数

bitflip 32/8

连续4个byte翻转

  stage_name  = "bitflip 32/8";  //连续4个字节翻转
  stage_short = "flip32";
  stage_cur   = 0;
  stage_max   = len - 3;

  orig_hit_cnt = new_hit_cnt;

  for (i = 0; i < len - 3; i++) {

    /* Let's consult the effector map... */
    if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] &&
        !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) {
          //判断eff_map连续四项是否为空
      stage_max--;  //--跳过
      continue;
    }

    stage_cur_byte = i;

    *(u32*)(out_buf + i) ^= 0xFFFFFFFF;   //连续4个字节跳转

    if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
    //调用common_fuzz_stuff测试记录
    //返回值为1,跳转至abandon_entry
    stage_cur++;  //计数器+1

    *(u32*)(out_buf + i) ^= 0xFFFFFFFF;  //翻转恢复

  }

  new_hit_cnt = queued_paths + unique_crashes;

  stage_finds[STAGE_FLIP32]  += new_hit_cnt - orig_hit_cnt;
  //stage_finds[STAGE_FLIP32]累加出现的新路径与crash次数的和
  stage_cycles[STAGE_FLIP32] += stage_max;
  //stage_cycles[STAGE_FLIP32]累加bitflip 32/8变异次数

skip_bitflip:

  if (no_arith) goto skip_arith;
ARITHMETIC INC/DEC

在该阶段会做加减变异,这个阶段和上一个bitfilp阶段差不多==》按照位数递增变异==》

该阶段的变异存在上限:

#define ARITH_MAX           35 //config.h~143行

在config.h中定义了ARITH_MAX宏,默认为35==》

会对目标整数进行+1,+2,+3……+35,-1,-2,~-35==》

整数存在大端序和小端序==》该阶段会对这两种端序进行变异

跳过字节变异情况:

1、选中字节对应的在effector map中是无效的==》跳过

2、变异结果在bitflip阶段已经完成了(在上一个阶段已经测试过了,避免重复)==》跳过

arith 8/8

单字(8 bit)加减运算

/**********************
 * ARITHMETIC INC/DEC *
 **********************/

/* 8-bit arithmetics. */

stage_name  = "arith 8/8"; //arith 8/8变异策略,对每个byte(8 bit)进行加减运算
stage_short = "arith8";
stage_cur   = 0;
stage_max   = 2 * len * ARITH_MAX;

stage_val_type = STAGE_VAL_LE;

orig_hit_cnt = new_hit_cnt;

for (i = 0; i < len; i++) {  //遍历out_buf

  u8 orig = out_buf[i];  //orig为一个字节

  /* Let's consult the effector map... */

  if (!eff_map[EFF_APOS(i)]) {  //判断该字节对应eff_map中是否有效
    stage_max -= 2 * ARITH_MAX;
    //如果无效,stage_max减去2倍的ARITH_MAX,跳过本次变异
    continue;
  }

  stage_cur_byte = i;

  for (j = 1; j <= ARITH_MAX; j++) {

    u8 r = orig ^ (orig + j);  //orig与orig+j做亦或

    /* Do arithmetic operations only if the result couldn't be a product
       of a bitflip. */

    if (!could_be_bitflip(r)) {
      //判断是否可以通过上一种bitfilp变异方式来获得本次变异结果,如果不能进入分支

      stage_cur_val = j;
      out_buf[i] = orig + j;   //按字节做加法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试和记录
      //如果返回值为1,则跳转至abandon_entry
      stage_cur++;  //变异次数+1

    } else stage_max--;  //如果能够通过bitfilp获得,则跳过这个byte

    r =  orig ^ (orig - j);  //orig与orig - j做亦或

    if (!could_be_bitflip(r)) {
      //判断是否可以通过上一种bitfilp变异方式来获得本次变异结果,如果不能进入分支

      stage_cur_val = -j;
      out_buf[i] = orig - j; //按字节做减法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试和记录
      //如果返回值为1,则跳转至abandon_entry
      stage_cur++;  //变异次数+1

    } else stage_max--;  //如果能够通过bitfilp获得,则跳过这个byte

    out_buf[i] = orig;  //复位

  }

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_ARITH8]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_ARITH8]累加出现的新路径与crash次数的和
stage_cycles[STAGE_ARITH8] += stage_max;
//stage_cycles[STAGE_ARITH8]累加arith 8/8变异次数

arith 16/8

对每个双字(16 bit)进行加减运算(大小端序)

/* 16-bit arithmetics, both endians. */

if (len < 2) goto skip_arith;

stage_name  = "arith 16/8";
//arith 16/8变异策略,对每个双字(16 bit)进行加减运算
stage_short = "arith16";
stage_cur   = 0;
stage_max   = 4 * (len - 1) * ARITH_MAX;

orig_hit_cnt = new_hit_cnt;

for (i = 0; i < len - 1; i++) {//遍历out_buf

  u16 orig = *(u16*)(out_buf + i);

  /* Let's consult the effector map... */

  if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) {
    //检验eff_map中本次循环中对应的双字是否有效
    stage_max -= 4 * ARITH_MAX;
    //如果无效则跳过
    continue;
  }

  stage_cur_byte = i;

  for (j = 1; j <= ARITH_MAX; j++) {
    //SWAP16((_ret << 8) | (_ret >> 8))

    u16 r1 = orig ^ (orig + j),  //orig与orig+j做亦或(小端)
        r2 = orig ^ (orig - j),  //orig与orig-j做亦或(小端)
        r3 = orig ^ SWAP16(SWAP16(orig) + j),   //orig与orig+j做亦或(大端)
        r4 = orig ^ SWAP16(SWAP16(orig) - j);   //orig与orig-j做亦或(大端)

    /* Try little endian addition and subtraction first. Do it only
       if the operation would affect more than one byte (hence the 
       & 0xff overflow checks) and if it couldn't be a product of
       a bitflip. */

    stage_val_type = STAGE_VAL_LE; 

    if ((orig & 0xff) + j > 0xff && !could_be_bitflip(r1)) {
      //如果r1不能通过bitflip得到且(orig & 0xff) + j > 0xff

      stage_cur_val = j;
      *(u16*)(out_buf + i) = orig + j;  //做加法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异次数计数器+1
 
    } else stage_max--;  //跳过

    if ((orig & 0xff) < j && !could_be_bitflip(r2)) {
      //如果r2不能通过bitflip得到且(orig & 0xff) < j

      stage_cur_val = -j;
      *(u16*)(out_buf + i) = orig - j;  //做减法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异次数计数器+1

    } else stage_max--;  //跳过

    /* Big endian comes next. Same deal. */

    stage_val_type = STAGE_VAL_BE;


    if ((orig >> 8) + j > 0xff && !could_be_bitflip(r3)) {
      //如果r3不能通过bitflip得到且(orig >> 8) + j > 0xff

      stage_cur_val = j;
      *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) + j);  //大端做加法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异次数计数器+1

    } else stage_max--;  //跳过

    if ((orig >> 8) < j && !could_be_bitflip(r4)) {
      //如果r4不能通过bitflip得到且(orig >> 8) < j

      stage_cur_val = -j;
      *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) - j);  //大端做减法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异次数计数器+1

    } else stage_max--;  //跳过

    *(u16*)(out_buf + i) = orig;  //复位

  }

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_ARITH16]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_ARITH16]累加出现的新路径与crash次数的和
stage_cycles[STAGE_ARITH16] += stage_max;
//stage_cycles[STAGE_ARITH16]累加arith 16/8变异次数

arith 32/8

对每四字(32 bit)的大小端序进行加减运算

/* 32-bit arithmetics, both endians. */

if (len < 4) goto skip_arith;

stage_name  = "arith 32/8";
//arith 32/8变异策略,对每个四字(32 bit)进行加减运算
stage_short = "arith32";
stage_cur   = 0;
stage_max   = 4 * (len - 3) * ARITH_MAX;

orig_hit_cnt = new_hit_cnt;

for (i = 0; i < len - 3; i++) {  //遍历out_buf

  u32 orig = *(u32*)(out_buf + i);

  /* Let's consult the effector map... */

  if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] &&
      !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) {
        //检验本次循环中对应的四字在eff_map中是否有效
    stage_max -= 4 * ARITH_MAX;  //如果无效跳过本次循环
    continue;
  }

  stage_cur_byte = i;

  for (j = 1; j <= ARITH_MAX; j++) {

    u32 r1 = orig ^ (orig + j),  //orig与orig+j做亦或(小端)
        r2 = orig ^ (orig - j),  //orig与orig-j做亦或(小端)
        r3 = orig ^ SWAP32(SWAP32(orig) + j),  //orig与orig+j做亦或(大端)
        r4 = orig ^ SWAP32(SWAP32(orig) - j);  //orig与orig-j做亦或(大端)

    /* Little endian first. Same deal as with 16-bit: we only want to
       try if the operation would have effect on more than two bytes. */

    stage_val_type = STAGE_VAL_LE;

    if ((orig & 0xffff) + j > 0xffff && !could_be_bitflip(r1)) {
      //如果r1不能由bitflip得到且(orig & 0xffff) + j > 0xffff

      stage_cur_val = j;
      *(u32*)(out_buf + i) = orig + j;  //做加法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

    if ((orig & 0xffff) < j && !could_be_bitflip(r2)) {
      //如果r2不能由bitflip得到且(orig & 0xffff) < j

      stage_cur_val = -j;
      *(u32*)(out_buf + i) = orig - j;  //做减法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

    /* Big endian next. */

    stage_val_type = STAGE_VAL_BE;

    if ((SWAP32(orig) & 0xffff) + j > 0xffff && !could_be_bitflip(r3)) {
      //如果r3不能由bitflip得到且(SWAP32(orig) & 0xffff) + j > 0xffff

      stage_cur_val = j;
      *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) + j);  //大端做加法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

    if ((SWAP32(orig) & 0xffff) < j && !could_be_bitflip(r4)) {
      //如果r4不能由bitflip得到且(SWAP32(orig) & 0xffff) < j

      stage_cur_val = -j;
      *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) - j);  //大端做减法

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

    *(u32*)(out_buf + i) = orig;  //复位

  }

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_ARITH32]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_ARITH32]累加出现的新路径与crash次数的和
stage_cycles[STAGE_ARITH32] += stage_max;
//stage_cycles[STAGE_ARITH32]累加arith 32/8变异次数
INTERESTING VALUES

该阶段主要将out_buf中的字节替换成AFL内部预设的数值,该数值在config.h文件中:

存放着一些整型溢出的数据,在每一次变异时会去检查在前两个阶段bitfilp和arithmetic阶段是否已经产生过==》

若已经产生过==》跳过,验证effector map中对于目标字节的项是否有效

若无效==》跳过

static s8 interesting_8[] = {INTERESTING_8};
static s16 interesting_16[] = {INTERESTING_8, INTERESTING_16};
static s32 interesting_32[] = {INTERESTING_8, INTERESTING_16, INTERESTING_32};


#define INTERESTING_8 \
  -128,          /* Overflow signed 8-bit when decremented  */ \
  -1,            /*                                         */ \
   0,            /*                                         */ \
   1,            /*                                         */ \
   16,           /* One-off with common buffer size         */ \
   32,           /* One-off with common buffer size         */ \
   64,           /* One-off with common buffer size         */ \
   100,          /* One-off with common buffer size         */ \
   127           /* Overflow signed 8-bit when incremented  */

#define INTERESTING_16 \
  -32768,        /* Overflow signed 16-bit when decremented */ \
  -129,          /* Overflow signed 8-bit                   */ \
   128,          /* Overflow signed 8-bit                   */ \
   255,          /* Overflow unsig 8-bit when incremented   */ \
   256,          /* Overflow unsig 8-bit                    */ \
   512,          /* One-off with common buffer size         */ \
   1000,         /* One-off with common buffer size         */ \
   1024,         /* One-off with common buffer size         */ \
   4096,         /* One-off with common buffer size         */ \
   32767         /* Overflow signed 16-bit when incremented */

#define INTERESTING_32 \
  -2147483648LL, /* Overflow signed 32-bit when decremented */ \
  -100663046,    /* Large negative number (endian-agnostic) */ \
  -32769,        /* Overflow signed 16-bit                  */ \
   32768,        /* Overflow signed 16-bit                  */ \
   65535,        /* Overflow unsig 16-bit when incremented  */ \
   65536,        /* Overflow unsig 16 bit                   */ \
   100663045,    /* Large positive number (endian-agnostic) */ \
   2147483647    /* Overflow signed 32-bit when incremented */

interest 8/8

按字节替换interest数据

/**********************
 * INTERESTING VALUES *
 **********************/

stage_name  = "interest 8/8";  //interest 8/8 按字节替换interest数据变异策略
stage_short = "int8";
stage_cur   = 0;
stage_max   = len * sizeof(interesting_8);

stage_val_type = STAGE_VAL_LE;

orig_hit_cnt = new_hit_cnt;

/* Setting 8-bit integers. */

for (i = 0; i < len; i++) {  //遍历out_buf

  u8 orig = out_buf[i];

  /* Let's consult the effector map... */

  if (!eff_map[EFF_APOS(i)]) {  //判断本次循环中对应在eff_map中的项是否为有效的
    stage_max -= sizeof(interesting_8);  //如果无效则跳过本次循环
    continue;
  }

  stage_cur_byte = i;

  for (j = 0; j < sizeof(interesting_8); j++) {
    //遍历interesting_8数组中的数据

    /* Skip if the value could be a product of bitflips or arithmetics. */

    if (could_be_bitflip(orig ^ (u8)interesting_8[j]) ||
        could_be_arith(orig, (u8)interesting_8[j], 1)) {
          //如果orig ^ (u8)interesting_8[j]的结果可以通过bitflip或arith阶段获得
      stage_max--;  //跳过本次循环
      continue;
    }

    stage_cur_val = interesting_8[j];
    out_buf[i] = interesting_8[j];  //将循环指定的字节替换成interesting_8中的数据

    if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
    //调用common_fuzz_stuff进行测试记录
    //返回值为1跳转至abandon_entry

    out_buf[i] = orig;   //复位
    stage_cur++;  //变异次数+1

  }

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_INTEREST8]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_INTEREST8]累加出现的新路径与crash次数的和
stage_cycles[STAGE_INTEREST8] += stage_max;
//stage_cycles[STAGE_INTEREST8]累加interest 8/8变异次数

interest 16/8

按双字节替换interest数据变异策略

/* Setting 16-bit integers, both endians. */

if (no_arith || len < 2) goto skip_interest;

stage_name  = "interest 16/8";  //interest 16/8 按双字节替换interest数据变异策略
stage_short = "int16";
stage_cur   = 0;
stage_max   = 2 * (len - 1) * (sizeof(interesting_16) >> 1);

orig_hit_cnt = new_hit_cnt;

for (i = 0; i < len - 1; i++) {  //遍历out_buf

  u16 orig = *(u16*)(out_buf + i);

  /* Let's consult the effector map... */

  if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) {
    //判断本次循环中相邻的双字对应在eff_map中的项是否为有效的
    stage_max -= sizeof(interesting_16);  //跳过本次循环
    continue;
  }

  stage_cur_byte = i;

  for (j = 0; j < sizeof(interesting_16) / 2; j++) {
    //遍历interesting_16,这里除以2是因为interesting_16中包含两组数据

    stage_cur_val = interesting_16[j];

    /* Skip if this could be a product of a bitflip, arithmetics,
       or single-byte interesting value insertion. */

    if (!could_be_bitflip(orig ^ (u16)interesting_16[j]) &&
        !could_be_arith(orig, (u16)interesting_16[j], 2) &&
        !could_be_interest(orig, (u16)interesting_16[j], 2, 0)) {
          //判断orig ^ (u16)interesting_16[j]是否可以通过bitflip阶段、arith阶段、interest第一阶段可以获得

      stage_val_type = STAGE_VAL_LE;

      *(u16*)(out_buf + i) = interesting_16[j];  //如果都不能,则进行替换本次循环中out_buf的双字

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

    if ((u16)interesting_16[j] != SWAP16(interesting_16[j]) &&
        !could_be_bitflip(orig ^ SWAP16(interesting_16[j])) &&
        !could_be_arith(orig, SWAP16(interesting_16[j]), 2) &&
        !could_be_interest(orig, SWAP16(interesting_16[j]), 2, 1)) {
        //判断orig ^ SWAP16(interesting_16[j])是否可以通过bitflip阶段、arith阶段、interest第一阶段可以获得(大端)

      stage_val_type = STAGE_VAL_BE;

      *(u16*)(out_buf + i) = SWAP16(interesting_16[j]);  //替换(大端)
      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

  }

  *(u16*)(out_buf + i) = orig;  //复位

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_INTEREST16]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_INTEREST16]累加出现的新路径与crash次数的和
stage_cycles[STAGE_INTEREST16] += stage_max;
//stage_cycles[STAGE_INTEREST8]累加interest 16/8变异次数

interest 32/8

按四字节替换interest数据变异策略

/* Setting 32-bit integers, both endians. */

stage_name  = "interest 32/8";  //interest 32/8 按四字节替换interest数据变异策略
stage_short = "int32";
stage_cur   = 0;
stage_max   = 2 * (len - 3) * (sizeof(interesting_32) >> 2);

orig_hit_cnt = new_hit_cnt;

for (i = 0; i < len - 3; i++) {  //遍历out_buf

  u32 orig = *(u32*)(out_buf + i);

  /* Let's consult the effector map... */

  if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] &&
      !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) {
        //判断本次循环中相邻的四字对应在eff_map中的项是否为有效的
    stage_max -= sizeof(interesting_32) >> 1;  //跳过本次循环
    continue;
  }

  stage_cur_byte = i;

  for (j = 0; j < sizeof(interesting_32) / 4; j++) {  //遍历interesting_32

    stage_cur_val = interesting_32[j];

    /* Skip if this could be a product of a bitflip, arithmetics,
       or word interesting value insertion. */

    if (!could_be_bitflip(orig ^ (u32)interesting_32[j]) &&
        !could_be_arith(orig, interesting_32[j], 4) &&
        !could_be_interest(orig, interesting_32[j], 4, 0)) {
          //判断orig ^ (u32)interesting_32[j]是否可以通过bitflip阶段、arith阶段、interest第一阶段可以获得

      stage_val_type = STAGE_VAL_LE;

      *(u32*)(out_buf + i) = interesting_32[j];  //替换四字

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

    if ((u32)interesting_32[j] != SWAP32(interesting_32[j]) &&
        !could_be_bitflip(orig ^ SWAP32(interesting_32[j])) &&
        !could_be_arith(orig, SWAP32(interesting_32[j]), 4) &&
        !could_be_interest(orig, SWAP32(interesting_32[j]), 4, 1)) {
          //判断orig ^ SWAP32(interesting_32[j])是否可以通过bitflip阶段、arith阶段、interest第一阶段可以获得(大端)

      stage_val_type = STAGE_VAL_BE;

      *(u32*)(out_buf + i) = SWAP32(interesting_32[j]);  //替换四字(大端)
      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    } else stage_max--;  //跳过

  }

  *(u32*)(out_buf + i) = orig;  //复位

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_INTEREST32]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_INTEREST32]累加出现的新路径与crash次数的和
stage_cycles[STAGE_INTEREST32] += stage_max;
//stage_cycles[STAGE_INTEREST32]累加interest 32/8变异次数
DICTIONARY STUFF

主要是基于用户提供的extra token来进行一定的变异,主要为替换和插入

user extras (over)

将用户指定的extras token与out_buf进行替换策略

/********************
 * DICTIONARY STUFF *
 ********************/

if (!extras_cnt) goto skip_user_extras;
//如果用户没有通过-x选项指定token,直接跳转至skip_user_extras

/* Overwrite with user-supplied extras. */

stage_name  = "user extras (over)";  //以用户token替换out_buf策略
stage_short = "ext_UO";
stage_cur   = 0;
stage_max   = extras_cnt * len;

stage_val_type = STAGE_VAL_NONE;

orig_hit_cnt = new_hit_cnt;

for (i = 0; i < len; i++) {

  u32 last_len = 0;

  stage_cur_byte = i;

  /* Extras are sorted by size, from smallest to largest. This means
     that we don't have to worry about restoring the buffer in
     between writes at a particular offset determined by the outer
     loop. */

  for (j = 0; j < extras_cnt; j++) {  //遍历extras token

    /* Skip extras probabilistically if extras_cnt > MAX_DET_EXTRAS. Also
       skip them if there's no room to insert the payload, if the token
       is redundant, or if its entire span has no bytes set in the effector
       map. */

    if ((extras_cnt > MAX_DET_EXTRAS && UR(extras_cnt) >= MAX_DET_EXTRAS) ||
        extras[j].len > len - i ||
        !memcmp(extras[j].data, out_buf + i, extras[j].len) ||
        !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, extras[j].len))) {

      stage_max--;  //跳过本次循环
      continue;

    }

    last_len = extras[j].len;
    memcpy(out_buf + i, extras[j].data, last_len);
    //利用memcpy函数将当前指定的extras token覆写进out_buf中

    if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
    //调用common_fuzz_stuff进行测试记录
    //返回值为1跳转至abandon_entry
    stage_cur++;  //变异计数器+1

  }

  /* Restore all the clobbered memory. */
  memcpy(out_buf + i, in_buf + i, last_len);  //从in_buf中取值恢复out_buf

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_EXTRAS_UO]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_EXTRAS_UO]累加出现的新路径与crash次数的和
stage_cycles[STAGE_EXTRAS_UO] += stage_max;
//stage_cycles[STAGE_EXTRAS_UO]累加user extras (over)变异次数

user extras (insert)

以用户token插入out_buf策略

/* Insertion of user-supplied extras. */

stage_name  = "user extras (insert)";  //以用户token插入out_buf策略
stage_short = "ext_UI";
stage_cur   = 0;
stage_max   = extras_cnt * (len + 1);

orig_hit_cnt = new_hit_cnt;

ex_tmp = ck_alloc(len + MAX_DICT_FILE);  //为ex_tmp开辟空间,后续会以ex_tmp作为输入进行fuzz

for (i = 0; i <= len; i++) {

  stage_cur_byte = i;

  for (j = 0; j < extras_cnt; j++) {  //遍历extras token

    if (len + extras[j].len > MAX_FILE) {
      //如果out_buf长度+token长度大于MAX_FILE(默认1024*1024)
      stage_max--; //跳过本次循环
      continue;
    }

    /* Insert token */
    memcpy(ex_tmp + i, extras[j].data, extras[j].len);
    //利用memcpy在ex_tmp对应位置插入token

    /* Copy tail */
    memcpy(ex_tmp + i + extras[j].len, out_buf + i, len - i);
    //在对应位置追加out_buf对应后续的内容

    if (common_fuzz_stuff(argv, ex_tmp, len + extras[j].len)) {
      //调用common_fuzz_stuff进行测试记录
      ck_free(ex_tmp);  //如果返回值为1,释放ex_tmp空间
      goto abandon_entry;  //跳转至abandon_entry
    }

    stage_cur++;  //变异计数器+1

  }

  /* Copy head */
  ex_tmp[i] = out_buf[i];
  //将out_buf[i]对应的前置内容赋值给ex_tmp[i],因为在插入阶段插入的位置是ex_tmp + i,后续补上out_buf + 1的内容

}

ck_free(ex_tmp);

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_EXTRAS_UI]  += new_hit_cnt - orig_hit_cnt;
//stage_finds[STAGE_EXTRAS_UI]累加出现的新路径与crash次数的和
stage_cycles[STAGE_EXTRAS_UI] += stage_max;
//stage_cycles[STAGE_EXTRAS_UI]累加user extras (insert)变异次数

auto extras (over)

和第一个策略差不多,以a_extras中的token替换out_buf策略

  if (!a_extras_cnt) goto skip_extras;
  //如果用户没有通过-x选项指定token,直接跳转至skip_user_extras

  stage_name  = "auto extras (over)";
  //替换阶段2,这里替换的不是extras token,而是a_extras token
  stage_short = "ext_AO";
  stage_cur   = 0;
  stage_max   = MIN(a_extras_cnt, USE_AUTO_EXTRAS) * len;

  stage_val_type = STAGE_VAL_NONE;

  orig_hit_cnt = new_hit_cnt;

  for (i = 0; i < len; i++) {

    u32 last_len = 0;

    stage_cur_byte = i;

    for (j = 0; j < MIN(a_extras_cnt, USE_AUTO_EXTRAS); j++) {  //遍历a_extras

      /* See the comment in the earlier code; extras are sorted by size. */

      if (a_extras[j].len > len - i ||
          !memcmp(a_extras[j].data, out_buf + i, a_extras[j].len) ||
          !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, a_extras[j].len))) {
            //如果a_extras大小不满足上述条件

        stage_max--;  //跳出本次循环
        continue;

      }

      last_len = a_extras[j].len;
      memcpy(out_buf + i, a_extras[j].data, last_len);
      //调用memcpy函数将out_buf位置替换成a_extras token

      if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
      //调用common_fuzz_stuff进行测试记录
      //返回值为1跳转至abandon_entry
      stage_cur++;  //变异计数器+1

    }

    /* Restore all the clobbered memory. */
    memcpy(out_buf + i, in_buf + i, last_len);
    //从in_buf中取值恢复out_buf

  }

  new_hit_cnt = queued_paths + unique_crashes;

  stage_finds[STAGE_EXTRAS_AO]  += new_hit_cnt - orig_hit_cnt;
  //stage_finds[STAGE_EXTRAS_AO]累加出现的新路径与crash次数的和
  stage_cycles[STAGE_EXTRAS_AO] += stage_max;
  //stage_cycles[STAGE_EXTRAS_UI]累加auto extras (over)变异次数

skip_extras:

  /* If we made this to here without jumping to havoc_stage or abandon_entry,
     we're properly done with deterministic steps and can mark it as such
     in the .state/ directory. */

  if (!queue_cur->passed_det) mark_as_det_done(queue_cur);
  //如果没有设置queue_cur->passed_det
  //调用mark_as_det_done函数对queue进行标记,如.state/目录
RANDOM HAVOC

随机毁灭阶段

随机轮次选择随机内容做随机变化

  /****************
   * RANDOM HAVOC *
   ****************/

havoc_stage:

  stage_cur_byte = -1;

  /* The havoc stage mutation code is also invoked when splicing files; if the
     splice_cycle variable is set, generate different descriptions and such. */

  if (!splice_cycle) {  //检测splice_cycle

    stage_name  = "havoc";  //标记havoc
    stage_short = "havoc";  //标记此阶段为havoc
    stage_max   = (doing_det ? HAVOC_CYCLES_INIT : HAVOC_CYCLES) *
                  perf_score / havoc_div / 100;

  } else {

    static u8 tmp[32];

    perf_score = orig_perf;

    sprintf(tmp, "splice %u", splice_cycle);
    stage_name  = tmp;
    stage_short = "splice";  //否则标记此阶段为splice
    stage_max   = SPLICE_HAVOC * perf_score / havoc_div / 100;

  }

  if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN;

  temp_len = len;

  orig_hit_cnt = queued_paths + unique_crashes;

  havoc_queued = queued_paths;

  /* We essentially just do several thousand runs (depending on perf_score)
     where we take the input file and make random stacked tweaks. */

  for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {

    u32 use_stacking = 1 << (1 + UR(HAVOC_STACK_POW2));
    //产生随机数use_stacking,该随机数决定一次stage中变化的次数

    stage_cur_val = use_stacking;
 
    for (i = 0; i < use_stacking; i++) {  //循环use_stacking次

      switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) {
        //根据是否存在extras token来决定UR()函数参数(17或15),随机产生的值作为变换方式的case选项

        case 0:

          /* Flip a single bit somewhere. Spooky! */

          FLIP_BIT(out_buf, UR(temp_len << 3));
          break;

        case 1: 
          //随机选中interesting_8[]中的随机byte替换out_buf中的随机byte
          /* Set byte to interesting value. */

          out_buf[UR(temp_len)] = interesting_8[UR(sizeof(interesting_8))];
          break;

        case 2:

          /* Set word to interesting value, randomly choosing endian. */

          if (temp_len < 2) break;

          if (UR(2)) {
            //随机选择大小端序后,选中interesting_16[]中的随机word替换out_buf中的随机word

            *(u16*)(out_buf + UR(temp_len - 1)) =
              interesting_16[UR(sizeof(interesting_16) >> 1)];

          } else {

            *(u16*)(out_buf + UR(temp_len - 1)) = SWAP16(
              interesting_16[UR(sizeof(interesting_16) >> 1)]);

          }

          break;

        case 3:

          /* Set dword to interesting value, randomly choosing endian. */

          if (temp_len < 4) break;

          if (UR(2)) {
            //随机选择大小端序后,选中interesting_32[]中的随机dword替换out_buf中的随机dword
  
            *(u32*)(out_buf + UR(temp_len - 3)) =
              interesting_32[UR(sizeof(interesting_32) >> 2)];

          } else {

            *(u32*)(out_buf + UR(temp_len - 3)) = SWAP32(
              interesting_32[UR(sizeof(interesting_32) >> 2)]);

          }

          break;

        case 4:

          /* Randomly subtract from byte. */
          //选择out_buf[]中的随机byte减随机数值

          out_buf[UR(temp_len)] -= 1 + UR(ARITH_MAX);
          break;

        case 5:

          /* Randomly add to byte. */
          //选择out_buf[]中的随机byte加随机数值

          out_buf[UR(temp_len)] += 1 + UR(ARITH_MAX);
          break;

        case 6:

          /* Randomly subtract from word, random endian. */

          if (temp_len < 2) break;
          //随机选择大小端序后,选择out_buf[]中的随机word减随机数值

          if (UR(2)) {

            u32 pos = UR(temp_len - 1);

            *(u16*)(out_buf + pos) -= 1 + UR(ARITH_MAX);

          } else {

            u32 pos = UR(temp_len - 1);
            u16 num = 1 + UR(ARITH_MAX);

            *(u16*)(out_buf + pos) =
              SWAP16(SWAP16(*(u16*)(out_buf + pos)) - num);

          }

          break;

        case 7:

          /* Randomly add to word, random endian. */

          if (temp_len < 2) break;
          //随机选择大小端序后,选择out_buf[]中的随机word加随机数值

          if (UR(2)) {

            u32 pos = UR(temp_len - 1);

            *(u16*)(out_buf + pos) += 1 + UR(ARITH_MAX);

          } else {

            u32 pos = UR(temp_len - 1);
            u16 num = 1 + UR(ARITH_MAX);

            *(u16*)(out_buf + pos) =
              SWAP16(SWAP16(*(u16*)(out_buf + pos)) + num);

          }

          break;

        case 8:

          /* Randomly subtract from dword, random endian. */

          if (temp_len < 4) break;
          //随机选择大小端序后,选择out_buf[]中的随机dword减随机数值

          if (UR(2)) {

            u32 pos = UR(temp_len - 3);

            *(u32*)(out_buf + pos) -= 1 + UR(ARITH_MAX);

          } else {

            u32 pos = UR(temp_len - 3);
            u32 num = 1 + UR(ARITH_MAX);

            *(u32*)(out_buf + pos) =
              SWAP32(SWAP32(*(u32*)(out_buf + pos)) - num);

          }

          break;

        case 9:

          /* Randomly add to dword, random endian. */

          if (temp_len < 4) break;
          //随机选择大小端序后,选择out_buf[]中的随机dword加随机数值

          if (UR(2)) {

            u32 pos = UR(temp_len - 3);

            *(u32*)(out_buf + pos) += 1 + UR(ARITH_MAX);

          } else {

            u32 pos = UR(temp_len - 3);
            u32 num = 1 + UR(ARITH_MAX);

            *(u32*)(out_buf + pos) =
              SWAP32(SWAP32(*(u32*)(out_buf + pos)) + num);

          }

          break;

        case 10:

          /* Just set a random byte to a random value. Because,
             why not. We use XOR with 1-255 to eliminate the
             possibility of a no-op. */
          //选择out_buf[]中的随机byte做异或翻转

          out_buf[UR(temp_len)] ^= 1 + UR(255);
          break;

        case 11 ... 12: {

            /* Delete bytes. We're making this a bit more likely
               than insertion (the next option) in hopes of keeping
               files reasonably small. */

            u32 del_from, del_len;

            if (temp_len < 2) break;

            /* Don't delete too much. */

            del_len = choose_block_len(temp_len - 1);

            del_from = UR(temp_len - del_len + 1);
            //选择out_buf[]中的随机byte进行删除

            memmove(out_buf + del_from, out_buf + del_from + del_len,
                    temp_len - del_from - del_len);

            temp_len -= del_len;

            break;

          }

        case 13:
        //选择out_buf[]中的随机位置插入随机长度的内容

          if (temp_len + HAVOC_BLK_XL < MAX_FILE) {

            /* Clone bytes (75%) or insert a block of constant bytes (25%). */
            /*这段内容有75%的概率是out_buf[]中的内容
              有25%的概率是一段相同的随机数字
                这串数字有50%的概率是随机生成的数字
                有50%的概率从out_buf[]中随机选取一个字节*/

            u8  actually_clone = UR(4);
            u32 clone_from, clone_to, clone_len;
            u8* new_buf;

            if (actually_clone) {

              clone_len  = choose_block_len(temp_len);
              clone_from = UR(temp_len - clone_len + 1);

            } else {

              clone_len = choose_block_len(HAVOC_BLK_XL);
              clone_from = 0;

            }

            clone_to   = UR(temp_len);

            new_buf = ck_alloc_nozero(temp_len + clone_len);

            /* Head */

            memcpy(new_buf, out_buf, clone_to);

            /* Inserted part */

            if (actually_clone)
              memcpy(new_buf + clone_to, out_buf + clone_from, clone_len);
            else
              memset(new_buf + clone_to,
                     UR(2) ? UR(256) : out_buf[UR(temp_len)], clone_len);

            /* Tail */
            memcpy(new_buf + clone_to + clone_len, out_buf + clone_to,
                   temp_len - clone_to);

            ck_free(out_buf);
            out_buf = new_buf;
            temp_len += clone_len;

          }

          break;

        case 14: {
          //选择out_buf[]中的随机位置覆盖随机长度的内容

            /* Overwrite bytes with a randomly selected chunk (75%) or fixed
               bytes (25%). */
            /*这段内容有75%的概率是out_buf[]中的内容
              有25%的概率是一段相同的随机数字
                这串数字有50%的概率是随机生成的数字
                有50%的概率从out_buf[]中随机选取一个字节*/

            u32 copy_from, copy_to, copy_len;

            if (temp_len < 2) break;

            copy_len  = choose_block_len(temp_len - 1);

            copy_from = UR(temp_len - copy_len + 1);
            copy_to   = UR(temp_len - copy_len + 1);

            if (UR(4)) {

              if (copy_from != copy_to)
                memmove(out_buf + copy_to, out_buf + copy_from, copy_len);

            } else memset(out_buf + copy_to,
                          UR(2) ? UR(256) : out_buf[UR(temp_len)], copy_len);

            break;

          }

        /* Values 15 and 16 can be selected only if there are any extras
           present in the dictionaries. */

        case 15: {
          //随机选取一段内容覆盖成extra token,token来自a_extras[use_extra].data或者extras[use_extra].data

            /* Overwrite bytes with an extra. */

            if (!extras_cnt || (a_extras_cnt && UR(2))) {

              /* No user-specified extras or odds in our favor. Let's use an
                 auto-detected one. */

              u32 use_extra = UR(a_extras_cnt);
              u32 extra_len = a_extras[use_extra].len;
              u32 insert_at;

              if (extra_len > temp_len) break;

              insert_at = UR(temp_len - extra_len + 1);
              memcpy(out_buf + insert_at, a_extras[use_extra].data, extra_len);

            } else {

              /* No auto extras or odds in our favor. Use the dictionary. */

              u32 use_extra = UR(extras_cnt);
              u32 extra_len = extras[use_extra].len;
              u32 insert_at;

              if (extra_len > temp_len) break;

              insert_at = UR(temp_len - extra_len + 1);
              memcpy(out_buf + insert_at, extras[use_extra].data, extra_len);

            }

            break;

          }

        case 16: {
          //随机选取一段内容插入extra token,token来自a_extras[use_extra].data或者extras[use_extra].data

            u32 use_extra, extra_len, insert_at = UR(temp_len + 1);
            u8* new_buf;

            /* Insert an extra. Do the same dice-rolling stuff as for the
               previous case. */

            if (!extras_cnt || (a_extras_cnt && UR(2))) {

              use_extra = UR(a_extras_cnt);
              extra_len = a_extras[use_extra].len;

              if (temp_len + extra_len >= MAX_FILE) break;

              new_buf = ck_alloc_nozero(temp_len + extra_len);

              /* Head */
              memcpy(new_buf, out_buf, insert_at);

              /* Inserted part */
              memcpy(new_buf + insert_at, a_extras[use_extra].data, extra_len);

            } else {

              use_extra = UR(extras_cnt);
              extra_len = extras[use_extra].len;

              if (temp_len + extra_len >= MAX_FILE) break;

              new_buf = ck_alloc_nozero(temp_len + extra_len);

              /* Head */
              memcpy(new_buf, out_buf, insert_at);

              /* Inserted part */
              memcpy(new_buf + insert_at, extras[use_extra].data, extra_len);

            }

            /* Tail */
            memcpy(new_buf + insert_at + extra_len, out_buf + insert_at,
                   temp_len - insert_at);

            ck_free(out_buf);
            out_buf   = new_buf;
            temp_len += extra_len;

            break;

          }

      }

    }
    //循环叠加变换use_stacking次
    if (common_fuzz_stuff(argv, out_buf, temp_len))
    //调用common_fuzz_stuff进行记录和测试
    //返回值为1调用abandon_entry
      goto abandon_entry;

    /* out_buf might have been mangled a bit, so let's restore it to its
       original size and shape. */

    if (temp_len < len) out_buf = ck_realloc(out_buf, len);
    temp_len = len;
    memcpy(out_buf, in_buf, len);

    /* If we're finding new stuff, let's run for a bit longer, limits
       permitting. */
    //fuzz后queued_paths和havoc_queued不一样==》说明发现了新路径
    if (queued_paths != havoc_queued) {

      if (perf_score <= HAVOC_MAX_MULT * 100) {
        stage_max  *= 2;   //更新stage_max
        perf_score *= 2;   //更新perf_score
      }

      havoc_queued = queued_paths;  //更新havoc_queued

    }

  }

  new_hit_cnt = queued_paths + unique_crashes;

  if (!splice_cycle) {
    stage_finds[STAGE_HAVOC]  += new_hit_cnt - orig_hit_cnt;
    //stage_finds[STAGE_EXTRAS_UI]累加出现的新路径与crash次数的和
    stage_cycles[STAGE_HAVOC] += stage_max;
    //stage_cycles[STAGE_EXTRAS_UI]累加user extras (insert)变异次数
  } else {
    stage_finds[STAGE_SPLICE]  += new_hit_cnt - orig_hit_cnt;
    //stage_finds[STAGE_EXTRAS_UI]累加出现的新路径与crash次数的和
    stage_cycles[STAGE_SPLICE] += stage_max;
    //stage_cycles[STAGE_EXTRAS_UI]累加user extras (insert)变异次数
  }
SPLICING

在经历RANDOM HAVOC阶段后没有什么效果==》

进入SPLICING阶段==》尝试拼接两个测试用例中的内容==》

拼接完成重新执行一遍RANDOM HAVOC阶段

#ifndef IGNORE_FINDS

  /************
   * SPLICING *
   ************/

  /* This is a last-resort strategy triggered by a full round with no findings.
     It takes the current input file, randomly selects another input, and
     splices them together at some offset, then relies on the havoc
     code to mutate that blob. */

retry_splicing:

  if (use_splicing && splice_cycle++ < SPLICE_CYCLES &&
      queued_paths > 1 && queue_cur->len > 1) {
        //如果存在外部输入的fuzzer且数量<SPLICE_CYCLES(默认为15)且queue数量>1且长度>1

    struct queue_entry* target;
    u32 tid, split_at;
    u8* new_buf;
    s32 f_diff, l_diff;

    /* First of all, if we've modified in_buf for havoc, let's clean that
       up... */

    if (in_buf != orig_in) {
      //如果在经历过havoc阶段后修改过in_buf,那么在这里就需要清理一下in_buf,并重新给in_buf赋值成orig_in
      ck_free(in_buf);
      in_buf = orig_in;
      len = queue_cur->len;
    }

    /* Pick a random queue entry and seek to it. Don't splice with yourself. */

    do { tid = UR(queued_paths); } while (tid == current_entry);

    splicing_with = tid;
    target = queue;  //随机在queue中挑选一个target

    while (tid >= 100) { target = target->next_100; tid -= 100; }
    //纠正target取处
    while (tid--) target = target->next;

    /* Make sure that the target has a reasonable length. */

    while (target && (target->len < 2 || target == queue_cur)) {
      //确保target长度合法
      target = target->next;
      splicing_with++;
    }

    if (!target) goto retry_splicing;

    /* Read the testcase into a new buffer. */

    fd = open(target->fname, O_RDONLY);
    //以只读方式打开target

    if (fd < 0) PFATAL("Unable to open '%s'", target->fname);
    //没打开则抛出异常

    new_buf = ck_alloc_nozero(target->len);
    //创建target长度空间

    ck_read(fd, new_buf, target->len, target->fname);
    //将target中的内容督导new_buf内存空间里

    close(fd);

    /* Find a suitable splicing location, somewhere between the first and
       the last differing byte. Bail out if the difference is just a single
       byte or so. */

    locate_diffs(in_buf, new_buf, MIN(len, target->len), &f_diff, &l_diff);
    //拼接in_buf和new_buf的辅助函数

    if (f_diff < 0 || l_diff < 2 || f_diff == l_diff) {
      //如果被拼接文件只有一个字节左右的长度,那么就退出splicing阶段
      ck_free(new_buf);
      goto retry_splicing;
    }

    /* Split somewhere between the first and last differing byte. */

    split_at = f_diff + UR(l_diff - f_diff);
    //随机在第一个字节和最后一个字节之间选取一个位置

    /* Do the thing. */

    len = target->len;
    memcpy(new_buf, in_buf, split_at);
    //调用memcpy函数在new_buf的split_at位置拼接in_buf
    in_buf = new_buf;  //更新in_buf

    ck_free(out_buf);  //释放out_buf
    out_buf = ck_alloc_nozero(len);  //重新创建一个len长度的内存空间out_buf
    memcpy(out_buf, in_buf, len);  //将拼接后的in_buf拷贝到out_buf中

    goto havoc_stage;  //重新执行havoc阶段

  }

#endif /* !IGNORE_FINDS */

  ret_val = 0;  //设置ret_val为0

abandon_entry:  //abandon_entry阶段

  splicing_with = -1;

  /* Update pending_not_fuzzed count if we made it through the calibration
     cycle and have not seen this entry before. */

  if (!stop_soon && !queue_cur->cal_failed && !queue_cur->was_fuzzed) {
    //如果没有中断,且queue没有校准失败,且没有被fuzz过
    queue_cur->was_fuzzed = 1;
    //标记当前queue已经被fuzz过了
    pending_not_fuzzed--;
    //pending_not_fuzzed计数器-1
    if (queue_cur->favored) pending_favored--;
    //如果当前queue是favored
    //pending_favored-1
  }

  munmap(orig_in, queue_cur->len);

  if (in_buf != orig_in) ck_free(in_buf);  //清理缓存
  ck_free(out_buf);
  ck_free(eff_map);

  return ret_val;  //返回ret_val

#undef FLIP_BIT

}

trim_case

对case进行修剪,在TRIMMING中被调用

/* Trim all new test cases to save cycles when doing deterministic checks. The
   trimmer uses power-of-two increments somewhere between 1/16 and 1/1024 of
   file size, to keep the stage short and sweet. */
//对case进行修剪
static u8 trim_case(char** argv, struct queue_entry* q, u8* in_buf) {

  static u8 tmp[64];
  static u8 clean_trace[MAP_SIZE];

  u8  needs_write = 0, fault = 0;
  u32 trim_exec = 0;
  u32 remove_len;
  u32 len_p2;

  /* Although the trimmer will be less useful when variable behavior is
     detected, it will still work to some extent, so we don't check for
     this. */

  if (q->len < 5) return 0;
  //如果case的len小于5字节,就不需要进行修剪
  //直接返回0

  stage_name = tmp;  //让stage_name指向tmp数组首位
  bytes_trim_in += q->len;  //bytes_trim_in累加q->len

  /* Select initial chunk len, starting with large steps. */

  len_p2 = next_p2(q->len);  //len_p2为大于等于q->len的第一个2的幂次

  remove_len = MAX(len_p2 / TRIM_START_STEPS, TRIM_MIN_BYTES);
  //remove_len取len_p2/16或4之间最大的那个,作为初始步长

  /* Continue until the number of steps gets too high or the stepover
     gets too small. */

  while (remove_len >= MAX(len_p2 / TRIM_END_STEPS, TRIM_MIN_BYTES)) {
    //如果remove_len大于等于(len_p2/1024)和(4)之间最大的

    u32 remove_pos = remove_len;
    //设置remove_pos为remove_len

    sprintf(tmp, "trim %s/%s", DI(remove_len), DI(remove_len));
    //调用sprintf格式化字符串到tmp变量中

    stage_cur = 0;
    stage_max = q->len / remove_len;

    while (remove_pos < q->len) {  //循环每次前进remove_len个步长,直到整个文件都被遍历完为止

      u32 trim_avail = MIN(remove_len, q->len - remove_pos);
      u32 cksum;

      write_with_gap(in_buf, q->len, remove_pos, trim_avail);
      //有in_buf中的remove_pos处开始,向后跳过remove_len个字节,写入到.cur_input中

      fault = run_target(argv, exec_tmout);
      //调用run_target,返回值赋值给fault中
      trim_execs++;  //trim_execs计数器+1

      if (stop_soon || fault == FAULT_ERROR) goto abort_trimming;
      //如果主动中断,或返回值报错,直接跳转至abort_trimming

      /* Note that we don't keep track of crashes or hangs here; maybe TODO? */

      cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); //调用hash32进行计算

      /* If the deletion had no impact on the trace, make it permanent. This
         isn't perfect for variable-path inputs, but we're just making a
         best-effort pass, so it's not a big deal if we end up with false
         negatives every now and then. */

      if (cksum == q->exec_cksum) {  //判断校验值如果相等

        u32 move_tail = q->len - remove_pos - trim_avail;
        //从q->len中减去remove_len个字节

        q->len -= trim_avail;
        len_p2  = next_p2(q->len); //并重新计算出一个len_p2

        memmove(in_buf + remove_pos, in_buf + remove_pos + trim_avail, 
                move_tail);
        //将in_buf + remove_pos + trim_avail到最后的字节前移到in_buf + remove_pos处
        //即删除了remove_pos向后的remove_len个字节

        /* Let's save a clean trace, which will be needed by
           update_bitmap_score once we're done with the trimming stuff. */

        if (!needs_write) {  //如果needs_write为0

          needs_write = 1;  //设置needs_write为1
          memcpy(clean_trace, trace_bits, MAP_SIZE);
          //保存当前的trace_bits到clean_trace中

        }

      } else remove_pos += remove_len;
      //如果不为0
      //remove_pos累加remove_len,即前移remove_len个字节,如果相等则无需前移

      /* Since this can be slow, update the screen every now and then. */

      if (!(trim_exec++ % stats_update_freq)) show_stats();//在trim过程中,每执行stats_update_freq次
      //刷新一次界面
      stage_cur++;  //stage_cur计数器+1

    }

    remove_len >>= 1;  //remove_len减半

  }

  /* If we have made changes to in_buf, we also need to update the on-disk
     version of the test case. */

  if (needs_write) {

    s32 fd;

    unlink(q->fname); /* ignore errors 删除原来的q->fname */

    fd = open(q->fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
    //创建一个新的q->fname

    if (fd < 0) PFATAL("Unable to create '%s'", q->fname);

    ck_write(fd, in_buf, q->len, q->fname);  //将in_buf里的内容写入
    close(fd);

    memcpy(trace_bits, clean_trace, MAP_SIZE);
    //利用clean_trace恢复trace_bits
    update_bitmap_score(q);

  }

abort_trimming:

  bytes_trim_out += q->len;
  return fault;

}

calculate_score

根据queue entry的执行速度,覆盖到的path数和路径深度来评估出一个得分==》

该得分perf_score在后面havoc的时候使用

在PERFORMANCE SCORE中被引用

/* Calculate case desirability score to adjust the length of havoc fuzzing.
   A helper function for fuzz_one(). Maybe some of these constants should
   go into config.h. */

static u32 calculate_score(struct queue_entry* q) {

  u32 avg_exec_us = total_cal_us / total_cal_cycles;  //计算平均时间
  u32 avg_bitmap_size = total_bitmap_size / total_bitmap_entries;  //计算平均bitmap大小
  u32 perf_score = 100;  //设置初始值perf_score为100

  /* Adjust score based on execution speed of this path, compared to the
     global average. Multiplier ranges from 0.1x to 3x. Fast inputs are
     less expensive to fuzz, so we're giving them more air time. */

  /*以q->exec_us乘以一个系数==》判断和avg_exec_us的大小调整perf_score*/
  if (q->exec_us * 0.1 > avg_exec_us) perf_score = 10;
  else if (q->exec_us * 0.25 > avg_exec_us) perf_score = 25;
  else if (q->exec_us * 0.5 > avg_exec_us) perf_score = 50;
  else if (q->exec_us * 0.75 > avg_exec_us) perf_score = 75;
  else if (q->exec_us * 4 < avg_exec_us) perf_score = 300;
  else if (q->exec_us * 3 < avg_exec_us) perf_score = 200;
  else if (q->exec_us * 2 < avg_exec_us) perf_score = 150;

  /* Adjust score based on bitmap size. The working theory is that better
     coverage translates to better targets. Multiplier from 0.25x to 3x. */

  /*以q->bitmap_size乘以一个系数==》判断与avg_bitmap_size的大小来调整perf_score*/
  if (q->bitmap_size * 0.3 > avg_bitmap_size) perf_score *= 3;
  else if (q->bitmap_size * 0.5 > avg_bitmap_size) perf_score *= 2;
  else if (q->bitmap_size * 0.75 > avg_bitmap_size) perf_score *= 1.5;
  else if (q->bitmap_size * 3 < avg_bitmap_size) perf_score *= 0.25;
  else if (q->bitmap_size * 2 < avg_bitmap_size) perf_score *= 0.5;
  else if (q->bitmap_size * 1.5 < avg_bitmap_size) perf_score *= 0.75;

  /* Adjust score based on handicap. Handicap is proportional to how late
     in the game we learned about this path. Latecomers are allowed to run
     for a bit longer until they catch up with the rest. */

  if (q->handicap >= 4) {  //q->handicap大于等于4

    perf_score *= 4;
    q->handicap -= 4;

  } else if (q->handicap) {  //如果q->handicap不为0

    perf_score *= 2;
    q->handicap--;

  }

  /* Final adjustment based on input depth, under the assumption that fuzzing
     deeper test cases is more likely to reveal stuff that can't be
     discovered with traditional fuzzers. */

  switch (q->depth) {  //通过深度来调整perf_score

    case 0 ... 3:   break;
    case 4 ... 7:   perf_score *= 2; break;
    case 8 ... 13:  perf_score *= 3; break;
    case 14 ... 25: perf_score *= 4; break;
    default:        perf_score *= 5;

  }

  /* Make sure that we don't go over limit. */

  if (perf_score > HAVOC_MAX_MULT * 100) perf_score = HAVOC_MAX_MULT * 100;
  //确保调整后的值不会超过最大的接线

  return perf_score;  //返回perf_score

}

common_fuzz_stuff

写入文件执行后判断返回结果

在SIMPLE BITFLIP中被调用

/* Write a modified test case, run program, process results. Handle
   error conditions, returning 1 if it's time to bail out. This is
   a helper function for fuzz_one(). */

EXP_ST u8 common_fuzz_stuff(char** argv, u8* out_buf, u32 len) {

  u8 fault;

  if (post_handler) {  //如果定义了post_handler

    out_buf = post_handler(out_buf, &len);  //调用post_handler返回out_buf
    if (!out_buf || !len) return 0;
    //如果out_buf或len其中一个为0
    //直接返回0

  }

  write_to_testcase(out_buf, len);  //写入

  fault = run_target(argv, exec_tmout);  //run_target运行一下返回结果

  if (stop_soon) return 1;  //如果主动中断

  if (fault == FAULT_TMOUT) {  //如果fault值为FAULT_TMOUT

    if (subseq_tmouts++ > TMOUT_LIMIT) {
      //TMOUT_LIMIT(默认250)
      cur_skipped_paths++;
      return 1;
    }

  } else subseq_tmouts = 0;
  //当返回结果为其他时
  //设置连续超时计数器为0

  /* Users can hit us with SIGUSR1 to request the current input
     to be abandoned. */

  if (skip_requested) {  //如果设置了skip_requested

     skip_requested = 0; //skip_requested清0
     cur_skipped_paths++; //cur_skipped_paths计数器+1
     return 1;

  }

  /* This handles FAULT_ERROR for us: */

  queued_discovered += save_if_interesting(argv, out_buf, len, fault);
  //如果发现了新的路径queued_discovered+1

  if (!(stage_cur % stats_update_freq) || stage_cur + 1 == stage_max)
    show_stats();
    //如果stage_cur除以stats_update_freq余数是0,或者其加一等于stage_max
    //更新展示界面show_stats

  return 0;

}

sync_fuzzers:

首先查找有哪些fuzzer文件夹==》读取其他fuzzer文件夹下的queue文件夹里的case==》

依次执行==》如果发现了新的path==》保存在自己的queue文件夹里==》

将最后一个sync的case id写入到**.synced/其他fuzzer**文件里

/* Grab interesting test cases from other fuzzers. */
////读取sync文件夹写的queue文件,保存到自己的queue中
static void sync_fuzzers(char** argv) {

  DIR* sd;
  struct dirent* sd_ent;
  u32 sync_cnt = 0;

  sd = opendir(sync_dir);  //尝试打开sync_dir目录
  if (!sd) PFATAL("Unable to open '%s'", sync_dir);
  //打开失败则抛出异常

  stage_max = stage_cur = 0;
  cur_depth = 0;

  /* Look at the entries created for every other fuzzer in the sync directory. */

  while ((sd_ent = readdir(sd))) {
    //循环读取该文件夹下的目录和文件

    static u8 stage_tmp[128];

    DIR* qd;
    struct dirent* qd_ent;
    u8 *qd_path, *qd_synced_path;
    u32 min_accept = 0, next_min_accept;

    s32 id_fd;

    /* Skip dot files and our own output directory. */

    if (sd_ent->d_name[0] == '.' || !strcmp(sync_id, sd_ent->d_name)) continue;
    //跳过“.”文件和sync_id(输出文件夹)

    /* Skip anything that doesn't have a queue/ subdirectory. */

    qd_path = alloc_printf("%s/%s/queue", sync_dir, sd_ent->d_name);
    //拼接sync_dir/sd_ent->d_name/queue路径
    if (!(qd = opendir(qd_path))) {
      //尝试打开路径,如果失败则退出本次循环
      ck_free(qd_path);
      continue;
    }

    /* Retrieve the ID of the last seen test case. */

    qd_synced_path = alloc_printf("%s/.synced/%s", out_dir, sd_ent->d_name);
    //拼接out_dir/.synced/sd_ent->d_name目录

    id_fd = open(qd_synced_path, O_RDWR | O_CREAT, 0600);
    //尝试打开拼接目录

    if (id_fd < 0) PFATAL("Unable to create '%s'", qd_synced_path);
    //打开失败则抛出异常

    if (read(id_fd, &min_accept, sizeof(u32)) > 0) 
    //从id_fd中读取4个字节到min_accept中
      lseek(id_fd, 0, SEEK_SET);
      //如果成功读取,调动lseek函数调整文件内指针到开头

    next_min_accept = min_accept;
    //更新next_min_accept,该变量代表之前从这个文件夹里读取到的最后一个queue的id

    /* Show stats */    

    sprintf(stage_tmp, "sync %u", ++sync_cnt);
    //sync_cnt计数器+1,"sync %u"格式化到stage_tmp中
    stage_name = stage_tmp;
    stage_cur  = 0;
    stage_max  = 0;

    /* For every file queued by this fuzzer, parse ID and see if we have looked at
       it before; exec a test case if not. */

    while ((qd_ent = readdir(qd))) {
      //循环读取sync_dir/sd_ent->d_name/queue文件夹中的目录和文件

      u8* path;
      s32 fd;
      struct stat st;

      if (qd_ent->d_name[0] == '.' ||
          sscanf(qd_ent->d_name, CASE_PREFIX "%06u", &syncing_case) != 1 || 
          syncing_case < min_accept) continue;
          //如果文件是以“.”开头,或者syncing_case标识小于min_accept文件,因为这些文件已经被sync过了,则跳出本次循环

      /* OK, sounds like a new one. Let's give it a try. */

      if (syncing_case >= next_min_accept)
      //如果syncing_case标识大于等于next_min_accept
        next_min_accept = syncing_case + 1;
        //设置next_min_accept为syncing_case + 1,标明next

      path = alloc_printf("%s/%s", qd_path, qd_ent->d_name);
      //拼接路径qd_path/qd_ent->d_name

      /* Allow this to fail in case the other fuzzer is resuming or so... */

      fd = open(path, O_RDONLY);  //打开路径

      if (fd < 0) {  //打开路径失败
         ck_free(path);  //释放path,跳出本次循环
         continue;
      }

      if (fstat(fd, &st)) PFATAL("fstat() failed");

      /* Ignore zero-sized or oversized files. */

      if (st.st_size && st.st_size <= MAX_FILE) {
        //如果文件大小不为0,且小于MAX_FILE(默认1024 * 1024)

        u8  fault;
        u8* mem = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
        //将文件中的内容映射进内存中

        if (mem == MAP_FAILED) PFATAL("Unable to mmap '%s'", path);
        //内存映射报错抛异常

        /* See what happens. We rely on save_if_interesting() to catch major
           errors and save the test case. */

        write_to_testcase(mem, st.st_size);
        //通过write_to_testcase将mem写到out_file中

        fault = run_target(argv, exec_tmout);
        //运行对应文件,返回值赋值给fault

        if (stop_soon) return;
        //如果中断,直接返回

        syncing_party = sd_ent->d_name;
        //通过save_if_interesting来决定是否要将这个queue导入到自己的queue里,如果发现了新的path,就导入
        queued_imported += save_if_interesting(argv, mem, st.st_size, fault);
        //如果返回值为1,则queued_imported计数器+1
        syncing_party = 0;

        munmap(mem, st.st_size);

        if (!(stage_cur++ % stats_update_freq)) show_stats();
        //stage_cur计数器+1,如果stage_cur是stats_update_freq的倍数,就刷新一次界面

      }

      ck_free(path);
      close(fd);

    }

    ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path);
    //向当前id_fd中写入当前的next_min_accept值

    close(id_fd);
    closedir(qd);
    ck_free(qd_path);
    ck_free(qd_synced_path);
    
  }  

  closedir(sd);

}

save_if_interesting

检查这个case的执行结果是否是interesting的,决定是否保存或跳过

/* Check if the result of an execve() during routine fuzzing is interesting,
   save or queue the input test case for further analysis if so. Returns 1 if
   entry is saved, 0 otherwise. */

static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) {

  u8  *fn = "";
  u8  hnb;
  s32 fd;
  u8  keeping = 0, res;

  if (fault == crash_mode) {  //如果fault为crash_mode模式

    /* Keep only if there are new bits in the map, add to queue for
       future fuzzing, etc. */

    if (!(hnb = has_new_bits(virgin_bits))) {  //检查是否出现了new bits
      if (crash_mode) total_crashes++;
      //没有出现的话,如果设置了crash_mode
      //total_crashes计数器+1
      return 0;
       //如果没有设置crash_mode返回0
    }    

#ifndef SIMPLE_FILES  //如果出现了new bits

    fn = alloc_printf("%s/queue/id:%06u,%s", out_dir, queued_paths,
                      describe_op(hnb));
    //拼接路径out_dir/queue/id:00000queued_paths,describe_op(hnb)

#else

    fn = alloc_printf("%s/queue/id_%06u", out_dir, queued_paths);

#endif /* ^!SIMPLE_FILES */

    add_to_queue(fn, len, 0);  //调用add_to_queue将拼接路径加入到队列中

    if (hnb == 2) {
      queue_top->has_new_cov = 1;
      queued_with_cov++;
    }

    queue_top->exec_cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
    //利用hash32计算trace_bits,赋值给exec_cksum

    /* Try to calibrate inline; this also calls update_bitmap_score() when
       successful. */

    res = calibrate_case(argv, queue_top, mem, queue_cycle - 1, 0);
    //调用calibrate_case函数对新添加的queue进行校验

    if (res == FAULT_ERROR)
      FATAL("Unable to execute target application");
      //如果校验返回值为FAULT_ERROR,抛异常

    fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
    if (fd < 0) PFATAL("Unable to create '%s'", fn);
    ck_write(fd, mem, len, fn);
    //打开拼接路径,将mem的内容写入文件fn
    close(fd);

    keeping = 1;  //设置keeping为1

  }

  switch (fault) {  //通过Switch判断fault类型做进一步处理

    case FAULT_TMOUT:  //fault == FAULT_TMOUT时

      /* Timeouts are not very interesting, but we're still obliged to keep
         a handful of samples. We use the presence of new bits in the
         hang-specific bitmap as a signal of uniqueness. In "dumb" mode, we
         just keep everything. */

      total_tmouts++;  //设置total_tmouts计数器+1

      if (unique_hangs >= KEEP_UNIQUE_HANG) return keeping;
      //如果unique_hangs的个数超过能保存的最大数量KEEP_UNIQUE_HANG(默认为500)
      //直接返回keeping

      if (!dumb_mode) {
        //如果不是dumb_mode

#ifdef WORD_SIZE_64
        simplify_trace((u64*)trace_bits);
#else
        simplify_trace((u32*)trace_bits);
#endif /* ^WORD_SIZE_64 */

        if (!has_new_bits(virgin_tmout)) return keeping;
        //如果没有新的超时路径
        //直接返回keeping

      }

      unique_tmouts++;  //unique_tmouts计数器+1

      /* Before saving, we make sure that it's a genuine hang by re-running
         the target with a more generous timeout (unless the default timeout
         is already generous). */

      if (exec_tmout < hang_tmout) {
        //如果exec_tmout < hang_tmout

        u8 new_fault;
        write_to_testcase(mem, len);  //将mem写入out_file
        new_fault = run_target(argv, hang_tmout);
        //调用run_target运行一次,返回new_fault

        /* A corner case that one user reported bumping into: increasing the
           timeout actually uncovers a crash. Make sure we don't discard it if
           so. */

        if (!stop_soon && new_fault == FAULT_CRASH) goto keep_as_crash;
        //如果没有发生主动中断,并且new_fault的结果为FAULT_CRASH,那么直接跳转到keep_as_crash

        if (stop_soon || new_fault != FAULT_TMOUT) return keeping;
        //如果发生主动中断或者new_fault的结果不为FAULT_CRASH,直接返回keeping

      }

#ifndef SIMPLE_FILES

      fn = alloc_printf("%s/hangs/id:%06llu,%s", out_dir,
                        unique_hangs, describe_op(0));
      //拼接字符串fn:out_dir/hangs/id:000000unique_hangs,describe_op(0)

#else

      fn = alloc_printf("%s/hangs/id_%06llu", out_dir,
                        unique_hangs);

#endif /* ^!SIMPLE_FILES */

      unique_hangs++;  //unique_hangs计数器+1

      last_hang_time = get_cur_time();
      //更新last_hang_time的值并保存找到fn中

      break;

    case FAULT_CRASH: //fault == FAULT_CRASH时

keep_as_crash:

      /* This is handled in a manner roughly similar to timeouts,
         except for slightly different limits and no need to re-run test
         cases. */

      total_crashes++;  //total_crashes计数器+1

      if (unique_crashes >= KEEP_UNIQUE_CRASH) return keeping;
      //如果unique_crashes数量大于等于能保存的最大数量KEEP_UNIQUE_CRASH(默认5000)
      //直接返回keeping

      if (!dumb_mode) {
        //如果不是dumb_mode

#ifdef WORD_SIZE_64
        simplify_trace((u64*)trace_bits);
#else
        simplify_trace((u32*)trace_bits);
        //调用simplify_trace对trace_bits进行调整
#endif /* ^WORD_SIZE_64 */

        if (!has_new_bits(virgin_crash)) return keeping;
        //如果没有发现新的crash路径
        //直接返回keeping

      }

      if (!unique_crashes) write_crash_readme();
      //如果unique_crashes为0
      //调用write_crash_readme编写崩溃目录信息

#ifndef SIMPLE_FILES

      fn = alloc_printf("%s/crashes/id:%06llu,sig:%02u,%s", out_dir,
                        unique_crashes, kill_signal, describe_op(0));
                        //拼接路径

#else

      fn = alloc_printf("%s/crashes/id_%06llu_%02u", out_dir, unique_crashes,
                        kill_signal);
                        //拼接路径

#endif /* ^!SIMPLE_FILES */

      unique_crashes++;  //unique_crashes计数器+1

      last_crash_time = get_cur_time();  //更新last_crash_time时间
      last_crash_execs = total_execs;  //更新last_crash_execs

      break;

    case FAULT_ERROR: FATAL("Unable to execute target application");
    //如果fault == FAULT_ERROR,抛出异常

    default: return keeping;//对于其他情况直接返回keeping

  }

  /* If we're here, we apparently want to save the crash or hang
     test case, too. */

  fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
  if (fd < 0) PFATAL("Unable to create '%s'", fn);
  ck_write(fd, mem, len, fn);
  close(fd);

  ck_free(fn);

  return keeping;

}