diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp index 5fd511f15..6a81277af 100644 --- a/libc/malloc_debug/Config.cpp +++ b/libc/malloc_debug/Config.cpp @@ -82,6 +82,19 @@ const std::unordered_map Config::kOptions = { {REAR_GUARD | TRACK_ALLOCS, &Config::SetRearGuard}, }, + { + "backtrace_size", + {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceSize}, + }, + { + "backtrace_min_size", + {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceMinSize}, + }, + { + "backtrace_max_size", + {BACKTRACE_SPECIFIC_SIZES, &Config::SetBacktraceMaxSize}, + }, + { "backtrace", {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktrace}, @@ -297,6 +310,23 @@ bool Config::SetBacktraceDumpPrefix(const std::string&, const std::string& value return true; } +bool Config::SetBacktraceSize(const std::string& option, const std::string& value) { + if (!ParseValue(option, value, 1, SIZE_MAX, &backtrace_min_size_bytes_)) { + return false; + } + backtrace_max_size_bytes_ = backtrace_min_size_bytes_; + + return true; +} + +bool Config::SetBacktraceMinSize(const std::string& option, const std::string& value) { + return ParseValue(option, value, 1, SIZE_MAX, &backtrace_min_size_bytes_); +} + +bool Config::SetBacktraceMaxSize(const std::string& option, const std::string& value) { + return ParseValue(option, value, 1, SIZE_MAX, &backtrace_max_size_bytes_); +} + bool Config::SetExpandAlloc(const std::string& option, const std::string& value) { return ParseValue(option, value, DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, &expand_alloc_bytes_); } @@ -403,6 +433,8 @@ bool Config::Init(const char* options_str) { backtrace_enabled_ = false; backtrace_dump_on_exit_ = false; backtrace_dump_prefix_ = DEFAULT_BACKTRACE_DUMP_PREFIX; + backtrace_min_size_bytes_ = 0; + backtrace_max_size_bytes_ = SIZE_MAX; check_unreachable_signal_ = SIGRTMAX - 16; // Process each option name we can find. diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h index 5e3ff7169..ef1d2a932 100644 --- a/libc/malloc_debug/Config.h +++ b/libc/malloc_debug/Config.h @@ -47,6 +47,7 @@ constexpr uint64_t BACKTRACE_FULL = 0x400; constexpr uint64_t ABORT_ON_ERROR = 0x800; constexpr uint64_t VERBOSE = 0x1000; constexpr uint64_t CHECK_UNREACHABLE_ON_SIGNAL = 0x2000; +constexpr uint64_t BACKTRACE_SPECIFIC_SIZES = 0x4000; // In order to guarantee posix compliance, set the minimum alignment // to 8 bytes for 32 bit systems and 16 bytes for 64 bit systems. @@ -90,6 +91,9 @@ class Config { uint8_t fill_alloc_value() const { return fill_alloc_value_; } uint8_t fill_free_value() const { return fill_free_value_; } + size_t backtrace_min_size_bytes() const { return backtrace_min_size_bytes_; } + size_t backtrace_max_size_bytes() const { return backtrace_max_size_bytes_; } + int record_allocs_signal() const { return record_allocs_signal_; } size_t record_allocs_num_entries() const { return record_allocs_num_entries_; } const std::string& record_allocs_file() const { return record_allocs_file_; } @@ -121,6 +125,10 @@ class Config { bool SetBacktraceDumpOnExit(const std::string& option, const std::string& value); bool SetBacktraceDumpPrefix(const std::string& option, const std::string& value); + bool SetBacktraceSize(const std::string& option, const std::string& value); + bool SetBacktraceMinSize(const std::string& option, const std::string& value); + bool SetBacktraceMaxSize(const std::string& option, const std::string& value); + bool SetExpandAlloc(const std::string& option, const std::string& value); bool SetFreeTrack(const std::string& option, const std::string& value); @@ -145,6 +153,8 @@ class Config { size_t backtrace_frames_ = 0; bool backtrace_dump_on_exit_ = false; std::string backtrace_dump_prefix_; + size_t backtrace_min_size_bytes_ = 0; + size_t backtrace_max_size_bytes_ = 0; size_t fill_on_alloc_bytes_ = 0; size_t fill_on_free_bytes_ = 0; diff --git a/libc/malloc_debug/PointerData.cpp b/libc/malloc_debug/PointerData.cpp index d062086f5..5ab2232f2 100644 --- a/libc/malloc_debug/PointerData.cpp +++ b/libc/malloc_debug/PointerData.cpp @@ -136,7 +136,22 @@ bool PointerData::Initialize(const Config& config) NO_THREAD_SAFETY_ANALYSIS { return true; } -size_t PointerData::AddBacktrace(size_t num_frames) { +static inline bool ShouldBacktraceAllocSize(size_t size_bytes) { + static bool only_backtrace_specific_sizes = + g_debug->config().options() & BACKTRACE_SPECIFIC_SIZES; + if (!only_backtrace_specific_sizes) { + return true; + } + static size_t min_size_bytes = g_debug->config().backtrace_min_size_bytes(); + static size_t max_size_bytes = g_debug->config().backtrace_max_size_bytes(); + return size_bytes >= min_size_bytes && size_bytes <= max_size_bytes; +} + +size_t PointerData::AddBacktrace(size_t num_frames, size_t size_bytes) { + if (!ShouldBacktraceAllocSize(size_bytes)) { + return kBacktraceEmptyIndex; + } + std::vector frames; std::vector frames_info; if (g_debug->config().options() & BACKTRACE_FULL) { @@ -198,7 +213,7 @@ void PointerData::RemoveBacktrace(size_t hash_index) { void PointerData::Add(const void* ptr, size_t pointer_size) { size_t hash_index = 0; if (backtrace_enabled_) { - hash_index = AddBacktrace(g_debug->config().backtrace_frames()); + hash_index = AddBacktrace(g_debug->config().backtrace_frames(), pointer_size); } std::lock_guard pointer_guard(pointer_mutex_); @@ -336,11 +351,11 @@ void PointerData::VerifyFreedPointer(const FreePointerInfoType& info) { } } -void* PointerData::AddFreed(const void* ptr) { +void* PointerData::AddFreed(const void* ptr, size_t size_bytes) { size_t hash_index = 0; size_t num_frames = g_debug->config().free_track_backtrace_num_frames(); if (num_frames) { - hash_index = AddBacktrace(num_frames); + hash_index = AddBacktrace(num_frames, size_bytes); } void* last = nullptr; diff --git a/libc/malloc_debug/PointerData.h b/libc/malloc_debug/PointerData.h index 97eec0aee..3194bab65 100644 --- a/libc/malloc_debug/PointerData.h +++ b/libc/malloc_debug/PointerData.h @@ -137,13 +137,13 @@ class PointerData : public OptionData { static void IteratePointers(std::function fn); - static size_t AddBacktrace(size_t num_frames); + static size_t AddBacktrace(size_t num_frames, size_t size_bytes); static void RemoveBacktrace(size_t hash_index); static void Add(const void* pointer, size_t size); static void Remove(const void* pointer); - static void* AddFreed(const void* pointer); + static void* AddFreed(const void* pointer, size_t size_bytes); static void LogFreeError(const FreePointerInfoType& info, size_t usable_size); static void LogFreeBacktrace(const void* ptr); static void VerifyFreedPointer(const FreePointerInfoType& info); diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md index 6b046db26..366762406 100644 --- a/libc/malloc_debug/README.md +++ b/libc/malloc_debug/README.md @@ -160,6 +160,41 @@ When this value is changed from the default, then the filename chosen on the signal will be backtrace\_dump\_prefix.**PID**.txt. The filename chosen when the program exits will be backtrace\_dump\_prefix.**PID**.exit.txt. +### backtrace\_min\_size=ALLOCATION\_SIZE\_BYTES +As of U, setting this in combination with the backtrace option means +that only allocations of a size greater than or equal to +**ALLOCATION\_SIZE\_BYTES** will be backtraced. When used in combination +with the backtrace\_max\_size option, then allocations greater than or +equal to backtrace\_min\_size and less than or equal to +backtrace\_max\_size will be backtraced. The backtrace\_size option +overrides this option, and should not be used at the same time. + +This option can also be used in combination with other tools such +as [libmemunreachable](https://android.googlesource.com/platform/system/memory/libmemunreachable/+/master/README.md) +to only get backtraces for sizes of allocations listed as being leaked. + +### backtrace\_max\_size=ALLOCATION\_SIZE\_BYTES +As of U, setting this in combination with the backtrace option means +that only allocations of a size less than or equal to +**ALLOCATION\_SIZE\_BYTES** will be backtraced. When used in combination +with the backtrace\_min\_size option, then allocations greater than or +equal to backtrace\_min\_size and less than or equal to +backtrace\_max\_size will be backtraced. The backtrace\_size option +overrides this option, and should not be used at the same time. + +This option can also be used in combination with other tools such +as [libmemunreachable](https://android.googlesource.com/platform/system/memory/libmemunreachable/+/master/README.md) +to only get backtraces for sizes of allocations listed as being leaked. + +### backtrace\_size=ALLOCATION\_SIZE\_BYTES +As of U, setting this in combination with the backtrace option means +that only allocations of size **ALLOCATION\_SIZE\_BYTES** will be backtraced. +This option overrides the backtrace\_min\_size and the backtrace\_max\_size. + +This option can also be used in combination with other tools such +as [libmemunreachable](https://android.googlesource.com/platform/system/memory/libmemunreachable/+/master/README.md) +to only get backtraces for sizes of allocations listed as being leaked. + ### backtrace\_full As of Q, any time that a backtrace is gathered, a different algorithm is used that is extra thorough and can unwind through Java frames. This will run diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp index 35cda838c..617858a02 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -522,8 +522,8 @@ static void InternalFree(void* pointer) { if (g_debug->config().options() & FILL_ON_FREE) { size_t fill_bytes = g_debug->config().fill_on_free_bytes(); - bytes = (bytes < fill_bytes) ? bytes : fill_bytes; - memset(pointer, g_debug->config().fill_free_value(), bytes); + fill_bytes = (bytes < fill_bytes) ? bytes : fill_bytes; + memset(pointer, g_debug->config().fill_free_value(), fill_bytes); } if (g_debug->TrackPointers()) { @@ -536,7 +536,7 @@ static void InternalFree(void* pointer) { // frees at the same time and we wind up trying to really free this // pointer from another thread, while still trying to free it in // this function. - pointer = PointerData::AddFreed(pointer); + pointer = PointerData::AddFreed(pointer, bytes); if (pointer != nullptr) { if (g_debug->HeaderEnabled()) { pointer = g_debug->GetHeader(pointer)->orig_pointer; diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp index d08ba98f6..0a0eaefdc 100644 --- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp @@ -779,3 +779,69 @@ TEST_F(MallocDebugConfigTest, trigger_check_unreachable_on_signal_fail) { "which does not take a value\n"); ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str()); } + +TEST_F(MallocDebugConfigTest, size) { + ASSERT_TRUE(InitConfig("backtrace_size=37")) << getFakeLogPrint(); + ASSERT_EQ(BACKTRACE_SPECIFIC_SIZES, config->options()); + ASSERT_EQ(37U, config->backtrace_min_size_bytes()); + ASSERT_EQ(37U, config->backtrace_max_size_bytes()); + + ASSERT_FALSE(InitConfig("backtrace_size")) << getFakeLogPrint(); + ASSERT_FALSE(InitConfig("backtrace_size=0")) << getFakeLogPrint(); + ASSERT_FALSE(InitConfig("backtrace_size=-1")) << getFakeLogPrint(); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string log_msg("6 malloc_debug malloc_testing: bad value for option 'backtrace_size'\n" + + usage_string + + "6 malloc_debug malloc_testing: bad value for option 'backtrace_size', value " + "must be >= 1: 0\n" + + usage_string + + "6 malloc_debug malloc_testing: bad value for option 'backtrace_size', value " + "cannot be negative: -1\n" + + usage_string); + ASSERT_STREQ(log_msg.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, min_size) { + ASSERT_TRUE(InitConfig("backtrace_min_size=9")) << getFakeLogPrint(); + ASSERT_EQ(BACKTRACE_SPECIFIC_SIZES, config->options()); + ASSERT_EQ(9U, config->backtrace_min_size_bytes()); + ASSERT_EQ(SIZE_MAX, config->backtrace_max_size_bytes()); + + ASSERT_FALSE(InitConfig("backtrace_min_size")) << getFakeLogPrint(); + ASSERT_FALSE(InitConfig("backtrace_min_size=0")) << getFakeLogPrint(); + ASSERT_FALSE(InitConfig("backtrace_min_size=-1")) << getFakeLogPrint(); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string log_msg("6 malloc_debug malloc_testing: bad value for option 'backtrace_min_size'\n" + + usage_string + + "6 malloc_debug malloc_testing: bad value for option 'backtrace_min_size', " + "value must be >= 1: 0\n" + + usage_string + + "6 malloc_debug malloc_testing: bad value for option 'backtrace_min_size', " + "value cannot be negative: -1\n" + + usage_string); + ASSERT_STREQ(log_msg.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, max_size) { + ASSERT_TRUE(InitConfig("backtrace_max_size=13")) << getFakeLogPrint(); + ASSERT_EQ(BACKTRACE_SPECIFIC_SIZES, config->options()); + ASSERT_EQ(0U, config->backtrace_min_size_bytes()); + ASSERT_EQ(13U, config->backtrace_max_size_bytes()); + + ASSERT_FALSE(InitConfig("backtrace_max_size")) << getFakeLogPrint(); + ASSERT_FALSE(InitConfig("backtrace_max_size=0")) << getFakeLogPrint(); + ASSERT_FALSE(InitConfig("backtrace_max_size=-1")) << getFakeLogPrint(); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string log_msg("6 malloc_debug malloc_testing: bad value for option 'backtrace_max_size'\n" + + usage_string + + "6 malloc_debug malloc_testing: bad value for option 'backtrace_max_size', " + "value must be >= 1: 0\n" + + usage_string + + "6 malloc_debug malloc_testing: bad value for option 'backtrace_max_size', " + "value cannot be negative: -1\n" + + usage_string); + ASSERT_STREQ(log_msg.c_str(), getFakeLogPrint().c_str()); +} diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp index b97e6384f..961ddd36f 100644 --- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp @@ -2754,3 +2754,178 @@ TEST_F(MallocDebugTest, check_unreachable_on_signal) { "6 malloc_debug Unreachable check failed, run setenforce 0 and try again.\n", getFakeLogPrint().c_str()); } + +TEST_F(MallocDebugTest, backtrace_only_some_sizes_with_backtrace_size) { + Init("leak_track backtrace backtrace_size=120"); + + backtrace_fake_add(std::vector{0x1000, 0x2000, 0x3000}); + + void* pointer1 = debug_malloc(119); + ASSERT_TRUE(pointer1 != nullptr); + + backtrace_fake_add(std::vector{0xa000, 0xb000, 0xc000, 0xd000}); + + void* pointer2 = debug_malloc(120); + ASSERT_TRUE(pointer2 != nullptr); + + backtrace_fake_add(std::vector{0xfe000, 0xde000, 0xce000, 0xbe000, 0xae000}); + + void* pointer3 = debug_malloc(121); + ASSERT_TRUE(pointer3 != nullptr); + + debug_finalize(); + initialized = false; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 121 at %p (leak 1 of 3)\n", pointer3); + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 120 at %p (leak 2 of 3)\n", pointer2); + expected_log += "6 malloc_debug Backtrace at time of allocation:\n"; + expected_log += "6 malloc_debug #00 pc 0x1000\n"; + expected_log += "6 malloc_debug #01 pc 0x2000\n"; + expected_log += "6 malloc_debug #02 pc 0x3000\n"; + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 119 at %p (leak 3 of 3)\n", pointer1); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugTest, backtrace_only_some_sizes_with_backtrace_min_size) { + Init("leak_track backtrace backtrace_min_size=1000"); + + backtrace_fake_add(std::vector{0x1000, 0x2000, 0x3000}); + + void* pointer1 = debug_malloc(500); + ASSERT_TRUE(pointer1 != nullptr); + + backtrace_fake_add(std::vector{0xa000, 0xb000, 0xc000, 0xd000}); + + void* pointer2 = debug_malloc(1000); + ASSERT_TRUE(pointer2 != nullptr); + + backtrace_fake_add(std::vector{0xfe000, 0xde000, 0xce000, 0xbe000, 0xae000}); + + void* pointer3 = debug_malloc(1001); + ASSERT_TRUE(pointer3 != nullptr); + + debug_finalize(); + initialized = false; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 1001 at %p (leak 1 of 3)\n", + pointer3); + expected_log += "6 malloc_debug Backtrace at time of allocation:\n"; + expected_log += "6 malloc_debug #00 pc 0xa000\n"; + expected_log += "6 malloc_debug #01 pc 0xb000\n"; + expected_log += "6 malloc_debug #02 pc 0xc000\n"; + expected_log += "6 malloc_debug #03 pc 0xd000\n"; + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 1000 at %p (leak 2 of 3)\n", + pointer2); + expected_log += "6 malloc_debug Backtrace at time of allocation:\n"; + expected_log += "6 malloc_debug #00 pc 0x1000\n"; + expected_log += "6 malloc_debug #01 pc 0x2000\n"; + expected_log += "6 malloc_debug #02 pc 0x3000\n"; + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 500 at %p (leak 3 of 3)\n", pointer1); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugTest, backtrace_only_some_sizes_with_backtrace_max_size) { + Init("leak_track backtrace backtrace_max_size=1000"); + + backtrace_fake_add(std::vector{0x1000, 0x2000, 0x3000}); + + void* pointer1 = debug_malloc(1000); + ASSERT_TRUE(pointer1 != nullptr); + + backtrace_fake_add(std::vector{0xa000, 0xb000, 0xc000, 0xd000}); + + void* pointer2 = debug_malloc(1001); + ASSERT_TRUE(pointer2 != nullptr); + + backtrace_fake_add(std::vector{0xfe000, 0xde000, 0xce000, 0xbe000, 0xae000}); + + void* pointer3 = debug_malloc(5000); + ASSERT_TRUE(pointer3 != nullptr); + + debug_finalize(); + initialized = false; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 5000 at %p (leak 1 of 3)\n", + pointer3); + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 1001 at %p (leak 2 of 3)\n", + pointer2); + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 1000 at %p (leak 3 of 3)\n", + pointer1); + expected_log += "6 malloc_debug Backtrace at time of allocation:\n"; + expected_log += "6 malloc_debug #00 pc 0x1000\n"; + expected_log += "6 malloc_debug #01 pc 0x2000\n"; + expected_log += "6 malloc_debug #02 pc 0x3000\n"; + + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugTest, backtrace_only_some_sizes_with_backtrace_min_max_size) { + Init("leak_track backtrace backtrace_min_size=50 backtrace_max_size=1000"); + + backtrace_fake_add(std::vector{0x1000, 0x2000, 0x3000}); + + void* pointer1 = debug_malloc(49); + ASSERT_TRUE(pointer1 != nullptr); + + backtrace_fake_add(std::vector{0xa000, 0xb000, 0xc000, 0xd000}); + + void* pointer2 = debug_malloc(50); + ASSERT_TRUE(pointer2 != nullptr); + + backtrace_fake_add(std::vector{0xfe000, 0xde000, 0xce000, 0xbe000, 0xae000}); + + void* pointer3 = debug_malloc(1000); + ASSERT_TRUE(pointer3 != nullptr); + + backtrace_fake_add(std::vector{0x1a000, 0x1b000, 0x1c000, 0x1d000, 0x1e000}); + + void* pointer4 = debug_malloc(1001); + ASSERT_TRUE(pointer4 != nullptr); + + debug_finalize(); + initialized = false; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 1001 at %p (leak 1 of 4)\n", + pointer4); + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 1000 at %p (leak 2 of 4)\n", + pointer3); + expected_log += "6 malloc_debug Backtrace at time of allocation:\n"; + expected_log += "6 malloc_debug #00 pc 0xa000\n"; + expected_log += "6 malloc_debug #01 pc 0xb000\n"; + expected_log += "6 malloc_debug #02 pc 0xc000\n"; + expected_log += "6 malloc_debug #03 pc 0xd000\n"; + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 50 at %p (leak 3 of 4)\n", pointer2); + expected_log += "6 malloc_debug Backtrace at time of allocation:\n"; + expected_log += "6 malloc_debug #00 pc 0x1000\n"; + expected_log += "6 malloc_debug #01 pc 0x2000\n"; + expected_log += "6 malloc_debug #02 pc 0x3000\n"; + + expected_log += android::base::StringPrintf( + "6 malloc_debug +++ malloc_testing leaked block of size 49 at %p (leak 4 of 4)\n", pointer1); + + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +}