/*
  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.
*/

// 这部分是版权声明和许可协议，声明该文件受到 Apache 2.0 许可证保护。

/*
   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.

*/

// 该段注释解释了代码的功能
// 它是一个用于 GNU as 汇编器的包装器
// 目的是预处理由 GCC/Clang 生成的汇编文件并注入必要的仪器代码

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

// 定义宏 AFL_MAIN，通常用于标识主程序或相关模块。
// 包含内部的头文件，这些文件提供了项目的配置、类型定义、调试功能以及内存分配等支持。
// 包含标准的C库和POSIX库头文件，用于文件操作、内存管理、时间处理等。

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'  */

//声明静态变量：
// as_params：保存传递给实际 as 汇编器的参数。
// input_file：保存输入的文件路径。
// modified_file：保存经过修改的文件路径（即注入了仪器代码后的文件）。

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                    */

// 声明其他控制程序行为的静态变量：

// be_quiet：控制是否启用静默模式，不输出标准错误。
// clang_mode：是否处于 clang 模式。
// pass_thru：是否跳过修改，直接传递数据。
// just_version：是否只显示版本信息。
// sanitizer：是否启用了地址或内存错误检测工具（如 ASAN 或 MSAN）。

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

// 声明静态变量：

// inst_ratio：仪器插入的概率（百分比）。
// as_par_cnt：传递给汇编器 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;

#else

static u8   use_64bit = 0;

#ifdef __APPLE__
#  error "Sorry, 32-bit Apple platforms are not supported."
#endif /* __APPLE__ */

#endif /* ^WORD_SIZE_64 */

// 根据编译时的定义判断是否使用 64 位模式。
// 如果没有定义 WORD_SIZE_64，则默认使用 32 位模式。
// 如果是苹果平台，还会抛出一个错误，表明不支持 32 位 Apple 平台。

/* 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) {

// 定义函数 edit_params，用于修改传递给 as 汇编器的参数。


  u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS");
  u32 i;

// 获取环境变量 TMPDIR 和 AFL_AS，并声明变量 i。

#ifdef __APPLE__

  u8 use_clang_as = 0;

// 如果是在 Apple 平台上，声明一个变量 use_clang_as 来标识是否使用 clang 作为汇编器。

  /* 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) {

    use_clang_as = 1;

    afl_as = getenv("AFL_CC");
    if (!afl_as) afl_as = getenv("AFL_CXX");
    if (!afl_as) afl_as = "clang";

// 如果在 clang 模式下且没有指定 AFL_AS，则尝试使用 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";


//如果没有设置 TMPDIR，则尝试使用其他环境变量（如 TEMP 和 TMP），如果都没有设置，则默认使用 /tmp。

  as_params = ck_alloc((argc + 32) * sizeof(u8*));

// 为汇编器参数分配内存，留出额外的空间。

  as_params[0] = afl_as ? afl_as : (u8*)"as";

// 设置 as 汇编器命令，如果没有设置 AFL_AS，则默认使用 "as"。

  as_params[argc] = 0;

  for (i = 1; i < argc - 1; i++) {

    if (!strcmp(argv[i], "--64")) use_64bit = 1;
    else if (!strcmp(argv[i], "--32")) use_64bit = 0;

#ifdef __APPLE__

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

    if (!strcmp(argv[i], "-arch") && i + 1 < argc) {

      if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1;
      else if (!strcmp(argv[i + 1], "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;

#endif /* __APPLE__ */

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

  }

// 遍历所有传递给程序的参数，如果参数为 --64 或 --32，则根据平台设置 use_64bit
// 对于 Apple 平台，还会检查架构并设置为 64 位。

#ifdef __APPLE__

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

// 这段注释说明接下来的操作是调用真正的 as 汇编器，并传递适当的参数。
// 如果一切顺利，as 会返回一个 0 的退出代码，程序会传播这个退出代码。

  if (use_clang_as) {

    as_params[as_par_cnt++] = "-c";
    as_params[as_par_cnt++] = "-x";
    as_params[as_par_cnt++] = "assembler";

  }


// 在 exec_real_as 函数中：

// 如果处于静默模式（be_quiet），则关闭标准错误输出并将其重定向到 /dev/null，避免打印调试信息。
// 使用 execvp 执行汇编器命令，传递修改后的参数。
// 如果执行失败，则打印错误信息并退出程序。

#endif /* __APPLE__ */

   input_file = argv[argc - 1];

  if (input_file[0] == '-') {

    if (!strcmp(input_file + 1, "-version")) {
      just_version = 1;
      modified_file = input_file;
      goto wrap_things_up;  // 跳到 wrap_things_up 标签，处理版本信息
    }

    if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)");
      else input_file = NULL;

  } else {

    /* 检查输入文件路径是否看起来像是编译程序的一部分，而不是使用 gcc 编译一个临时的 .s 文件 */
    /* 这段代码是为了绕过某些特殊情况，比如编译 NSS 库时的情况 */

    if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) &&
        strncmp(input_file, "/var/tmp/", 9) &&
        strncmp(input_file, "/tmp/", 5)) pass_thru = 1;  // 启用通过模式

  }

  modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(),
                               (u32)time(NULL));  // 为生成的文件名分配内存并格式化

wrap_things_up:

  as_params[as_par_cnt++] = modified_file;  // 将修改后的文件名添加到参数列表
  as_params[as_par_cnt]   = NULL;  // 参数列表以 NULL 结尾
}
// 此段代码注释说明了程序如何检查和处理输入文件，包括：

// 如果文件名以 - 开头，可能是传递版本信息或者其他特殊的命令行参数。
// 如果文件路径看起来是临时目录之外的路径，则启用“通过模式”（pass_thru），这表示不进行修改，直接传递给汇编器。

/* Process input file, generate modified_file. Insert instrumentation in all
   the appropriate places. */
/* 处理输入文件，生成修改后的文件，并在所有适当的位置插入仪器代码 */

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");  // 打开输入文件进行读取
    if (!inf) PFATAL("Unable to read '%s'", input_file);  // 如果打开失败，打印错误并退出

  } else inf = stdin;  // 如果没有指定输入文件，使用标准输入

  outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600);  // 创建输出文件

  if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file);  // 如果创建失败，打印错误并退出

  outf = fdopen(outfd, "w");  // 获取文件流

  if (!outf) PFATAL("fdopen() failed");  // 如果 fdopen 失败，打印错误并退出

  while (fgets(line, MAX_LINE, inf)) {  // 逐行读取输入文件

    /* 在某些情况下，我们希望在写入仪器跳板之前先跳过某些标签、宏或注释。
       如果启用了这个模式，并且当前行以制表符和字母开头，那么我们现在就插入仪器跳板。 */

    if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&
        instrument_next && line[0] == '\t' && isalpha(line[1])) {

      fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
              R(MAP_SIZE));  // 根据使用的位数选择合适的跳板格式

      instrument_next = 0;
      ins_lines++;  // 统计插入的行数

    }

    /* 输出当前行，只有在通过模式下才跳过插入仪器 */

    fputs(line, outf);

    if (pass_thru) continue;  // 如果启用了通过模式，则跳过剩下的处理

    /* 以下代码处理实际的插桩逻辑，只在 .text 段插入仪器代码 */

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

      /* OpenBSD 将跳转表直接内联到代码中，这很麻烦。它们使用特定的 p2align 指令格式，
         所以我们以此为信号进行处理。 */

      if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&
          isdigit(line[10]) && line[11] == '\n') skip_next_label = 1;

      /* 如果检测到 .text 段或相关段，启用插桩 */
      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;  // 继续处理下一行
      }

      /* 如果是其他段（如 bss、data），禁用插桩 */
      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;  // 继续处理下一行
      }

    }

    /* 检测并跳过特定的汇编节（如 .code） */

    if (strstr(line, ".code")) {

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

    }

    /* 检测并跳过 Intel 语法块 */
    if (strstr(line, ".intel_syntax")) skip_intel = 1;
    if (strstr(line, ".att_syntax")) skip_intel = 0;

    /* 跳过 ad-hoc 的 __asm__ 块 */
    if (line[0] == '#' || line[1] == '#') {

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

    }

    /* 检查并插入仪器代码：主要是函数标签、条件标签等 */
    if (skip_intel || skip_app || skip_csect || !instr_ok ||
        line[0] == '#' || line[0] == ' ') continue;  // 跳过不需要插入的行

    /* 条件分支指令（如 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));  // 插入仪器代码

        ins_lines++;  // 统计插入的行数

      }

      continue;

    }

    /* 标签的处理。标签可能是分支目标，我们需要根据格式区分处理 */

#ifdef __APPLE__

    /* 苹果系统标签格式：L<whatever><digit>: */

    if ((colon_pos = strstr(line, ":"))) {

      if (line[0] == 'L' && isdigit(*(colon_pos - 1))) {

#else

    /* 其他平台的标签格式：.L<whatever>: */

    if (strstr(line, ":")) {

      if (line[0] == '.') {

#endif /* __APPLE__ */

        /* 处理跳转目标标签（如 .L0: 或 LBB0_0:） */

#ifdef __APPLE__

        /* 苹果：L<num> / LBB<num> */

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

#else

        /* 其他平台：.L<num> / .LBB<num> */

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

#endif /* __APPLE__ */

          /* 如果符合条件，则插入仪器代码 */

          if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;

        }

      } else {

        /* 函数标签，插入仪器代码 */

        instrument_next = 1;

      }

    }

  }

  fclose(inf);  // 关闭输入文件
  fclose(outf);  // 关闭输出文件

}


/* Main entry point */

// 在 main 函数中：

// 检查是否只需要显示版本信息，如果是，则调用 print_version 函数。
// 解析命令行参数：
// 解析 -q 启用静默模式。
// 解析 --clang 启用 clang 模式。
// 解析 --pass-through 启用数据通过模式。
// 解析 --sanitizer 启用 sanitizer。
// 解析 --inst-ratio 设置仪器插入的概率。
// 解析 --version 仅显示版本信息。
// 确保输入文件已经指定，如果没有指定，则退出并报错。
// 调用 edit_params 函数修改传递给汇编器的参数。
// 最后，调用 exec_real_as 函数执行实际的汇编器命令。

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

  s32 pid;
  u32 rand_seed;
  int status;
  u8* inst_ratio_str = getenv("AFL_INST_RATIO");

  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();

  srandom(rand_seed);

  edit_params(argc, argv);

  if (inst_ratio_str) {

    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)");

  }

  if (getenv(AS_LOOP_ENV_VAR))
    FATAL("Endless loop when calling 'as' (remove '.' from your PATH)");

  setenv(AS_LOOP_ENV_VAR, "1", 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")) {
    sanitizer = 1;
    inst_ratio /= 3;
  }

  if (!just_version) add_instrumentation();

  if (!(pid = fork())) {

    execvp(as_params[0], (char**)as_params);
    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);

  exit(WEXITSTATUS(status));

}

