diff options
author | A. Jesse Jiryu Davis <jesse@mongodb.com> | 2019-05-30 15:15:08 -0400 |
---|---|---|
committer | Andrew Morrow <acm@mongodb.com> | 2019-07-17 10:49:15 -0400 |
commit | cbead440945c78b577c29dab8c11e81a58ee7731 (patch) | |
tree | 424c0395df4ac4bfcf2ab145df259e567006b8f3 /src | |
parent | 19d0d47fb83d01edb18e0bcb235d6c39bbe16bbd (diff) | |
download | mongo-cbead440945c78b577c29dab8c11e81a58ee7731.tar.gz |
SERVER-36242 Optionally use libunwind for backtraces
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/SConscript | 20 | ||||
-rw-r--r-- | src/mongo/base/SConscript | 7 | ||||
-rw-r--r-- | src/mongo/base/backtrace_visibility_test.cpp | 74 | ||||
-rw-r--r-- | src/mongo/base/backtrace_visibility_test.h | 48 | ||||
-rw-r--r-- | src/mongo/base/unwind_test.cpp | 63 | ||||
-rw-r--r-- | src/mongo/platform/compiler_gcc.h | 1 | ||||
-rw-r--r-- | src/mongo/platform/compiler_msvc.h | 1 | ||||
-rw-r--r-- | src/mongo/util/signal_handlers_synchronous.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/stacktrace.h | 3 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_posix.cpp | 5 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_unwind.cpp | 607 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_windows.cpp | 9 | ||||
-rw-r--r-- | src/third_party/SConscript | 73 | ||||
-rw-r--r-- | src/third_party/unwind/SConscript | 2 |
14 files changed, 868 insertions, 47 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 985c2389ef5..93510378483 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -13,6 +13,7 @@ Import("has_option") Import("get_option") Import("usemozjs") Import("use_system_version_of_library") +Import("use_libunwind") Import("wiredtiger") env = env.Clone() @@ -50,10 +51,18 @@ env.SConscript( ], ) -# NOTE: This library does not really belong here. Its presence here is -# temporary. Do not add to this library, do not remove from it, and do -# not declare other libraries in this file. -env.Library( +# NOTE: The 'base' library does not really belong here. Its presence +# here is temporary. Do not add to this library, do not remove from +# it, and do not declare other libraries in this file. +baseEnv = env.Clone() + +if use_libunwind: + baseEnv.InjectThirdParty('unwind') + stacktrace_impl_cpp = 'util/stacktrace_unwind.cpp' +else: + stacktrace_impl_cpp = 'util/stacktrace_${TARGET_OS_FAMILY}.cpp' + +baseEnv.Library( target='base', source=[ 'base/data_range.cpp', @@ -131,7 +140,7 @@ env.Library( 'util/shell_exec.cpp', 'util/signal_handlers_synchronous.cpp', 'util/stacktrace.cpp', - 'util/stacktrace_${TARGET_OS_FAMILY}.cpp', + stacktrace_impl_cpp, 'util/str.cpp', 'util/system_clock_source.cpp', 'util/system_tick_source.cpp', @@ -151,6 +160,7 @@ env.Library( '$BUILD_DIR/third_party/shim_fmt', '$BUILD_DIR/third_party/shim_intel_decimal128', '$BUILD_DIR/third_party/shim_pcrecpp', + '$BUILD_DIR/third_party/shim_unwind' if use_libunwind else [], 'boost_assert_shim', 'util/quick_exit', ], diff --git a/src/mongo/base/SConscript b/src/mongo/base/SConscript index dd9f19eb95b..d2cad69641a 100644 --- a/src/mongo/base/SConscript +++ b/src/mongo/base/SConscript @@ -1,6 +1,7 @@ # -*- mode: python -*- Import("env") +Import("use_libunwind") env = env.Clone() @@ -106,8 +107,7 @@ env.CppUnitTest( ], ) -# To be enabled by a '--enable-libunwind' option coming soon. -if False: +if use_libunwind: unwindTestEnv = env.Clone() unwindTestEnv.InjectThirdParty(libraries=['unwind']) unwindTestEnv.CppUnitTest( @@ -115,9 +115,10 @@ if False: 'unwind_test', ], source=[ + 'backtrace_visibility_test.cpp', 'unwind_test.cpp', ], LIBDEPS=[ '$BUILD_DIR/third_party/shim_unwind', - ] + ], ) diff --git a/src/mongo/base/backtrace_visibility_test.cpp b/src/mongo/base/backtrace_visibility_test.cpp new file mode 100644 index 00000000000..aa11d676141 --- /dev/null +++ b/src/mongo/base/backtrace_visibility_test.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +/** + * This source file and its header are for testing that static, hidden, etc. + * functions appear in backtraces, see unwind_test.cpp. + */ + +#include "backtrace_visibility_test.h" + +#include "mongo/platform/compiler.h" +#include "mongo/util/stacktrace.h" + +#include <sstream> + +// "static" means internal linkage only for functions in global namespace. +NOINLINE_DECL static void static_function(std::string& s) { + std::ostringstream ostream; + mongo::printStackTrace(ostream); + s = ostream.str(); + // Prevent tail-call optimization. + asm volatile(""); // NOLINT +} + +namespace { +NOINLINE_DECL void anonymous_namespace_function(std::string& s) { + static_function(s); + // Prevent tail-call optimization. + asm volatile(""); // NOLINT +} +} // namespace + +NOINLINE_DECL MONGO_COMPILER_API_HIDDEN_FUNCTION void hidden_function(std::string& s) { + anonymous_namespace_function(s); + // Prevent tail-call optimization. + asm volatile(""); // NOLINT +} + +namespace mongo { + +namespace unwind_test_detail { +NOINLINE_DECL void normal_function(std::string& s) { + hidden_function(s); + // Prevent tail-call optimization. + asm volatile(""); // NOLINT +} +} // namespace unwind_test_detail +} // namespace mongo diff --git a/src/mongo/base/backtrace_visibility_test.h b/src/mongo/base/backtrace_visibility_test.h new file mode 100644 index 00000000000..9d8e248fad8 --- /dev/null +++ b/src/mongo/base/backtrace_visibility_test.h @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +/** + * This header and its source file are for testing that static, hidden, etc. + * functions appear in backtraces, see unwind_test.cpp. + */ + +#include "mongo/bson/inline_decls.h" + +#include <string> + +namespace mongo { + +namespace unwind_test_detail { + +// Store a stack trace in s. +NOINLINE_DECL void normal_function(std::string& s); + +} // namespace unwind_test_detail + +} // namespace mongo diff --git a/src/mongo/base/unwind_test.cpp b/src/mongo/base/unwind_test.cpp index f125c315b99..6afea43e89b 100644 --- a/src/mongo/base/unwind_test.cpp +++ b/src/mongo/base/unwind_test.cpp @@ -32,6 +32,7 @@ #include <cstdio> #include <cstdlib> #include <cxxabi.h> +#include <functional> #include <sstream> #include <fmt/format.h> @@ -39,9 +40,10 @@ #include <libunwind.h> -#include "mongo/stdx/functional.h" +#include "mongo/base/backtrace_visibility_test.h" #include "mongo/unittest/unittest.h" #include "mongo/util/if_constexpr.h" +#include "mongo/util/stacktrace.h" namespace mongo { @@ -89,7 +91,7 @@ std::string trace() { } struct Context { - std::vector<stdx::function<void(Context&)>> plan; + std::vector<std::function<void(Context&)>> plan; std::string s; }; @@ -102,12 +104,50 @@ void callNext(Context& ctx) { else { ctx.plan[N - 1](ctx); } + // Forces compiler to invoke next plan with `call` instead of `jmp`. asm volatile(""); // NOLINT } +void assertAndRemovePrefix(std::string_view& v, const std::string_view prefix) { + auto pos = v.find(prefix); + ASSERT(pos != v.npos) << "expected to find '{}' in '{}'"_format(prefix, v); + v.remove_prefix(pos); + v.remove_prefix(prefix.length()); +} + +void assertAndRemoveSuffix(std::string_view& v, const std::string_view suffix) { + auto pos = v.rfind(suffix); + ASSERT(pos != v.npos) << "expected to find '{}' in '{}'"_format(suffix, v); + v.remove_suffix(v.length() - pos); +} + +template <size_t size> +void assertTraceContains(const std::string (&names)[size], const std::string stacktrace) { + std::string_view view{stacktrace}; + assertAndRemovePrefix(view, "----- BEGIN BACKTRACE -----"); + assertAndRemovePrefix(view, "{\"backtrace\":"); + // Remove the rest of the JSON object, which is all one line. + assertAndRemovePrefix(view, "\n"); + assertAndRemoveSuffix(view, "----- END BACKTRACE -----"); + std::string_view remainder{stacktrace}; + for (const auto& name : names) { + auto pos = remainder.find(name); + + if (pos == remainder.npos) { + unittest::log().setIsTruncatable(false) << std::endl + << "--- BEGIN SAMPLE BACKTRACE ---" << std::endl + << std::string(stacktrace) + << "--- END SAMPLE BACKTRACE ---"; + FAIL("name '{}' is missing or out of order in sample backtrace"_format( + std::string(name))); + } + remainder.remove_prefix(pos); + } +} + TEST(Unwind, Demangled) { - // Trickery with std::vector<stdx::function> is to hide from the optimizer. + // Trickery with std::vector<std::function> is to hide from the optimizer. Context ctx{{ callNext<0>, callNext<1>, callNext<2>, callNext<3>, callNext<4>, callNext<5>, }}; @@ -130,5 +170,22 @@ TEST(Unwind, Demangled) { } } +TEST(Unwind, Linkage) { + std::string stacktrace; + + // From backtrace_visibility_test.h. Calls a chain of functions and stores the backtrace at the + // bottom in the "stacktrace" argument. + normal_function(stacktrace); + + // Check that these function names appear in the trace, in order. The tracing code which + // preceded our libunwind integration could *not* symbolize hidden/static_function. + const std::string frames[] = {"printStackTrace", + "static_function", + "anonymous_namespace_function", + "hidden_function", + "normal_function"}; + assertTraceContains(frames, stacktrace); +} + } // namespace unwind_test_detail } // namespace mongo diff --git a/src/mongo/platform/compiler_gcc.h b/src/mongo/platform/compiler_gcc.h index bfb8cf7513c..839ef93e401 100644 --- a/src/mongo/platform/compiler_gcc.h +++ b/src/mongo/platform/compiler_gcc.h @@ -86,6 +86,7 @@ #define MONGO_COMPILER_API_EXPORT __attribute__((__visibility__("default"))) #define MONGO_COMPILER_API_IMPORT +#define MONGO_COMPILER_API_HIDDEN_FUNCTION __attribute__((visibility("hidden"))) #define MONGO_COMPILER_API_CALLING_CONVENTION #define MONGO_likely(x) static_cast<bool>(__builtin_expect(static_cast<bool>(x), 1)) diff --git a/src/mongo/platform/compiler_msvc.h b/src/mongo/platform/compiler_msvc.h index c9f50eba68a..ac71c1ddd24 100644 --- a/src/mongo/platform/compiler_msvc.h +++ b/src/mongo/platform/compiler_msvc.h @@ -50,6 +50,7 @@ #define MONGO_COMPILER_API_EXPORT __declspec(dllexport) #define MONGO_COMPILER_API_IMPORT __declspec(dllimport) +#define MONGO_COMPILER_API_HIDDEN_FUNCTION #define MONGO_WARN_UNUSED_RESULT_CLASS #define MONGO_WARN_UNUSED_RESULT_FUNCTION diff --git a/src/mongo/util/signal_handlers_synchronous.cpp b/src/mongo/util/signal_handlers_synchronous.cpp index e1cd8371177..d6e595218e2 100644 --- a/src/mongo/util/signal_handlers_synchronous.cpp +++ b/src/mongo/util/signal_handlers_synchronous.cpp @@ -179,7 +179,7 @@ void writeMallocFreeStreamToLog() { // must hold MallocFreeOStreamGuard to call void printSignalAndBacktrace(int signalNum) { mallocFreeOStream << "Got signal: " << signalNum << " (" << strsignal(signalNum) << ").\n"; - printStackTrace(mallocFreeOStream); + printStackTraceFromSignal(mallocFreeOStream); writeMallocFreeStreamToLog(); } diff --git a/src/mongo/util/stacktrace.h b/src/mongo/util/stacktrace.h index 910a445720d..ea2908284fa 100644 --- a/src/mongo/util/stacktrace.h +++ b/src/mongo/util/stacktrace.h @@ -47,6 +47,9 @@ namespace mongo { void printStackTrace(std::ostream& os); void printStackTrace(); +// Signal-safe variant. +void printStackTraceFromSignal(std::ostream& os); + #if defined(_WIN32) // Print stack trace (using a specified stack context) to "os", default to the log stream. void printWindowsStackTrace(CONTEXT& context, std::ostream& os); diff --git a/src/mongo/util/stacktrace_posix.cpp b/src/mongo/util/stacktrace_posix.cpp index 116065533f8..d9d63b89109 100644 --- a/src/mongo/util/stacktrace_posix.cpp +++ b/src/mongo/util/stacktrace_posix.cpp @@ -247,6 +247,11 @@ void printStackTrace(std::ostream& os) { #endif +void printStackTraceFromSignal(std::ostream& os) { + printStackTrace(os); +} + +// From here down, a copy of stacktrace_unwind.cpp. namespace { void addOSComponentsToSoMap(BSONObjBuilder* soMap); diff --git a/src/mongo/util/stacktrace_unwind.cpp b/src/mongo/util/stacktrace_unwind.cpp new file mode 100644 index 00000000000..a70667e45bf --- /dev/null +++ b/src/mongo/util/stacktrace_unwind.cpp @@ -0,0 +1,607 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +/** + * Stacktraces using libunwind for non-Windows OSes. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kControl + +#include "mongo/platform/basic.h" + +#include "mongo/util/stacktrace.h" + +#include <cstdlib> +#include <dlfcn.h> +#include <fmt/format.h> +#include <fmt/printf.h> +#include <iostream> +#include <libunwind.h> +#include <limits.h> +#include <string> +#include <sys/utsname.h> + +#include "mongo/base/init.h" +#include "mongo/config.h" +#include "mongo/db/jsobj.h" +#include "mongo/util/hex.h" +#include "mongo/util/log.h" +#include "mongo/util/str.h" +#include "mongo/util/version.h" + +namespace mongo { + +namespace { + +using namespace fmt::literals; + +/// Maximum number of stack frames to appear in a backtrace. +constexpr int maxBackTraceFrames = 100; + +/// Maximum size of a function name and signature. This is an arbitrary limit we impose to avoid +/// having to malloc while backtracing. +constexpr int maxSymbolLen = 512; + +/// Optional string containing extra unwinding information. Should take the form of a +/// JSON document. +std::string* soMapJson = NULL; + +/** + * Returns the "basename" of a path. The returned StringData is valid until the data referenced + * by "path" goes out of scope or mutates. + * + * E.g., for "/foo/bar/my.txt", returns "my.txt". + */ +StringData getBaseName(StringData path) { + size_t lastSlash = path.rfind('/'); + if (lastSlash == std::string::npos) + return path; + return path.substr(lastSlash + 1); +} + +struct frame { + frame() { + symbol[0] = '\0'; + filename[0] = '\0'; + } + + void* address = nullptr; + // Some getFrames() implementations can fill this in, some can't. + constexpr static ssize_t kOffsetUnknown = -1; + ssize_t offset = kOffsetUnknown; + char symbol[maxSymbolLen]; + + // Filled in by completeFrame(). + void* baseAddress = nullptr; + char filename[PATH_MAX]; +}; + +void strncpyTerm(char* dst, const char* src, size_t len) { + strncpy(dst, src, len); + // In case src is longer than dst, ensure dst is terminated. + dst[len - 1] = '\0'; +} + +void completeFrame(frame* pFrame, std::ostream& logStream) { + Dl_info dlinfo{}; + if (dladdr(pFrame->address, &dlinfo)) { + pFrame->baseAddress = dlinfo.dli_fbase; + if (pFrame->offset == frame::kOffsetUnknown) { + pFrame->offset = + static_cast<char*>(pFrame->address) - static_cast<char*>(dlinfo.dli_saddr); + } + if (dlinfo.dli_fname) { + strncpyTerm(pFrame->filename, dlinfo.dli_fname, sizeof(pFrame->filename)); + } + + // Don't overwrite pFrame->symbol if getFrames() has already figured out the symbol name. + if (dlinfo.dli_sname && !*pFrame->symbol) { + strncpyTerm(pFrame->symbol, dlinfo.dli_sname, sizeof(pFrame->symbol)); + } + } else { + logStream << "error: unable to obtain symbol information for function " + << "{:p}\n"_format(pFrame->address) << std::endl; + } + + // Don't log errors, they're expected for C functions in the stack, like "main". + int status; + if (char* demangled_name = abi::__cxa_demangle(pFrame->symbol, nullptr, nullptr, &status)) { + strncpyTerm(pFrame->symbol, demangled_name, sizeof(pFrame->symbol)); + free(demangled_name); + } +} + +#define MONGO_UNWIND_CHECK(FUNC, ...) \ + do { \ + unw_status = FUNC(__VA_ARGS__); \ + if (unw_status < 0) { \ + logStream << "error from {}: {}"_format(#FUNC, unw_strerror(unw_status)); \ + return 0; \ + } \ + } while (0) + +size_t getFrames(frame* frames, bool fromSignal, std::ostream& logStream) { + size_t nFrames = 0; + frame* pFrame = frames; + unw_cursor_t cursor; + unw_context_t context; + int unw_status; + + // Initialize cursor to current frame for local unwinding. + MONGO_UNWIND_CHECK(unw_getcontext, &context); + MONGO_UNWIND_CHECK(unw_init_local2, &cursor, &context, fromSignal ? UNW_INIT_SIGNAL_FRAME : 0); + + // Inspect this frame, then step to calling frame, and repeat. + while (nFrames < maxBackTraceFrames) { + unw_word_t pc; + MONGO_UNWIND_CHECK(unw_get_reg, &cursor, UNW_REG_IP, &pc); + if (pc == 0) { + break; + } + + pFrame->address = reinterpret_cast<void*>(pc); + unw_word_t offset; + if ((unw_status = unw_get_proc_name( + &cursor, pFrame->symbol, sizeof(pFrame->symbol), &offset)) != 0) { + logStream << "error: unable to obtain symbol name for function " + << "{:p}: {}\n"_format(pFrame->address, unw_strerror(unw_status)) + << std::endl; + } else { + pFrame->offset = static_cast<size_t>(offset); + } + + nFrames++; + pFrame++; + + MONGO_UNWIND_CHECK(unw_step, &cursor); + if (unw_status == 0) { + // Finished. + break; + } + } + + return nFrames; +} + +/** + * Prints a stack backtrace for the current thread to the specified ostream. + * + * Does not malloc, does not throw. + * + * The format of the backtrace is: + * + * ----- BEGIN BACKTRACE ----- + * JSON backtrace + * Human-readable backtrace + * ----- END BACKTRACE ----- + * + * The JSON backtrace will be a JSON object with a "backtrace" field, and optionally others. + * The "backtrace" field is an array, whose elements are frame objects. A frame object has a + * "b" field, which is the base-address of the library or executable containing the symbol, and + * an "o" field, which is the offset into said library or executable of the symbol. + * + * The JSON backtrace may optionally contain additional information useful to a backtrace + * analysis tool. For example, on Linux it contains a subobject named "somap", describing + * the objects referenced in the "b" fields of the "backtrace" list. + * + * Notes for future refinements: we have a goal of making this malloc-free so it's signal-safe. This + * huge stack-allocated structure reduces the need for malloc, at the risk of a stack overflow while + * trying to print a stack trace, which would make life very hard for us when we're debugging + * crashes for customers. It would also be bad to crash from stack overflow when printing backtraces + * on demand (SERVER-33445). + * + * A better idea is to get rid of the "frame" struct idea. Instead, iterate stack frames with + * unw_step while printing the JSON stack, then reset the cursor and iterate with unw_step again + * while printing the human-readable stack. Then there's no need for a large amount of storage. + * + * @param os ostream& to receive printed stack backtrace + */ +void printStackTraceInternal(std::ostream& os, bool fromSignal) { + frame frames[maxBackTraceFrames]; + + //////////////////////////////////////////////////////////// + // Get the backtrace. + //////////////////////////////////////////////////////////// + size_t nFrames = getFrames(frames, fromSignal, os); + if (!nFrames) { + // getFrames logged an error to "os". + return; + } + + for (size_t i = 0; i < nFrames; i++) { + completeFrame(&frames[i], os); + } + + os << std::hex << std::uppercase; + for (size_t i = 0; i < nFrames; ++i) { + os << ' ' << frames[i].address; + } + + os << "\n----- BEGIN BACKTRACE -----\n"; + + //////////////////////////////////////////////////////////// + // Display the JSON backtrace + //////////////////////////////////////////////////////////// + + os << "{\"backtrace\":["; + for (size_t i = 0; i < nFrames; ++i) { + if (i) + os << ','; + frame* pFrame = &frames[i]; + const uintptr_t fileOffset = + static_cast<char*>(pFrame->address) - static_cast<char*>(pFrame->baseAddress); + os << "{\"b\":\"" << pFrame->baseAddress << "\",\"o\":\"" << fileOffset; + if (*pFrame->symbol) { + os << "\",\"s\":\"" << pFrame->symbol; + } + os << "\"}"; + } + os << ']'; + + if (soMapJson) + os << ",\"processInfo\":" << *soMapJson; + os << "}\n"; + + //////////////////////////////////////////////////////////// + // Display the human-readable trace + //////////////////////////////////////////////////////////// + + for (size_t i = 0; i < nFrames; ++i) { + frame* pFrame = &frames[i]; + os << ' ' << getBaseName(pFrame->filename); + if (pFrame->baseAddress) { + os << '('; + if (*pFrame->symbol) { + os << pFrame->symbol; + } + + if (pFrame->offset != frame::kOffsetUnknown) { + os << "+0x" << pFrame->offset; + } + + os << ')'; + } + os << " [0x" << reinterpret_cast<uintptr_t>(pFrame->address) << ']' << std::endl; + } + + os << std::dec << std::nouppercase; + os << "----- END BACKTRACE -----" << std::endl; +} + +} // namespace + +void printStackTrace(std::ostream& os) { + printStackTraceInternal(os, false /* fromSignal */); +} + +void printStackTraceFromSignal(std::ostream& os) { + printStackTraceInternal(os, true /* fromSignal */); +} + +// From here down, a copy of stacktrace_posix.cpp. +namespace { + +void addOSComponentsToSoMap(BSONObjBuilder* soMap); + +/** + * Builds the "soMapJson" string, which is a JSON encoding of various pieces of information + * about a running process, including the map from load addresses to shared objects loaded at + * those addresses. + */ +MONGO_INITIALIZER(ExtractSOMap)(InitializerContext*) { + BSONObjBuilder soMap; + + auto&& vii = VersionInfoInterface::instance(VersionInfoInterface::NotEnabledAction::kFallback); + soMap << "mongodbVersion" << vii.version(); + soMap << "gitVersion" << vii.gitVersion(); + soMap << "compiledModules" << vii.modules(); + + struct utsname unameData; + if (!uname(&unameData)) { + BSONObjBuilder unameBuilder(soMap.subobjStart("uname")); + unameBuilder << "sysname" << unameData.sysname << "release" << unameData.release + << "version" << unameData.version << "machine" << unameData.machine; + } + addOSComponentsToSoMap(&soMap); + soMapJson = new std::string(soMap.done().jsonString(Strict)); + return Status::OK(); +} +} // namespace + +} // namespace mongo + +#if defined(__linux__) + +#include <elf.h> +#include <link.h> + +namespace mongo { +namespace { + +/** + * Rounds a byte offset up to the next highest offset that is aligned with an ELF Word. + */ +size_t roundUpToElfWordAlignment(size_t offset) { + static const size_t elfWordSizeBytes = sizeof(ElfW(Word)); + return (offset + (elfWordSizeBytes - 1)) & ~(elfWordSizeBytes - 1); +} + +/** + * Returns the size in bytes of an ELF note entry with the given header. + */ +size_t getNoteSizeBytes(const ElfW(Nhdr) & noteHeader) { + return sizeof(noteHeader) + roundUpToElfWordAlignment(noteHeader.n_namesz) + + roundUpToElfWordAlignment(noteHeader.n_descsz); +} + +/** + * Returns true of the given ELF program header refers to a runtime-readable segment. + */ +bool isSegmentMappedReadable(const ElfW(Phdr) & phdr) { + return phdr.p_flags & PF_R; +} + +/** + * Processes an ELF Phdr for a NOTE segment, updating "soInfo". + * + * Looks for the GNU Build ID NOTE, and adds a buildId field to soInfo if it finds one. + */ +void processNoteSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONObjBuilder* soInfo) { +#ifdef NT_GNU_BUILD_ID + const char* const notesBegin = reinterpret_cast<const char*>(info.dlpi_addr) + phdr.p_vaddr; + const char* const notesEnd = notesBegin + phdr.p_memsz; + ElfW(Nhdr) noteHeader; + for (const char* notesCurr = notesBegin; (notesCurr + sizeof(noteHeader)) < notesEnd; + notesCurr += getNoteSizeBytes(noteHeader)) { + memcpy(¬eHeader, notesCurr, sizeof(noteHeader)); + if (noteHeader.n_type != NT_GNU_BUILD_ID) + continue; + const char* const noteNameBegin = notesCurr + sizeof(noteHeader); + if (StringData(noteNameBegin, noteHeader.n_namesz - 1) != ELF_NOTE_GNU) { + continue; + } + const char* const noteDescBegin = + noteNameBegin + roundUpToElfWordAlignment(noteHeader.n_namesz); + soInfo->append("buildId", toHex(noteDescBegin, noteHeader.n_descsz)); + } +#endif +} + +/** + * Processes an ELF Phdr for a LOAD segment, updating "soInfo". + * + * The goal of this operation is to find out if the current object is an executable or a shared + * object, by looking for the LOAD segment that maps the first several bytes of the file (the + * ELF header). If it's an executable, this method updates soInfo with the load address of the + * segment + */ +void processLoadSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONObjBuilder* soInfo) { + if (phdr.p_offset) + return; + if (phdr.p_memsz < sizeof(ElfW(Ehdr))) + return; + + // Segment includes beginning of file and is large enough to hold the ELF header + ElfW(Ehdr) eHeader; + memcpy(&eHeader, reinterpret_cast<const char*>(info.dlpi_addr) + phdr.p_vaddr, sizeof(eHeader)); + + std::string quotedFileName = "\"" + str::escape(info.dlpi_name) + "\""; + + if (memcmp(&eHeader.e_ident[0], ELFMAG, SELFMAG)) { + warning() << "Bad ELF magic number in image of " << quotedFileName; + return; + } + +#if defined(__ELF_NATIVE_CLASS) +#define ARCH_BITS __ELF_NATIVE_CLASS +#else //__ELF_NATIVE_CLASS +#if defined(__x86_64__) || defined(__aarch64__) +#define ARCH_BITS 64 +#elif defined(__arm__) +#define ARCH_BITS 32 +#else +#error Unknown target architecture. +#endif //__aarch64__ +#endif //__ELF_NATIVE_CLASS + +#define MKELFCLASS(N) _MKELFCLASS(N) +#define _MKELFCLASS(N) ELFCLASS##N + if (eHeader.e_ident[EI_CLASS] != MKELFCLASS(ARCH_BITS)) { + warning() << "Expected elf file class of " << quotedFileName << " to be " + << MKELFCLASS(ARCH_BITS) << "(" << ARCH_BITS << "-bit), but found " + << int(eHeader.e_ident[4]); + return; + } + +#undef ARCH_BITS + + if (eHeader.e_ident[EI_VERSION] != EV_CURRENT) { + warning() << "Wrong ELF version in " << quotedFileName << ". Expected " << EV_CURRENT + << " but found " << int(eHeader.e_ident[EI_VERSION]); + return; + } + + soInfo->append("elfType", eHeader.e_type); + + switch (eHeader.e_type) { + case ET_EXEC: + break; + case ET_DYN: + return; + default: + warning() << "Surprised to find " << quotedFileName << " is ELF file of type " + << eHeader.e_type; + return; + } + + soInfo->append("b", integerToHex(phdr.p_vaddr)); +} + +/** + * Callback that processes an ELF object linked into the current address space. + * + * Used by dl_iterate_phdr in ExtractSOMap, below, to build up the list of linked + * objects. + * + * Each entry built by an invocation of ths function may have the following fields: + * * "b", the base address at which an object is loaded. + * * "path", the path on the file system to the object. + * * "buildId", the GNU Build ID of the object. + * * "elfType", the ELF type of the object, typically 2 or 3 (executable or SO). + * + * At post-processing time, the buildId field can be used to identify the file containing + * debug symbols for objects loaded at the given "laodAddr", which in turn can be used with + * the "backtrace" displayed in printStackTrace to get detailed unwind information. + */ +int outputSOInfo(dl_phdr_info* info, size_t sz, void* data) { + BSONObjBuilder soInfo(reinterpret_cast<BSONArrayBuilder*>(data)->subobjStart()); + if (info->dlpi_addr) + soInfo.append("b", integerToHex(ElfW(Addr)(info->dlpi_addr))); + if (info->dlpi_name && *info->dlpi_name) + soInfo.append("path", info->dlpi_name); + + for (ElfW(Half) i = 0; i < info->dlpi_phnum; ++i) { + const ElfW(Phdr) & phdr(info->dlpi_phdr[i]); + if (!isSegmentMappedReadable(phdr)) + continue; + switch (phdr.p_type) { + case PT_NOTE: + processNoteSegment(*info, phdr, &soInfo); + break; + case PT_LOAD: + processLoadSegment(*info, phdr, &soInfo); + break; + default: + break; + } + } + return 0; +} + +void addOSComponentsToSoMap(BSONObjBuilder* soMap) { + BSONArrayBuilder soList(soMap->subarrayStart("somap")); + dl_iterate_phdr(outputSOInfo, &soList); + soList.done(); +} + +} // namespace + +} // namespace mongo + +#elif defined(__APPLE__) && defined(__MACH__) + +#include <mach-o/dyld.h> +#include <mach-o/ldsyms.h> +#include <mach-o/loader.h> + +namespace mongo { +namespace { +const char* lcNext(const char* lcCurr) { + const load_command* cmd = reinterpret_cast<const load_command*>(lcCurr); + return lcCurr + cmd->cmdsize; +} + +uint32_t lcType(const char* lcCurr) { + const load_command* cmd = reinterpret_cast<const load_command*>(lcCurr); + return cmd->cmd; +} + +template <typename SegmentCommandType> +bool maybeAppendLoadAddr(BSONObjBuilder* soInfo, const SegmentCommandType* segmentCommand) { + if (StringData(SEG_TEXT) != segmentCommand->segname) { + return false; + } + *soInfo << "vmaddr" << integerToHex(segmentCommand->vmaddr); + return true; +} + +void addOSComponentsToSoMap(BSONObjBuilder* soMap) { + const uint32_t numImages = _dyld_image_count(); + BSONArrayBuilder soList(soMap->subarrayStart("somap")); + for (uint32_t i = 0; i < numImages; ++i) { + BSONObjBuilder soInfo(soList.subobjStart()); + const char* name = _dyld_get_image_name(i); + if (name) + soInfo << "path" << name; + const mach_header* header = _dyld_get_image_header(i); + if (!header) + continue; + size_t headerSize; + if (header->magic == MH_MAGIC) { + headerSize = sizeof(mach_header); + } else if (header->magic == MH_MAGIC_64) { + headerSize = sizeof(mach_header_64); + } else { + continue; + } + soInfo << "machType" << header->filetype; + soInfo << "b" << integerToHex(reinterpret_cast<intptr_t>(header)); + const char* const loadCommandsBegin = reinterpret_cast<const char*>(header) + headerSize; + const char* const loadCommandsEnd = loadCommandsBegin + header->sizeofcmds; + + // Search the "load command" data in the Mach object for the entry encoding the UUID of the + // object, and for the __TEXT segment. Adding the "vmaddr" field of the __TEXT segment load + // command of an executable or dylib to an offset in that library provides an address + // suitable to passing to atos or llvm-symbolizer for symbolization. + // + // See, for example, http://lldb.llvm.org/symbolication.html. + bool foundTextSegment = false; + for (const char* lcCurr = loadCommandsBegin; lcCurr < loadCommandsEnd; + lcCurr = lcNext(lcCurr)) { + switch (lcType(lcCurr)) { + case LC_UUID: { + const auto uuidCmd = reinterpret_cast<const uuid_command*>(lcCurr); + soInfo << "buildId" << toHex(uuidCmd->uuid, 16); + break; + } + case LC_SEGMENT_64: + if (!foundTextSegment) { + foundTextSegment = maybeAppendLoadAddr( + &soInfo, reinterpret_cast<const segment_command_64*>(lcCurr)); + } + break; + case LC_SEGMENT: + if (!foundTextSegment) { + foundTextSegment = maybeAppendLoadAddr( + &soInfo, reinterpret_cast<const segment_command*>(lcCurr)); + } + break; + } + } + } +} +} // namepace +} // namespace mongo +#else +namespace mongo { +namespace { +void addOSComponentsToSoMap(BSONObjBuilder* soMap) {} +} // namepace +} // namespace mongo +#endif diff --git a/src/mongo/util/stacktrace_windows.cpp b/src/mongo/util/stacktrace_windows.cpp index 6552e70bad6..6aef676a4b2 100644 --- a/src/mongo/util/stacktrace_windows.cpp +++ b/src/mongo/util/stacktrace_windows.cpp @@ -235,8 +235,6 @@ static const int maxBackTraceFrames = 100; /** * Print a stack backtrace for the current thread to the specified ostream. - * - * @param os ostream& to receive printed stack backtrace */ void printStackTrace(std::ostream& os) { CONTEXT context; @@ -246,6 +244,13 @@ void printStackTrace(std::ostream& os) { printWindowsStackTrace(context, os); } +/** + * Print a stack backtrace for the current thread to the specified ostream, signal-safe variant. + * (Currently the same as printStackTrace.) + */ +void printStackTraceFromSignal(std::ostream& os) { + printStackTrace(os); +} /** * Print stack trace (using a specified stack context) to "os" diff --git a/src/third_party/SConscript b/src/third_party/SConscript index 1b7b80f23b2..5c0c129b307 100644 --- a/src/third_party/SConscript +++ b/src/third_party/SConscript @@ -4,8 +4,11 @@ import libdeps import json Import("env use_system_version_of_library usemozjs get_option") -Import("wiredtiger") Import("mobile_se") +Import("use_libunwind") +Import("use_system_libunwind") +Import("use_vendored_libunwind") +Import("wiredtiger") boostSuffix = "-1.70.0" snappySuffix = '-1.1.7' @@ -187,6 +190,18 @@ if not use_system_version_of_library('kms-message'): 'CPPDEFINES' :['KMS_MSG_STATIC'] } +if use_system_libunwind: + thirdPartyEnvironmentModifications['unwind'] = { + 'CPPDEFINES' : ['MONGO_USE_LIBUNWIND', 'UNW_LOCAL_ONLY'], + 'SYSLIBDEPS' : [env['LIBDEPS_UNWIND_SYSLIBDEP'], 'lzma'], + } +elif use_vendored_libunwind: + thirdPartyEnvironmentModifications['unwind'] = { + 'CPPDEFINES' : ['MONGO_USE_LIBUNWIND', 'UNW_LOCAL_ONLY'], + 'LIBDEPS' : ['$BUILD_DIR/third_party/shim_unwind'], + 'SYSLIBDEPS' : ['lzma'], + } + def injectThirdParty(thisEnv, libraries=[], parts=[]): libraries = thisEnv.Flatten([libraries]) parts = thisEnv.Flatten([parts]) @@ -212,43 +227,37 @@ s2Env.InjectThirdParty(libraries=['s2', 'boost', 'abseil-cpp', 'fmt', 'safeint'] s2Env.InjectMongoIncludePaths() s2Env.SConscript('s2/SConscript', exports={'env' : s2Env}) -unwindEnv = None -if use_system_version_of_library("unwind"): - unwindEnv = env.Clone( - SYSLIBDEPS=[ - env['LIBDEPS_UNWIND_SYSLIBDEP'], - ]) -else: - unwindEnv = env.Clone() - # SCons uses the "$CC" tool for both C and assembler files. Distinguish them for the sake of - # later tools like our Ninja SCons module. - unwindEnv['ASPP'] = '$CC' - unwindEnv['ASPPCOM'] = unwindEnv['ASPPCOM'].replace('$CC ', '$ASPP ') - unwindEnv.InjectThirdParty(libraries=['unwind']) - unwindEnv.InjectMongoIncludePaths() +if use_libunwind: + if use_system_libunwind: + unwindEnv = env.Clone( + SYSLIBDEPS=[ + env['LIBDEPS_UNWIND_SYSLIBDEP'], + ]) + else: + unwindEnv = env.Clone() - def registerConsumerModifications(env, **kwargs): - for k,v in kwargs.items(): - thirdPartyEnvironmentModifications['unwind'][k] = v + # SCons uses the "$CC" tool for both C and assembler files. Distinguish them for the sake of + # later tools like our Ninja SCons module. + unwindEnv['ASPP'] = '$CC' + unwindEnv['ASPPCOM'] = unwindEnv['ASPPCOM'].replace('$CC ', '$ASPP ') - unwindEnv.AddMethod(registerConsumerModifications, 'RegisterConsumerModifications') - unwindEnv.SConscript('unwind/SConscript', exports={'env' : unwindEnv}) + def registerConsumerModifications(env, **kwargs): + for k,v in kwargs.items(): + thirdPartyEnvironmentModifications['unwind'][k] = v - print("third_party/unwind modifications:", - ",".join(thirdPartyEnvironmentModifications['unwind'].keys())) + unwindEnv.AddMethod(registerConsumerModifications, 'RegisterConsumerModifications') + unwindEnv.SConscript('unwind/SConscript', exports={'env' : unwindEnv}) + unwindEnv.Append( + LIBDEPS_INTERFACE=[ + 'unwind/unwind', + ]) - unwindEnv = unwindEnv.Clone( - LIBDEPS_INTERFACE=[ - 'unwind/unwind', + unwindEnv.Library( + target="shim_unwind", + source=[ + 'shim_unwind.cpp', ]) -unwindEnv.Library( - target="shim_unwind", - source=[ - 'shim_unwind.cpp', - ]) - - if use_system_version_of_library("fmt"): fmtEnv = env.Clone( SYSLIBDEPS=[ diff --git a/src/third_party/unwind/SConscript b/src/third_party/unwind/SConscript index dd205b19b49..60e608958c4 100644 --- a/src/third_party/unwind/SConscript +++ b/src/third_party/unwind/SConscript @@ -117,7 +117,7 @@ env.Append( # propagates to consumers that Inject (depend on) unwind. env.RegisterConsumerModifications( CPPPATH=[unwind_platform.Dir("install/include")], - CPPDEFINES=['UNW_LOCAL_ONLY'], + CPPDEFINES=['UNW_LOCAL_ONLY', 'MONGO_USE_LIBUNWIND'], LIBS=['lzma']) env.Append( |