This is an automated email from the ASF dual-hosted git repository. wwbmmm pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push: new 75763d4c Fix malloc deadlock caused by contention profiler (#2684) 75763d4c is described below commit 75763d4c5064d6cc847fc93383087bd8cdbaf1be Author: Bright Chen <chenguangmin...@foxmail.com> AuthorDate: Tue Jul 23 10:25:56 2024 +0800 Fix malloc deadlock caused by contention profiler (#2684) --- src/brpc/builtin/hotspots_service.cpp | 4 +- src/brpc/builtin/pprof_service.cpp | 4 +- src/brpc/policy/rtmp_protocol.cpp | 2 +- src/brpc/socket.cpp | 3 +- src/bthread/mutex.cpp | 43 ++++++++--- src/butil/debug/stack_trace.cc | 9 ++- src/butil/debug/stack_trace.h | 12 ++- src/butil/debug/stack_trace_posix.cc | 20 ++++- src/butil/iobuf_profiler.cpp | 6 +- src/butil/object_pool.h | 5 ++ src/butil/object_pool_inl.h | 12 +++ .../dynamic_annotations/dynamic_annotations.c | 2 +- src/butil/third_party/symbolize/symbolize.cc | 86 +++++++++++++++------- src/butil/third_party/symbolize/symbolize.h | 2 + test/stack_trace_unittest.cc | 79 +++++++++++++++++--- 15 files changed, 221 insertions(+), 68 deletions(-) diff --git a/src/brpc/builtin/hotspots_service.cpp b/src/brpc/builtin/hotspots_service.cpp index abb0bb4e..f714843d 100644 --- a/src/brpc/builtin/hotspots_service.cpp +++ b/src/brpc/builtin/hotspots_service.cpp @@ -33,8 +33,8 @@ #include "brpc/details/tcmalloc_extension.h" extern "C" { -int __attribute__((weak)) ProfilerStart(const char* fname); -void __attribute__((weak)) ProfilerStop(); +int BAIDU_WEAK ProfilerStart(const char* fname); +void BAIDU_WEAK ProfilerStop(); } namespace bthread { diff --git a/src/brpc/builtin/pprof_service.cpp b/src/brpc/builtin/pprof_service.cpp index eba71377..e9591698 100644 --- a/src/brpc/builtin/pprof_service.cpp +++ b/src/brpc/builtin/pprof_service.cpp @@ -41,8 +41,8 @@ extern "C" { #if defined(OS_LINUX) extern char *program_invocation_name; #endif -int __attribute__((weak)) ProfilerStart(const char* fname); -void __attribute__((weak)) ProfilerStop(); +int BAIDU_WEAK ProfilerStart(const char* fname); +void BAIDU_WEAK ProfilerStop(); } namespace bthread { diff --git a/src/brpc/policy/rtmp_protocol.cpp b/src/brpc/policy/rtmp_protocol.cpp index 33a0c0c6..99a3e085 100644 --- a/src/brpc/policy/rtmp_protocol.cpp +++ b/src/brpc/policy/rtmp_protocol.cpp @@ -39,7 +39,7 @@ // we mark the symbol as weak. If the runtime does not have the function, // handshaking will fallback to the simple one. extern "C" { -const EVP_MD* __attribute__((weak)) EVP_sha256(void); +const EVP_MD* BAIDU_WEAK EVP_sha256(void); } diff --git a/src/brpc/socket.cpp b/src/brpc/socket.cpp index 40af0204..8fcfae9c 100644 --- a/src/brpc/socket.cpp +++ b/src/brpc/socket.cpp @@ -57,8 +57,7 @@ #endif namespace bthread { -size_t __attribute__((weak)) -get_sizes(const bthread_id_list_t* list, size_t* cnt, size_t n); +size_t BAIDU_WEAK get_sizes(const bthread_id_list_t* list, size_t* cnt, size_t n); } diff --git a/src/bthread/mutex.cpp b/src/bthread/mutex.cpp index 87a88876..8212c84c 100644 --- a/src/bthread/mutex.cpp +++ b/src/bthread/mutex.cpp @@ -20,7 +20,6 @@ // Date: Sun Aug 3 12:46:15 CST 2014 #include <pthread.h> -#include <execinfo.h> #include <dlfcn.h> // dlsym #include <fcntl.h> // O_RDONLY #include "butil/atomicops.h" @@ -34,9 +33,12 @@ #include "butil/files/file_path.h" #include "butil/file_util.h" #include "butil/unique_ptr.h" +#include "butil/memory/scope_guard.h" #include "butil/third_party/murmurhash3/murmurhash3.h" +#include "butil/third_party/symbolize/symbolize.h" #include "butil/logging.h" #include "butil/object_pool.h" +#include "butil/debug/stack_trace.h" #include "bthread/butex.h" // butex_* #include "bthread/mutex.h" // bthread_mutex_t #include "bthread/sys_futex.h" @@ -44,16 +46,12 @@ #include "butil/debug/stack_trace.h" extern "C" { -extern void* __attribute__((weak)) _dl_sym(void* handle, const char* symbol, void* caller); +extern void* BAIDU_WEAK _dl_sym(void* handle, const char* symbol, void* caller); } -extern int __attribute__((weak)) GetStackTrace(void** result, int max_depth, int skip_count); namespace bthread { // Warm up backtrace before main(). -void* dummy_buf[4]; -const int ALLOW_UNUSED dummy_bt = GetStackTrace - ? GetStackTrace(dummy_buf, arraysize(dummy_buf), 0) - : backtrace(dummy_buf, arraysize(dummy_buf)); +const butil::debug::StackTrace ALLOW_UNUSED dummy_bt; // For controlling contentions collected per second. static bvar::CollectorSpeedLimit g_cp_sl = BVAR_COLLECTOR_SPEED_LIMIT_INITIALIZER; @@ -468,6 +466,10 @@ inline uint64_t hash_mutex_ptr(const Mutex* m) { // code are never sampled, otherwise deadlock may occur. static __thread bool tls_inside_lock = false; +// Warn up some singleton objects used in contention profiler +// to avoid deadlock in malloc call stack. +static __thread bool tls_warn_up = false; + // ++tls_pthread_lock_count when pthread locking, // --tls_pthread_lock_count when pthread unlocking. // Only when it is equal to 0, it is safe for the bthread to be scheduled. @@ -559,6 +561,26 @@ inline bool remove_pthread_contention_site(const Mutex* mutex, // Submit the contention along with the callsite('s stacktrace) void submit_contention(const bthread_contention_site_t& csite, int64_t now_ns) { tls_inside_lock = true; + BRPC_SCOPE_EXIT { + tls_inside_lock = false; + }; + + butil::debug::StackTrace stack(true); // May lock. + if (0 == stack.FrameCount()) { + return; + } + // There are two situations where we need to check whether in the + // malloc call stack: + // 1. Warn up some singleton objects used in `submit_contention' + // to avoid deadlock in malloc call stack. + // 2. LocalPool is empty, GlobalPool may allocate memory by malloc. + if (!tls_warn_up || butil::local_pool_free_empty<SampledContention>()) { + // In malloc call stack, can not submit contention. + if (stack.FindSymbol((void*)malloc)) { + return; + } + } + auto sc = butil::get_object<SampledContention>(); // Normalize duration_us and count so that they're addable in later // processings. Notice that sampling_range is adjusted periodically by @@ -566,11 +588,10 @@ void submit_contention(const bthread_contention_site_t& csite, int64_t now_ns) { sc->duration_ns = csite.duration_ns * bvar::COLLECTOR_SAMPLING_BASE / csite.sampling_range; sc->count = bvar::COLLECTOR_SAMPLING_BASE / (double)csite.sampling_range; - sc->nframes = GetStackTrace - ? GetStackTrace(sc->stack, arraysize(sc->stack), 0) - : backtrace(sc->stack, arraysize(sc->stack)); // may lock + sc->nframes = stack.CopyAddressTo(sc->stack, arraysize(sc->stack)); sc->submit(now_ns / 1000); // may lock - tls_inside_lock = false; + // Once submit a contention, complete warn up. + tls_warn_up = true; } namespace internal { diff --git a/src/butil/debug/stack_trace.cc b/src/butil/debug/stack_trace.cc index d8d60ecb..38abede9 100644 --- a/src/butil/debug/stack_trace.cc +++ b/src/butil/debug/stack_trace.cc @@ -21,9 +21,6 @@ StackTrace::StackTrace(const void* const* trace, size_t count) { count_ = count; } -StackTrace::~StackTrace() { -} - const void *const *StackTrace::Addresses(size_t* count) const { *count = count_; if (count_) @@ -31,6 +28,12 @@ const void *const *StackTrace::Addresses(size_t* count) const { return NULL; } +size_t StackTrace::CopyAddressTo(void** buffer, size_t max_nframes) const { + size_t nframes = std::min(count_, max_nframes); + memcpy(buffer, trace_, nframes * sizeof(void*)); + return nframes; +} + std::string StackTrace::ToString() const { std::stringstream stream; #if !defined(__UCLIBC__) diff --git a/src/butil/debug/stack_trace.h b/src/butil/debug/stack_trace.h index 574f12d6..5c6545ad 100644 --- a/src/butil/debug/stack_trace.h +++ b/src/butil/debug/stack_trace.h @@ -59,12 +59,20 @@ class BUTIL_EXPORT StackTrace { // Copying and assignment are allowed with the default functions. - ~StackTrace(); - // Gets an array of instruction pointer values. |*count| will be set to the // number of elements in the returned array. const void* const* Addresses(size_t* count) const; + // Gets the number of frames in the stack trace. + size_t FrameCount() const { return count_; } + + // Copies the stack trace to |buffer|, + // where the size is min(max_nframes, frame_count()). + size_t CopyAddressTo(void** dest, size_t max_nframes) const; + + // Whether if the given symbol is found in the stack trace. + bool FindSymbol(void* symbol) const; + // Prints the stack trace to stderr. void Print() const; diff --git a/src/butil/debug/stack_trace_posix.cc b/src/butil/debug/stack_trace_posix.cc index df16d498..878f94a7 100644 --- a/src/butil/debug/stack_trace_posix.cc +++ b/src/butil/debug/stack_trace_posix.cc @@ -45,7 +45,7 @@ #include "butil/third_party/symbolize/symbolize.h" #endif -extern int __attribute__((weak)) GetStackTrace(void** result, int max_depth, int skip_count); +extern int BAIDU_WEAK GetStackTrace(void** result, int max_depth, int skip_count); namespace butil { namespace debug { @@ -768,6 +768,24 @@ StackTrace::StackTrace(bool exclude_self) { } } +bool StackTrace::FindSymbol(void* symbol) const { +#if !defined(__UCLIBC__) + for (size_t i = 0; i < count_; ++i) { + uint64_t saddr; + // Subtract by one as return address of function may be in the next + // function when a function is annotated as noreturn. + void* address = static_cast<char*>(trace_[i]) - 1; + if (!google::SymbolizeAddress(address, &saddr)) { + continue; + } + if ((void*)saddr == symbol) { + return true; + } + } +#endif + return false; +} + void StackTrace::Print() const { // NOTE: This code MUST be async-signal safe (it's used by in-process // stack dumping signal handler). NO malloc or stdio is allowed here. diff --git a/src/butil/iobuf_profiler.cpp b/src/butil/iobuf_profiler.cpp index 15356955..11353beb 100644 --- a/src/butil/iobuf_profiler.cpp +++ b/src/butil/iobuf_profiler.cpp @@ -24,7 +24,7 @@ #include "butil/hash.h" #include <execinfo.h> -extern int __attribute__((weak)) GetStackTrace(void** result, int max_depth, int skip_count); +extern int BAIDU_WEAK GetStackTrace(void** result, int max_depth, int skip_count); namespace butil { @@ -296,9 +296,7 @@ void SubmitIOBufSample(IOBuf::Block* block, int64_t ref) { auto sample = IOBufSample::New(); sample->block = block; sample->count = ref; - sample->nframes = GetStackTrace(sample->stack, - arraysize(sample->stack), - 0); // may lock + sample->nframes = GetStackTrace(sample->stack, arraysize(sample->stack), 0); IOBufProfiler::GetInstance()->Submit(sample); } diff --git a/src/butil/object_pool.h b/src/butil/object_pool.h index b663fe84..c8690821 100644 --- a/src/butil/object_pool.h +++ b/src/butil/object_pool.h @@ -69,6 +69,11 @@ template <typename T> struct ObjectPoolValidator { namespace butil { +// Whether if FreeChunk of LocalPool is empty. +template <typename T> inline bool local_pool_free_empty() { + return ObjectPool<T>::singleton()->local_free_empty(); +} + // Get an object typed |T|. The object should be cleared before usage. // NOTE: T must be default-constructible. template <typename T> inline T* get_object() { diff --git a/src/butil/object_pool_inl.h b/src/butil/object_pool_inl.h index 0371eff0..6f10e03a 100644 --- a/src/butil/object_pool_inl.h +++ b/src/butil/object_pool_inl.h @@ -216,6 +216,10 @@ public: return -1; } + inline bool free_empty() const { + return 0 == _cur_free.nfree; + } + private: ObjectPool* _pool; Block* _cur_block; @@ -223,6 +227,14 @@ public: FreeChunk _cur_free; }; + inline bool local_free_empty() { + LocalPool* lp = get_or_new_local_pool(); + if (BAIDU_LIKELY(lp != NULL)) { + return lp->free_empty(); + } + return true; + } + inline T* get_object() { LocalPool* lp = get_or_new_local_pool(); if (BAIDU_LIKELY(lp != NULL)) { diff --git a/src/butil/third_party/dynamic_annotations/dynamic_annotations.c b/src/butil/third_party/dynamic_annotations/dynamic_annotations.c index a68a826b..b746203c 100644 --- a/src/butil/third_party/dynamic_annotations/dynamic_annotations.c +++ b/src/butil/third_party/dynamic_annotations/dynamic_annotations.c @@ -255,7 +255,7 @@ static int GetRunningOnValgrind(void) { } /* See the comments in dynamic_annotations.h */ -int __attribute__((weak)) RunningOnValgrind(void) { +int DYNAMIC_ANNOTATIONS_ATTRIBUTE_WEAK RunningOnValgrind(void) { static volatile int running_on_valgrind = -1; /* C doesn't have thread-safe initialization of statics, and we don't want to depend on pthread_once here, so hack it. */ diff --git a/src/butil/third_party/symbolize/symbolize.cc b/src/butil/third_party/symbolize/symbolize.cc index bb693a72..80a8ce3d 100644 --- a/src/butil/third_party/symbolize/symbolize.cc +++ b/src/butil/third_party/symbolize/symbolize.cc @@ -279,8 +279,8 @@ bool BAIDU_WEAK GetSectionHeaderByName(int fd, const char *name, size_t name_len // inlined. static ATTRIBUTE_NOINLINE bool FindSymbol(uint64_t pc, const int fd, char *out, int out_size, - uint64_t symbol_offset, const ElfW(Shdr) *strtab, - const ElfW(Shdr) *symtab) { + uint64_t *out_saddr, uint64_t symbol_offset, + const ElfW(Shdr) *strtab, const ElfW(Shdr) *symtab) { if (symtab == NULL) { return false; } @@ -311,10 +311,15 @@ FindSymbol(uint64_t pc, const int fd, char *out, int out_size, if (symbol.st_value != 0 && // Skip null value symbols. symbol.st_shndx != 0 && // Skip undefined symbols. start_address <= pc && pc < end_address) { - ssize_t len1 = ReadFromOffset(fd, out, out_size, - strtab->sh_offset + symbol.st_name); - if (len1 <= 0 || memchr(out, '\0', out_size) == NULL) { - return false; + if (NULL != out) { + ssize_t len1 = ReadFromOffset( + fd, out, out_size, strtab->sh_offset + symbol.st_name); + if (len1 <= 0 || memchr(out, '\0', out_size) == NULL) { + return false; + } + } + if (NULL != out_saddr) { + *out_saddr = start_address; } return true; // Obtained the symbol name. } @@ -330,6 +335,7 @@ FindSymbol(uint64_t pc, const int fd, char *out, int out_size, // false. static bool GetSymbolFromObjectFile(const int fd, uint64_t pc, char *out, int out_size, + uint64_t *out_saddr, uint64_t map_start_address) { // Read the ELF header. ElfW(Ehdr) elf_header; @@ -351,8 +357,8 @@ static bool GetSymbolFromObjectFile(const int fd, uint64_t pc, symtab.sh_link * sizeof(symtab))) { return false; } - if (FindSymbol(pc, fd, out, out_size, symbol_offset, - &strtab, &symtab)) { + if (FindSymbol(pc, fd, out, out_size, out_saddr, + symbol_offset, &strtab, &symtab)) { return true; // Found the symbol in a regular symbol table. } } @@ -364,8 +370,8 @@ static bool GetSymbolFromObjectFile(const int fd, uint64_t pc, symtab.sh_link * sizeof(symtab))) { return false; } - if (FindSymbol(pc, fd, out, out_size, symbol_offset, - &strtab, &symtab)) { + if (FindSymbol(pc, fd, out, out_size, out_saddr, + symbol_offset, &strtab, &symtab)) { return true; // Found the symbol in a dynamic symbol table. } } @@ -727,17 +733,21 @@ void SafeAppendHexNumber(uint64_t value, char* dest, int dest_size) { // To keep stack consumption low, we would like this function to not // get inlined. static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, - int out_size) { + int out_size, + uint64_t *out_saddr) { uint64_t pc0 = reinterpret_cast<uintptr_t>(pc); uint64_t start_address = 0; uint64_t base_address = 0; int object_fd = -1; - if (out_size < 1) { + if ((NULL == out || out_size < 1) && + NULL == out_saddr) { return false; } - out[0] = '\0'; - SafeAppendString("(", out, out_size); + if (NULL != out) { + out[0] = '\0'; + SafeAppendString("(", out, out_size); + } if (g_symbolize_open_object_file_callback) { object_fd = g_symbolize_open_object_file_callback(pc0, start_address, @@ -752,7 +762,7 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, // Check whether a file name was returned. if (object_fd < 0) { - if (out[1]) { + if (NULL != out && out[1] && NULL == out_saddr) { // The object file containing PC was determined successfully however the // object file was not opened successfully. This is still considered // success because the object file name and offset are known and tools @@ -785,12 +795,15 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, } } if (!GetSymbolFromObjectFile(wrapped_object_fd.get(), pc0, - out, out_size, start_address)) { + out, out_size, out_saddr, + start_address)) { return false; } - // Symbolization succeeded. Now we try to demangle the symbol. - DemangleInplace(out, out_size); + if (NULL != out) { + // Symbolization succeeded. Now we try to demangle the symbol. + DemangleInplace(out, out_size); + } return true; } @@ -804,17 +817,24 @@ _END_GOOGLE_NAMESPACE_ _START_GOOGLE_NAMESPACE_ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out, - int out_size) { - Dl_info info; - if (dladdr(pc, &info)) { - if ((int)strlen(info.dli_sname) < out_size) { - strcpy(out, info.dli_sname); - // Symbolization succeeded. Now we try to demangle the symbol. - DemangleInplace(out, out_size); - return true; + int out_size, + uint64_t *out_saddr) { + Dl_info info{}; + if (0 == dladdr(pc, &info)) { + return false; + } + if (NULL != out) { + if ((int)strlen(info.dli_sname) >= out_size) { + return false; } + strcpy(out, info.dli_sname); + // Symbolization succeeded. Now we try to demangle the symbol. + DemangleInplace(out, out_size); } - return false; + if (NULL != out_saddr) { + *out_saddr = (uint64_t)info.dli_saddr; + } + return true; } _END_GOOGLE_NAMESPACE_ @@ -827,7 +847,12 @@ _START_GOOGLE_NAMESPACE_ bool BAIDU_WEAK Symbolize(void *pc, char *out, int out_size) { SAFE_ASSERT(out_size >= 0); - return SymbolizeAndDemangle(pc, out, out_size); + return SymbolizeAndDemangle(pc, out, out_size, NULL); +} + +bool BAIDU_WEAK SymbolizeAddress(void *pc, uint64_t *out) { + SAFE_ASSERT(NULL != out); + return SymbolizeAndDemangle(pc, NULL, 0, out); } _END_GOOGLE_NAMESPACE_ @@ -846,6 +871,11 @@ bool BAIDU_WEAK Symbolize(void *pc, char *out, int out_size) { return false; } +bool BAIDU_WEAK SymbolizeAddress(void *pc, uint64_t *out) { + assert(0); + return false; +} + _END_GOOGLE_NAMESPACE_ #endif diff --git a/src/butil/third_party/symbolize/symbolize.h b/src/butil/third_party/symbolize/symbolize.h index 4c857c12..7bd31e41 100644 --- a/src/butil/third_party/symbolize/symbolize.h +++ b/src/butil/third_party/symbolize/symbolize.h @@ -150,6 +150,8 @@ _START_GOOGLE_NAMESPACE_ // returns false. bool Symbolize(void *pc, char *out, int out_size); +bool SymbolizeAddress(void *pc, uint64_t *out); + _END_GOOGLE_NAMESPACE_ #endif // BUTIL_SYMBOLIZE_H_ diff --git a/test/stack_trace_unittest.cc b/test/stack_trace_unittest.cc index e2052b6f..35a226e9 100644 --- a/test/stack_trace_unittest.cc +++ b/test/stack_trace_unittest.cc @@ -8,8 +8,33 @@ #include "butil/debug/stack_trace.h" #include "butil/logging.h" +#include "butil/scoped_lock.h" #include <gtest/gtest.h> +extern "C" { +void TestFindSymbol1(); +void TestFindSymbol2(); +void TestFindSymbol3(); + +void TestFindSymbol1() { + butil::debug::StackTrace trace; + ASSERT_TRUE(trace.ToString().find("TestFindSymbol1") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol2") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol3") == std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol2)) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol1)) << trace.ToString(); + ASSERT_FALSE(trace.FindSymbol((void*)TestFindSymbol3)) << trace.ToString(); +} + +void TestFindSymbol2() { + TestFindSymbol1(); +} + +void TestFindSymbol3() { + TestFindSymbol1(); +} +} + namespace butil { namespace debug { @@ -107,20 +132,25 @@ TEST_F(StackTraceTest, MAYBE_OutputToStream) { #endif // define(OS_MACOSX) } -// The test is used for manual testing, e.g., to see the raw output. -TEST_F(StackTraceTest, DebugOutputToStream) { - { - StackTrace trace; - std::ostringstream os; - trace.OutputToStream(&os); - VLOG(1) << os.str(); - } - { - StackTrace trace(true); +void CheckDebugOutputToStream(bool exclude_self) { + StackTrace trace(exclude_self); + size_t count; + const void* const* addrs = trace.Addresses(&count); + ASSERT_EQ(count, trace.FrameCount()); + void* addr = malloc(sizeof(void*) * count); + size_t copied_count = trace.CopyAddressTo((void**)addr, count); + ASSERT_EQ(count, copied_count); + ASSERT_EQ(0, memcmp(addrs, addr, sizeof(void*) * count)); + std::ostringstream os; trace.OutputToStream(&os); VLOG(1) << os.str(); - } +} + +// The test is used for manual testing, e.g., to see the raw output. +TEST_F(StackTraceTest, DebugOutputToStream) { + CheckDebugOutputToStream(false); + CheckDebugOutputToStream(true); } // The test is used for manual testing, e.g., to see the raw output. @@ -206,5 +236,32 @@ TEST_F(StackTraceTest, itoa_r) { } #endif // defined(OS_POSIX) && !defined(OS_ANDROID) +void TestFindSymbol1(); +void TestFindSymbol2(); +void TestFindSymbol3(); + +void TestFindSymbol1() { + butil::debug::StackTrace trace; + ASSERT_TRUE(trace.ToString().find("TestFindSymbol1") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol2") != std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.ToString().find("TestFindSymbol3") == std::string::npos) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol2)) << trace.ToString(); + ASSERT_TRUE(trace.FindSymbol((void*)TestFindSymbol1)) << trace.ToString(); + ASSERT_FALSE(trace.FindSymbol((void*)TestFindSymbol3)) << trace.ToString(); +} + +void TestFindSymbol2() { + TestFindSymbol1(); +} + +void TestFindSymbol3() { + TestFindSymbol1(); +} + +TEST_F(StackTraceTest, find_symbol) { + ::TestFindSymbol2(); + TestFindSymbol2(); +} + } // namespace debug } // namespace butil --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@brpc.apache.org For additional commands, e-mail: dev-h...@brpc.apache.org