-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[scudo] Add __scudo_get_info
symbol to export stats to a buffer.
#130273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-compiler-rt-sanitizer Author: None (piwicode) ChangesAlso make possible to get the fragmentation stats from the primary allocator. Currenty, Full diff: https://github.com/llvm/llvm-project/pull/130273.diff 8 Files Affected:
diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h
index 5deb8c97f1c86..7ab17c5d204c0 100644
--- a/compiler-rt/lib/scudo/standalone/combined.h
+++ b/compiler-rt/lib/scudo/standalone/combined.h
@@ -633,14 +633,8 @@ class Allocator {
// sizing purposes.
uptr getStats(char *Buffer, uptr Size) {
ScopedString Str;
- const uptr Length = getStats(&Str) + 1;
- if (Length < Size)
- Size = Length;
- if (Buffer && Size) {
- memcpy(Buffer, Str.data(), Size);
- Buffer[Size - 1] = '\0';
- }
- return Length;
+ getStats(&Str);
+ return CopyToBuffer(Str, Buffer, Size);
}
void printStats() {
@@ -649,6 +643,12 @@ class Allocator {
Str.output();
}
+ uptr getFragmentationInfo(char *Buffer, uptr Size) {
+ ScopedString Str;
+ Primary.getFragmentationInfo(&Str);
+ return CopyToBuffer(Str, Buffer, Size);
+ }
+
void printFragmentationInfo() {
ScopedString Str;
Primary.getFragmentationInfo(&Str);
@@ -1739,6 +1739,20 @@ class Allocator {
return (Bytes - sizeof(AllocationRingBuffer)) /
sizeof(typename AllocationRingBuffer::Entry);
}
+
+ // Writes the string content to the specified buffer as a null terminated
+ // string, and returns the input string size (including terminal '\0') for
+ // buffer sizing purpose.
+ size_t copyToBuffer(const ScopedString &input, char *output_base,
+ size_t output_length) {
+ if (!output_base)
+ return 0;
+ // Assume that ScopedString internal vector terminal null exists and is not
+ // reported by length.
+ const size_t written = Min(input.length() + 1, output_length);
+ memcpy(output_base, input.data(), written);
+ return written;
+ }
};
} // namespace scudo
diff --git a/compiler-rt/lib/scudo/standalone/include/scudo/interface.h b/compiler-rt/lib/scudo/standalone/include/scudo/interface.h
index a2dedea910cc0..6845b40c8b5cc 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, char *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..e60ae991352cf 100644
--- a/compiler-rt/lib/scudo/standalone/string_utils.cpp
+++ b/compiler-rt/lib/scudo/standalone/string_utils.cpp
@@ -238,4 +238,16 @@ void Printf(const char *Format, ...) {
va_end(Args);
}
+size_t CopyToBuffer(const ScopedString &input, char *output_base,
+ size_t output_length) {
+ if (output_base && output_length) {
+ // Assume that ScopedString internal vector ends with '\0' and that the
+ // terminal null is not reported by `length`.
+ const size_t written = Min(input.length(), output_length - 1);
+ memcpy(output_base, input.data(), written);
+ output_base[written] = '\0';
+ }
+ return input.length() + 1;
+}
+
} // namespace scudo
diff --git a/compiler-rt/lib/scudo/standalone/string_utils.h b/compiler-rt/lib/scudo/standalone/string_utils.h
index cf61e150f20e5..ccb3cf516d8fe 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');
@@ -45,6 +45,12 @@ class ScopedString {
void Printf(const char *Format, ...) FORMAT(1, 2);
+// 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(const ScopedString &input, char *output_base,
+ size_t output_length);
+
} // namespace scudo
#endif // SCUDO_STRING_UTILS_H_
diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
index 9d665ef88c402..30f54ae4d0db8 100644
--- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
@@ -291,6 +291,12 @@ void ScudoCombinedTest<Config>::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..71968ad308675 100644
--- a/compiler-rt/lib/scudo/standalone/tests/strings_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/strings_test.cpp
@@ -128,6 +128,46 @@ TEST(ScudoStringsTest, Padding) {
testAgainstLibc<int>("%03d - %03d", -12, -1234);
}
+TEST(ScudoStringsTest, CopyToBuffer) {
+ { // Call with null buffer.
+ scudo::ScopedString Str;
+ Str.append("abc");
+ EXPECT_EQ(4U, scudo::CopyToBuffer(Str, nullptr, 0));
+ }
+
+ { // Empty string.
+ scudo::ScopedString Str;
+ char buf[256] = {'0', '1'};
+ EXPECT_EQ(1U, scudo::CopyToBuffer(Str, buf, sizeof(buf)));
+ EXPECT_STREQ("", buf);
+ EXPECT_EQ(0, buf[0]); // Rest of the buffer remains unchanged.
+ }
+
+ { // Empty string in empty buffer.
+ scudo::ScopedString Str;
+ char buf[256] = {'0', '1'};
+ EXPECT_EQ(1U, scudo::CopyToBuffer(Str, buf, 0));
+ EXPECT_EQ('0', buf[0]); // Nothing changed because provided size is 0.
+ }
+
+ { // Some text fittinh in the buffer.
+ scudo::ScopedString Str;
+ Str.append("abc");
+ char buf[256] = {'0', '1', '2', '3', '4', '5'};
+ EXPECT_EQ(4U, scudo::CopyToBuffer(
+ Str, buf, sizeof(buf))); // Size includes terminal null.
+ EXPECT_STREQ("abc", buf);
+ }
+
+ { // Some text with overflows.
+ scudo::ScopedString Str;
+ Str.append("abc");
+ char buf[256] = {'0', '1', '2', '3', '4', '5'};
+ EXPECT_EQ(4U, scudo::CopyToBuffer(Str, buf, 3));
+ EXPECT_STREQ("ab", buf);
+ }
+}
+
#if defined(__linux__)
#include <sys/resource.h>
diff --git a/compiler-rt/lib/scudo/standalone/wrappers_c.cpp b/compiler-rt/lib/scudo/standalone/wrappers_c.cpp
index 60014a0f66bf5..725e3f3e25996 100644
--- a/compiler-rt/lib/scudo/standalone/wrappers_c.cpp
+++ b/compiler-rt/lib/scudo/standalone/wrappers_c.cpp
@@ -37,4 +37,15 @@ scudo::Allocator<scudo::Config, SCUDO_PREFIX(malloc_postinit)> SCUDO_ALLOCATOR;
extern "C" INTERFACE void __scudo_print_stats(void) { Allocator.printStats(); }
+extern "C" INTERFACE size_t __scudo_get_info(uint32_t topic, char *buffer,
+ size_t size) {
+ switch (topic) {
+ case M_INFO_TOPIC_STATS:
+ return Allocator.getStats(buffer, size);
+ case M_INFO_TOPIC_FRAGMENTATION:
+ return Allocator.getFragmentationInfo(buffer, size);
+ }
+ 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..f649059b9c878 100644
--- a/compiler-rt/lib/scudo/standalone/wrappers_c_bionic.cpp
+++ b/compiler-rt/lib/scudo/standalone/wrappers_c_bionic.cpp
@@ -38,6 +38,16 @@ static scudo::Allocator<scudo::Config, SCUDO_PREFIX(malloc_postinit)>
// TODO(kostyak): support both allocators.
INTERFACE void __scudo_print_stats(void) { Allocator.printStats(); }
+INTERFACE size_t __scudo_get_info(uint32_t topic, char *buffer, size_t size) {
+ switch (topic) {
+ case M_INFO_TOPIC_STATS:
+ return Allocator.getStats(buffer, size);
+ case M_INFO_TOPIC_FRAGMENTATION:
+ return Allocator.getFragmentationInfo(buffer, size);
+ }
+ 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,
|
58148b5
to
8d5d2de
Compare
// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All parameters are capitalized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like parameters use snake_case in the interface.h. Could you please confirm that we want to Capitalize?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, we have so many different cases. We should probably change this in the future, but your interface is fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Please take another look.
// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like parameters use snake_case in the interface.h. Could you please confirm that we want to Capitalize?
// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, we have so many different cases. We should probably change this in the future, but your interface is fine.
// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I had a comment about this, but I may have missed your reply.
Is there a reason to have this as one function and not two different functions that do different things? For example, are you planning to add more info types in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I want to allow for future topic additions without altering the binary interface. This approach enables us to experiment with new topics and validate their usefulness before formally documenting them. It also provides a graceful way to deprecate topics without breaking existing callers or cluttering the symbol table.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
@@ -229,6 +229,15 @@ void ScopedString::append(const char *Format, ...) { | |||
va_end(Args); | |||
} | |||
|
|||
size_t ScopedString::copyToBuffer(char *OutputBase, size_t OutputLength) { | |||
if (OutputBase && OutputLength) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In any case we may need the null check here? If possible, I tend to make it always non-null. (a DCHECK if needed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, I'm reusing the existing APIs style.
In combined.h, getStats uses that protocol, where the caller can provide a buffer, and if it is too small, the value gets truncated, and the caller knows it from the returned size being larger that the provided buffer. Calling with null returns the required buffer size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I think that needs to be fixed as well. Let's do the non-null check and returning number of bytes copied here first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I added a DCHECK to prevent call with null.
I left the original getstat
code path unchanged, that is to say it accepts null buffer, and I will update that codepath in a separate change.
EXPECT_LE(0, Allocator->getStats(buffer, sizeof(buffer))); | ||
EXPECT_LE(0, Allocator->getFragmentationInfo(buffer, sizeof(buffer))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change these to EXPECT_GT(Allocator->..., 0);
Same as the uses in the tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
memcpy(OutputBase, data(), Written); | ||
OutputBase[Written] = '\0'; | ||
} | ||
return length() + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should return the length of data copied?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I tried to conform to existing APIs, they are a bit surprising, but it makes sense.
In combined.h, getStats uses that protocol, where the caller can provide a buffer, and if it is too small, the value gets truncated, and the caller knows it from the returned size. I reuse the same principle here.
TEST(ScudoStringsTest, CopyIntoNullBuffer) { | ||
scudo::ScopedString Str; | ||
Str.append("abc"); | ||
EXPECT_EQ(4U, Str.copyToBuffer(nullptr, 0)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we agree the suggestion mentioned above, we may not need this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I removed the test.
TEST(ScudoStringsTest, CopyIntoLargeEnoughBuffer) { | ||
scudo::ScopedString Str; | ||
Str.append("abc"); | ||
char buf[256] = {'0', '1', '2', '3', '4', '5'}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that we don't need to init the buf with string nubmers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to check for overflow. Fixed.
case M_INFO_TOPIC_FRAGMENTATION: | ||
return Allocator.getFragmentationInfo(reinterpret_cast<char *>(buffer), | ||
size); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you want a default case here and below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can rewrite with a default case. Is it better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think it's better to warn any invalid options than silent it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Now it prints a warning message.
c347614
to
3a4578e
Compare
Sorry I may not say this clearly. It's better to have |
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.
Thanks for the review. Please take another look. |
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.