[llvm-opt-fuzzer] Introduce llvm-opt-fuzzer for fuzzing optimization passes

This change adds generic fuzzing tools capable of running libFuzzer tests on
any optimization pass or combination of them.

Differential Revision: https://reviews.llvm.org/D39555

llvm-svn: 317883
This commit is contained in:
Igor Laevsky 2017-11-10 12:19:08 +00:00
parent 4e493f2316
commit 7782f60123
6 changed files with 364 additions and 0 deletions

View File

@ -100,6 +100,28 @@ mode, the same example could be run like so:
% bin/llvm-isel-fuzzer--aarch64-O0-gisel <corpus-dir>
llvm-opt-fuzzer
---------------
A |LLVM IR fuzzer| aimed at finding bugs in optimization passes.
It receives optimzation pipeline and runs it for each fuzzer input.
Interface of this fuzzer almost directly mirrors ``llvm-isel-fuzzer``. Both
``mtriple`` and ``passes`` arguments are required. Passes are specified in a
format suitable for the new pass manager.
.. code-block:: shell
% bin/llvm-opt-fuzzer <corpus-dir> -ignore_remaining_args=1 -mtriple x86_64 -passes instcombine
Similarly to the ``llvm-isel-fuzzer`` arguments in some predefined configurations
might be embedded directly into the binary file name:
.. code-block:: shell
% bin/llvm-opt-fuzzer--x86_64-instcombine <corpus-dir>
llvm-mc-assemble-fuzzer
-----------------------

View File

@ -36,6 +36,10 @@ void parseFuzzerCLOpts(int ArgC, char *ArgV[]);
/// of passing in command line arguments in the normal way.
void handleExecNameEncodedBEOpts(StringRef ExecName);
/// Handle optimizer options which are encoded in the executable name.
/// Same semantics as in 'handleExecNameEncodedBEOpts'.
void handleExecNameEncodedOptimizerOpts(StringRef ExecName);
using FuzzerTestFun = int (*)(const uint8_t *Data, size_t Size);
using FuzzerInitFun = int (*)(int *argc, char ***argv);

View File

@ -67,6 +67,40 @@ void llvm::handleExecNameEncodedBEOpts(StringRef ExecName) {
cl::ParseCommandLineOptions(CLArgs.size(), CLArgs.data());
}
void llvm::handleExecNameEncodedOptimizerOpts(StringRef ExecName) {
// TODO: Refactor parts common with the 'handleExecNameEncodedBEOpts'
std::vector<std::string> Args{ExecName};
auto NameAndArgs = ExecName.split("--");
if (NameAndArgs.second.empty())
return;
SmallVector<StringRef, 4> Opts;
NameAndArgs.second.split(Opts, '-');
for (StringRef Opt : Opts) {
if (Opt.startswith("instcombine")) {
Args.push_back("-passes=instcombine");
} else if (Triple(Opt).getArch()) {
Args.push_back("-mtriple=" + Opt.str());
} else {
errs() << ExecName << ": Unknown option: " << Opt << ".\n";
exit(1);
}
}
errs() << NameAndArgs.first << ": Injected args:";
for (int I = 1, E = Args.size(); I < E; ++I)
errs() << " " << Args[I];
errs() << "\n";
std::vector<const char *> CLArgs;
CLArgs.reserve(Args.size());
for (std::string &S : Args)
CLArgs.push_back(S.c_str());
cl::ParseCommandLineOptions(CLArgs.size(), CLArgs.data());
}
int llvm::runFuzzerOnInputs(int ArgC, char *ArgV[], FuzzerTestFun TestOne,
FuzzerInitFun Init) {
errs() << "*** This tool was not linked to libFuzzer.\n"

View File

@ -0,0 +1,24 @@
set(LLVM_LINK_COMPONENTS
${LLVM_TARGETS_TO_BUILD}
Analysis
BitWriter
CodeGen
Core
Coroutines
IPO
IRReader
InstCombine
Instrumentation
FuzzMutate
MC
ObjCARCOpts
ScalarOpts
Support
Target
TransformUtils
Vectorize
Passes
)
add_llvm_fuzzer(llvm-opt-fuzzer llvm-opt-fuzzer.cpp
DUMMY_MAIN DummyOptFuzzer.cpp)

View File

@ -0,0 +1,21 @@
//===--- DummyOptFuzzer.cpp - Entry point to sanity check the fuzzer ------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Implementation of main so we can build and test without linking libFuzzer.
//
//===----------------------------------------------------------------------===//
#include "llvm/FuzzMutate/FuzzerCLI.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv);
int main(int argc, char *argv[]) {
return llvm::runFuzzerOnInputs(argc, argv, LLVMFuzzerTestOneInput,
LLVMFuzzerInitialize);
}

View File

@ -0,0 +1,259 @@
//===--- llvm-opt-fuzzer.cpp - Fuzzer for instruction selection ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Tool to fuzz optimization passes using libFuzzer.
//
//===----------------------------------------------------------------------===//
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/CodeGen/CommandFlags.h"
#include "llvm/FuzzMutate/FuzzerCLI.h"
#include "llvm/FuzzMutate/IRMutator.h"
#include "llvm/FuzzMutate/Operations.h"
#include "llvm/FuzzMutate/Random.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/Support/TargetSelect.h"
using namespace llvm;
static cl::opt<std::string>
TargetTripleStr("mtriple", cl::desc("Override target triple for module"));
// Passes to run for this fuzzer instance. Expects new pass manager syntax.
static cl::opt<std::string> PassPipeline(
"passes",
cl::desc("A textual description of the pass pipeline for testing"));
static std::unique_ptr<IRMutator> Mutator;
static std::unique_ptr<TargetMachine> TM;
// This function is mostly copied from the llvm-isel-fuzzer.
// TODO: Move this into FuzzMutate library and reuse.
static std::unique_ptr<Module> parseModule(const uint8_t *Data, size_t Size,
LLVMContext &Context) {
if (Size <= 1)
// We get bogus data given an empty corpus - just create a new module.
return llvm::make_unique<Module>("M", Context);
auto Buffer = MemoryBuffer::getMemBuffer(
StringRef(reinterpret_cast<const char *>(Data), Size), "Fuzzer input",
/*RequiresNullTerminator=*/false);
SMDiagnostic Err;
auto M = parseBitcodeFile(Buffer->getMemBufferRef(), Context);
if (Error E = M.takeError()) {
errs() << toString(std::move(E)) << "\n";
return nullptr;
}
return std::move(M.get());
}
// This function is copied from the llvm-isel-fuzzer.
// TODO: Move this into FuzzMutate library and reuse.
static size_t writeModule(const Module &M, uint8_t *Dest, size_t MaxSize) {
std::string Buf;
{
raw_string_ostream OS(Buf);
WriteBitcodeToFile(&M, OS);
}
if (Buf.size() > MaxSize)
return 0;
memcpy(Dest, Buf.data(), Buf.size());
return Buf.size();
}
std::unique_ptr<IRMutator> createOptMutator() {
std::vector<TypeGetter> Types{
Type::getInt1Ty, Type::getInt8Ty, Type::getInt16Ty, Type::getInt32Ty,
Type::getInt64Ty, Type::getFloatTy, Type::getDoubleTy};
std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
Strategies.push_back(
llvm::make_unique<InjectorIRStrategy>(
InjectorIRStrategy::getDefaultOps()));
Strategies.push_back(
llvm::make_unique<InstDeleterIRStrategy>());
return llvm::make_unique<IRMutator>(std::move(Types), std::move(Strategies));
}
extern "C" LLVM_ATTRIBUTE_USED size_t LLVMFuzzerCustomMutator(
uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed) {
assert(Mutator &&
"IR mutator should have been created during fuzzer initialization");
LLVMContext Context;
auto M = parseModule(Data, Size, Context);
if (!M || verifyModule(*M, &errs())) {
errs() << "error: mutator input module is broken!\n";
return 0;
}
Mutator->mutateModule(*M, Seed, Size, MaxSize);
#ifndef NDEBUG
if (verifyModule(*M, &errs())) {
errs() << "mutation result doesn't pass verification\n";
M->dump();
abort();
}
#endif
return writeModule(*M, Data, MaxSize);
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
assert(TM && "Should have been created during fuzzer initialization");
if (Size <= 1)
// We get bogus data given an empty corpus - ignore it.
return 0;
// Parse module
//
LLVMContext Context;
auto M = parseModule(Data, Size, Context);
if (!M || verifyModule(*M, &errs())) {
errs() << "error: input module is broken!\n";
return 0;
}
// Set up target dependant options
//
M->setTargetTriple(TM->getTargetTriple().normalize());
M->setDataLayout(TM->createDataLayout());
setFunctionAttributes(TM->getTargetCPU(), TM->getTargetFeatureString(), *M);
// Create pass pipeline
//
PassBuilder PB(TM.get());
LoopAnalysisManager LAM;
FunctionAnalysisManager FAM;
CGSCCAnalysisManager CGAM;
ModulePassManager MPM;
ModuleAnalysisManager MAM;
FAM.registerPass([&] { return PB.buildDefaultAAPipeline(); });
PB.registerModuleAnalyses(MAM);
PB.registerCGSCCAnalyses(CGAM);
PB.registerFunctionAnalyses(FAM);
PB.registerLoopAnalyses(LAM);
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
bool Ok = PB.parsePassPipeline(MPM, PassPipeline, false, false);
assert(Ok && "Should have been checked during fuzzer initialization");
// Run passes which we need to test
//
MPM.run(*M, MAM);
// Check that passes resulted in a correct code
if (verifyModule(*M, &errs())) {
errs() << "Transformation resulted in an invalid module\n";
abort();
}
return 0;
}
static void handleLLVMFatalError(void *, const std::string &Message, bool) {
// TODO: Would it be better to call into the fuzzer internals directly?
dbgs() << "LLVM ERROR: " << Message << "\n"
<< "Aborting to trigger fuzzer exit handling.\n";
abort();
}
extern "C" LLVM_ATTRIBUTE_USED int LLVMFuzzerInitialize(
int *argc, char ***argv) {
EnableDebugBuffering = true;
// Make sure we print the summary and the current unit when LLVM errors out.
install_fatal_error_handler(handleLLVMFatalError, nullptr);
// Initialize llvm
//
InitializeAllTargets();
InitializeAllTargetMCs();
PassRegistry &Registry = *PassRegistry::getPassRegistry();
initializeCore(Registry);
initializeCoroutines(Registry);
initializeScalarOpts(Registry);
initializeObjCARCOpts(Registry);
initializeVectorization(Registry);
initializeIPO(Registry);
initializeAnalysis(Registry);
initializeTransformUtils(Registry);
initializeInstCombine(Registry);
initializeInstrumentation(Registry);
initializeTarget(Registry);
// Parse input options
//
handleExecNameEncodedOptimizerOpts(*argv[0]);
parseFuzzerCLOpts(*argc, *argv);
// Create TargetMachine
//
if (TargetTripleStr.empty()) {
errs() << *argv[0] << ": -mtriple must be specified\n";
exit(1);
}
Triple TargetTriple = Triple(Triple::normalize(TargetTripleStr));
std::string Error;
const Target *TheTarget =
TargetRegistry::lookupTarget(MArch, TargetTriple, Error);
if (!TheTarget) {
errs() << *argv[0] << ": " << Error;
exit(1);
}
TargetOptions Options = InitTargetOptionsFromCodeGenFlags();
TM.reset(TheTarget->createTargetMachine(
TargetTriple.getTriple(), getCPUStr(), getFeaturesStr(),
Options, getRelocModel(), getCodeModel(), CodeGenOpt::Default));
assert(TM && "Could not allocate target machine!");
// Check that pass pipeline is specified and correct
//
if (PassPipeline.empty()) {
errs() << *argv[0] << ": at least one pass should be specified\n";
exit(1);
}
PassBuilder PB(TM.get());
ModulePassManager MPM;
if (!PB.parsePassPipeline(MPM, PassPipeline, false, false)) {
errs() << *argv[0] << ": can't parse pass pipeline\n";
exit(1);
}
// Create mutator
//
Mutator = createOptMutator();
return 0;
}