summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/SConscript8
-rw-r--r--src/mongo/base/unwind_test.cpp77
-rw-r--r--src/mongo/util/SConscript8
-rw-r--r--src/mongo/util/stacktrace.cpp4
-rw-r--r--src/mongo/util/stacktrace.h4
-rw-r--r--src/mongo/util/stacktrace_posix.cpp707
-rw-r--r--src/mongo/util/stacktrace_somap.cpp340
-rw-r--r--src/mongo/util/stacktrace_somap.h54
-rw-r--r--src/mongo/util/stacktrace_test.cpp359
-rw-r--r--src/mongo/util/stacktrace_unwind.cpp607
10 files changed, 1094 insertions, 1074 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 9b4a524382a..93ca72b1504 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -58,9 +58,8 @@ 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'
+
+stacktrace_impl_cpp = [ File('util/stacktrace_${TARGET_OS_FAMILY}.cpp') ]
baseEnv.Library(
target='base',
@@ -141,7 +140,8 @@ baseEnv.Library(
'util/shell_exec.cpp',
'util/signal_handlers_synchronous.cpp',
'util/stacktrace.cpp',
- stacktrace_impl_cpp,
+ 'util/stacktrace_${TARGET_OS_FAMILY}.cpp',
+ 'util/stacktrace_somap.cpp',
'util/str.cpp',
'util/system_clock_source.cpp',
'util/system_tick_source.cpp',
diff --git a/src/mongo/base/unwind_test.cpp b/src/mongo/base/unwind_test.cpp
index 292c20076a4..e0f67946484 100644
--- a/src/mongo/base/unwind_test.cpp
+++ b/src/mongo/base/unwind_test.cpp
@@ -94,7 +94,6 @@ struct Context {
std::string s;
};
-// Disable clang-format for the "if constexpr"
template <int N>
void callNext(Context& ctx) {
if constexpr (N == 0) {
@@ -102,46 +101,19 @@ 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) {
+void assertAndRemovePrefix(std::string_view& v, 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());
+ v.remove_prefix(pos + prefix.size());
}
-void assertAndRemoveSuffix(std::string_view& v, const std::string_view suffix) {
+void assertAndRemoveSuffix(std::string_view& v, 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);
- }
+ v.remove_suffix(v.size() - pos);
}
TEST(Unwind, Demangled) {
@@ -180,14 +152,43 @@ TEST(Unwind, Linkage) {
// bottom in the "stacktrace" argument.
normal_function(stacktrace);
+ std::string_view view = stacktrace;
+
+ if (1) {
+ unittest::log().setIsTruncatable(false) << "trace:\n{{{\n" << stacktrace << "\n}}}\n";
+ }
+
+ // Remove the backtrace JSON object, which is all one line.
+ assertAndRemovePrefix(view, "----- BEGIN BACKTRACE -----");
+ assertAndRemovePrefix(view, R"({"backtrace":)");
+ assertAndRemovePrefix(view, "}\n");
+ assertAndRemoveSuffix(view, "----- END BACKTRACE -----");
+
+ std::string_view remainder = 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);
+ std::string frames[] = {
+ "printStackTrace",
+ "static_function",
+ "anonymous_namespace_function",
+ "hidden_function",
+ "normal_function",
+ };
+
+ for (const auto& name : frames) {
+ auto pos = remainder.find(name);
+ if (pos == remainder.npos) {
+ unittest::log().setIsTruncatable(false) //
+ << "\n" //
+ << "--- BEGIN ACTUAL BACKTRACE ---\n" //
+ << stacktrace //
+ << "--- END ACTUAL BACKTRACE ---";
+ FAIL("name '{}' is missing or out of order in sample backtrace"_format(name));
+ }
+ unittest::log() << "removing prefix `" << std::string(remainder.substr(0, pos)) << "`";
+ remainder.remove_prefix(pos);
+ }
}
} // namespace unwind_test_detail
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript
index f14915d2bb0..034fcf96d27 100644
--- a/src/mongo/util/SConscript
+++ b/src/mongo/util/SConscript
@@ -628,3 +628,11 @@ generateItoATable = env.Command(
)
env.Alias('generated-sources', generateItoATable)
+env.CppUnitTest(
+ target='stacktrace_test',
+ source=[
+ 'stacktrace_test.cpp',
+ ],
+ LIBDEPS=[
+ ],
+)
diff --git a/src/mongo/util/stacktrace.cpp b/src/mongo/util/stacktrace.cpp
index 14dc55838b4..0650de92848 100644
--- a/src/mongo/util/stacktrace.cpp
+++ b/src/mongo/util/stacktrace.cpp
@@ -31,10 +31,8 @@
// Setting kDefault to preserve previous behavior in (defunct) getStacktraceLogger().
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
-#include "mongo/platform/basic.h"
-
#include "mongo/util/stacktrace.h"
-
+#include "mongo/platform/basic.h"
#include "mongo/util/log.h"
namespace mongo {
diff --git a/src/mongo/util/stacktrace.h b/src/mongo/util/stacktrace.h
index 2b45b4d183b..be94d5e7865 100644
--- a/src/mongo/util/stacktrace.h
+++ b/src/mongo/util/stacktrace.h
@@ -36,9 +36,7 @@
#include <iosfwd>
#if defined(_WIN32)
-// We need to pick up a decl for CONTEXT. Forward declaring would be preferable, but it is
-// unclear that we can do so.
-#include "mongo/platform/windows_basic.h"
+#include "mongo/platform/windows_basic.h" // for CONTEXT
#endif
namespace mongo {
diff --git a/src/mongo/util/stacktrace_posix.cpp b/src/mongo/util/stacktrace_posix.cpp
index 29775d035b1..7a63e6c26f0 100644
--- a/src/mongo/util/stacktrace_posix.cpp
+++ b/src/mongo/util/stacktrace_posix.cpp
@@ -32,43 +32,55 @@
#include "mongo/platform/basic.h"
#include "mongo/util/stacktrace.h"
+#include "mongo/util/stacktrace_somap.h"
+#include <array>
+#include <boost/optional.hpp>
+#include <climits>
#include <cstdlib>
#include <dlfcn.h>
+#include <iomanip>
#include <iostream>
#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/platform/compiler_gcc.h"
#include "mongo/util/log.h"
-#include "mongo/util/str.h"
+#include "mongo/util/scopeguard.h"
#include "mongo/util/version.h"
-#if defined(MONGO_CONFIG_HAVE_EXECINFO_BACKTRACE)
+#define MONGO_STACKTRACE_BACKEND_LIBUNWIND 1
+#define MONGO_STACKTRACE_BACKEND_EXECINFO 2
+#define MONGO_STACKTRACE_BACKEND_NONE 3
+
+#if defined(MONGO_USE_LIBUNWIND)
+#define MONGO_STACKTRACE_BACKEND MONGO_STACKTRACE_BACKEND_LIBUNWIND
+#elif defined(MONGO_CONFIG_HAVE_EXECINFO_BACKTRACE)
+#define MONGO_STACKTRACE_BACKEND MONGO_STACKTRACE_BACKEND_EXECINFO
+#else
+#define MONGO_STACKTRACE_BACKEND MONGO_STACKTRACE_BACKEND_NONE
+#endif
+
+#if MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_LIBUNWIND
+#include <libunwind.h>
+#elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_EXECINFO
#include <execinfo.h>
-#elif defined(__sun)
-#include <ucontext.h>
#endif
namespace mongo {
+namespace stacktrace_detail {
-namespace {
-/// Maximum number of stack frames to appear in a backtrace.
-const int maxBackTraceFrames = 100;
+constexpr int kFrameMax = 100;
+constexpr size_t kSymbolMax = 512;
+constexpr StringData kUnknownFileName = "???"_sd;
-/// Optional string containing extra unwinding information. Should take the form of a
-/// JSON document.
-std::string* soMapJson = nullptr;
+struct StackTraceOptions {
+ bool withProcessInfo = true;
+ bool withHumanReadable = true;
+};
-/**
- * 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".
- */
+// E.g., for "/foo/bar/my.txt", returns "my.txt".
StringData getBaseName(StringData path) {
size_t lastSlash = path.rfind('/');
if (lastSlash == std::string::npos)
@@ -76,80 +88,55 @@ StringData getBaseName(StringData path) {
return path.substr(lastSlash + 1);
}
-// All platforms we build on have execinfo.h and we use backtrace() directly, with one exception
-#if defined(MONGO_CONFIG_HAVE_EXECINFO_BACKTRACE)
-using ::backtrace;
+struct NameBase {
+ StringData name;
+ uintptr_t base;
+};
+
+// Metadata about an instruction address.
+// Beyond that, it may have an enclosing shared object file.
+// Further, it may have an enclosing symbol within that shared object.
+struct AddressMetadata {
+ uintptr_t address{};
+ boost::optional<NameBase> soFile{};
+ boost::optional<NameBase> symbol{};
+};
-// On Solaris 10, there is no execinfo.h, so we need to emulate it.
-// Solaris 11 has execinfo.h, and this code doesn't get used.
-#elif defined(__sun)
-class WalkcontextCallback {
+class IterationIface {
public:
- WalkcontextCallback(uintptr_t* array, int size)
- : _position(0), _count(size), _addresses(array) {}
-
- // This callback function is called from C code, and so must not throw exceptions
- //
- static int callbackFunction(uintptr_t address,
- int signalNumber,
- WalkcontextCallback* thisContext) {
- if (thisContext->_position < thisContext->_count) {
- thisContext->_addresses[thisContext->_position++] = address;
- return 0;
- }
- return 1;
- }
- int getCount() const {
- return static_cast<int>(_position);
- }
-
-private:
- size_t _position;
- size_t _count;
- uintptr_t* _addresses;
+ enum Flags {
+ kRaw = 0,
+ kSymbolic = 1, // Also gather symbolic metadata.
+ };
+
+ virtual ~IterationIface() = default;
+ virtual void start(Flags f) = 0;
+ virtual bool done() const = 0;
+ virtual const AddressMetadata& deref() const = 0;
+ virtual void advance() = 0;
};
-typedef int (*WalkcontextCallbackFunc)(uintptr_t address, int signalNumber, void* thisContext);
+template <typename T>
+struct Quoted {
+ explicit Quoted(const T& v) : v(v) {}
-int backtrace(void** array, int size) {
- WalkcontextCallback walkcontextCallback(reinterpret_cast<uintptr_t*>(array), size);
- ucontext_t context;
- if (getcontext(&context) != 0) {
- return 0;
+ friend std::ostream& operator<<(std::ostream& os, const Quoted& q) {
+ return os << "\"" << q.v << "\"";
}
- int wcReturn = walkcontext(
- &context,
- reinterpret_cast<WalkcontextCallbackFunc>(WalkcontextCallback::callbackFunction),
- static_cast<void*>(&walkcontextCallback));
- if (wcReturn == 0) {
- return walkcontextCallback.getCount();
- }
- return 0;
-}
-#else
-// On unsupported platforms, we print an error instead of printing a stacktrace.
-#define MONGO_NO_BACKTRACE
-#endif
-
-} // namespace
-#if defined(MONGO_NO_BACKTRACE)
-void printStackTrace(std::ostream& os) {
- os << "This platform does not support printing stacktraces" << std::endl;
-}
+ const T& v;
+};
-#else
/**
* 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 -----
+ * hexAddresses ... // space-separated
+ * ----- BEGIN BACKTRACE -----
+ * {backtrace:..., processInfo:...} // json
+ * 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
@@ -159,396 +146,278 @@ void printStackTrace(std::ostream& os) {
* 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.
- *
- * @param os ostream& to receive printed stack backtrace
*/
-void printStackTrace(std::ostream& os) {
- static const char unknownFileName[] = "???";
- void* addresses[maxBackTraceFrames];
- Dl_info dlinfoForFrames[maxBackTraceFrames];
-
- ////////////////////////////////////////////////////////////
- // Get the backtrace addresses.
- ////////////////////////////////////////////////////////////
-
- const int addressCount = backtrace(addresses, maxBackTraceFrames);
- if (addressCount == 0) {
- const int err = errno;
- os << "Unable to collect backtrace addresses (errno: " << err << ' ' << strerror(err) << ')'
- << std::endl;
- return;
- }
-
- ////////////////////////////////////////////////////////////
- // Collect symbol information for each backtrace address.
- ////////////////////////////////////////////////////////////
-
+void printStackTraceGeneric(IterationIface& source,
+ std::ostream& os,
+ const StackTraceOptions& options) {
+ // TODO: make this signal-safe.
os << std::hex << std::uppercase;
- for (int i = 0; i < addressCount; ++i) {
- Dl_info& dlinfo(dlinfoForFrames[i]);
- if (!dladdr(addresses[i], &dlinfo)) {
- dlinfo.dli_fname = unknownFileName;
- dlinfo.dli_fbase = nullptr;
- dlinfo.dli_sname = nullptr;
- dlinfo.dli_saddr = nullptr;
- }
- os << ' ' << addresses[i];
+ auto sg = makeGuard([&] { os << std::dec << std::nouppercase; });
+ // First, just the raw backtrace addresses.
+ for (source.start(source.kRaw); !source.done(); source.advance()) {
+ const auto& f = source.deref();
+ os << ' ' << f.address;
}
os << "\n----- BEGIN BACKTRACE -----\n";
- ////////////////////////////////////////////////////////////
// Display the JSON backtrace
- ////////////////////////////////////////////////////////////
-
- os << "{\"backtrace\":[";
- for (int i = 0; i < addressCount; ++i) {
- const Dl_info& dlinfo = dlinfoForFrames[i];
- const uintptr_t fileOffset = uintptr_t(addresses[i]) - uintptr_t(dlinfo.dli_fbase);
- if (i)
- os << ',';
- os << "{\"b\":\"" << uintptr_t(dlinfo.dli_fbase) << "\",\"o\":\"" << fileOffset;
- if (dlinfo.dli_sname) {
- os << "\",\"s\":\"" << dlinfo.dli_sname;
+ {
+ os << "{";
+ os << Quoted("backtrace") << ":";
+ {
+ os << "[";
+ StringData frameComma;
+ for (source.start(source.kSymbolic); !source.done(); source.advance()) {
+ const auto& f = source.deref();
+ os << frameComma;
+ frameComma = ","_sd;
+ {
+ os << "{";
+ StringData fieldSep = "";
+ auto addField = [&](StringData k, auto&& v) {
+ os << fieldSep << Quoted(k) << ":" << Quoted(v);
+ fieldSep = ","_sd;
+ };
+ if (f.soFile) {
+ // Cast because integers obey `uppercase` and pointers don't.
+ addField("b", f.soFile->base);
+ addField("o", (f.address - f.soFile->base));
+ if (f.symbol) {
+ addField("s", f.symbol->name);
+ }
+ } else {
+ addField("b", uintptr_t{0});
+ addField("o", f.address);
+ }
+ os << "}";
+ }
+ }
+ os << "]";
+ }
+ if (options.withProcessInfo) {
+ if (auto soMap = globalSharedObjectMapInfo())
+ os << "," << Quoted("processInfo") << ":" << soMap->json();
}
- os << "\"}";
+ os << "}";
}
- os << ']';
-
- if (soMapJson)
- os << ",\"processInfo\":" << *soMapJson;
- os << "}\n";
-
- ////////////////////////////////////////////////////////////
+ os << "\n";
// Display the human-readable trace
- ////////////////////////////////////////////////////////////
- for (int i = 0; i < addressCount; ++i) {
- Dl_info& dlinfo(dlinfoForFrames[i]);
- os << ' ';
- if (dlinfo.dli_fbase) {
- os << getBaseName(dlinfo.dli_fname) << '(';
- if (dlinfo.dli_sname) {
- const uintptr_t offset = uintptr_t(addresses[i]) - uintptr_t(dlinfo.dli_saddr);
- os << dlinfo.dli_sname << "+0x" << offset;
+ if (options.withHumanReadable) {
+ for (source.start(source.kSymbolic); !source.done(); source.advance()) {
+ const auto& f = source.deref();
+ os << " ";
+ if (f.soFile) {
+ os << getBaseName(f.soFile->name);
+ os << "(";
+ if (f.symbol) {
+ os << f.symbol->name << "+0x" << (f.address - f.symbol->base);
+ } else {
+ // No symbol, so fall back to the `soFile` offset.
+ os << "+0x" << (f.address - f.soFile->base);
+ }
+ os << ")";
} else {
- const uintptr_t offset = uintptr_t(addresses[i]) - uintptr_t(dlinfo.dli_fbase);
- os << "+0x" << offset;
+ // Not even shared object information, just punt with unknown filename.
+ os << kUnknownFileName;
}
- os << ')';
- } else {
- os << unknownFileName;
+ os << " [0x" << f.address << "]\n";
}
- os << " [" << addresses[i] << ']' << std::endl;
}
-
- os << std::dec << std::nouppercase;
- os << "----- END BACKTRACE -----" << std::endl;
-}
-
-#endif
-
-void printStackTraceFromSignal(std::ostream& os) {
- printStackTrace(os);
+ os << "----- END BACKTRACE -----\n";
}
-// From here down, a copy of stacktrace_unwind.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;
+void mergeDlInfo(AddressMetadata& f) {
+ Dl_info dli;
+ // `man dladdr`:
+ // On success, these functions return a nonzero value. If the address
+ // specified in addr could be matched to a shared object, but not to a
+ // symbol in the shared object, then the info->dli_sname and
+ // info->dli_saddr fields are set to NULL.
+ if (dladdr(reinterpret_cast<void*>(f.address), &dli) == 0) {
+ return; // f.address doesn't map to a shared object
+ }
+ if (!f.soFile) {
+ f.soFile = NameBase{dli.dli_fname, reinterpret_cast<uintptr_t>(dli.dli_fbase)};
+ }
+ if (!f.symbol) {
+ if (dli.dli_saddr) {
+ // matched to a symbol in the shared object
+ f.symbol = NameBase{dli.dli_sname, reinterpret_cast<uintptr_t>(dli.dli_saddr)};
+ }
}
- addOSComponentsToSoMap(&soMap);
- soMapJson = new std::string(soMap.done().jsonString(Strict));
- return Status::OK();
}
-} // namespace
-} // namespace mongo
-
-#if defined(__linux__)
+#if MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_LIBUNWIND
-#include <elf.h>
-#include <link.h>
+class Iteration : public IterationIface {
+public:
+ explicit Iteration(std::ostream& os, bool fromSignal) : _os(os), _fromSignal(fromSignal) {
+ if (int r = unw_getcontext(&_context); r < 0) {
+ _os << "unw_getcontext: " << unw_strerror(r) << std::endl;
+ _failed = true;
+ }
+ }
-namespace mongo {
-namespace {
+private:
+ void start(Flags f) override {
+ _flags = f;
+ _end = false;
-/**
- * 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);
-}
+ if (_failed) {
+ _end = true;
+ return;
+ }
+ int r = unw_init_local2(&_cursor, &_context, _fromSignal ? UNW_INIT_SIGNAL_FRAME : 0);
+ if (r < 0) {
+ _os << "unw_init_local2: " << unw_strerror(r) << std::endl;
+ _end = true;
+ return;
+ }
+ _load();
+ }
-/**
- * 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);
-}
+ bool done() const override {
+ return _end;
+ }
-/**
- * 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;
-}
+ const AddressMetadata& deref() const override {
+ return _f;
+ }
-/**
- * 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;
+ void advance() override {
+ int r = unw_step(&_cursor);
+ if (r <= 0) {
+ if (r < 0) {
+ _os << "error: unw_step: " << unw_strerror(r) << std::endl;
+ }
+ _end = true;
+ }
+ if (!_end) {
+ _load();
}
- 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;
+ void _load() {
+ _f = {};
+ unw_word_t pc;
+ if (int r = unw_get_reg(&_cursor, UNW_REG_IP, &pc); r < 0) {
+ _os << "unw_get_reg: " << unw_strerror(r) << std::endl;
+ _end = true;
+ return;
+ }
+ if (pc == 0) {
+ _end = true;
+ return;
+ }
+ _f.address = pc;
+ if (_flags & kSymbolic) {
+ unw_word_t offset;
+ if (int r = unw_get_proc_name(&_cursor, _symbolBuf, sizeof(_symbolBuf), &offset);
+ r < 0) {
+ _os << "unw_get_proc_name(" << _f.address << "): " << unw_strerror(r) << std::endl;
+ } else {
+ _f.symbol = NameBase{_symbolBuf, _f.address - offset};
+ }
+ mergeDlInfo(_f);
+ }
+ }
- // 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::ostream& _os;
+ bool _fromSignal;
- std::string quotedFileName = "\"" + str::escape(info.dlpi_name) + "\"";
+ Flags _flags;
+ AddressMetadata _f{};
- if (memcmp(&eHeader.e_ident[0], ELFMAG, SELFMAG)) {
- warning() << "Bad ELF magic number in image of " << quotedFileName;
- return;
- }
+ bool _failed = false;
+ bool _end = false;
-#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;
- }
+ unw_context_t _context;
+ unw_cursor_t _cursor;
-#undef ARCH_BITS
+ char _symbolBuf[kSymbolMax];
+};
- 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;
- }
+MONGO_COMPILER_NOINLINE
+void printStackTrace(std::ostream& os, bool fromSignal) {
+ Iteration iteration(os, fromSignal);
+ printStackTraceGeneric(iteration, os, StackTraceOptions{});
+}
- soInfo->append("elfType", eHeader.e_type);
+#elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_EXECINFO
- 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;
+class Iteration : public IterationIface {
+public:
+ explicit Iteration(std::ostream& os, bool fromSignal) {
+ _n = ::backtrace(_addresses.data(), _addresses.size());
+ if (_n == 0) {
+ int err = errno;
+ os << "Unable to collect backtrace addresses (errno: " << err << ' ' << strerror(err)
+ << ')' << std::endl;
return;
+ }
}
- soInfo->append("b", integerToHex(phdr.p_vaddr));
-}
+private:
+ void start(Flags f) override {
+ _flags = f;
+ _i = 0;
+ if (!done())
+ _load();
+ }
+ bool done() const override {
+ return _i >= _n;
+ }
+ const AddressMetadata& deref() const override {
+ return _f;
+ }
+ void advance() override {
+ ++_i;
+ if (!done())
+ _load();
+ }
-/**
- * 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;
+ void _load() {
+ _f = {};
+ _f.address = reinterpret_cast<uintptr_t>(_addresses[_i]);
+ if (_flags & kSymbolic) {
+ mergeDlInfo(_f);
}
}
- return 0;
-}
-
-void addOSComponentsToSoMap(BSONObjBuilder* soMap) {
- BSONArrayBuilder soList(soMap->subarrayStart("somap"));
- dl_iterate_phdr(outputSOInfo, &soList);
- soList.done();
-}
-} // namespace
+ Flags _flags;
+ AddressMetadata _f;
-} // namespace mongo
+ std::array<void*, kFrameMax> _addresses;
+ size_t _n = 0;
+ size_t _i = 0;
+};
-#elif defined(__APPLE__) && defined(__MACH__)
+MONGO_COMPILER_NOINLINE
+void printStackTrace(std::ostream& os, bool fromSignal) {
+ Iteration iteration(os, fromSignal);
+ printStackTraceGeneric(iteration, os, StackTraceOptions{});
+}
-#include <mach-o/dyld.h>
-#include <mach-o/ldsyms.h>
-#include <mach-o/loader.h>
+#elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_NONE
-namespace mongo {
-namespace {
-const char* lcNext(const char* lcCurr) {
- const load_command* cmd = reinterpret_cast<const load_command*>(lcCurr);
- return lcCurr + cmd->cmdsize;
+MONGO_COMPILER_NOINLINE
+void printStackTrace(std::ostream& os, bool fromSignal) {
+ os << "This platform does not support printing stacktraces" << std::endl;
}
-uint32_t lcType(const char* lcCurr) {
- const load_command* cmd = reinterpret_cast<const load_command*>(lcCurr);
- return cmd->cmd;
-}
+#endif // MONGO_STACKTRACE_BACKEND
-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;
+} // namespace stacktrace_detail
+
+MONGO_COMPILER_NOINLINE
+void printStackTrace(std::ostream& os) {
+ stacktrace_detail::printStackTrace(os, false);
}
-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" << static_cast<int32_t>(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;
- }
- }
- }
+MONGO_COMPILER_NOINLINE
+void printStackTraceFromSignal(std::ostream& os) {
+ stacktrace_detail::printStackTrace(os, true);
}
-} // namespace
-} // namespace mongo
-#else
-namespace mongo {
-namespace {
-void addOSComponentsToSoMap(BSONObjBuilder* soMap) {}
-} // namespace
+
} // namespace mongo
-#endif
diff --git a/src/mongo/util/stacktrace_somap.cpp b/src/mongo/util/stacktrace_somap.cpp
new file mode 100644
index 00000000000..ef278df9498
--- /dev/null
+++ b/src/mongo/util/stacktrace_somap.cpp
@@ -0,0 +1,340 @@
+/**
+ * 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.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kControl
+#include "mongo/platform/basic.h"
+
+#include "mongo/util/stacktrace_somap.h"
+
+#include <climits>
+#include <cstdlib>
+#include <string>
+
+#if defined(__linux__)
+#include <elf.h>
+#include <link.h>
+#elif defined(__APPLE__) && defined(__MACH__)
+#include <mach-o/dyld.h>
+#include <mach-o/ldsyms.h>
+#include <mach-o/loader.h>
+#endif
+
+#if !defined(_WIN32)
+#include <sys/utsname.h>
+#endif
+
+#include "mongo/base/init.h"
+#include "mongo/bson/bsonobjbuilder.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"
+
+// Given `#define A aaa` and `#define B bbb`, `TOKEN_CAT(A, B)` evaluates to `aaabbb`.
+#define TOKEN_CAT(a, b) TOKEN_CAT_PRIMITIVE(a, b)
+#define TOKEN_CAT_PRIMITIVE(a, b) a##b
+
+namespace mongo {
+
+namespace {
+
+void addUnameToSoMap(BSONObjBuilder* soMap) {
+#if !defined(_WIN32)
+ struct utsname unameData;
+ if (!uname(&unameData)) {
+ BSONObjBuilder unameBuilder(soMap->subobjStart("uname"));
+ unameBuilder << "sysname" << unameData.sysname << "release" << unameData.release
+ << "version" << unameData.version << "machine" << unameData.machine;
+ }
+#endif
+}
+
+#if defined(__linux__)
+
+#if defined(__ELF_NATIVE_CLASS) // determine ARCH_BITS
+#define ARCH_BITS __ELF_NATIVE_CLASS
+#elif defined(__x86_64__) || defined(__aarch64__)
+#define ARCH_BITS 64
+#elif defined(__arm__)
+#define ARCH_BITS 32
+#else
+#error Unknown target architecture.
+#endif // determine ARCH_BITS
+
+#define ARCH_ELFCLASS TOKEN_CAT(ELFCLASS, ARCH_BITS)
+
+/**
+ * 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;
+ // Returns the size in bytes of an ELF note entry with the given header.
+ auto roundUpToElfWordAlignment = [](size_t offset) -> size_t {
+ static const size_t elfWordSizeBytes = sizeof(ElfW(Word));
+ return (offset + (elfWordSizeBytes - 1)) & ~(elfWordSizeBytes - 1);
+ };
+ auto getNoteSizeBytes = [&](const ElfW(Nhdr) & noteHeader) -> size_t {
+ return sizeof(noteHeader) + roundUpToElfWordAlignment(noteHeader.n_namesz) +
+ roundUpToElfWordAlignment(noteHeader.n_descsz);
+ };
+ 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;
+ }
+
+ static constexpr int kArchBits = ARCH_BITS;
+ if (eHeader.e_ident[EI_CLASS] != ARCH_ELFCLASS) {
+ warning() << "Expected elf file class of " << quotedFileName << " to be " << ARCH_ELFCLASS
+ << "(" << kArchBits << "-bit), but found " << int(eHeader.e_ident[4]);
+ return;
+ }
+
+ 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) {
+ auto isSegmentMappedReadable = [](const ElfW(Phdr) & phdr) -> bool {
+ return phdr.p_flags & PF_R;
+ };
+ 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) {
+ addUnameToSoMap(soMap);
+ BSONArrayBuilder soList(soMap->subarrayStart("somap"));
+ dl_iterate_phdr(outputSOInfo, &soList);
+ soList.done();
+}
+
+#elif defined(__APPLE__) && defined(__MACH__)
+
+void addOSComponentsToSoMap(BSONObjBuilder* soMap) {
+ addUnameToSoMap(soMap);
+ auto lcNext = [](const char* lcCurr) -> const char* {
+ return lcCurr + reinterpret_cast<const load_command*>(lcCurr)->cmdsize;
+ };
+ auto lcType = [](const char* lcCurr) -> uint32_t {
+ return reinterpret_cast<const load_command*>(lcCurr)->cmd;
+ };
+ auto maybeAppendLoadAddr = [](BSONObjBuilder* soInfo, const auto* segmentCommand) -> bool {
+ if (StringData(SEG_TEXT) != segmentCommand->segname) {
+ return false;
+ }
+ *soInfo << "vmaddr" << integerToHex(segmentCommand->vmaddr);
+ return true;
+ };
+ 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" << static_cast<int32_t>(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;
+ }
+ }
+ }
+}
+
+#else // unknown OS
+
+void addOSComponentsToSoMap(BSONObjBuilder* soMap) {}
+
+#endif // unknown OS
+
+SharedObjectMapInfo* _globalSharedObjectMapInfo = nullptr;
+
+/**
+ * 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();
+
+ addOSComponentsToSoMap(&soMap);
+ _globalSharedObjectMapInfo = new SharedObjectMapInfo(soMap.done());
+ return Status::OK();
+}
+
+} // namespace
+
+SharedObjectMapInfo::SharedObjectMapInfo(BSONObj obj)
+ : _obj(std::move(obj)), _json(_obj.jsonString(Strict)) {}
+
+SharedObjectMapInfo* globalSharedObjectMapInfo() {
+ return _globalSharedObjectMapInfo;
+}
+
+} // namespace mongo
diff --git a/src/mongo/util/stacktrace_somap.h b/src/mongo/util/stacktrace_somap.h
new file mode 100644
index 00000000000..a392c2838cd
--- /dev/null
+++ b/src/mongo/util/stacktrace_somap.h
@@ -0,0 +1,54 @@
+/**
+ * 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.
+ */
+#pragma once
+
+#include <string>
+
+#include "mongo/bson/bsonobj.h"
+
+namespace mongo {
+
+class SharedObjectMapInfo {
+public:
+ explicit SharedObjectMapInfo(BSONObj obj);
+
+ // Optional string containing extra unwinding information in JSON form.
+ const std::string& json() const {
+ return _json;
+ }
+
+private:
+ BSONObj _obj;
+ std::string _json;
+};
+
+// Available after the MONGO_INITIALIZER has run.
+SharedObjectMapInfo* globalSharedObjectMapInfo();
+
+} // namespace mongo
diff --git a/src/mongo/util/stacktrace_test.cpp b/src/mongo/util/stacktrace_test.cpp
new file mode 100644
index 00000000000..8a11f53ec32
--- /dev/null
+++ b/src/mongo/util/stacktrace_test.cpp
@@ -0,0 +1,359 @@
+/**
+ * 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <fmt/format.h>
+#include <fmt/printf.h>
+#include <functional>
+#include <regex>
+#include <sstream>
+#include <vector>
+
+#include "mongo/base/backtrace_visibility_test.h"
+#include "mongo/bson/json.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/stacktrace.h"
+
+
+namespace mongo {
+
+namespace {
+
+using namespace fmt::literals;
+
+constexpr bool kSuperVerbose = 0; // Devel instrumentation
+
+#if defined(_WIN32)
+constexpr bool kIsWindows = true;
+#else
+constexpr bool kIsWindows = false;
+#endif
+
+struct RecursionParam {
+ std::ostream& out;
+ std::vector<std::function<void(RecursionParam&)>> stack;
+};
+
+// Pops a callable and calls it. printStackTrace when we're out of callables.
+// Calls itself a few times to synthesize a nice big call stack.
+MONGO_COMPILER_NOINLINE void recursionTest(RecursionParam& p) {
+ if (p.stack.empty()) {
+ // I've come to invoke `stack` elements and test `printStackTrace()`,
+ // and I'm all out of `stack` elements.
+ printStackTrace(p.out);
+ return;
+ }
+ auto f = std::move(p.stack.back());
+ p.stack.pop_back();
+ f(p);
+}
+
+class LogAdapter {
+public:
+ std::string toString() const { // LAME
+ std::ostringstream os;
+ os << *this;
+ return os.str();
+ }
+ friend std::ostream& operator<<(std::ostream& os, const LogAdapter& x) {
+ x.doPrint(os);
+ return os;
+ }
+
+private:
+ virtual void doPrint(std::ostream& os) const = 0;
+};
+
+template <typename T>
+class LogVec : public LogAdapter {
+public:
+ explicit LogVec(const T& v, StringData sep = ","_sd) : v(v), sep(sep) {}
+
+private:
+ void doPrint(std::ostream& os) const override {
+ os << std::hex;
+ os << "{";
+ StringData s;
+ for (auto&& e : v) {
+ os << s << e;
+ s = sep;
+ }
+ os << "}";
+ os << std::dec;
+ }
+ const T& v;
+ StringData sep = ","_sd;
+};
+
+class LogJson : public LogAdapter {
+public:
+ explicit LogJson(const BSONObj& obj) : obj(obj) {}
+
+private:
+ void doPrint(std::ostream& os) const override {
+ os << tojson(obj, Strict, /*pretty=*/true);
+ }
+ const BSONObj& obj;
+};
+
+auto tlog() {
+ auto r = unittest::log();
+ r.setIsTruncatable(false);
+ return r;
+}
+
+uintptr_t fromHex(const std::string& s) {
+ return static_cast<uintptr_t>(std::stoull(s, nullptr, 16));
+}
+
+std::string getBaseName(std::string path) {
+ size_t lastSlash = path.rfind('/');
+ if (lastSlash == std::string::npos)
+ return path;
+ return path.substr(lastSlash + 1);
+}
+
+struct HumanFrame {
+ uintptr_t addr;
+ std::string soFileName;
+ uintptr_t soFileOffset;
+ std::string symbolName;
+ uintptr_t symbolOffset;
+};
+
+std::vector<HumanFrame> parseTraceBody(const std::string& traceBody) {
+ std::vector<HumanFrame> r;
+ // Three choices:
+ // just raw: " ???[0x7F0A71AD4238]"
+ // just soFile: " libfoo.so(+0xABC408)[0x7F0A71AD4238]"
+ // soFile + symb: " libfoo.so(someSym+0x408)[0x7F0A71AD4238]"
+ const std::regex re(R"re( ()re" // line pattern open
+ R"re((?:)re" // choice open non-capturing
+ R"re((\?\?\?))re" // capture just raw case "???"
+ R"re(|)re" // next choice
+ R"re(([^(]*)\((.*)\+(0x[0-9A-F]+)\))re" // so "(" sym offset ")"
+ R"re())re" // choice close
+ R"re( \[(0x[0-9A-F]+)\])re" // raw addr suffix
+ R"re()\n)re"); // line pattern close, newline
+ for (auto i = std::sregex_iterator(traceBody.begin(), traceBody.end(), re);
+ i != std::sregex_iterator();
+ ++i) {
+ if (kSuperVerbose) {
+ tlog() << "{";
+ for (size_t ei = 1; ei < i->size(); ++ei) {
+ tlog() << " {:2d}: `{}`"_format(ei, (*i)[ei]);
+ }
+ tlog() << "}";
+ }
+ size_t patternIdx = 1;
+ std::string line = (*i)[patternIdx++];
+ std::string rawOnly = (*i)[patternIdx++]; // "???" or empty
+ std::string soFile = (*i)[patternIdx++];
+ std::string symbol = (*i)[patternIdx++];
+ std::string offset = (*i)[patternIdx++];
+ std::string addr = (*i)[patternIdx++];
+ if (kSuperVerbose) {
+ tlog() << " rawOnly:`{}`, soFile:`{}`, symbol:`{}`, offset: `{}`, addr:`{}`"
+ ""_format(rawOnly, soFile, symbol, offset, addr);
+ }
+ HumanFrame hf{};
+ hf.addr = fromHex(addr);
+ if (rawOnly.empty()) {
+ // known soFile
+ hf.soFileName = soFile;
+ if (symbol.empty()) {
+ hf.soFileOffset = fromHex(offset);
+ } else {
+ // known symbol
+ hf.symbolName = symbol;
+ hf.symbolOffset = fromHex(offset);
+ }
+ }
+ r.push_back(hf);
+ }
+ return r;
+}
+
+// Break down a printStackTrace output for a contrived call tree and sanity-check it.
+TEST(StackTrace, PosixFormat) {
+ if (kIsWindows) {
+ return;
+ }
+
+ const std::string trace = [&] {
+ std::ostringstream os;
+ RecursionParam param{os, {3, recursionTest}};
+ recursionTest(param);
+ return os.str();
+ }();
+
+ if (kSuperVerbose) {
+ tlog() << "trace:{" << trace << "}";
+ }
+
+ std::smatch m;
+ ASSERT_TRUE(
+ std::regex_match(trace,
+ m,
+ std::regex(R"re(((?: [0-9A-F]+)+)\n)re" // raw void* list `addrLine`
+ R"re(----- BEGIN BACKTRACE -----\n)re" // header line
+ R"re((.*)\n)re" // huge `jsonLine`
+ R"re(((?:.*\n)+))re" // multi-line human-readable `traceBody`
+ R"re(----- END BACKTRACE -----\n)re"))) // footer line
+ << "trace: {}"_format(trace);
+ std::string addrLine = m[1].str();
+ std::string jsonLine = m[2].str();
+ std::string traceBody = m[3].str();
+
+ if (kSuperVerbose) {
+ tlog() << "addrLine:{" << addrLine << "}";
+ tlog() << "jsonLine:{" << jsonLine << "}";
+ tlog() << "traceBody:{" << traceBody << "}";
+ }
+
+ std::vector<uintptr_t> addrs;
+ {
+ const std::regex re(R"re( ([0-9A-F]+))re");
+ for (auto i = std::sregex_iterator(addrLine.begin(), addrLine.end(), re);
+ i != std::sregex_iterator();
+ ++i) {
+ addrs.push_back(fromHex((*i)[1]));
+ }
+ }
+ if (kSuperVerbose) {
+ tlog() << "addrs[] = " << LogVec(addrs);
+ }
+
+ BSONObj jsonObj = fromjson(jsonLine); // throwy
+ ASSERT_TRUE(jsonObj.hasField("backtrace"));
+ ASSERT_TRUE(jsonObj.hasField("processInfo"));
+
+ if (kSuperVerbose) {
+ for (auto& elem : jsonObj["backtrace"].Array()) {
+ tlog() << " btelem=" << LogJson(elem.Obj());
+ }
+ tlog() << " processInfo=" << LogJson(jsonObj["processInfo"].Obj());
+ }
+
+ ASSERT_TRUE(jsonObj["processInfo"].Obj().hasField("somap"));
+ struct SoMapEntry {
+ uintptr_t base;
+ std::string path;
+ };
+
+ std::map<uintptr_t, SoMapEntry> soMap;
+ for (const auto& so : jsonObj["processInfo"]["somap"].Array()) {
+ auto soObj = so.Obj();
+ SoMapEntry ent{};
+ ent.base = fromHex(soObj["b"].String());
+ if (soObj.hasField("path")) {
+ ent.path = soObj["path"].String();
+ }
+ soMap[ent.base] = ent;
+ }
+
+ std::vector<HumanFrame> humanFrames = parseTraceBody(traceBody);
+
+ {
+ // Extract all the humanFrames .addr into a vector and match against the addr line.
+ std::vector<uintptr_t> humanAddrs;
+ std::transform(humanFrames.begin(),
+ humanFrames.end(),
+ std::back_inserter(humanAddrs),
+ [](auto&& h) { return h.addr; });
+ ASSERT_TRUE(addrs == humanAddrs) << LogVec(addrs) << " vs " << LogVec(humanAddrs);
+ }
+
+ {
+ // Match humanFrames against the "backtrace" json array
+ auto btArray = jsonObj["backtrace"].Array();
+ for (size_t i = 0; i < humanFrames.size(); ++i) {
+ const auto& hf = humanFrames[i];
+ const BSONObj& bt = btArray[i].Obj();
+ ASSERT_TRUE(bt.hasField("b"));
+ ASSERT_TRUE(bt.hasField("o"));
+ ASSERT_EQUALS(!hf.symbolName.empty(), bt.hasField("s"));
+
+ if (!hf.soFileName.empty()) {
+ uintptr_t btBase = fromHex(bt["b"].String());
+ auto soEntryIter = soMap.find(btBase);
+ ASSERT_TRUE(soEntryIter != soMap.end()) << "not in soMap: 0x{:X}"_format(btBase);
+ std::string soPath = getBaseName(soEntryIter->second.path);
+ if (soPath.empty()) {
+ // As a special case, the "main" shared object has an empty soPath.
+ } else {
+ ASSERT_EQUALS(hf.soFileName, soPath)
+ << "hf.soFileName:`{}`,soPath:`{}`"_format(hf.soFileName, soPath);
+ }
+ }
+ if (!hf.symbolName.empty()) {
+ ASSERT_EQUALS(hf.symbolName, bt["s"].String());
+ }
+ }
+ }
+}
+
+TEST(StackTrace, WindowsFormat) {
+ if (!kIsWindows) {
+ return;
+ }
+
+ // TODO: rough: string parts are not escaped and can contain the ' ' delimiter.
+ const std::string trace = [&] {
+ std::ostringstream os;
+ RecursionParam param{os, {3, recursionTest}};
+ recursionTest(param);
+ return os.str();
+ }();
+ const std::regex re(R"re(()re" // line pattern open
+ R"re(([^\\]?))re" // moduleName: cannot have backslashes
+ R"re(\s*)re" // pad
+ R"re(.*)re" // sourceAndLine: empty, or "...\dir\file(line)"
+ R"re(\s*)re" // pad
+ R"re((?:)re" // symbolAndOffset: choice open non-capturing
+ R"re(\?\?\?)re" // raw case: "???"
+ R"re(|)re" // or
+ R"re((.*)\+0x[0-9a-f]*)re" // "symbolname+0x" lcHex...
+ R"re())re" // symbolAndOffset: choice close
+ R"re()\n)re"); // line pattern close, newline
+ auto mark = trace.begin();
+ for (auto i = std::sregex_iterator(trace.begin(), trace.end(), re); i != std::sregex_iterator();
+ ++i) {
+ mark = (*i)[0].second;
+ }
+ ASSERT_TRUE(mark == trace.end())
+ << "cannot match suffix: `" << trace.substr(mark - trace.begin()) << "` "
+ << "of trace: `" << trace << "`";
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/util/stacktrace_unwind.cpp b/src/mongo/util/stacktrace_unwind.cpp
deleted file mode 100644
index c5aff514880..00000000000
--- a/src/mongo/util/stacktrace_unwind.cpp
+++ /dev/null
@@ -1,607 +0,0 @@
-/**
- * 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;
- }
- }
- }
-}
-} // namespace
-} // namespace mongo
-#else
-namespace mongo {
-namespace {
-void addOSComponentsToSoMap(BSONObjBuilder* soMap) {}
-} // namespace
-} // namespace mongo
-#endif