/Users/deen/code/yugabyte-db/src/yb/util/debug-util.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // |
18 | | // The following only applies to changes made to this file as part of YugaByte development. |
19 | | // |
20 | | // Portions Copyright (c) YugaByte, Inc. |
21 | | // |
22 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
23 | | // in compliance with the License. You may obtain a copy of the License at |
24 | | // |
25 | | // http://www.apache.org/licenses/LICENSE-2.0 |
26 | | // |
27 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
28 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
29 | | // or implied. See the License for the specific language governing permissions and limitations |
30 | | // under the License. |
31 | | // |
32 | | |
33 | | #include "yb/util/debug-util.h" |
34 | | |
35 | | #include <execinfo.h> |
36 | | #include <dirent.h> |
37 | | #include <sys/syscall.h> |
38 | | |
39 | | #ifdef __linux__ |
40 | | #include <link.h> |
41 | | #include <cxxabi.h> |
42 | | #endif // __linux__ |
43 | | |
44 | | #ifdef __linux__ |
45 | | #include <backtrace.h> |
46 | | #endif // __linux__ |
47 | | |
48 | | #include <string> |
49 | | #include <iostream> |
50 | | #include <mutex> |
51 | | #include <regex> |
52 | | |
53 | | #include <glog/logging.h> |
54 | | |
55 | | #include "yb/gutil/hash/city.h" |
56 | | #include "yb/gutil/linux_syscall_support.h" |
57 | | #include "yb/gutil/macros.h" |
58 | | #include "yb/gutil/singleton.h" |
59 | | #include "yb/gutil/stringprintf.h" |
60 | | #include "yb/gutil/strings/numbers.h" |
61 | | |
62 | | #include "yb/util/enums.h" |
63 | | #include "yb/util/errno.h" |
64 | | #include "yb/util/format.h" |
65 | | #include "yb/util/lockfree.h" |
66 | | #include "yb/util/monotime.h" |
67 | | #include "yb/util/source_location.h" |
68 | | #include "yb/util/status.h" |
69 | | #include "yb/util/thread.h" |
70 | | |
71 | | using namespace std::literals; |
72 | | |
73 | | #if defined(__linux__) && !defined(NDEBUG) |
74 | | constexpr bool kDefaultUseLibbacktrace = true; |
75 | | #else |
76 | | constexpr bool kDefaultUseLibbacktrace = false; |
77 | | #endif |
78 | | |
79 | | DEFINE_bool(use_libbacktrace, kDefaultUseLibbacktrace, |
80 | | "Whether to use the libbacktrace library for symbolizing stack traces"); |
81 | | |
82 | | // Evil hack to grab a few useful functions from glog |
83 | | namespace google { |
84 | | |
85 | | extern int GetStackTrace(void** result, int max_depth, int skip_count); |
86 | | |
87 | | // Symbolizes a program counter. On success, returns true and write the |
88 | | // symbol name to "out". The symbol name is demangled if possible |
89 | | // (supports symbols generated by GCC 3.x or newer). Otherwise, |
90 | | // returns false. |
91 | | bool Symbolize(void *pc, char *out, int out_size); |
92 | | |
93 | | namespace glog_internal_namespace_ { |
94 | | extern void DumpStackTraceToString(std::string *s); |
95 | | } // namespace glog_internal_namespace_ |
96 | | } // namespace google |
97 | | |
98 | | // The %p field width for printf() functions is two characters per byte. |
99 | | // For some environments, add two extra bytes for the leading "0x". |
100 | | static const int kPrintfPointerFieldWidth = 2 + 2 * sizeof(void*); |
101 | | |
102 | | using std::string; |
103 | | |
104 | | namespace yb { |
105 | | |
106 | | // https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html |
107 | | enum DemangleStatus : int { |
108 | | kDemangleOk = 0, |
109 | | kDemangleMemAllocFailure = -1, |
110 | | kDemangleInvalidMangledName = -2, |
111 | | kDemangleInvalidArgument = -3 |
112 | | }; |
113 | | |
114 | | |
115 | | namespace { |
116 | | |
117 | | const char kUnknownSymbol[] = "(unknown)"; |
118 | | const char kStackTraceEntryFormat[] = " @ %*p %s"; |
119 | | |
120 | | #ifdef __linux__ |
121 | | |
122 | | // Remove path prefixes up to what looks like the root of the YB source tree, or the source tree |
123 | | // of other recognizable codebases. |
124 | | const char* NormalizeSourceFilePath(const char* file_path) { |
125 | | if (file_path == nullptr) { |
126 | | return file_path; |
127 | | } |
128 | | |
129 | | // Remove the leading "../../../" stuff. |
130 | | while (strncmp(file_path, "../", 3) == 0) { |
131 | | file_path += 3; |
132 | | } |
133 | | |
134 | | // This could be called arbitrarily early or late in program execution as part of backtrace, |
135 | | // so we're not using any std::string static constants here. |
136 | | #define YB_HANDLE_SOURCE_SUBPATH(subpath, prefix_len_to_remove) \ |
137 | | do { \ |
138 | | const char* const subpath_ptr = strstr(file_path, (subpath)); \ |
139 | | if (subpath_ptr != nullptr) { \ |
140 | | return subpath_ptr + (prefix_len_to_remove); \ |
141 | | } \ |
142 | | } while (0); |
143 | | |
144 | | YB_HANDLE_SOURCE_SUBPATH("/src/yb/", 5); |
145 | | YB_HANDLE_SOURCE_SUBPATH("/src/postgres/src/", 5); |
146 | | YB_HANDLE_SOURCE_SUBPATH("/src/rocksdb/", 5); |
147 | | YB_HANDLE_SOURCE_SUBPATH("/thirdparty/build/", 1); |
148 | | |
149 | | // These are Linuxbrew gcc's standard headers. Keep the path starting from "gcc/...". |
150 | | YB_HANDLE_SOURCE_SUBPATH("/Cellar/gcc/", 8); |
151 | | |
152 | | // TODO: replace postgres_build with just postgres. |
153 | | YB_HANDLE_SOURCE_SUBPATH("/postgres_build/src/", 1); |
154 | | |
155 | | #undef YB_HANDLE_SOURCE_SUBPATH |
156 | | |
157 | | return file_path; |
158 | | } |
159 | | |
160 | | struct SymbolizationContext { |
161 | | StackTraceLineFormat stack_trace_line_format = StackTraceLineFormat::DEFAULT; |
162 | | string* buf = nullptr; |
163 | | }; |
164 | | |
165 | | void BacktraceErrorCallback(void* data, const char* msg, int errnum) { |
166 | | bool reported = false; |
167 | | string* buf_ptr = nullptr; |
168 | | if (data) { |
169 | | auto* context = static_cast<SymbolizationContext*>(data); |
170 | | if (context->buf) { |
171 | | buf_ptr = context->buf; |
172 | | buf_ptr->append(StringPrintf("Backtrace error: %s (errnum=%d)\n", msg, errnum)); |
173 | | reported = true; |
174 | | } |
175 | | } |
176 | | |
177 | | if (!reported) { |
178 | | // A backup mechanism for error reporting. |
179 | | fprintf(stderr, "%s called with data=%p, msg=%s, errnum=%d, buf_ptr=%p\n", __func__, |
180 | | data, msg, errnum, buf_ptr); |
181 | | } |
182 | | } |
183 | | |
184 | | class GlobalBacktraceState { |
185 | | public: |
186 | | GlobalBacktraceState() { |
187 | | bt_state_ = backtrace_create_state( |
188 | | /* filename */ nullptr, |
189 | | /* threaded = */ 0, |
190 | | BacktraceErrorCallback, |
191 | | /* data */ nullptr); |
192 | | |
193 | | // To complete initialization we should call backtrace, otherwise it could fail in case of |
194 | | // concurrent initialization. |
195 | | // We do this in a separate thread to avoid deadlock. |
196 | | // TODO: implement proper fix to avoid other potential deadlocks/stuck threads related |
197 | | // to locking common mutexes from signal handler. See |
198 | | // https://github.com/yugabyte/yugabyte-db/issues/6672 for more details. |
199 | | std::thread backtrace_thread([&] { |
200 | | backtrace_full(bt_state_, /* skip = */ 1, DummyCallback, |
201 | | BacktraceErrorCallback, nullptr); |
202 | | }); |
203 | | backtrace_thread.join(); |
204 | | } |
205 | | |
206 | | backtrace_state* GetState() { return bt_state_; } |
207 | | |
208 | | std::mutex* mutex() { return &mutex_; } |
209 | | |
210 | | private: |
211 | | static int DummyCallback(void *const data, const uintptr_t pc, |
212 | | const char* const filename, const int lineno, |
213 | | const char* const original_function_name) { |
214 | | return 0; |
215 | | } |
216 | | |
217 | | struct backtrace_state* bt_state_; |
218 | | std::mutex mutex_; |
219 | | }; |
220 | | |
221 | | int BacktraceFullCallback(void *const data, const uintptr_t pc, |
222 | | const char* const filename, const int lineno, |
223 | | const char* const original_function_name) { |
224 | | assert(data != nullptr); |
225 | | const SymbolizationContext& context = *pointer_cast<SymbolizationContext*>(data); |
226 | | string* const buf = context.buf; |
227 | | int demangle_status = 0; |
228 | | char* const demangled_function_name = |
229 | | original_function_name != nullptr ? |
230 | | abi::__cxa_demangle(original_function_name, |
231 | | nullptr, // output_buffer |
232 | | nullptr, // length |
233 | | &demangle_status) : |
234 | | nullptr; |
235 | | const char* function_name_to_use = original_function_name; |
236 | | if (original_function_name != nullptr) { |
237 | | if (demangle_status != kDemangleOk) { |
238 | | if (demangle_status != kDemangleInvalidMangledName) { |
239 | | // -2 means the mangled name is not a valid name under the C++ ABI mangling rules. |
240 | | // This happens when the name is e.g. "main", so we don't report the error. |
241 | | StringAppendF(buf, "Error: __cxa_demangle failed for '%s' with error code %d\n", |
242 | | original_function_name, demangle_status); |
243 | | } |
244 | | // Regardless of the exact reason for demangle failure, we use the original function name |
245 | | // provided by libbacktrace. |
246 | | } else if (demangled_function_name != nullptr) { |
247 | | // If __cxa_demangle returns 0 and a non-null string, we use that instead of the original |
248 | | // function name. |
249 | | function_name_to_use = demangled_function_name; |
250 | | } else { |
251 | | StringAppendF(buf, |
252 | | "Error: __cxa_demangle returned zero status but nullptr demangled function for '%s'\n", |
253 | | original_function_name); |
254 | | } |
255 | | } |
256 | | |
257 | | string pretty_function_name; |
258 | | if (function_name_to_use == nullptr) { |
259 | | pretty_function_name = kUnknownSymbol; |
260 | | } else { |
261 | | // Allocating regexes on the heap so that they would never get deallocated. This is because |
262 | | // the kernel watchdog thread could still be symbolizing stack traces as global destructors |
263 | | // are being called. |
264 | | static const std::regex* kStdColonColonOneRE = new std::regex("\\bstd::__1::\\b"); |
265 | | pretty_function_name = std::regex_replace(function_name_to_use, *kStdColonColonOneRE, "std::"); |
266 | | |
267 | | static const std::regex* kStringRE = new std::regex( |
268 | | "\\bstd::basic_string<char, std::char_traits<char>, std::allocator<char> >"); |
269 | | pretty_function_name = std::regex_replace(pretty_function_name, *kStringRE, "string"); |
270 | | |
271 | | static const std::regex* kRemoveStdPrefixRE = |
272 | | new std::regex("\\bstd::(string|tuple|shared_ptr|unique_ptr)\\b"); |
273 | | pretty_function_name = std::regex_replace(pretty_function_name, *kRemoveStdPrefixRE, "$1"); |
274 | | } |
275 | | |
276 | | const bool is_symbol_only_fmt = |
277 | | context.stack_trace_line_format == StackTraceLineFormat::SYMBOL_ONLY; |
278 | | |
279 | | // We have not appended an end-of-line character yet. Let's see if we have file name / line number |
280 | | // information first. BTW kStackTraceEntryFormat is used both here and in glog-based |
281 | | // symbolization. |
282 | | if (filename == nullptr) { |
283 | | // libbacktrace failed to produce an address. We still need to produce a line of the form: |
284 | | // " @ 0x7f2d98f9bd87 " |
285 | | char hex_pc_buf[32]; |
286 | | snprintf(hex_pc_buf, sizeof(hex_pc_buf), "0x%" PRIxPTR, pc); |
287 | | if (is_symbol_only_fmt) { |
288 | | // At least print out the hex address, otherwise we'd end up with an empty string. |
289 | | *buf += hex_pc_buf; |
290 | | } else { |
291 | | StringAppendF(buf, " @ %18s ", hex_pc_buf); |
292 | | } |
293 | | } else { |
294 | | const string frame_without_file_line = |
295 | | is_symbol_only_fmt ? pretty_function_name |
296 | | : StringPrintf( |
297 | | kStackTraceEntryFormat, kPrintfPointerFieldWidth, |
298 | | reinterpret_cast<void*>(pc), pretty_function_name.c_str()); |
299 | | |
300 | | // Got filename and line number from libbacktrace! No need to filter the output through |
301 | | // addr2line, etc. |
302 | | switch (context.stack_trace_line_format) { |
303 | | case StackTraceLineFormat::CLION_CLICKABLE: { |
304 | | const string file_line_prefix = StringPrintf("%s:%d: ", filename, lineno); |
305 | | StringAppendF(buf, "%-100s", file_line_prefix.c_str()); |
306 | | *buf += frame_without_file_line; |
307 | | break; |
308 | | } |
309 | | case StackTraceLineFormat::SHORT: { |
310 | | *buf += frame_without_file_line; |
311 | | StringAppendF(buf, " (%s:%d)", NormalizeSourceFilePath(filename), lineno); |
312 | | break; |
313 | | } |
314 | | case StackTraceLineFormat::SYMBOL_ONLY: { |
315 | | *buf += frame_without_file_line; |
316 | | StringAppendF(buf, " (%s:%d)", NormalizeSourceFilePath(filename), lineno); |
317 | | break; |
318 | | } |
319 | | } |
320 | | } |
321 | | |
322 | | buf->push_back('\n'); |
323 | | |
324 | | // No need to check for nullptr, free is a no-op in that case. |
325 | | free(demangled_function_name); |
326 | | return 0; |
327 | | } |
328 | | #endif // __linux__ |
329 | | |
330 | | bool IsDoubleUnderscoredAndInList( |
331 | 0 | const char* symbol, const std::initializer_list<const char*>& list) { |
332 | 0 | if (symbol[0] != '_' || symbol[1] != '_') { |
333 | 0 | return false; |
334 | 0 | } |
335 | 0 | for (const auto* idle_function : list) { |
336 | 0 | if (!strcmp(symbol, idle_function)) { |
337 | 0 | return true; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | return false; |
341 | 0 | } |
342 | | |
343 | 0 | bool IsIdle(const char* symbol) { |
344 | 0 | return IsDoubleUnderscoredAndInList(symbol, |
345 | 0 | { "__GI_epoll_wait", |
346 | 0 | "__pthread_cond_timedwait", |
347 | 0 | "__pthread_cond_wait" }); |
348 | 0 | } |
349 | | |
350 | 0 | bool IsWaiting(const char* symbol) { |
351 | 0 | return IsDoubleUnderscoredAndInList(symbol, |
352 | 0 | { "__GI___pthread_mutex_lock" }); |
353 | 0 | } |
354 | | |
355 | | } // anonymous namespace |
356 | | |
357 | 0 | Status ListThreads(std::vector<pid_t> *tids) { |
358 | | #if defined(__linux__) |
359 | | DIR *dir = opendir("/proc/self/task/"); |
360 | | if (dir == NULL) { |
361 | | return STATUS(IOError, "failed to open task dir", Errno(errno)); |
362 | | } |
363 | | struct dirent *d; |
364 | | while ((d = readdir(dir)) != NULL) { |
365 | | if (d->d_name[0] != '.') { |
366 | | uint32_t tid; |
367 | | if (!safe_strtou32(d->d_name, &tid)) { |
368 | | LOG(WARNING) << "bad tid found in procfs: " << d->d_name; |
369 | | continue; |
370 | | } |
371 | | tids->push_back(tid); |
372 | | } |
373 | | } |
374 | | closedir(dir); |
375 | | #endif // defined(__linux__) |
376 | 0 | return Status::OK(); |
377 | 0 | } |
378 | | |
379 | | std::string GetStackTrace(StackTraceLineFormat stack_trace_line_format, |
380 | 11.7k | int num_top_frames_to_skip) { |
381 | 11.7k | std::string buf; |
382 | | #ifdef __linux__ |
383 | | if (FLAGS_use_libbacktrace) { |
384 | | SymbolizationContext context; |
385 | | context.buf = &buf; |
386 | | context.stack_trace_line_format = stack_trace_line_format; |
387 | | |
388 | | // Use libbacktrace on Linux because that gives us file names and line numbers. |
389 | | auto* global_backtrace_state = Singleton<GlobalBacktraceState>::get(); |
390 | | struct backtrace_state* const backtrace_state = global_backtrace_state->GetState(); |
391 | | |
392 | | // Avoid multi-threaded access to libbacktrace which causes high memory consumption. |
393 | | std::lock_guard<std::mutex> l(*global_backtrace_state->mutex()); |
394 | | |
395 | | // TODO: https://yugabyte.atlassian.net/browse/ENG-4729 |
396 | | |
397 | | const int backtrace_full_rv = backtrace_full( |
398 | | backtrace_state, /* skip = */ num_top_frames_to_skip + 1, BacktraceFullCallback, |
399 | | BacktraceErrorCallback, &context); |
400 | | if (backtrace_full_rv != 0) { |
401 | | StringAppendF(&buf, "Error: backtrace_full return value is %d", backtrace_full_rv); |
402 | | } |
403 | | return buf; |
404 | | } |
405 | | #endif |
406 | 11.7k | google::glog_internal_namespace_::DumpStackTraceToString(&buf); |
407 | 11.7k | return buf; |
408 | 11.7k | } |
409 | | |
410 | 14.8k | std::string GetStackTraceHex() { |
411 | 14.8k | char buf[1024]; |
412 | 14.8k | HexStackTraceToString(buf, 1024); |
413 | 14.8k | return std::string(buf); |
414 | 14.8k | } |
415 | | |
416 | 14.8k | void HexStackTraceToString(char* buf, size_t size) { |
417 | 14.8k | StackTrace trace; |
418 | 14.8k | trace.Collect(1); |
419 | 14.8k | trace.StringifyToHex(buf, size); |
420 | 14.8k | } |
421 | | |
422 | 0 | string GetLogFormatStackTraceHex() { |
423 | 0 | StackTrace trace; |
424 | 0 | trace.Collect(1); |
425 | 0 | return trace.ToLogFormatHexString(); |
426 | 0 | } |
427 | | |
428 | 590M | void StackTrace::Collect(int skip_frames) { |
429 | | #if THREAD_SANITIZER || ADDRESS_SANITIZER |
430 | | num_frames_ = google::GetStackTrace(frames_, arraysize(frames_), skip_frames); |
431 | | #else |
432 | 590M | int max_frames = skip_frames + arraysize(frames_); |
433 | 590M | void** buffer = static_cast<void**>(alloca((max_frames) * sizeof(void*))); |
434 | 590M | num_frames_ = backtrace(buffer, max_frames); |
435 | 590M | if (num_frames_ > skip_frames) { |
436 | 590M | num_frames_ -= skip_frames; |
437 | 590M | memmove(frames_, buffer + skip_frames, num_frames_ * sizeof(void*)); |
438 | 18.4E | } else { |
439 | 18.4E | num_frames_ = 0; |
440 | 18.4E | } |
441 | 590M | #endif |
442 | 590M | } |
443 | | |
444 | 14.8k | void StackTrace::StringifyToHex(char* buf, size_t size, int flags) const { |
445 | 14.8k | char* dst = buf; |
446 | | |
447 | | // Reserve kHexEntryLength for the first iteration of the loop, 1 byte for a |
448 | | // space (which we may not need if there's just one frame), and 1 for a nul |
449 | | // terminator. |
450 | 14.8k | char* limit = dst + size - kHexEntryLength - 2; |
451 | 251k | for (int i = 0; i < num_frames_ && dst < limit; i++) { |
452 | 237k | if (i != 0) { |
453 | 222k | *dst++ = ' '; |
454 | 222k | } |
455 | | // See note in Symbolize() below about why we subtract 1 from each address here. |
456 | 237k | uintptr_t addr = reinterpret_cast<uintptr_t>(frames_[i]); |
457 | 237k | if (!(flags & NO_FIX_CALLER_ADDRESSES)) { |
458 | 237k | addr--; |
459 | 237k | } |
460 | 237k | FastHex64ToBuffer(addr, dst); |
461 | 237k | dst += kHexEntryLength; |
462 | 237k | } |
463 | 14.8k | *dst = '\0'; |
464 | 14.8k | } |
465 | | |
466 | 1 | string StackTrace::ToHexString(int flags) const { |
467 | | // Each frame requires kHexEntryLength, plus a space |
468 | | // We also need one more byte at the end for '\0' |
469 | 1 | char buf[kMaxFrames * (kHexEntryLength + 1) + 1]; |
470 | 1 | StringifyToHex(buf, arraysize(buf), flags); |
471 | 1 | return string(buf); |
472 | 1 | } |
473 | | |
474 | | // If group is specified it is filled with value corresponding to this stack trace. |
475 | | void SymbolizeAddress( |
476 | | const StackTraceLineFormat stack_trace_line_format, |
477 | | void* pc, |
478 | | string* buf, |
479 | | StackTraceGroup* group = nullptr |
480 | | #ifdef __linux__ |
481 | | , GlobalBacktraceState* global_backtrace_state = nullptr |
482 | | #endif |
483 | 106k | ) { |
484 | | // The return address 'pc' on the stack is the address of the instruction |
485 | | // following the 'call' instruction. In the case of calling a function annotated |
486 | | // 'noreturn', this address may actually be the first instruction of the next |
487 | | // function, because the function we care about ends with the 'call'. |
488 | | // So, we subtract 1 from 'pc' so that we're pointing at the 'call' instead |
489 | | // of the return address. |
490 | | // |
491 | | // For example, compiling a C program with -O2 that simply calls 'abort()' yields |
492 | | // the following disassembly: |
493 | | // Disassembly of section .text: |
494 | | // |
495 | | // 0000000000400440 <main>: |
496 | | // 400440: 48 83 ec 08 sub $0x8,%rsp |
497 | | // 400444: e8 c7 ff ff ff callq 400410 <abort@plt> |
498 | | // |
499 | | // 0000000000400449 <_start>: |
500 | | // 400449: 31 ed xor %ebp,%ebp |
501 | | // ... |
502 | | // |
503 | | // If we were to take a stack trace while inside 'abort', the return pointer |
504 | | // on the stack would be 0x400449 (the first instruction of '_start'). By subtracting |
505 | | // 1, we end up with 0x400448, which is still within 'main'. |
506 | | // |
507 | | // This also ensures that we point at the correct line number when using addr2line |
508 | | // on logged stacks. |
509 | 106k | pc = reinterpret_cast<void*>(reinterpret_cast<size_t>(pc) - 1); |
510 | | #ifdef __linux__ |
511 | | if (FLAGS_use_libbacktrace) { |
512 | | if (!global_backtrace_state) { |
513 | | global_backtrace_state = Singleton<GlobalBacktraceState>::get(); |
514 | | } |
515 | | struct backtrace_state* const backtrace_state = global_backtrace_state->GetState(); |
516 | | |
517 | | // Avoid multi-threaded access to libbacktrace which causes high memory consumption. |
518 | | std::lock_guard<std::mutex> l(*global_backtrace_state->mutex()); |
519 | | |
520 | | SymbolizationContext context; |
521 | | context.stack_trace_line_format = stack_trace_line_format; |
522 | | context.buf = buf; |
523 | | backtrace_pcinfo(backtrace_state, reinterpret_cast<uintptr_t>(pc), |
524 | | BacktraceFullCallback, BacktraceErrorCallback, &context); |
525 | | return; |
526 | | } |
527 | | #endif |
528 | 106k | char tmp[1024]; |
529 | 106k | const char* symbol = kUnknownSymbol; |
530 | | |
531 | 106k | if (google::Symbolize(pc, tmp, sizeof(tmp))) { |
532 | 97.6k | symbol = tmp; |
533 | 97.6k | if (group) { |
534 | 0 | if (IsWaiting(symbol)) { |
535 | 0 | *group = StackTraceGroup::kWaiting; |
536 | 0 | } else if (IsIdle(symbol)) { |
537 | 0 | *group = StackTraceGroup::kIdle; |
538 | 0 | } |
539 | 0 | } |
540 | 97.6k | } |
541 | | |
542 | 106k | StringAppendF(buf, kStackTraceEntryFormat, kPrintfPointerFieldWidth, pc, symbol); |
543 | | // We are appending the end-of-line character separately because we want to reuse the same |
544 | | // format string for libbacktrace callback and glog-based symbolization, and we have an extra |
545 | | // file name / line number component before the end-of-line in the libbacktrace case. |
546 | 106k | buf->push_back('\n'); |
547 | 106k | } |
548 | | |
549 | | // Symbolization function borrowed from glog and modified to use libbacktrace on Linux. |
550 | | string StackTrace::Symbolize( |
551 | 2.11k | const StackTraceLineFormat stack_trace_line_format, StackTraceGroup* group) const { |
552 | 2.11k | string buf; |
553 | | #ifdef __linux__ |
554 | | // Use libbacktrace for symbolization. |
555 | | auto* global_backtrace_state = |
556 | | FLAGS_use_libbacktrace ? Singleton<GlobalBacktraceState>::get() : nullptr; |
557 | | #endif |
558 | | |
559 | 2.11k | if (group) { |
560 | 0 | *group = StackTraceGroup::kActive; |
561 | 0 | } |
562 | | |
563 | 35.9k | for (int i = 0; i < num_frames_; i++) { |
564 | 33.8k | void* const pc = frames_[i]; |
565 | | |
566 | 33.8k | SymbolizeAddress(stack_trace_line_format, pc, &buf, group |
567 | | #ifdef __linux__ |
568 | | , global_backtrace_state |
569 | | #endif |
570 | 33.8k | ); |
571 | 33.8k | } |
572 | | |
573 | 2.11k | return buf; |
574 | 2.11k | } |
575 | | |
576 | 0 | string StackTrace::ToLogFormatHexString() const { |
577 | 0 | string buf; |
578 | 0 | for (int i = 0; i < num_frames_; i++) { |
579 | 0 | void* pc = frames_[i]; |
580 | 0 | StringAppendF(&buf, " @ %*p\n", kPrintfPointerFieldWidth, pc); |
581 | 0 | } |
582 | 0 | return buf; |
583 | 0 | } |
584 | | |
585 | 1 | uint64_t StackTrace::HashCode() const { |
586 | 1 | return util_hash::CityHash64(reinterpret_cast<const char*>(frames_), |
587 | 1 | sizeof(frames_[0]) * num_frames_); |
588 | 1 | } |
589 | | |
590 | | namespace { |
591 | | #ifdef __linux__ |
592 | | int DynamcLibraryListCallback(struct dl_phdr_info *info, size_t size, void *data) { |
593 | | if (*info->dlpi_name != '\0') { |
594 | | // We can't use LOG(...) yet because Google Logging might not be initialized. |
595 | | // It is also important to write the entire line at once so that it is less likely to be |
596 | | // interleaved with pieces of similar lines from other processes. |
597 | | std::cerr << StringPrintf( |
598 | | "Shared library '%s' loaded at address 0x%" PRIx64 "\n", info->dlpi_name, info->dlpi_addr); |
599 | | } |
600 | | return 0; |
601 | | } |
602 | | #endif |
603 | | |
604 | 0 | void PrintLoadedDynamicLibraries() { |
605 | | #ifdef __linux__ |
606 | | // Supported on Linux only. |
607 | | dl_iterate_phdr(DynamcLibraryListCallback, nullptr); |
608 | | #endif |
609 | 0 | } |
610 | | |
611 | 20.6k | bool PrintLoadedDynamicLibrariesOnceHelper() { |
612 | 20.6k | const char* list_dl_env_var = std::getenv("YB_LIST_LOADED_DYNAMIC_LIBS"); |
613 | 20.6k | if (list_dl_env_var != nullptr && *list_dl_env_var != '\0') { |
614 | 0 | PrintLoadedDynamicLibraries(); |
615 | 0 | } |
616 | 20.6k | return true; |
617 | 20.6k | } |
618 | | |
619 | | } // anonymous namespace |
620 | | |
621 | 73.1k | string SymbolizeAddress(void *pc, const StackTraceLineFormat stack_trace_line_format) { |
622 | 73.1k | string s; |
623 | 73.1k | SymbolizeAddress(stack_trace_line_format, pc, &s); |
624 | 73.1k | return s; |
625 | 73.1k | } |
626 | | |
627 | | // ------------------------------------------------------------------------------------------------ |
628 | | // Tracing function calls |
629 | | // ------------------------------------------------------------------------------------------------ |
630 | | |
631 | | namespace { |
632 | | |
633 | | // List the load addresses of dynamic libraries once on process startup if required. |
634 | | const bool __attribute__((unused)) kPrintedLoadedDynamicLibraries = |
635 | | PrintLoadedDynamicLibrariesOnceHelper(); |
636 | | |
637 | | } // namespace |
638 | | |
639 | 0 | std::string DemangleName(const char* mangled_name) { |
640 | 0 | int demangle_status = 0; |
641 | 0 | char* demangled_name = |
642 | 0 | abi::__cxa_demangle(mangled_name, nullptr /* output_buffer */, nullptr /* length */, |
643 | 0 | &demangle_status); |
644 | 0 | string ret_val = demangle_status == kDemangleOk ? demangled_name : mangled_name; |
645 | 0 | free(demangled_name); |
646 | 0 | return ret_val; |
647 | 0 | } |
648 | | |
649 | 2 | std::string SourceLocation::ToString() const { |
650 | 2 | return Format("$0:$1", file_name, line_number); |
651 | 2 | } |
652 | | |
653 | | ScopeLogger::ScopeLogger(const std::string& msg, std::function<void()> on_scope_bounds) |
654 | 0 | : msg_(msg), on_scope_bounds_(std::move(on_scope_bounds)) { |
655 | 0 | LOG(INFO) << ">>> " << msg_; |
656 | 0 | on_scope_bounds_(); |
657 | 0 | } |
658 | | |
659 | 0 | ScopeLogger::~ScopeLogger() { |
660 | 0 | on_scope_bounds_(); |
661 | 0 | LOG(INFO) << "<<< " << msg_; |
662 | 0 | } |
663 | | |
664 | | } // namespace yb |