[scudo][standalone] Add flags & related parsers

Summary:
As with other Sanitizers, and the current version of Scudo, we can
provide flags in differents way: at compile time, through a weak
function, through an environment variable.

This change adds support for the configuration flags, and the string
parsers. Those are fairly similar to the sanitizer_common way of doing
things.

Reviewers: morehouse, hctim, vitalybuka

Reviewed By: morehouse, vitalybuka

Subscribers: mgorny, delcypher, jdoerfert, #sanitizers, llvm-commits

Tags: #llvm, #sanitizers

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

llvm-svn: 358011
This commit is contained in:
Kostya Kortchinsky 2019-04-09 14:57:25 +00:00
parent d5173f5acf
commit 7e2b15382c
9 changed files with 510 additions and 0 deletions

View File

@ -37,6 +37,8 @@ set(SCUDO_SOURCES
checksum.cc
crc32_hw.cc
common.cc
flags.cc
flags_parser.cc
fuchsia.cc
linux.cc
report.cc
@ -57,6 +59,9 @@ set(SCUDO_HEADERS
atomic_helpers.h
bytemap.h
checksum.h
flags.h
flags_parser.h
interface.h
internal_defs.h
linux.h
list.h

View File

@ -0,0 +1,57 @@
//===-- flags.cc ------------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "flags.h"
#include "common.h"
#include "flags_parser.h"
#include "interface.h"
namespace scudo {
Flags *getFlags() {
static Flags F;
return &F;
}
void Flags::setDefaults() {
#define SCUDO_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue;
#include "flags.inc"
#undef SCUDO_FLAG
}
void registerFlags(FlagParser *Parser, Flags *F) {
#define SCUDO_FLAG(Type, Name, DefaultValue, Description) \
Parser->registerFlag(#Name, Description, FlagType::FT_##Type, \
reinterpret_cast<void *>(&F->Name));
#include "flags.inc"
#undef SCUDO_FLAG
}
static const char *getCompileDefinitionScudoDefaultOptions() {
#ifdef SCUDO_DEFAULT_OPTIONS
return STRINGIFY(SCUDO_DEFAULT_OPTIONS);
#else
return "";
#endif
}
static const char *getScudoDefaultOptions() {
return (&__scudo_default_options) ? __scudo_default_options() : "";
}
void initFlags() {
Flags *F = getFlags();
F->setDefaults();
FlagParser Parser;
registerFlags(&Parser, F);
Parser.parseString(getCompileDefinitionScudoDefaultOptions());
Parser.parseString(getScudoDefaultOptions());
Parser.parseString(getEnv("SCUDO_OPTIONS"));
}
} // namespace scudo

View File

@ -0,0 +1,30 @@
//===-- flags.h -------------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_FLAGS_H_
#define SCUDO_FLAGS_H_
#include "internal_defs.h"
namespace scudo {
struct Flags {
#define SCUDO_FLAG(Type, Name, DefaultValue, Description) Type Name;
#include "flags.inc"
#undef SCUDO_FLAG
void setDefaults();
};
Flags *getFlags();
void initFlags();
class FlagParser;
void registerFlags(FlagParser *Parser, Flags *F);
} // namespace scudo
#endif // SCUDO_FLAGS_H_

View File

@ -0,0 +1,50 @@
//===-- flags.inc -----------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_FLAG
#error "Define SCUDO_FLAG prior to including this file!"
#endif
SCUDO_FLAG(int, quarantine_size_kb, 0,
"Size (in kilobytes) of quarantine used to delay the actual "
"deallocation of chunks. Lower value may reduce memory usage but "
"decrease the effectiveness of the mitigation.")
SCUDO_FLAG(int, thread_local_quarantine_size_kb, 0,
"Size (in kilobytes) of per-thread cache used to offload the global "
"quarantine. Lower value may reduce memory usage but might increase "
"the contention on the global quarantine.")
SCUDO_FLAG(int, quarantine_max_chunk_size, 0,
"Size (in bytes) up to which chunks will be quarantined (if lower "
"than or equal to).")
SCUDO_FLAG(bool, dealloc_type_mismatch, false,
"Terminate on a type mismatch in allocation-deallocation functions, "
"eg: malloc/delete, new/free, new/delete[], etc.")
SCUDO_FLAG(bool, delete_size_mismatch, true,
"Terminate on a size mismatch between a sized-delete and the actual "
"size of a chunk (as provided to new/new[]).")
SCUDO_FLAG(bool, zero_contents, false, "Zero chunk contents on allocation.")
SCUDO_FLAG(int, rss_limit_mb, -1,
"Enforce an upper limit (in megabytes) to the process RSS. The "
"allocator will terminate or return NULL when allocations are "
"attempted past that limit (depending on may_return_null). Negative "
"values disable the feature.")
SCUDO_FLAG(bool, may_return_null, true,
"Indicate whether the allocator should terminate instead of "
"returning NULL in otherwise non-fatal error scenarios, eg: OOM, "
"invalid allocation alignments, etc.")
SCUDO_FLAG(int, release_to_os_interval_ms, 5000,
"Interval (in milliseconds) at which to attempt release of unused "
"memory to the OS. Negative values disable the feature.")

View File

@ -0,0 +1,163 @@
//===-- flags_parser.cc -----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "flags_parser.h"
#include "common.h"
#include "report.h"
#include <string.h>
namespace scudo {
class UnknownFlagsRegistry {
static const u32 MaxUnknownFlags = 16;
const char *UnknownFlagsNames[MaxUnknownFlags];
u32 NumberOfUnknownFlags;
public:
void add(const char *Name) {
CHECK_LT(NumberOfUnknownFlags, MaxUnknownFlags);
UnknownFlagsNames[NumberOfUnknownFlags++] = Name;
}
void report() {
if (!NumberOfUnknownFlags)
return;
Printf("Scudo WARNING: found %d unrecognized flag(s):\n",
NumberOfUnknownFlags);
for (u32 I = 0; I < NumberOfUnknownFlags; ++I)
Printf(" %s\n", UnknownFlagsNames[I]);
NumberOfUnknownFlags = 0;
}
};
static UnknownFlagsRegistry UnknownFlags;
void reportUnrecognizedFlags() { UnknownFlags.report(); }
void FlagParser::printFlagDescriptions() {
Printf("Available flags for Scudo:\n");
for (u32 I = 0; I < NumberOfFlags; ++I)
Printf("\t%s\n\t\t- %s\n", Flags[I].Name, Flags[I].Desc);
}
static bool isSeparator(char C) {
return C == ' ' || C == ',' || C == ':' || C == '\n' || C == '\t' ||
C == '\r';
}
static bool isSeparatorOrNull(char C) { return !C || isSeparator(C); }
void FlagParser::skipWhitespace() {
while (isSeparator(Buffer[Pos]))
++Pos;
}
void FlagParser::parseFlag() {
const uptr NameStart = Pos;
while (Buffer[Pos] != '=' && !isSeparatorOrNull(Buffer[Pos]))
++Pos;
if (Buffer[Pos] != '=')
reportError("expected '='");
const char *Name = Buffer + NameStart;
const uptr ValueStart = ++Pos;
const char *Value;
if (Buffer[Pos] == '\'' || Buffer[Pos] == '"') {
const char Quote = Buffer[Pos++];
while (Buffer[Pos] != 0 && Buffer[Pos] != Quote)
++Pos;
if (Buffer[Pos] == 0)
reportError("unterminated string");
Value = Buffer + ValueStart + 1;
++Pos; // consume the closing quote
} else {
while (!isSeparatorOrNull(Buffer[Pos]))
++Pos;
Value = Buffer + ValueStart;
}
if (!runHandler(Name, Value))
reportError("flag parsing failed.");
}
void FlagParser::parseFlags() {
while (true) {
skipWhitespace();
if (Buffer[Pos] == 0)
break;
parseFlag();
}
}
void FlagParser::parseString(const char *S) {
if (!S)
return;
// Backup current parser state to allow nested parseString() calls.
const char *OldBuffer = Buffer;
const uptr OldPos = Pos;
Buffer = S;
Pos = 0;
parseFlags();
Buffer = OldBuffer;
Pos = OldPos;
}
INLINE bool parseBool(const char *Value, bool *b) {
if (strncmp(Value, "0", 1) == 0 || strncmp(Value, "no", 2) == 0 ||
strncmp(Value, "false", 5) == 0) {
*b = false;
return true;
}
if (strncmp(Value, "1", 1) == 0 || strncmp(Value, "yes", 3) == 0 ||
strncmp(Value, "true", 4) == 0) {
*b = true;
return true;
}
return false;
}
bool FlagParser::runHandler(const char *Name, const char *Value) {
for (u32 I = 0; I < NumberOfFlags; ++I) {
const uptr Len = strlen(Flags[I].Name);
if (strncmp(Name, Flags[I].Name, Len) != 0 || Name[Len] != '=')
continue;
bool Ok = false;
switch (Flags[I].Type) {
case FlagType::FT_bool:
Ok = parseBool(Value, reinterpret_cast<bool *>(Flags[I].Var));
if (!Ok)
reportInvalidFlag("bool", Value);
break;
case FlagType::FT_int:
char *ValueEnd;
*reinterpret_cast<int *>(Flags[I].Var) =
static_cast<int>(strtol(Value, &ValueEnd, 10));
Ok =
*ValueEnd == '"' || *ValueEnd == '\'' || isSeparatorOrNull(*ValueEnd);
if (!Ok)
reportInvalidFlag("int", Value);
break;
}
return Ok;
}
// Unrecognized flag. This is not a fatal error, we may print a warning later.
UnknownFlags.add(Name);
return true;
}
void FlagParser::registerFlag(const char *Name, const char *Desc, FlagType Type,
void *Var) {
CHECK_LT(NumberOfFlags, MaxFlags);
Flags[NumberOfFlags].Name = Name;
Flags[NumberOfFlags].Desc = Desc;
Flags[NumberOfFlags].Type = Type;
Flags[NumberOfFlags].Var = Var;
++NumberOfFlags;
}
} // namespace scudo

View File

@ -0,0 +1,56 @@
//===-- flags_parser.h ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_FLAGS_PARSER_H_
#define SCUDO_FLAGS_PARSER_H_
#include "report.h"
#include "string_utils.h"
#include <stddef.h>
#include <stdlib.h>
namespace scudo {
enum class FlagType : u8 {
FT_bool,
FT_int,
};
class FlagParser {
public:
void registerFlag(const char *Name, const char *Desc, FlagType Type,
void *Var);
void parseString(const char *S);
void printFlagDescriptions();
private:
static const u32 MaxFlags = 12;
struct Flag {
const char *Name;
const char *Desc;
FlagType Type;
void *Var;
} Flags[MaxFlags];
u32 NumberOfFlags = 0;
const char *Buffer = nullptr;
uptr Pos = 0;
void reportFatalError(const char *Error);
void skipWhitespace();
void parseFlags();
void parseFlag();
bool runHandler(const char *Name, const char *Value);
};
void reportUnrecognizedFlags();
} // namespace scudo
#endif // SCUDO_FLAGS_PARSER_H_

View File

@ -0,0 +1,29 @@
//===-- interface.h ---------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_INTERFACE_H_
#define SCUDO_INTERFACE_H_
#include "internal_defs.h"
extern "C" {
WEAK INTERFACE const char *__scudo_default_options();
// Post-allocation & pre-deallocation hooks.
// They must be thread-safe and not use heap related functions.
WEAK INTERFACE void __scudo_allocate_hook(void *ptr, size_t size);
WEAK INTERFACE void __scudo_deallocate_hook(void *ptr);
WEAK INTERFACE void __scudo_print_stats(void);
typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg);
} // extern "C"
#endif // SCUDO_INTERFACE_H_

View File

@ -52,6 +52,7 @@ set(SCUDO_UNIT_TEST_SOURCES
atomic_test.cc
bytemap_test.cc
checksum_test.cc
flags_test.cc
list_test.cc
map_test.cc
mutex_test.cc

View File

@ -0,0 +1,119 @@
//===-- flags_test.cc -------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "flags.h"
#include "flags_parser.h"
#include "gtest/gtest.h"
#include <string.h>
static const char FlagName[] = "flag_name";
static const char FlagDesc[] = "flag description";
template <typename T>
static void testFlag(scudo::FlagType Type, T StartValue, const char *Env,
T FinalValue) {
scudo::FlagParser Parser;
T Flag = StartValue;
Parser.registerFlag(FlagName, FlagDesc, Type, &Flag);
Parser.parseString(Env);
EXPECT_EQ(FinalValue, Flag);
// Reporting unrecognized flags is needed to reset them.
scudo::reportUnrecognizedFlags();
}
TEST(ScudoFlagsTest, BooleanFlags) {
testFlag(scudo::FlagType::FT_bool, false, "flag_name=1", true);
testFlag(scudo::FlagType::FT_bool, false, "flag_name=yes", true);
testFlag(scudo::FlagType::FT_bool, false, "flag_name='yes'", true);
testFlag(scudo::FlagType::FT_bool, false, "flag_name=true", true);
testFlag(scudo::FlagType::FT_bool, true, "flag_name=0", false);
testFlag(scudo::FlagType::FT_bool, true, "flag_name=\"0\"", false);
testFlag(scudo::FlagType::FT_bool, true, "flag_name=no", false);
testFlag(scudo::FlagType::FT_bool, true, "flag_name=false", false);
testFlag(scudo::FlagType::FT_bool, true, "flag_name='false'", false);
}
TEST(ScudoFlagsDeathTest, BooleanFlags) {
EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name", true),
"expected '='");
EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=", true),
"invalid value for bool option: ''");
EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=2", true),
"invalid value for bool option: '2'");
EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=-1", true),
"invalid value for bool option: '-1'");
EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=on", true),
"invalid value for bool option: 'on'");
}
TEST(ScudoFlagsTest, IntFlags) {
testFlag(scudo::FlagType::FT_int, -11, nullptr, -11);
testFlag(scudo::FlagType::FT_int, -11, "flag_name=0", 0);
testFlag(scudo::FlagType::FT_int, -11, "flag_name='0'", 0);
testFlag(scudo::FlagType::FT_int, -11, "flag_name=42", 42);
testFlag(scudo::FlagType::FT_int, -11, "flag_name=-42", -42);
testFlag(scudo::FlagType::FT_int, -11, "flag_name=\"-42\"", -42);
// Unrecognized flags are ignored.
testFlag(scudo::FlagType::FT_int, -11, "--flag_name=42", -11);
testFlag(scudo::FlagType::FT_int, -11, "zzzzzzz=42", -11);
}
TEST(ScudoFlagsDeathTest, IntFlags) {
EXPECT_DEATH(testFlag(scudo::FlagType::FT_int, -11, "flag_name", 0),
"expected '='");
EXPECT_DEATH(testFlag(scudo::FlagType::FT_int, -11, "flag_name=42U", 0),
"invalid value for int option");
}
static void testTwoFlags(const char *Env, bool ExpectedFlag1,
const int ExpectedFlag2, const char *Name1 = "flag1",
const char *Name2 = "flag2") {
scudo::FlagParser Parser;
bool Flag1 = !ExpectedFlag1;
int Flag2;
Parser.registerFlag(Name1, FlagDesc, scudo::FlagType::FT_bool, &Flag1);
Parser.registerFlag(Name2, FlagDesc, scudo::FlagType::FT_int, &Flag2);
Parser.parseString(Env);
EXPECT_EQ(ExpectedFlag1, Flag1);
EXPECT_EQ(Flag2, ExpectedFlag2);
// Reporting unrecognized flags is needed to reset them.
scudo::reportUnrecognizedFlags();
}
TEST(ScudoFlagsTest, MultipleFlags) {
testTwoFlags("flag1=1 flag2=42", true, 42);
testTwoFlags("flag2=-1 flag1=0", false, -1);
testTwoFlags("flag1=false:flag2=1337", false, 1337);
testTwoFlags("flag2=42:flag1=yes", true, 42);
testTwoFlags("flag2=42\nflag1=yes", true, 42);
testTwoFlags("flag2=42\r\nflag1=yes", true, 42);
testTwoFlags("flag2=42\tflag1=yes", true, 42);
}
TEST(ScudoFlagsTest, CommonSuffixFlags) {
testTwoFlags("flag=1 other_flag=42", true, 42, "flag", "other_flag");
testTwoFlags("other_flag=42 flag=1", true, 42, "flag", "other_flag");
}
TEST(ScudoFlagsTest, AllocatorFlags) {
scudo::FlagParser Parser;
scudo::Flags Flags;
scudo::registerFlags(&Parser, &Flags);
Flags.setDefaults();
Flags.dealloc_type_mismatch = false;
Flags.delete_size_mismatch = false;
Flags.quarantine_max_chunk_size = 1024;
Parser.parseString("dealloc_type_mismatch=true:delete_size_mismatch=true:"
"quarantine_max_chunk_size=2048");
EXPECT_TRUE(Flags.dealloc_type_mismatch);
EXPECT_TRUE(Flags.delete_size_mismatch);
EXPECT_EQ(2048, Flags.quarantine_max_chunk_size);
}