| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "utils/BackTrace.h" |
| |
| #ifdef HAS_EXECINFO |
| #include <cxxabi.h> |
| #include <execinfo.h> |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| #include <dlfcn.h> |
| #ifdef __linux__ |
| #include <link.h> |
| #endif |
| #include <cstring> |
| #include <iostream> |
| #include <utility> |
| #endif |
| |
| #include "utils/OptionalUtils.h" |
| |
| #ifdef HAS_EXECINFO |
| namespace { |
| using org::apache::nifi::minifi::utils::optional; |
| using org::apache::nifi::minifi::utils::nullopt; |
| /** |
| * Demangles a symbol name using the cxx abi. |
| * @param symbol_name the mangled name of the symbol |
| * @return the demangled name on success, empty string on failure |
| */ |
| optional<std::string> demangle_symbol(const char* symbol_name) { |
| int status; |
| char* demangled = abi::__cxa_demangle(symbol_name, nullptr, nullptr, &status); |
| if (status == 0) { |
| std::string demangled_name = demangled; |
| free(demangled); |
| return { demangled_name }; |
| } else { |
| return {}; |
| } |
| } |
| } // namespace |
| #endif |
| |
| void pull_trace(uint8_t frames_to_skip /* = 1 */) { |
| #ifdef HAS_EXECINFO |
| void* stack_buffer[TRACE_BUFFER_SIZE + 1]; |
| |
| /* Get the backtrace of the current thread */ |
| int trace_size = backtrace(stack_buffer, TRACE_BUFFER_SIZE); |
| |
| /* We can skip the signal handler, call to pull_trace, and the first entry for backtrace_symbols */ |
| for (int i = frames_to_skip; i < trace_size; i++) { |
| const char* file_name = "???"; |
| uintptr_t symbol_offset = 0; |
| |
| /* Translate the address to symbolic information */ |
| Dl_info dl_info{}; |
| #ifdef __linux__ |
| struct link_map* l_map = nullptr; |
| int res = dladdr1(stack_buffer[i], &dl_info, reinterpret_cast<void**>(&l_map), RTLD_DL_LINKMAP); |
| #else |
| int res = dladdr(stack_buffer[i], &dl_info); |
| #endif |
| if (res == 0 || dl_info.dli_fname == nullptr || dl_info.dli_fname[0] == '\0') { |
| /* We could not determine symbolic information for this address*/ |
| TraceResolver::getResolver().addTraceLine(file_name, nullptr, symbol_offset); |
| continue; |
| } |
| |
| /* Determine the filename of the shared object */ |
| if (dl_info.dli_fname != nullptr) { |
| const char* last_slash = nullptr; |
| /* If the shared object name is a full path, we still only want the filename component */ |
| if ((last_slash = strrchr(dl_info.dli_fname, '/')) != nullptr) { |
| file_name = last_slash + 1; |
| } else { |
| file_name = dl_info.dli_fname; |
| } |
| } |
| |
| const std::string symbol_name = dl_info.dli_sname |
| ? demangle_symbol(dl_info.dli_sname).value_or(file_name) |
| : file_name; |
| |
| /* Determine our offset */ |
| uintptr_t base_address = 0; |
| if (dl_info.dli_sname != nullptr) { |
| /* If we could determine our symbol we will display our offset from the address of the symbol */ |
| base_address = reinterpret_cast<uintptr_t>(dl_info.dli_saddr); |
| } else { |
| /* Otherwise we will display our offset from base address of the shared object */ |
| #ifdef __linux__ |
| /* |
| * glibc uses l_addr from the link map instead of dli_fbase in backtrace_symbols for calculating this offset. |
| * I could not find a difference between the two in my limited measurements, but we will use it too, just to be sure. |
| */ |
| if (l_map != nullptr) { |
| dl_info.dli_fbase = reinterpret_cast<void*>(l_map->l_addr); |
| } |
| #endif |
| base_address = reinterpret_cast<uintptr_t>(dl_info.dli_fbase); |
| } |
| symbol_offset = reinterpret_cast<uintptr_t>(stack_buffer[i]) - base_address; |
| |
| TraceResolver::getResolver().addTraceLine(file_name, symbol_name.c_str(), symbol_offset); |
| } |
| #endif |
| } |
| |
| BackTrace TraceResolver::getBackTrace(std::string thread_name, std::thread::native_handle_type thread_handle) { |
| // lock so that we only perform one backtrace at a time. |
| #ifdef HAS_EXECINFO |
| std::lock_guard<std::mutex> lock(mutex_); |
| trace_ = BackTrace(std::move(thread_name)); |
| |
| if (0 == thread_handle || pthread_equal(pthread_self(), thread_handle)) { |
| pull_trace(); |
| } else { |
| if (thread_handle == 0) { |
| return std::move(trace_); |
| } |
| emplace_handler(); |
| std::unique_lock<std::mutex> ulock(trace_mutex_); |
| if (pthread_kill(thread_handle, SIGUSR2) != 0) { |
| return std::move(trace_); |
| } |
| pull_traces_ = false; |
| trace_condition_.wait(ulock, [this] { return pull_traces_; }); |
| } |
| #else |
| // even if tracing is disabled, include thread name into the trace object |
| trace_ = BackTrace(std::move(thread_name)); |
| #endif |
| return std::move(trace_); |
| } |
| #ifdef HAS_EXECINFO |
| void handler(int signr, siginfo_t *info, void *secret) { |
| std::unique_lock<std::mutex> lock(TraceResolver::getResolver().lock()); |
| pull_trace(); |
| TraceResolver::getResolver().notifyPullTracesDone(lock); |
| } |
| #endif |
| |
| void emplace_handler() { |
| #ifdef HAS_EXECINFO |
| struct sigaction sa{}; |
| sigfillset(&sa.sa_mask); |
| sa.sa_flags = SA_SIGINFO; |
| sa.sa_sigaction = handler; |
| sigaction(SIGUSR2, &sa, nullptr); |
| #endif |
| } |