From 8bc45f1e5aa869cd48404ebe202dc90b5da38cfd Mon Sep 17 00:00:00 2001 From: Pierre Labatut Date: Fri, 7 Mar 2025 11:55:40 +0100 Subject: [PATCH] [scudo] Add `__scudo_get_info` symbol to export stats to a buffer. Also make possible to get the fragmentation stats from the primary allocator. Currenty, `__scudo_print_stats` symbol writes the report to the provided printer, which is not convenient for processing the result. --- compiler-rt/lib/scudo/standalone/combined.h | 7 +++ .../standalone/include/scudo/interface.h | 17 ++++++++ .../lib/scudo/standalone/string_utils.cpp | 10 +++++ .../lib/scudo/standalone/string_utils.h | 9 +++- .../scudo/standalone/tests/combined_test.cpp | 6 +++ .../scudo/standalone/tests/strings_test.cpp | 43 +++++++++++++++++++ .../lib/scudo/standalone/wrappers_c.cpp | 16 +++++++ .../scudo/standalone/wrappers_c_bionic.cpp | 15 +++++++ 8 files changed, 121 insertions(+), 2 deletions(-) diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h index 43655642843cb..4a25508f2e381 100644 --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -638,6 +638,7 @@ class Allocator { // sizing purposes. uptr getStats(char *Buffer, uptr Size) { ScopedString Str; + // TODO: Use Str.copyToBuffer and fail when Buffer is NULL. const uptr Length = getStats(&Str) + 1; if (Length < Size) Size = Length; @@ -654,6 +655,12 @@ class Allocator { Str.output(); } + uptr getFragmentationInfo(char *Buffer, uptr Size) { + ScopedString Str; + Primary.getFragmentationInfo(&Str); + return Str.copyToBuffer(Buffer, Size); + } + void printFragmentationInfo() { ScopedString Str; Primary.getFragmentationInfo(&Str); diff --git a/compiler-rt/lib/scudo/standalone/include/scudo/interface.h b/compiler-rt/lib/scudo/standalone/include/scudo/interface.h index a2dedea910cc0..4113ccd7bd84e 100644 --- a/compiler-rt/lib/scudo/standalone/include/scudo/interface.h +++ b/compiler-rt/lib/scudo/standalone/include/scudo/interface.h @@ -35,6 +35,23 @@ __attribute__((weak)) void __scudo_realloc_deallocate_hook(void *old_ptr); void __scudo_print_stats(void); +// Reports all allocators configuration and general statistics as a null +// terminated text string. +#ifndef M_INFO_TOPIC_STATS +#define M_INFO_TOPIC_STATS 1 +#endif + +// Reports fragmentation statistics of the primary allocation as a null +// terminated text string. +#ifndef M_INFO_TOPIC_FRAGMENTATION +#define M_INFO_TOPIC_FRAGMENTATION 2 +#endif + +// Writes allocator statistics to the buffer, truncating to the specified size +// if necessary. Returns the full report size (before truncation) for buffer +// sizing purpose, or zero if the topic is not supported. +size_t __scudo_get_info(uint32_t topic, void *buffer, size_t size); + typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg); // Determine the likely cause of a tag check fault or other memory protection diff --git a/compiler-rt/lib/scudo/standalone/string_utils.cpp b/compiler-rt/lib/scudo/standalone/string_utils.cpp index e584bd806e579..5c3ce8ad03fea 100644 --- a/compiler-rt/lib/scudo/standalone/string_utils.cpp +++ b/compiler-rt/lib/scudo/standalone/string_utils.cpp @@ -229,6 +229,16 @@ void ScopedString::append(const char *Format, ...) { va_end(Args); } +size_t ScopedString::copyToBuffer(char *OutputBase, size_t OutputLength) { + DCHECK(OutputBase); + if (OutputLength) { + const size_t Written = Min(length(), OutputLength - 1); + memcpy(OutputBase, data(), Written); + OutputBase[Written] = '\0'; + } + return length() + 1; +} + void Printf(const char *Format, ...) { va_list Args; va_start(Args, Format); diff --git a/compiler-rt/lib/scudo/standalone/string_utils.h b/compiler-rt/lib/scudo/standalone/string_utils.h index cf61e150f20e5..8d320d8854665 100644 --- a/compiler-rt/lib/scudo/standalone/string_utils.h +++ b/compiler-rt/lib/scudo/standalone/string_utils.h @@ -19,8 +19,8 @@ namespace scudo { class ScopedString { public: explicit ScopedString() { String.push_back('\0'); } - uptr length() { return String.size() - 1; } - const char *data() { return String.data(); } + uptr length() const { return String.size() - 1; } + const char *data() const { return String.data(); } void clear() { String.clear(); String.push_back('\0'); @@ -31,6 +31,11 @@ class ScopedString { void reserve(size_t Size) { String.reserve(Size + 1); } uptr capacity() { return String.capacity() - 1; } + // Copies the string to the buffer, truncating if necessary. + // Null-terminates the output if output_length is greater than zero. + // Returns the original string's size (including null). + size_t copyToBuffer(char *OutputBase, size_t OutputLength); + private: void appendNumber(u64 AbsoluteValue, u8 Base, u8 MinNumberLength, bool PadWithZero, bool Negative, bool Upper); diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp index 7e8d5b4396d2e..9b34575f05a10 100644 --- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp @@ -333,6 +333,12 @@ void ScudoCombinedTest::BasicTest(scudo::uptr SizeLog) { Allocator->printStats(); Allocator->printFragmentationInfo(); + + { + char buffer[256] = {0}; + EXPECT_LE(0, Allocator->getStats(buffer, sizeof(buffer))); + EXPECT_LE(0, Allocator->getFragmentationInfo(buffer, sizeof(buffer))); + } } #define SCUDO_MAKE_BASIC_TEST(SizeLog) \ diff --git a/compiler-rt/lib/scudo/standalone/tests/strings_test.cpp b/compiler-rt/lib/scudo/standalone/tests/strings_test.cpp index f81e5036e83b0..e5e5d63a385ac 100644 --- a/compiler-rt/lib/scudo/standalone/tests/strings_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/strings_test.cpp @@ -128,6 +128,49 @@ TEST(ScudoStringsTest, Padding) { testAgainstLibc("%03d - %03d", -12, -1234); } +TEST(ScudoStringsTest, CopyFromAnEmptyString) { + scudo::ScopedString Str; + char buf[256] = {'0', '1'}; + EXPECT_EQ(1U, Str.copyToBuffer(buf, sizeof(buf))); + EXPECT_STREQ("", buf); + EXPECT_EQ(0, buf[0]); // Rest of the buffer remains unchanged. +} + +TEST(ScudoStringsTest, CopyFromAnEmptyStringIntoZeroSizeBuffer) { + scudo::ScopedString Str; + char buf[256] = {'0', '1'}; + EXPECT_EQ(1U, Str.copyToBuffer(buf, 0)); + EXPECT_EQ('0', buf[0]); // Nothing changed because provided size is 0. +} + +TEST(ScudoStringsTest, CopyIntoLargeEnoughBuffer) { + scudo::ScopedString Str; + Str.append("abc"); + char buf[256] = {'0', '1', '2', '3', '4', '5'}; + // Size includes terminal null. + EXPECT_EQ(4U, Str.copyToBuffer(buf, sizeof(buf))); + EXPECT_STREQ("abc", buf); + EXPECT_EQ(buf[4], '4'); +} + +TEST(ScudoStringsTest, CopyWithTextOverflow) { + scudo::ScopedString Str; + Str.append("abc"); + char buf[256] = {'0', '1', '2', '3', '4', '5'}; + EXPECT_EQ(4U, Str.copyToBuffer(buf, 3)); + EXPECT_STREQ("ab", buf); + EXPECT_EQ(buf[3], '3'); +} + +TEST(ScudoStringsTest, CopyIntoExactFit) { + scudo::ScopedString Str; + Str.append("abc"); + char buf[256] = {'0', '1', '2', '3', '4', '5'}; + EXPECT_EQ(4U, Str.copyToBuffer(buf, 4)); + EXPECT_STREQ("abc", buf); + EXPECT_EQ(buf[4], '4'); +} + #if defined(__linux__) #include diff --git a/compiler-rt/lib/scudo/standalone/wrappers_c.cpp b/compiler-rt/lib/scudo/standalone/wrappers_c.cpp index 60014a0f66bf5..64580fd6ba646 100644 --- a/compiler-rt/lib/scudo/standalone/wrappers_c.cpp +++ b/compiler-rt/lib/scudo/standalone/wrappers_c.cpp @@ -15,6 +15,7 @@ #include "internal_defs.h" #include "platform.h" #include "scudo/interface.h" +#include "string_utils.h" #include "wrappers_c.h" #include "wrappers_c_checks.h" @@ -37,4 +38,19 @@ scudo::Allocator SCUDO_ALLOCATOR; extern "C" INTERFACE void __scudo_print_stats(void) { Allocator.printStats(); } +extern "C" INTERFACE size_t __scudo_get_info(uint32_t topic, void *buffer, + size_t size) { + switch (topic) { + case M_INFO_TOPIC_STATS: + return Allocator.getStats(reinterpret_cast(buffer), size); + case M_INFO_TOPIC_FRAGMENTATION: + return Allocator.getFragmentationInfo(reinterpret_cast(buffer), + size); + default: + scudo::Printf("Scudo WARNING: unrecognized __scudo_get_info topic: %d\n", + topic); + return 0; + } +} + #endif // !SCUDO_ANDROID || !_BIONIC diff --git a/compiler-rt/lib/scudo/standalone/wrappers_c_bionic.cpp b/compiler-rt/lib/scudo/standalone/wrappers_c_bionic.cpp index e9d8c1e8d3db2..7ba8fbba45715 100644 --- a/compiler-rt/lib/scudo/standalone/wrappers_c_bionic.cpp +++ b/compiler-rt/lib/scudo/standalone/wrappers_c_bionic.cpp @@ -15,6 +15,7 @@ #include "internal_defs.h" #include "platform.h" #include "scudo/interface.h" +#include "string_utils.h" #include "wrappers_c.h" #include "wrappers_c_checks.h" @@ -38,6 +39,20 @@ static scudo::Allocator // TODO(kostyak): support both allocators. INTERFACE void __scudo_print_stats(void) { Allocator.printStats(); } +INTERFACE size_t __scudo_get_info(uint32_t topic, void *buffer, size_t size) { + switch (topic) { + case M_INFO_TOPIC_STATS: + return Allocator.getStats(reinterpret_cast(buffer), size); + case M_INFO_TOPIC_FRAGMENTATION: + return Allocator.getFragmentationInfo(reinterpret_cast(buffer), + size); + default: + scudo::Printf("Scudo WARNING: unrecognized __scudo_get_info topic: %d\n", + topic); + return 0; + } +} + INTERFACE void __scudo_get_error_info( struct scudo_error_info *error_info, uintptr_t fault_addr, const char *stack_depot, size_t stack_depot_size, const char *region_info,