diff options
-rw-r--r-- | src/mongo/SConscript | 8 | ||||
-rw-r--r-- | src/mongo/base/unwind_test.cpp | 77 | ||||
-rw-r--r-- | src/mongo/util/SConscript | 8 | ||||
-rw-r--r-- | src/mongo/util/stacktrace.cpp | 4 | ||||
-rw-r--r-- | src/mongo/util/stacktrace.h | 4 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_posix.cpp | 707 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_somap.cpp | 340 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_somap.h | 54 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_test.cpp | 359 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_unwind.cpp | 607 |
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(¬eHeader, notesCurr, sizeof(noteHeader)); - if (noteHeader.n_type != NT_GNU_BUILD_ID) - continue; - const char* const noteNameBegin = notesCurr + sizeof(noteHeader); - if (StringData(noteNameBegin, noteHeader.n_namesz - 1) != ELF_NOTE_GNU) { - continue; + 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(¬eHeader, notesCurr, sizeof(noteHeader)); + if (noteHeader.n_type != NT_GNU_BUILD_ID) + continue; + const char* const noteNameBegin = notesCurr + sizeof(noteHeader); + if (StringData(noteNameBegin, noteHeader.n_namesz - 1) != ELF_NOTE_GNU) { + continue; + } + const char* const noteDescBegin = + noteNameBegin + roundUpToElfWordAlignment(noteHeader.n_namesz); + soInfo->append("buildId", toHex(noteDescBegin, noteHeader.n_descsz)); + } +#endif +} + +/** + * Processes an ELF Phdr for a LOAD segment, updating "soInfo". + * + * The goal of this operation is to find out if the current object is an executable or a shared + * object, by looking for the LOAD segment that maps the first several bytes of the file (the + * ELF header). If it's an executable, this method updates soInfo with the load address of the + * segment + */ +void processLoadSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONObjBuilder* soInfo) { + if (phdr.p_offset) + return; + if (phdr.p_memsz < sizeof(ElfW(Ehdr))) + return; + + // Segment includes beginning of file and is large enough to hold the ELF header + ElfW(Ehdr) eHeader; + memcpy(&eHeader, reinterpret_cast<const char*>(info.dlpi_addr) + phdr.p_vaddr, sizeof(eHeader)); + + std::string quotedFileName = "\"" + str::escape(info.dlpi_name) + "\""; + + if (memcmp(&eHeader.e_ident[0], ELFMAG, SELFMAG)) { + warning() << "Bad ELF magic number in image of " << quotedFileName; + return; + } + + 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(¬eHeader, notesCurr, sizeof(noteHeader)); - if (noteHeader.n_type != NT_GNU_BUILD_ID) - continue; - const char* const noteNameBegin = notesCurr + sizeof(noteHeader); - if (StringData(noteNameBegin, noteHeader.n_namesz - 1) != ELF_NOTE_GNU) { - continue; - } - const char* const noteDescBegin = - noteNameBegin + roundUpToElfWordAlignment(noteHeader.n_namesz); - soInfo->append("buildId", toHex(noteDescBegin, noteHeader.n_descsz)); - } -#endif -} - -/** - * Processes an ELF Phdr for a LOAD segment, updating "soInfo". - * - * The goal of this operation is to find out if the current object is an executable or a shared - * object, by looking for the LOAD segment that maps the first several bytes of the file (the - * ELF header). If it's an executable, this method updates soInfo with the load address of the - * segment - */ -void processLoadSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONObjBuilder* soInfo) { - if (phdr.p_offset) - return; - if (phdr.p_memsz < sizeof(ElfW(Ehdr))) - return; - - // Segment includes beginning of file and is large enough to hold the ELF header - ElfW(Ehdr) eHeader; - memcpy(&eHeader, reinterpret_cast<const char*>(info.dlpi_addr) + phdr.p_vaddr, sizeof(eHeader)); - - std::string quotedFileName = "\"" + str::escape(info.dlpi_name) + "\""; - - if (memcmp(&eHeader.e_ident[0], ELFMAG, SELFMAG)) { - warning() << "Bad ELF magic number in image of " << quotedFileName; - return; - } - -#if defined(__ELF_NATIVE_CLASS) -#define ARCH_BITS __ELF_NATIVE_CLASS -#else //__ELF_NATIVE_CLASS -#if defined(__x86_64__) || defined(__aarch64__) -#define ARCH_BITS 64 -#elif defined(__arm__) -#define ARCH_BITS 32 -#else -#error Unknown target architecture. -#endif //__aarch64__ -#endif //__ELF_NATIVE_CLASS - -#define MKELFCLASS(N) _MKELFCLASS(N) -#define _MKELFCLASS(N) ELFCLASS##N - if (eHeader.e_ident[EI_CLASS] != MKELFCLASS(ARCH_BITS)) { - warning() << "Expected elf file class of " << quotedFileName << " to be " - << MKELFCLASS(ARCH_BITS) << "(" << ARCH_BITS << "-bit), but found " - << int(eHeader.e_ident[4]); - return; - } - -#undef ARCH_BITS - - if (eHeader.e_ident[EI_VERSION] != EV_CURRENT) { - warning() << "Wrong ELF version in " << quotedFileName << ". Expected " << EV_CURRENT - << " but found " << int(eHeader.e_ident[EI_VERSION]); - return; - } - - soInfo->append("elfType", eHeader.e_type); - - switch (eHeader.e_type) { - case ET_EXEC: - break; - case ET_DYN: - return; - default: - warning() << "Surprised to find " << quotedFileName << " is ELF file of type " - << eHeader.e_type; - return; - } - - soInfo->append("b", integerToHex(phdr.p_vaddr)); -} - -/** - * Callback that processes an ELF object linked into the current address space. - * - * Used by dl_iterate_phdr in ExtractSOMap, below, to build up the list of linked - * objects. - * - * Each entry built by an invocation of ths function may have the following fields: - * * "b", the base address at which an object is loaded. - * * "path", the path on the file system to the object. - * * "buildId", the GNU Build ID of the object. - * * "elfType", the ELF type of the object, typically 2 or 3 (executable or SO). - * - * At post-processing time, the buildId field can be used to identify the file containing - * debug symbols for objects loaded at the given "laodAddr", which in turn can be used with - * the "backtrace" displayed in printStackTrace to get detailed unwind information. - */ -int outputSOInfo(dl_phdr_info* info, size_t sz, void* data) { - BSONObjBuilder soInfo(reinterpret_cast<BSONArrayBuilder*>(data)->subobjStart()); - if (info->dlpi_addr) - soInfo.append("b", integerToHex(ElfW(Addr)(info->dlpi_addr))); - if (info->dlpi_name && *info->dlpi_name) - soInfo.append("path", info->dlpi_name); - - for (ElfW(Half) i = 0; i < info->dlpi_phnum; ++i) { - const ElfW(Phdr) & phdr(info->dlpi_phdr[i]); - if (!isSegmentMappedReadable(phdr)) - continue; - switch (phdr.p_type) { - case PT_NOTE: - processNoteSegment(*info, phdr, &soInfo); - break; - case PT_LOAD: - processLoadSegment(*info, phdr, &soInfo); - break; - default: - break; - } - } - return 0; -} - -void addOSComponentsToSoMap(BSONObjBuilder* soMap) { - BSONArrayBuilder soList(soMap->subarrayStart("somap")); - dl_iterate_phdr(outputSOInfo, &soList); - soList.done(); -} - -} // namespace - -} // namespace mongo - -#elif defined(__APPLE__) && defined(__MACH__) - -#include <mach-o/dyld.h> -#include <mach-o/ldsyms.h> -#include <mach-o/loader.h> - -namespace mongo { -namespace { -const char* lcNext(const char* lcCurr) { - const load_command* cmd = reinterpret_cast<const load_command*>(lcCurr); - return lcCurr + cmd->cmdsize; -} - -uint32_t lcType(const char* lcCurr) { - const load_command* cmd = reinterpret_cast<const load_command*>(lcCurr); - return cmd->cmd; -} - -template <typename SegmentCommandType> -bool maybeAppendLoadAddr(BSONObjBuilder* soInfo, const SegmentCommandType* segmentCommand) { - if (StringData(SEG_TEXT) != segmentCommand->segname) { - return false; - } - *soInfo << "vmaddr" << integerToHex(segmentCommand->vmaddr); - return true; -} - -void addOSComponentsToSoMap(BSONObjBuilder* soMap) { - const uint32_t numImages = _dyld_image_count(); - BSONArrayBuilder soList(soMap->subarrayStart("somap")); - for (uint32_t i = 0; i < numImages; ++i) { - BSONObjBuilder soInfo(soList.subobjStart()); - const char* name = _dyld_get_image_name(i); - if (name) - soInfo << "path" << name; - const mach_header* header = _dyld_get_image_header(i); - if (!header) - continue; - size_t headerSize; - if (header->magic == MH_MAGIC) { - headerSize = sizeof(mach_header); - } else if (header->magic == MH_MAGIC_64) { - headerSize = sizeof(mach_header_64); - } else { - continue; - } - soInfo << "machType" << header->filetype; - soInfo << "b" << integerToHex(reinterpret_cast<intptr_t>(header)); - const char* const loadCommandsBegin = reinterpret_cast<const char*>(header) + headerSize; - const char* const loadCommandsEnd = loadCommandsBegin + header->sizeofcmds; - - // Search the "load command" data in the Mach object for the entry encoding the UUID of the - // object, and for the __TEXT segment. Adding the "vmaddr" field of the __TEXT segment load - // command of an executable or dylib to an offset in that library provides an address - // suitable to passing to atos or llvm-symbolizer for symbolization. - // - // See, for example, http://lldb.llvm.org/symbolication.html. - bool foundTextSegment = false; - for (const char* lcCurr = loadCommandsBegin; lcCurr < loadCommandsEnd; - lcCurr = lcNext(lcCurr)) { - switch (lcType(lcCurr)) { - case LC_UUID: { - const auto uuidCmd = reinterpret_cast<const uuid_command*>(lcCurr); - soInfo << "buildId" << toHex(uuidCmd->uuid, 16); - break; - } - case LC_SEGMENT_64: - if (!foundTextSegment) { - foundTextSegment = maybeAppendLoadAddr( - &soInfo, reinterpret_cast<const segment_command_64*>(lcCurr)); - } - break; - case LC_SEGMENT: - if (!foundTextSegment) { - foundTextSegment = maybeAppendLoadAddr( - &soInfo, reinterpret_cast<const segment_command*>(lcCurr)); - } - break; - } - } - } -} -} // namespace -} // namespace mongo -#else -namespace mongo { -namespace { -void addOSComponentsToSoMap(BSONObjBuilder* soMap) {} -} // namespace -} // namespace mongo -#endif |