[libFuzzer] Port to Windows

Summary:
Port libFuzzer to windows-msvc.
This patch allows libFuzzer targets to be built and run on Windows, using -fsanitize=fuzzer and/or fsanitize=fuzzer-no-link. It allows these forms of coverage instrumentation to work on Windows as well.
It does not fix all issues, such as those with -fsanitize-coverage=stack-depth, which is not usable on Windows as of this patch.
It also does not fix any libFuzzer integration tests. Nearly all of them fail to compile, fixing them will come in a later patch, so libFuzzer tests are disabled on Windows until them.

Patch By: metzman

Reviewers: morehouse, rnk

Reviewed By: morehouse, rnk

Subscribers: #sanitizers, delcypher, morehouse, kcc, eraman

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

llvm-svn: 341082
This commit is contained in:
Matt Morehouse 2018-08-30 15:54:44 +00:00
parent a733d08db2
commit 7e042bb1d1
15 changed files with 161 additions and 26 deletions

View File

@ -365,6 +365,17 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back(Args.MakeArgString(std::string("-implib:") + ImplibName));
}
if (TC.getSanitizerArgs().needsFuzzer()) {
if (!Args.hasArg(options::OPT_shared))
CmdArgs.push_back(
Args.MakeArgString(std::string("-wholearchive:") +
TC.getCompilerRTArgString(Args, "fuzzer", false)));
CmdArgs.push_back(Args.MakeArgString("-debug"));
// Prevent the linker from padding sections we use for instrumentation
// arrays.
CmdArgs.push_back(Args.MakeArgString("-incremental:no"));
}
if (TC.getSanitizerArgs().needsAsanRt()) {
CmdArgs.push_back(Args.MakeArgString("-debug"));
CmdArgs.push_back(Args.MakeArgString("-incremental:no"));
@ -1298,6 +1309,8 @@ MSVCToolChain::ComputeEffectiveClangTriple(const ArgList &Args,
SanitizerMask MSVCToolChain::getSupportedSanitizers() const {
SanitizerMask Res = ToolChain::getSupportedSanitizers();
Res |= SanitizerKind::Address;
Res |= SanitizerKind::Fuzzer;
Res |= SanitizerKind::FuzzerNoLink;
Res &= ~SanitizerKind::CFIMFCall;
return Res;
}

View File

@ -619,7 +619,10 @@ else()
endif()
if (COMPILER_RT_HAS_SANITIZER_COMMON AND FUZZER_SUPPORTED_ARCH AND
OS_NAME MATCHES "Android|Darwin|Linux|NetBSD|FreeBSD|OpenBSD|Fuchsia")
OS_NAME MATCHES "Android|Darwin|Linux|NetBSD|FreeBSD|OpenBSD|Fuchsia|Windows" AND
# TODO: Support builds with MSVC.
NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" AND
NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
set(COMPILER_RT_HAS_FUZZER TRUE)
else()
set(COMPILER_RT_HAS_FUZZER FALSE)

View File

@ -129,8 +129,15 @@
#if LIBFUZZER_WINDOWS
#define ATTRIBUTE_INTERFACE __declspec(dllexport)
// This is used for __sancov_lowest_stack which is needed for
// -fsanitize-coverage=stack-depth. That feature is not yet available on
// Windows, so make the symbol static to avoid linking errors.
#define ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC \
__attribute__((tls_model("initial-exec"))) thread_local static
#else
#define ATTRIBUTE_INTERFACE __attribute__((visibility("default")))
#define ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC \
ATTRIBUTE_INTERFACE __attribute__((tls_model("initial-exec"))) thread_local
#endif
namespace fuzzer {

View File

@ -100,14 +100,6 @@ std::string DirPlusFile(const std::string &DirPath,
return DirPath + GetSeparator() + FileName;
}
std::string Basename(const std::string &Path, char Separator) {
size_t Pos = Path.rfind(Separator);
if (Pos == std::string::npos)
return Path;
assert(Pos < Path.size());
return Path.substr(Pos + 1);
}
void DupAndCloseStderr() {
int OutputFd = DuplicateFile(2);
if (OutputFd > 0) {

View File

@ -68,7 +68,7 @@ void GetSizedFilesFromDir(const std::string &Dir, Vector<SizedFile> *V);
char GetSeparator();
// Similar to the basename utility: returns the file name w/o the dir prefix.
std::string Basename(const std::string &Path, char Separator = GetSeparator());
std::string Basename(const std::string &Path);
FILE* OpenFile(int Fd, const char *Mode);

View File

@ -46,6 +46,13 @@ size_t FileSize(const std::string &Path) {
return St.st_size;
}
std::string Basename(const std::string &Path) {
size_t Pos = Path.rfind(GetSeparator());
if (Pos == std::string::npos) return Path;
assert(Pos < Path.size());
return Path.substr(Pos + 1);
}
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
Vector<std::string> *V, bool TopDir) {
auto E = GetEpoch(Dir);

View File

@ -72,6 +72,26 @@ bool IsFile(const std::string &Path) {
return IsFile(Path, Att);
}
std::string Basename(const std::string &Path) {
size_t Pos = Path.find_last_of("/\\");
if (Pos == std::string::npos) return Path;
assert(Pos < Path.size());
return Path.substr(Pos + 1);
}
size_t FileSize(const std::string &Path) {
WIN32_FILE_ATTRIBUTE_DATA attr;
if (!GetFileAttributesExA(Path.c_str(), GetFileExInfoStandard, &attr)) {
Printf("GetFileAttributesExA() failed for \"%s\" (Error code: %lu).\n",
Path.c_str(), GetLastError());
return 0;
}
ULARGE_INTEGER size;
size.HighPart = attr.nFileSizeHigh;
size.LowPart = attr.nFileSizeLow;
return size.QuadPart;
}
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
Vector<std::string> *V, bool TopDir) {
auto E = GetEpoch(Dir);

View File

@ -32,8 +32,7 @@ ATTRIBUTE_INTERFACE
uintptr_t __sancov_trace_pc_pcs[fuzzer::TracePC::kNumPCs];
// Used by -fsanitize-coverage=stack-depth to track stack depth
ATTRIBUTE_INTERFACE __attribute__((tls_model("initial-exec")))
thread_local uintptr_t __sancov_lowest_stack;
ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC uintptr_t __sancov_lowest_stack;
namespace fuzzer {

View File

@ -179,7 +179,9 @@ const void *SearchMemory(const void *Data, size_t DataLen, const void *Patt,
}
std::string DisassembleCmd(const std::string &FileName) {
if (ExecuteCommand("dumpbin /summary > nul") == 0)
Vector<std::string> command_vector;
command_vector.push_back("dumpbin /summary > nul");
if (ExecuteCommand(Command(command_vector)) == 0)
return "dumpbin /disasm " + FileName;
Printf("libFuzzer: couldn't find tool to disassemble (dumpbin)\n");
exit(1);

View File

@ -17,6 +17,8 @@ list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS --driver-mode=g++)
if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -lc++ -lpthread)
elseif(WIN32)
list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -lstdc++)
else()
list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -lstdc++ -lpthread)
endif()

View File

@ -34,6 +34,13 @@ TEST(Fuzzer, Basename) {
EXPECT_EQ(Basename("/bar"), "bar");
EXPECT_EQ(Basename("foo/x"), "x");
EXPECT_EQ(Basename("foo/"), "");
#if LIBFUZZER_WINDOWS
EXPECT_EQ(Basename("foo\\bar"), "bar");
EXPECT_EQ(Basename("foo\\bar/baz"), "baz");
EXPECT_EQ(Basename("\\bar"), "bar");
EXPECT_EQ(Basename("foo\\x"), "x");
EXPECT_EQ(Basename("foo\\"), "");
#endif
}
TEST(Fuzzer, CrossOver) {

View File

@ -7,16 +7,57 @@
//
//===----------------------------------------------------------------------===//
//
// This file defines delimiters for Sanitizer Coverage's section.
// This file defines delimiters for Sanitizer Coverage's section. It contains
// Windows specific tricks to coax the linker into giving us the start and stop
// addresses of a section, as ELF linkers can do, to get the size of certain
// arrays. According to https://msdn.microsoft.com/en-us/library/7977wcck.aspx
// sections with the same name before "$" are sorted alphabetically by the
// string that comes after "$" and merged into one section. We take advantage
// of this by putting data we want the size of into the middle (M) of a section,
// by using the letter "M" after "$". We get the start of this data (ie:
// __start_section_name) by making the start variable come at the start of the
// section (using the letter A after "$"). We do the same to get the end of the
// data by using the letter "Z" after "$" to make the end variable come after
// the data. Note that because of our technique the address of the start
// variable is actually the address of data that comes before our middle
// section. We also need to prevent the linker from adding any padding. Each
// technique we use for this is explained in the comments below.
//===----------------------------------------------------------------------===//
#include "sanitizer_platform.h"
#if SANITIZER_WINDOWS
#include <stdint.h>
#pragma section(".SCOV$A", read, write) // NOLINT
#pragma section(".SCOV$Z", read, write) // NOLINT
extern "C" {
__declspec(allocate(".SCOV$A")) uint32_t __start___sancov_guards = 0;
__declspec(allocate(".SCOV$Z")) uint32_t __stop___sancov_guards = 0;
// The Guard array and counter array should both be merged into the .data
// section to reduce the number of PE sections However, because PCTable is
// constant it should be merged with the .rdata section.
#pragma section(".SCOV$GA", read, write) // NOLINT
// Use align(1) to avoid adding any padding that will mess up clients trying to
// determine the start and end of the array.
__declspec(allocate(".SCOV$GA")) __declspec(align(1)) uint64_t
__start___sancov_guards = 0;
#pragma section(".SCOV$GZ", read, write) // NOLINT
__declspec(allocate(".SCOV$GZ")) __declspec(align(1)) uint64_t
__stop___sancov_guards = 0;
#pragma section(".SCOV$CA", read, write) // NOLINT
__declspec(allocate(".SCOV$CA")) __declspec(align(1)) uint64_t
__start___sancov_cntrs = 0;
#pragma section(".SCOV$CZ", read, write) // NOLINT
__declspec(allocate(".SCOV$CZ")) __declspec(align(1)) uint64_t
__stop___sancov_cntrs = 0;
#pragma comment(linker, "/MERGE:.SCOV=.data")
// Use uint64_t so there won't be any issues if the linker tries to word align
// the pc array.
#pragma section(".SCOVP$A", read) // NOLINT
__declspec(allocate(".SCOVP$A")) __declspec(align(1)) uint64_t
__start___sancov_pcs = 0;
#pragma section(".SCOVP$Z", read) // NOLINT
__declspec(allocate(".SCOVP$Z")) __declspec(align(1)) uint64_t
__stop___sancov_pcs = 0;
#pragma comment(linker, "/MERGE:.SCOVP=.rdata")
}
#endif // SANITIZER_WINDOWS
#endif // SANITIZER_WINDOWS

View File

@ -62,7 +62,10 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
compiler_rt_test_runtime(sanitizer_common)
# OpenBSD not supporting asan, cannot run the tests
if(COMPILER_RT_BUILD_LIBFUZZER AND NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD" AND NOT ANDROID)
# Tests are broken for now on Windows
if(COMPILER_RT_BUILD_LIBFUZZER
AND NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD"
AND NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" AND NOT ANDROID)
compiler_rt_test_runtime(fuzzer)
endif()

View File

@ -273,9 +273,20 @@ Function *SanitizerCoverageModule::CreateInitCallsForSections(
auto SecStart = SecStartEnd.first;
auto SecEnd = SecStartEnd.second;
Function *CtorFunc;
Value *SecStartPtr = nullptr;
// Account for the fact that on windows-msvc __start_* symbols actually
// point to a uint64_t before the start of the array.
if (TargetTriple.getObjectFormat() == Triple::COFF) {
auto SecStartI8Ptr = IRB.CreatePointerCast(SecStart, Int8PtrTy);
auto GEP = IRB.CreateGEP(SecStartI8Ptr,
ConstantInt::get(IntptrTy, sizeof(uint64_t)));
SecStartPtr = IRB.CreatePointerCast(GEP, Ty);
} else {
SecStartPtr = IRB.CreatePointerCast(SecStart, Ty);
}
std::tie(CtorFunc, std::ignore) = createSanitizerCtorAndInitFunctions(
M, SanCovModuleCtorName, InitFunctionName, {Ty, Ty},
{IRB.CreatePointerCast(SecStart, Ty), IRB.CreatePointerCast(SecEnd, Ty)});
{SecStartPtr, IRB.CreatePointerCast(SecEnd, Ty)});
if (TargetTriple.supportsCOMDAT()) {
// Use comdat to dedup CtorFunc.
@ -397,9 +408,20 @@ bool SanitizerCoverageModule::runOnModule(Module &M) {
Function *InitFunction = declareSanitizerInitFunction(
M, SanCovPCsInitName, {IntptrPtrTy, IntptrPtrTy});
IRBuilder<> IRBCtor(Ctor->getEntryBlock().getTerminator());
IRBCtor.CreateCall(InitFunction,
{IRB.CreatePointerCast(SecStartEnd.first, IntptrPtrTy),
IRB.CreatePointerCast(SecStartEnd.second, IntptrPtrTy)});
Value *SecStartPtr = nullptr;
// Account for the fact that on windows-msvc __start_pc_table actually
// points to a uint64_t before the start of the PC table.
if (TargetTriple.getObjectFormat() == Triple::COFF) {
auto SecStartI8Ptr = IRB.CreatePointerCast(SecStartEnd.first, Int8PtrTy);
auto GEP = IRB.CreateGEP(SecStartI8Ptr,
ConstantInt::get(IntptrTy, sizeof(uint64_t)));
SecStartPtr = IRB.CreatePointerCast(GEP, IntptrPtrTy);
} else {
SecStartPtr = IRB.CreatePointerCast(SecStartEnd.first, IntptrPtrTy);
}
IRBCtor.CreateCall(
InitFunction,
{SecStartPtr, IRB.CreatePointerCast(SecStartEnd.second, IntptrPtrTy)});
}
// We don't reference these arrays directly in any of our runtime functions,
// so we need to prevent them from being dead stripped.
@ -809,8 +831,13 @@ void SanitizerCoverageModule::InjectCoverageAtBlock(Function &F, BasicBlock &BB,
std::string
SanitizerCoverageModule::getSectionName(const std::string &Section) const {
if (TargetTriple.getObjectFormat() == Triple::COFF)
return ".SCOV$M";
if (TargetTriple.getObjectFormat() == Triple::COFF) {
if (Section == SanCovCountersSectionName)
return ".SCOV$CM";
if (Section == SanCovPCsSectionName)
return ".SCOVP$M";
return ".SCOV$GM"; // For SanCovGuardsSectionName.
}
if (TargetTriple.isOSBinFormatMachO())
return "__DATA,__" + Section;
return "__" + Section;

View File

@ -0,0 +1,12 @@
; Checks that the PC and 8-bit Counter Arrays are placed in their own sections in COFF binaries.
; RUN: opt < %s -sancov -sanitizer-coverage-level=1 -sanitizer-coverage-inline-8bit-counters=1 -sanitizer-coverage-pc-table=1 -S | FileCheck %s
target datalayout = "e-m:w-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc19.14.26433"
define void @foo() {
entry:
ret void
}
; CHECK-DAG: section ".SCOV{{\$}}CM",
; CHECK-DAG: section ".SCOVP{{\$}}M",