From 35faeb089167e1ed3cb8ee7808d72da3855c85f1 Mon Sep 17 00:00:00 2001 From: Dmitriy Evgenevich Gonzha Date: Wed, 14 Mar 2018 13:47:05 +0300 Subject: [PATCH] libsanitizer: re-implement heap profiler. In the previos heap profiler implementation HeapProfile instance was created every time BackgroundThread wanted to collect statistical data. This commit simply applies the singleton pattern: now there's only one HeapProfile object which can both decide wether it's time to collect the statistics and print profiling data. Signed-off-by: Andrey Drobyshev Change-Id: I8dfa245d826c52749564346a2cd50788c5d91124 --- .../c-c++-common/asan/auto_memory_profile.c | 35 +++ .../asan/auto_memory_profile_decrease.c | 37 +++ .../asan/auto_memory_profile_timestamp.c | 29 ++ libsanitizer/asan/asan_memory_profile.cc | 305 ++++++++++++++++++++- libsanitizer/sanitizer_common/sanitizer_common.cc | 4 + .../sanitizer_common/sanitizer_common_libcdep.cc | 11 +- libsanitizer/sanitizer_common/sanitizer_flags.inc | 27 +- libsanitizer/sanitizer_common/sanitizer_libc.h | 4 + libsanitizer/sanitizer_common/sanitizer_printf.cc | 6 + 9 files changed, 436 insertions(+), 22 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/asan/auto_memory_profile.c create mode 100644 gcc/testsuite/c-c++-common/asan/auto_memory_profile_decrease.c create mode 100644 gcc/testsuite/c-c++-common/asan/auto_memory_profile_timestamp.c diff --git a/gcc/testsuite/c-c++-common/asan/auto_memory_profile.c b/gcc/testsuite/c-c++-common/asan/auto_memory_profile.c new file mode 100644 index 0000000..e71a98b --- /dev/null +++ b/gcc/testsuite/c-c++-common/asan/auto_memory_profile.c @@ -0,0 +1,35 @@ +// Based on LLVM Heap Profile test. Ported to C, adopted for GCC +// Tests heap_profile=1. +// Printing memory profiling only works in the configuration where we can +// detect leaks. + +/* { dg-do run } */ +/* { dg-set-target-env-var ASAN_OPTIONS "heap_profile=1" } */ + +#include +#include +#include +#include + +char *sink[1000]; + +int main() { + int i; + for (i = 0; i < 3; i++) { + const size_t kSize = 13000000; + char *x = (char*)malloc(sizeof(char) * kSize); + memset(x, i, kSize); + sink[i] = x; + sleep(1); + } + sleep(1); + for (i = 0; i < 3; i++) { + free(sink[i]); + } + return 0; +} + +/* { dg-output "HEAP PROFILE at RSS \[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output ".*13000000 byte\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output ".*HEAP PROFILE at RSS \[^\n\r]*(\n|\r\n|\r)Live\[^\n\r]*(\n|\r\n|\r)26000000 byte\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output ".*HEAP PROFILE at RSS \[^\n\r]*(\n|\r\n|\r)Live\[^\n\r]*(\n|\r\n|\r)39000000 byte\[^\n\r]*(\n|\r\n|\r)" } */ diff --git a/gcc/testsuite/c-c++-common/asan/auto_memory_profile_decrease.c b/gcc/testsuite/c-c++-common/asan/auto_memory_profile_decrease.c new file mode 100644 index 0000000..cb59c2a --- /dev/null +++ b/gcc/testsuite/c-c++-common/asan/auto_memory_profile_decrease.c @@ -0,0 +1,37 @@ +// Tests heap_profile=1 with heap_profile_out_decrease=1. +// Fast memory decrease detection requires higher RSS decrease resolution, so +// heap_profile_out_full_lim=1 is required. +// Printing memory profiling only works in the configuration where we can +// detect leaks. + +/* { dg-do run } */ +/* { dg-set-target-env-var ASAN_OPTIONS "heap_profile=1:heap_profile_out_decrease=1:heap_profile_out_full_lim=1" } */ + +#include +#include +#include +#include + +char *sink[1000]; + +int main() { + int i; + for (i = 0; i < 3; i++) { + const size_t kSize = 13000000; + char *x = (char*)malloc(sizeof(char) * kSize); + memset(x, i, kSize); + sink[i] = x; + //sleep(1); + } + sleep(1); + for (i = 0; i < 3; i++) { + free(sink[i]); + sleep(1); + } + sleep(1); + return 0; +} + +/* { dg-output ".*HEAP PROFILE at RSS \[^\n\r]*(\n|\r\n|\r)Live\[^\n\r]*(\n|\r\n|\r)39000000 byte\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output ".*HEAP PROFILE at RSS \[^\n\r]*(\n|\r\n|\r)Live\[^\n\r]*(\n|\r\n|\r)26000000 byte\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output ".*HEAP PROFILE at RSS \[^\n\r]*(\n|\r\n|\r)Live\[^\n\r]*(\n|\r\n|\r)13000000 byte\[^\n\r]*(\n|\r\n|\r)" } */ diff --git a/gcc/testsuite/c-c++-common/asan/auto_memory_profile_timestamp.c b/gcc/testsuite/c-c++-common/asan/auto_memory_profile_timestamp.c new file mode 100644 index 0000000..31df815 --- /dev/null +++ b/gcc/testsuite/c-c++-common/asan/auto_memory_profile_timestamp.c @@ -0,0 +1,29 @@ +// Based on LLVM Heap Profile test. Ported to C, adopted for GCC +// Tests heap_profile=1. +// Printing memory profiling only works in the configuration where we can +// detect leaks. + +/* { dg-do run } */ +/* { dg-set-target-env-var ASAN_OPTIONS "heap_profile=1:heap_profile_timestamp=1" } */ + +#include +#include +#include +#include + +char *sink[1000]; + +int main() { + int i; + const size_t kSize = 13000000; + char *x = (char*)malloc(sizeof(char) * kSize); + memset(x, 0, kSize); + sink[0] = x; + sleep(1); + free(x); + + return 0; +} + +/* { dg-output "HEAP PROFILE at RSS \[^\n\r]* time \[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output ".* byte\[^\n\r]*(\n|\r\n|\r)" } */ diff --git a/libsanitizer/asan/asan_memory_profile.cc b/libsanitizer/asan/asan_memory_profile.cc index 5a25785..8a8ae22 100644 --- a/libsanitizer/asan/asan_memory_profile.cc +++ b/libsanitizer/asan/asan_memory_profile.cc @@ -10,17 +10,31 @@ // This file implements __sanitizer_print_memory_profile. //===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_stackdepot.h" #include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer_common/sanitizer_stacktrace_printer.h" #include "sanitizer_common/sanitizer_stoptheworld.h" +#include "sanitizer_common/sanitizer_placement_new.h" #include "lsan/lsan_common.h" #include "asan/asan_allocator.h" #if CAN_SANITIZE_LEAKS +#include +#include + +#if SANITIZER_LINUX && !SANITIZER_GO +#include +#include +#endif + namespace __asan { +class HeapProfile; // Pointer to the global object +HeapProfile *heap_profile = nullptr; // responsible for profiling + struct AllocationSite { u32 id; uptr total_size; @@ -29,7 +43,26 @@ struct AllocationSite { class HeapProfile { public: - HeapProfile() : allocations_(1024) {} + explicit HeapProfile(const CommonFlags *cf) + : allocations_(1024), hp_flags_(cf), hp_start_ts_(NanoTime()) { + if (internal_strcmp(hp_flags_->heap_profile_log_path, "") == 0) { + // HeapProfiler's report_file initialized to the global one + hp_report_file_ = &report_file; + } + else { + // ... or to the private one + hp_report_file_ = &hp_private_report_file_; + hp_report_file_->SetReportPath(hp_flags_->heap_profile_log_path); + } + } + + void HPPrintf(const char *format, ...) { + va_list args; + va_start(args, format); + HPVPrintf(format, args); + va_end(args); + } + void Insert(u32 id, uptr size) { total_allocated_ += size; total_count_++; @@ -44,20 +77,148 @@ class HeapProfile { allocations_.push_back({id, size, 1}); } + enum class HPProfileType { + NONE, + SHORT, + FULL + }; + + HPProfileType NeededProfileType() { + HPProfileType res = HPProfileType::NONE; + static float lim_percent = + (float)(hp_flags_->heap_profile_out_full_lim) / 100; + uptr cur_rss = GetRSS(); + u64 cur_time = NanoTime(); + + // Full profile initiated if and only if one of the following is true: + // * memory usage (Resident Set Size) has increased from the + // previous time; + // * memory usage has decreased, and we're set to track it; + // * profile timeout has expired, and we're set to track it. + if ((cur_rss > prev_rss_ * (1 + lim_percent)) || + (hp_flags_->heap_profile_out_decrease && + (cur_rss < prev_rss_ * (1 - lim_percent))) || + (hp_flags_->heap_profile_full_out_time && + TimeoutHasExpired(HPProfileType::FULL, cur_time))) { + res = HPProfileType::FULL; + prev_rss_ = cur_rss; + prev_profile_ts_ = cur_time; + prev_short_profile_ts_ = cur_time; + } + // Short profile initiated if and only if profile timeout has expired, + // and we're set to track it. + else if (hp_flags_->heap_profile_short_out_time && + TimeoutHasExpired(HPProfileType::SHORT, cur_time)) { + res = HPProfileType::SHORT; + prev_short_profile_ts_ = cur_time; + } + + return res; + } + + void HPPrintHeader(HPProfileType hp_prof_type) { + switch (hp_prof_type) { + case HPProfileType::NONE: + break; + case HPProfileType::SHORT: + HPPrintf("SHORT "); + case HPProfileType::FULL: + // prev_rss_ already set to current RSS + HPPrintf("HEAP PROFILE at RSS %zd%s", + hp_flags_->heap_profile_rss_mb ? prev_rss_ >> 20 : prev_rss_, + hp_flags_->heap_profile_rss_mb ? "Mb" : "b"); + if (hp_flags_->heap_profile_timestamp) { + // print timestamps since creation of HeapProfile object. + // prev_short_profile_ts_ set to current time in either case + u64 nsecs_since_start = prev_short_profile_ts_ - hp_start_ts_; + u64 ts_sec = nsecs_since_start / (1000 * 1000 * 1000); + u64 ts_usec = (nsecs_since_start % (1000 * 1000 * 1000)) / 1000; + HPPrintf(" time %llu.%06llu", ts_sec, ts_usec); + } + HPPrintf("\n"); + break; + } + } + + void HPPrintFooter(HPProfileType hp_prof_type) { + switch (hp_prof_type) { + case HPProfileType::SHORT: + case HPProfileType::FULL: + HPPrintf("\n\n"); + case HPProfileType::NONE: + break; + } + } + + void HPClearAllocPool() { + total_count_ = 0; + total_allocated_ = 0; + allocations_.clear(); + }; + + + private: + // Basically same as __sanitizer::StackTrace::Print(), + // but with HPPrintf() calls. + void PrintStackTrace(StackTrace stack_trace) { + if (stack_trace.trace == nullptr || stack_trace.size == 0) { + HPPrintf(" \n\n"); + return; + } + + InternalScopedString frame_desc(GetPageSizeCached() * 2); + InternalScopedString dedup_token(GetPageSizeCached()); + int dedup_frames = common_flags()->dedup_token_length; + uptr frame_num = 0; + + for (uptr i = 0; i < stack_trace.size && stack_trace.trace[i]; i++) { + // PCs in stack traces are actually the return addresses, that is, + // addresses of the next instructions after the call. + uptr pc = StackTrace::GetPreviousInstructionPc(stack_trace.trace[i]); + SymbolizedStack *frames = Symbolizer::GetOrInit()->SymbolizePC(pc); + CHECK(frames); + + for (SymbolizedStack *cur = frames; cur; cur = cur->next) { + frame_desc.clear(); + RenderFrame(&frame_desc, common_flags()->stack_trace_format, + frame_num++, cur->info, common_flags()->symbolize_vs_style, + common_flags()->strip_path_prefix); + HPPrintf("%s\n", frame_desc.data()); + + if (dedup_frames-- > 0) { + if (dedup_token.length()) + dedup_token.append("--"); + dedup_token.append(cur->info.function); + } + } + + frames->ClearAll(); + } + + // Always print a trailing empty line after stack trace. + HPPrintf("\n"); + if (dedup_token.length()) + HPPrintf("DEDUP_TOKEN: %s\n", dedup_token.data()); + } + + + public: void Print(uptr top_percent) { InternalSort(&allocations_, allocations_.size(), [](const AllocationSite &a, const AllocationSite &b) { return a.total_size > b.total_size; }); - CHECK(total_allocated_); - uptr total_shown = 0; - Printf("Live Heap Allocations: %zd bytes from %zd allocations; " + HPPrintf("Live Heap Allocations: %zd bytes from %zd allocations; " "showing top %zd%%\n", total_allocated_, total_count_, top_percent); + if (UNLIKELY(!total_allocated_)) { + return; + } + uptr total_shown = 0; for (uptr i = 0; i < allocations_.size(); i++) { auto &a = allocations_[i]; - Printf("%zd byte(s) (%zd%%) in %zd allocation(s)\n", a.total_size, - a.total_size * 100 / total_allocated_, a.count); - StackDepotGet(a.id).Print(); + HPPrintf("%zd byte(s) (%zd%%) in %zd allocation(s)\n", + a.total_size, a.total_size * 100 / total_allocated_, a.count); + PrintStackTrace(StackDepotGet(a.id)); total_shown += a.total_size; if (total_shown * 100 / total_allocated_ > top_percent) break; @@ -68,31 +229,145 @@ class HeapProfile { uptr total_allocated_ = 0; uptr total_count_ = 0; InternalMmapVector allocations_; + + const CommonFlags *hp_flags_; + + SpinMutex hp_report_file_mu_; + ReportFile hp_private_report_file_ = {&hp_report_file_mu_, + kStderrFd, "", "", 0}; + ReportFile *hp_report_file_; + + // Keep track of previous profile values + uptr prev_rss_ = 0; + u64 prev_profile_ts_ = 0; + u64 prev_short_profile_ts_ = 0; + u64 hp_start_ts_; + + void HPRawWrite(const char *buffer) { + hp_report_file_->Write(buffer, internal_strlen(buffer)); + } + + // A helper function dumping arbitrary sized buffer to report file. + // Basically same as __sanitizer::SharedPrintfCode. + void HPVPrintf(const char *format, va_list args) { + va_list args2; + va_copy(args2, args); + const int kLen = 16 * 1024; + + // |local_buffer| is small enough not to overflow the stack and/or violate + // the stack limit enforced by TSan (-Wframe-larger-than=512). On the other + // hand, the bigger the buffer is, the more the chance the error report will + // fit into it. + char local_buffer[400]; + + int needed_length; + char *buffer = local_buffer; + int buffer_size = ARRAY_SIZE(local_buffer); + + // First try to print a message using a local buffer, and then fall back to + // mmaped buffer. + for (int use_mmap = 0; use_mmap < 2; use_mmap++) { + if (use_mmap) { + va_end(args); + va_copy(args, args2); + buffer = (char*)MmapOrDie(kLen, "HPVPrintf"); + buffer_size = kLen; + } + + needed_length = 0; + needed_length += internal_vsnprintf(buffer + needed_length, + buffer_size - needed_length, + format, args); + + // Check that data fits into the current buffer. + // If it does not, report and die. + // If it does, just print it. + if (needed_length >= buffer_size) { + if (!use_mmap) continue; + RAW_CHECK_MSG(needed_length < kLen, + "Buffer in Report is too short!\n"); + } + break; + } + + HPRawWrite(buffer); + + // If we had mapped any memory, clean it up. + if (buffer != local_buffer) + UnmapOrDie((void *)buffer, buffer_size); + va_end(args2); + } + + bool TimeoutHasExpired(HPProfileType hp_prof_type, u64 cur_time) { + if (UNLIKELY(hp_prof_type == HPProfileType::NONE)) { + return false; + } + + u64 timeout_msecs; + u64 prev_ts; + u64 threshold_ts; + + switch (hp_prof_type) { + case HPProfileType::FULL: + timeout_msecs = hp_flags_->heap_profile_full_out_time; + prev_ts = prev_profile_ts_; + break; + + case HPProfileType::SHORT: + timeout_msecs = hp_flags_->heap_profile_short_out_time; + prev_ts = prev_short_profile_ts_; + break; + + default: + break; + } + + threshold_ts = prev_ts + timeout_msecs * 1000 * 1000; + return cur_time >= threshold_ts; + } }; static void ChunkCallback(uptr chunk, void *arg) { - HeapProfile *hp = reinterpret_cast(arg); AsanChunkView cv = FindHeapChunkByAllocBeg(chunk); if (!cv.IsAllocated()) return; u32 id = cv.GetAllocStackId(); if (!id) return; - hp->Insert(id, cv.UsedSize()); + heap_profile->Insert(id, cv.UsedSize()); } static void MemoryProfileCB(const SuspendedThreadsList &suspended_threads_list, void *argument) { - HeapProfile hp; - __lsan::ForEachChunk(ChunkCallback, &hp); - hp.Print(reinterpret_cast(argument)); + heap_profile->HPClearAllocPool(); + // No need to pass anything to callback, so just pass null + __lsan::ForEachChunk(ChunkCallback, nullptr); + heap_profile->Print(reinterpret_cast(argument)); } +// Static buffer to be used in placement new +static unsigned char heap_profile_buf[sizeof(HeapProfile)]; + } // namespace __asan +#endif // CAN_SANITIZE_LEAKS + extern "C" { SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_print_memory_profile(uptr top_percent) { - __sanitizer::StopTheWorld(__asan::MemoryProfileCB, (void*)top_percent); +#if CAN_SANITIZE_LEAKS + // If this function is ever called, then the global + // heap_profile object is guaranteed to be used. + if (UNLIKELY(__asan::heap_profile == nullptr)) { + __asan::heap_profile = new(__asan::heap_profile_buf) + __asan::HeapProfile(__sanitizer::common_flags()); + } + + __asan::HeapProfile::HPProfileType hp_prof_type = + __asan::heap_profile->NeededProfileType(); + __asan::heap_profile->HPPrintHeader(hp_prof_type); + if (hp_prof_type == __asan::HeapProfile::HPProfileType::FULL) { + __sanitizer::StopTheWorld(__asan::MemoryProfileCB, (void*)top_percent); + } + __asan::heap_profile->HPPrintFooter(hp_prof_type); +#endif } } // extern "C" - -#endif // CAN_SANITIZE_LEAKS diff --git a/libsanitizer/sanitizer_common/sanitizer_common.cc b/libsanitizer/sanitizer_common/sanitizer_common.cc index d84cd8a..21be01d 100644 --- a/libsanitizer/sanitizer_common/sanitizer_common.cc +++ b/libsanitizer/sanitizer_common/sanitizer_common.cc @@ -519,5 +519,9 @@ SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void __sanitizer_print_memory_profile(int top_percent) { (void)top_percent; } + +SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE +void HeapProfilePrintf(const char *format, va_list args) { +} #endif } // extern "C" diff --git a/libsanitizer/sanitizer_common/sanitizer_common_libcdep.cc b/libsanitizer/sanitizer_common/sanitizer_common_libcdep.cc index a50ab14..3c42911 100644 --- a/libsanitizer/sanitizer_common/sanitizer_common_libcdep.cc +++ b/libsanitizer/sanitizer_common/sanitizer_common_libcdep.cc @@ -76,7 +76,7 @@ void BackgroundThread(void *arg) { uptr prev_reported_rss = 0; uptr prev_reported_stack_depot_size = 0; bool reached_soft_rss_limit = false; - uptr rss_during_last_reported_profile = 0; + while (true) { SleepForMillis(100); uptr current_rss_mb = GetRSS() >> 20; @@ -118,11 +118,10 @@ void BackgroundThread(void *arg) { SoftRssLimitExceededCallback(false); } } - if (heap_profile && - current_rss_mb > rss_during_last_reported_profile * 1.1) { - Printf("\n\nHEAP PROFILE at RSS %zdMb\n", current_rss_mb); - __sanitizer_print_memory_profile(90); - rss_during_last_reported_profile = current_rss_mb; + if (heap_profile) { + __sanitizer_print_memory_profile( + common_flags()->heap_profile_top_percent + ); } } } diff --git a/libsanitizer/sanitizer_common/sanitizer_flags.inc b/libsanitizer/sanitizer_common/sanitizer_flags.inc index 4b651ca..836f872 100644 --- a/libsanitizer/sanitizer_common/sanitizer_flags.inc +++ b/libsanitizer/sanitizer_common/sanitizer_flags.inc @@ -121,7 +121,32 @@ COMMON_FLAG(uptr, soft_rss_limit_mb, 0, " until the RSS goes below the soft limit." " This limit does not affect memory allocations other than" " malloc/new.") -COMMON_FLAG(bool, heap_profile, false, "Experimental heap profiler, asan-only") +COMMON_FLAG(bool, heap_profile, false, + "Experimental. Enables heap profiler (asan-only).") +COMMON_FLAG(bool, heap_profile_timestamp, false, + "If set, prints timestamps along with heap profiler data.") +COMMON_FLAG(int, heap_profile_out_full_lim, 10, + "Threshold value (in percentages) of RSS growth which" + " triggers printing memory usage by heap profiler.") +COMMON_FLAG(bool, heap_profile_out_decrease, false, + "If set, prints memory profile on memory usage decrease.") +COMMON_FLAG(uptr, heap_profile_full_out_time, 0, + "If non-zero, sets timeout (in milliseconds) for unconditional" + " printing of memory usage data by heap profiler; otherwise" + " there's no timeout.") +COMMON_FLAG(uptr, heap_profile_short_out_time, 0, + "If non-zero, sets timeout (in milliseconds) for unconditional" + " printing of short memory usage data by heap profiler;" + " otherwise there's no timeout. This should be less than" + " heap_profile_full_out_time or no short output will be produced," + " as printing full memory profile has higher precedence.") +COMMON_FLAG(int, heap_profile_top_percent, 90, + "Sets threshold (in percentages) for memory allocations to be" + " shown, from largest to smallest.") +COMMON_FLAG(bool, heap_profile_rss_mb, true, + "If true, prints RSS memory usage in MBs instead of bytes.") +COMMON_FLAG(const char *, heap_profile_log_path, "", + "If set, uses separate file to write heap profile reports.") COMMON_FLAG(s32, allocator_release_to_os_interval_ms, kReleaseToOSIntervalNever, "Experimental. Only affects a 64-bit allocator. If set, tries to " "release unused memory to the OS, but not more often than this " diff --git a/libsanitizer/sanitizer_common/sanitizer_libc.h b/libsanitizer/sanitizer_common/sanitizer_libc.h index 98a4a9d..f817ef8 100644 --- a/libsanitizer/sanitizer_common/sanitizer_libc.h +++ b/libsanitizer/sanitizer_common/sanitizer_libc.h @@ -15,6 +15,8 @@ #ifndef SANITIZER_LIBC_H #define SANITIZER_LIBC_H +#include + // ----------- ATTENTION ------------- // This header should NOT include any other headers from sanitizer runtime. #include "sanitizer_internal_defs.h" @@ -54,6 +56,8 @@ char *internal_strstr(const char *haystack, const char *needle); // Works only for base=10 and doesn't set errno. s64 internal_simple_strtoll(const char *nptr, char **endptr, int base); int internal_snprintf(char *buffer, uptr length, const char *format, ...); +int internal_vsnprintf(char *buffer, uptr length, + const char *format, va_list args); // Return true if all bytes in [mem, mem+size) are zero. // Optimized for the case when the result is true. diff --git a/libsanitizer/sanitizer_common/sanitizer_printf.cc b/libsanitizer/sanitizer_common/sanitizer_printf.cc index c11113d..b6e6dbf 100644 --- a/libsanitizer/sanitizer_common/sanitizer_printf.cc +++ b/libsanitizer/sanitizer_common/sanitizer_printf.cc @@ -305,6 +305,12 @@ void Report(const char *format, ...) { va_end(args); } +int internal_vsnprintf(char *buff, uptr length, + const char *format, + va_list args) { + return VSNPrintf(buff, length, format, args); +} + // Writes at most "length" symbols to "buffer" (including trailing '\0'). // Returns the number of symbols that should have been written to buffer // (not including trailing '\0'). Thus, the string is truncated -- 2.7.4