Index: lib/scudo/standalone/CMakeLists.txt =================================================================== --- lib/scudo/standalone/CMakeLists.txt +++ lib/scudo/standalone/CMakeLists.txt @@ -37,6 +37,8 @@ checksum.cc crc32_hw.cc common.cc + flags.cc + flags_parser.cc fuchsia.cc linux.cc report.cc @@ -57,6 +59,9 @@ atomic_helpers.h bytemap.h checksum.h + flags.h + flags_parser.h + interface.h internal_defs.h linux.h list.h Index: lib/scudo/standalone/flags.h =================================================================== --- /dev/null +++ lib/scudo/standalone/flags.h @@ -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_ Index: lib/scudo/standalone/flags.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/flags.cc @@ -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(&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 Index: lib/scudo/standalone/flags.inc =================================================================== --- /dev/null +++ lib/scudo/standalone/flags.inc @@ -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.") Index: lib/scudo/standalone/flags_parser.h =================================================================== --- /dev/null +++ lib/scudo/standalone/flags_parser.h @@ -0,0 +1,58 @@ +//===-- 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 +#include + +namespace scudo { + +enum class FlagType : u8 { + FT_bool, + FT_int, +}; + +class FlagParser { +public: + FlagParser() : NumberOfFlags(0), Buffer(nullptr), Pos(0) {} + 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; + + const char *Buffer; + uptr Pos; + + void reportFatalError(const char *Error); + bool isSeparator(char C); + void skipWhitespace(); + void parseFlags(); + void parseFlag(); + bool runHandler(const char *Name, const char *Value); +}; + +void reportUnrecognizedFlags(); + +} // namespace scudo + +#endif // SCUDO_FLAGS_PARSER_H_ Index: lib/scudo/standalone/flags_parser.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/flags_parser.cc @@ -0,0 +1,161 @@ +//===-- 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 + +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); +} + +bool FlagParser::isSeparator(char C) { + return C == ' ' || C == ',' || C == ':' || C == '\n' || C == '\t' || + C == '\r'; +} + +void FlagParser::skipWhitespace() { + while (isSeparator(Buffer[Pos])) + ++Pos; +} + +void FlagParser::parseFlag() { + const uptr NameStart = Pos; + while (Buffer[Pos] != 0 && Buffer[Pos] != '=' && !isSeparator(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 (Buffer[Pos] != 0 && !isSeparator(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(Flags[I].Var)); + if (!Ok) + reportInvalidFlag("bool", Value); + break; + case FlagType::FT_int: + char *ValueEnd; + *reinterpret_cast(Flags[I].Var) = + static_cast(strtol(Value, &ValueEnd, 10)); + Ok = *ValueEnd == 0 || *ValueEnd == '"' || *ValueEnd == '\'' || + isSeparator(*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 Index: lib/scudo/standalone/interface.h =================================================================== --- /dev/null +++ lib/scudo/standalone/interface.h @@ -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_ Index: lib/scudo/standalone/tests/CMakeLists.txt =================================================================== --- lib/scudo/standalone/tests/CMakeLists.txt +++ lib/scudo/standalone/tests/CMakeLists.txt @@ -52,6 +52,7 @@ atomic_test.cc bytemap_test.cc checksum_test.cc + flags_test.cc list_test.cc map_test.cc mutex_test.cc Index: lib/scudo/standalone/tests/flags_test.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/tests/flags_test.cc @@ -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 + +static const char FlagName[] = "flag_name"; +static const char FlagDesc[] = "flag description"; + +template +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); +}