/* GCC plugin for cmplog instrumentation of code for AFL++. Copyright 2014-2019 Free Software Foundation, Inc Copyright 2015, 2016 Google Inc. All rights reserved. Copyright 2019-2020 AFLplusplus Project. All rights reserved. Copyright 2019-2023 AdaCore Written by Alexandre Oliva , based on the AFL++ LLVM CmpLog pass by Andrea Fioraldi , and on the AFL GCC pass. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "afl-gcc-common.h" /* This plugin, being under the same license as GCC, satisfies the "GPL-compatible Software" definition in the GCC RUNTIME LIBRARY EXCEPTION, so it can be part of an "Eligible" "Compilation Process". */ int plugin_is_GPL_compatible = 1; namespace { static const struct pass_data afl_cmplog_pass_data = { .type = GIMPLE_PASS, .name = "aflcmplog", .optinfo_flags = OPTGROUP_NONE, .tv_id = TV_NONE, .properties_required = 0, .properties_provided = 0, .properties_destroyed = 0, .todo_flags_start = 0, .todo_flags_finish = (TODO_update_ssa | TODO_cleanup_cfg | TODO_verify_il | TODO_rebuild_cgraph_edges), }; struct afl_cmplog_pass : afl_base_pass { afl_cmplog_pass(bool quiet) : afl_base_pass(quiet, /*debug=*/false, afl_cmplog_pass_data), t8u(), cmplog_hooks() { } /* An unsigned 8-bit integral type. */ tree t8u; /* Declarations for the various cmplog hook functions, allocated on demand.. [0] is for __cmplog_ins_hookN, that accepts non-power-of-2 sizes. [n in 1..5] are for unsigned ints of 2^{n-1} bytes. */ tree cmplog_hooks[6]; tree cmplog_hook(unsigned i) { tree t, fnt; if (!t8u) { if (BITS_PER_UNIT == 8) t8u = unsigned_char_type_node; else t8u = build_nonstandard_integer_type(8, 1); } if (i <= ARRAY_SIZE(cmplog_hooks) && cmplog_hooks[i]) return cmplog_hooks[i]; switch (i) { case 0: #ifdef uint128_type_node t = uint128_type_node; #else t = build_nonstandard_integer_type(128, 1); #endif fnt = build_function_type_list(void_type_node, t, t, t8u, t8u, NULL_TREE); t = cmplog_hooks[0] = build_fn_decl("__cmplog_ins_hookN", fnt); break; case 1: t = t8u; fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); t = cmplog_hooks[1] = build_fn_decl("__cmplog_ins_hook1", fnt); break; case 2: t = uint16_type_node; fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); t = cmplog_hooks[2] = build_fn_decl("__cmplog_ins_hook2", fnt); break; case 3: t = uint32_type_node; fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); t = cmplog_hooks[3] = build_fn_decl("__cmplog_ins_hook4", fnt); break; case 4: t = uint64_type_node; fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); t = cmplog_hooks[4] = build_fn_decl("__cmplog_ins_hook8", fnt); break; case 5: #ifdef uint128_type_node t = uint128_type_node; #else t = build_nonstandard_integer_type(128, 1); #endif fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); t = cmplog_hooks[5] = build_fn_decl("__cmplog_ins_hook16", fnt); break; default: gcc_unreachable(); } /* Mark the newly-created decl as non-throwing, so that we can insert call within basic blocks. */ TREE_NOTHROW(t) = 1; return t; } /* Insert a cmplog hook call before GSI for a CODE compare between LHS and RHS. */ void insert_cmplog_call(gimple_stmt_iterator gsi, tree_code code, tree lhs, tree rhs) { gcc_checking_assert(TYPE_MAIN_VARIANT(TREE_TYPE(lhs)) == TYPE_MAIN_VARIANT(TREE_TYPE(rhs))); tree fn; bool pass_n = false; /* Obtain the compare operand size as a constant. */ tree st = TREE_TYPE(lhs); tree szt = TYPE_SIZE(st); if (!tree_fits_uhwi_p(szt)) return; unsigned HOST_WIDE_INT sz = tree_to_uhwi(szt); /* Round it up. */ if (sz % 8) sz = (((sz - 1) / 8) + 1) * 8; /* Select the hook function to call, based on the size. */ switch (sz) { default: fn = cmplog_hook(0); pass_n = true; break; case 8: fn = cmplog_hook(1); break; case 16: fn = cmplog_hook(2); break; case 32: fn = cmplog_hook(3); break; case 64: fn = cmplog_hook(4); break; case 128: fn = cmplog_hook(5); break; } /* Set attr according to the compare operation. */ unsigned char attr = 0; switch (code) { case UNORDERED_EXPR: case ORDERED_EXPR: /* ??? */ /* Fallthrough. */ case NE_EXPR: case LTGT_EXPR: break; case EQ_EXPR: case UNEQ_EXPR: attr += 1; break; case GT_EXPR: case UNGT_EXPR: attr += 2; break; case GE_EXPR: case UNGE_EXPR: attr += 3; break; case LT_EXPR: case UNLT_EXPR: attr += 4; break; case LE_EXPR: case UNLE_EXPR: attr += 5; break; default: gcc_unreachable(); } if (FLOAT_TYPE_P(TREE_TYPE(lhs))) { attr += 8; tree t = build_nonstandard_integer_type(sz, 1); tree s = make_ssa_name(t); gimple g = gimple_build_assign(s, VIEW_CONVERT_EXPR, build1(VIEW_CONVERT_EXPR, t, lhs)); lhs = s; gsi_insert_before(&gsi, g, GSI_SAME_STMT); s = make_ssa_name(t); g = gimple_build_assign(s, VIEW_CONVERT_EXPR, build1(VIEW_CONVERT_EXPR, t, rhs)); rhs = s; gsi_insert_before(&gsi, g, GSI_SAME_STMT); } /* Convert the operands to the hook arg type, if needed. */ tree t = TREE_VALUE(TYPE_ARG_TYPES(TREE_TYPE(fn))); lhs = fold_convert_loc(UNKNOWN_LOCATION, t, lhs); if (!is_gimple_val(lhs)) { tree s = make_ssa_name(t); gimple g = gimple_build_assign(s, lhs); lhs = s; gsi_insert_before(&gsi, g, GSI_SAME_STMT); } rhs = fold_convert_loc(UNKNOWN_LOCATION, t, rhs); if (!is_gimple_val(rhs)) { tree s = make_ssa_name(t); gimple g = gimple_build_assign(s, rhs); rhs = s; gsi_insert_before(&gsi, g, GSI_SAME_STMT); } /* Insert the call. */ tree att = build_int_cst(t8u, attr); gimple call; if (pass_n) call = gimple_build_call(fn, 4, lhs, rhs, att, build_int_cst(t8u, sz / 8 - 1)); else call = gimple_build_call(fn, 3, lhs, rhs, att); gsi_insert_before(&gsi, call, GSI_SAME_STMT); } virtual unsigned int execute(function *fn) { if (!isInInstrumentList(fn)) return 0; basic_block bb; FOR_EACH_BB_FN(bb, fn) { /* A GIMPLE_COND or GIMPLE_SWITCH will always be the last stmt in a BB. */ gimple_stmt_iterator gsi = gsi_last_bb(bb); if (gsi_end_p(gsi)) continue; gimple stmt = gsi_stmt(gsi); if (gimple_code(stmt) == GIMPLE_COND) { tree_code code = gimple_cond_code(stmt); tree lhs = gimple_cond_lhs(stmt); tree rhs = gimple_cond_rhs(stmt); insert_cmplog_call(gsi, code, lhs, rhs); } else if (gimple_code(stmt) == GIMPLE_SWITCH) { gswitch *sw = as_a(stmt); tree lhs = gimple_switch_index(sw); for (int i = 0, e = gimple_switch_num_labels(sw); i < e; i++) { tree clx = gimple_switch_label(sw, i); tree rhsl = CASE_LOW(clx); /* Default case labels exprs don't have a CASE_LOW. */ if (!rhsl) continue; tree rhsh = CASE_HIGH(clx); /* If there is a CASE_HIGH, issue range compares. */ if (rhsh) { insert_cmplog_call(gsi, GE_EXPR, lhs, rhsl); insert_cmplog_call(gsi, LE_EXPR, lhs, rhsh); } /* Otherwise, use a single equality compare. */ else insert_cmplog_call(gsi, EQ_EXPR, lhs, rhsl); } } else continue; } return 0; } }; static struct plugin_info afl_cmplog_plugin = { .version = "20220420", .help = G_("AFL gcc cmplog plugin\n\ \n\ Set AFL_QUIET in the environment to silence it.\n\ "), }; } // namespace /* This is the function GCC calls when loading a plugin. Initialize and register further callbacks. */ int plugin_init(struct plugin_name_args *info, struct plugin_gcc_version *version) { if (!plugin_default_version_check(version, &gcc_version)) FATAL(G_("GCC and plugin have incompatible versions, expected GCC %s, " "is %s"), gcc_version.basever, version->basever); /* Show a banner. */ bool quiet = false; if (isatty(2) && !getenv("AFL_QUIET")) SAYF(cCYA "afl-gcc-cmplog-pass " cBRI VERSION cRST " by \n"); else quiet = true; const char *name = info->base_name; register_callback(name, PLUGIN_INFO, NULL, &afl_cmplog_plugin); afl_cmplog_pass *aflp = new afl_cmplog_pass(quiet); struct register_pass_info pass_info = { .pass = aflp, .reference_pass_name = "ssa", .ref_pass_instance_number = 1, .pos_op = PASS_POS_INSERT_AFTER, }; register_callback(name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); return 0; }