summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@mongodb.com>2019-05-30 15:15:08 -0400
committerAndrew Morrow <acm@mongodb.com>2019-07-17 10:49:15 -0400
commitcbead440945c78b577c29dab8c11e81a58ee7731 (patch)
tree424c0395df4ac4bfcf2ab145df259e567006b8f3 /src
parent19d0d47fb83d01edb18e0bcb235d6c39bbe16bbd (diff)
downloadmongo-cbead440945c78b577c29dab8c11e81a58ee7731.tar.gz
SERVER-36242 Optionally use libunwind for backtraces
Diffstat (limited to 'src')
-rw-r--r--src/mongo/SConscript20
-rw-r--r--src/mongo/base/SConscript7
-rw-r--r--src/mongo/base/backtrace_visibility_test.cpp74
-rw-r--r--src/mongo/base/backtrace_visibility_test.h48
-rw-r--r--src/mongo/base/unwind_test.cpp63
-rw-r--r--src/mongo/platform/compiler_gcc.h1
-rw-r--r--src/mongo/platform/compiler_msvc.h1
-rw-r--r--src/mongo/util/signal_handlers_synchronous.cpp2
-rw-r--r--src/mongo/util/stacktrace.h3
-rw-r--r--src/mongo/util/stacktrace_posix.cpp5
-rw-r--r--src/mongo/util/stacktrace_unwind.cpp607
-rw-r--r--src/mongo/util/stacktrace_windows.cpp9
-rw-r--r--src/third_party/SConscript73
-rw-r--r--src/third_party/unwind/SConscript2
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(&noteHeader, 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(