diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/util/stacktrace.h | 166 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_json.cpp | 29 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_json.h | 16 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_posix.cpp | 382 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_test.cpp | 120 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_windows.cpp | 34 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_windows.h | 6 |
7 files changed, 477 insertions, 276 deletions
diff --git a/src/mongo/util/stacktrace.h b/src/mongo/util/stacktrace.h index 2be3842fc1a..bec1a17e67d 100644 --- a/src/mongo/util/stacktrace.h +++ b/src/mongo/util/stacktrace.h @@ -30,23 +30,21 @@ /** * Tools for working with in-process stack traces. */ - #pragma once #include <iosfwd> +#include <string> #include "mongo/base/string_data.h" namespace mongo { -namespace stack_trace { - -const size_t kFrameMax = 100; +const size_t kStackTraceFrameMax = 100; /** Abstract sink onto which stacktrace is piecewise emitted. */ -class Sink { +class StackTraceSink { public: - Sink& operator<<(StringData v) { + StackTraceSink& operator<<(StringData v) { doWrite(v); return *this; } @@ -55,9 +53,161 @@ private: virtual void doWrite(StringData v) = 0; }; -} // namespace stack_trace +class OstreamStackTraceSink : public StackTraceSink { +public: + explicit OstreamStackTraceSink(std::ostream& os) : _os(os) {} + +private: + void doWrite(StringData v) override { + _os << v; + } + std::ostream& _os; +}; + +class StringStackTraceSink : public StackTraceSink { +public: + StringStackTraceSink(std::string& s) : _s{s} {} + +private: + void doWrite(StringData v) override { + _s.append(v.rawData(), v.size()); + } + + std::string& _s; +}; + +#ifndef _WIN32 +/** + * 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. + * + * Support for StackTraceAddressMetadata is unimplemented on Windows. + */ +class StackTraceAddressMetadata { +public: + struct BaseAndName { + /** Disengaged when _base is null. */ + explicit operator bool() const { + return _base != 0; + } + + void clear() { + _base = 0; + _name.clear(); + } + + void assign(uintptr_t newBase, StringData newName) { + _base = newBase; + if (newBase != 0) + _name.assign(newName.begin(), newName.end()); + else + _name.clear(); + } + + uintptr_t base() const { + return _base; + } + StringData name() const { + return _name; + } + + uintptr_t _base{}; + std::string _name; + }; + + StackTraceAddressMetadata() = default; + + uintptr_t address() const { + return _address; + } + const BaseAndName& file() const { + return _file; + } + const BaseAndName& symbol() const { + return _symbol; + } + BaseAndName& file() { + return _file; + } + BaseAndName& symbol() { + return _symbol; + } + + void reset(uintptr_t addr = 0) { + _address = addr; + _file.assign(0, {}); + _symbol.assign(0, {}); + } + + void setAddress(uintptr_t address) { + _address = address; + } + + void printTo(StackTraceSink& sink) const; + +private: + uintptr_t _address{}; + BaseAndName _file{}; + BaseAndName _symbol{}; +}; + +/** + * Retrieves metadata for program addresses. + * Manages string storage internally as an optimization. + * + * Example: + * + * struct CapturedEvent { + * std::array<void*, kStackTraceFramesMax> trace; + * size_t traceSize; + * // ... + * }; + * + * CapturedEvent* event = ... + * // In a performance-sensitive event handler, capture a raw trace. + * event->traceSize = mongo::rawBacktrace(event->trace.data(), event->trace.size()); + * + * // Elsewhere, print a detailed trace of the captured event to a `sink`. + * CapturedEvent* event = ... + * StackTraceAddressMetadataGenerator metaGen; + * void** ptr = event->trace.data(); + * void** ptrEnd = event->trace.data() + event->traceSize; + * std::for_each(ptr, ptrEnd, [](void* addr) { + * const auto& meta = metaGen.load(addr); + * meta.printTo(sink); + * } + */ +class StackTraceAddressMetadataGenerator { +public: + /** + * Fill the internal meta structure with the metadata of `address`. + * The returned reference is valid until the next call to `load`. + */ + const StackTraceAddressMetadata& load(void* address); + + /** Access the internal metadata object without changing anything. */ + const StackTraceAddressMetadata& meta() const { + return _meta; + } + +private: + StackTraceAddressMetadata _meta; +}; + +/** + * Loads a raw backtrace into the `void*` range `[addrs, addrs + capacity)`. + * Returns number frames reported. + * AS-Unsafe with gnu libc. + * https://www.gnu.org/software/libc/manual/html_node/Backtraces.html + * AS-Safe with libunwind. + */ +size_t rawBacktrace(void** addrs, size_t capacity); + +#endif // _WIN32 -// Print stack trace information to "os", default to the log stream. +// Print stack trace information to a sink, defaults to the log stream. +void printStackTrace(StackTraceSink& sink); void printStackTrace(std::ostream& os); void printStackTrace(); diff --git a/src/mongo/util/stacktrace_json.cpp b/src/mongo/util/stacktrace_json.cpp index 96bae510bb4..4d401cf4d62 100644 --- a/src/mongo/util/stacktrace_json.cpp +++ b/src/mongo/util/stacktrace_json.cpp @@ -34,11 +34,11 @@ #include "mongo/bson/bsonobj.h" #include "mongo/util/assert_util.h" -namespace mongo::stack_trace { +namespace mongo::stack_trace_detail { namespace { /** - * Wrapper that streams a string-like object to a Sink, surrounded by double + * Wrapper that streams a string-like object to a StackTraceSink, surrounded by double * quotes. */ template <typename T> @@ -46,7 +46,7 @@ class Quoted { public: explicit Quoted(const T& v) : _v(v) {} - friend Sink& operator<<(Sink& sink, const Quoted& q) { + friend StackTraceSink& operator<<(StackTraceSink& sink, const Quoted& q) { return sink << kQuote << q._v << kQuote; } @@ -63,29 +63,34 @@ template <> constexpr StringData kDigits<10> = "0123456789"_sd; template <size_t base, typename Buf> -StringData toNumericBase(uint64_t x, Buf& buf) { +StringData toNumericBase(uint64_t x, Buf& buf, bool showBase) { auto it = buf.rbegin(); if (!x) { *it++ = '0'; } else { - for (; x && it != buf.rend(); ++it) { + for (; x; ++it) { *it = kDigits<base>[x % base]; x /= base; } + // base is prepended only when x is nonzero (matching printf) + if (base == 16 && showBase) { + static const auto kPrefix = "0x"_sd; + it = std::reverse_copy(kPrefix.begin(), kPrefix.end(), it); + } } - const char* p = buf.data() + (it.base() - buf.begin()); - size_t n = it - buf.rbegin(); + size_t n = std::distance(it.base(), buf.end()); + const char* p = buf.data() + buf.size() - n; return StringData(p, n); } } // namespace StringData Dec::toDec(uint64_t x, Buf& buf) { - return toNumericBase<10>(x, buf); + return toNumericBase<10>(x, buf, false); } -StringData Hex::toHex(uint64_t x, Buf& buf) { - return toNumericBase<16>(x, buf); +StringData Hex::toHex(uint64_t x, Buf& buf, bool showBase) { + return toNumericBase<16>(x, buf, showBase); } uint64_t Hex::fromHex(StringData s) { @@ -195,6 +200,6 @@ auto CheapJson::doc() -> Value { return Value(this); } -CheapJson::CheapJson(Sink& sink) : _sink(sink) {} +CheapJson::CheapJson(StackTraceSink& sink) : _sink(sink) {} -} // namespace mongo::stack_trace +} // namespace mongo::stack_trace_detail diff --git a/src/mongo/util/stacktrace_json.h b/src/mongo/util/stacktrace_json.h index 3917e093422..8f416ba74e2 100644 --- a/src/mongo/util/stacktrace_json.h +++ b/src/mongo/util/stacktrace_json.h @@ -36,7 +36,7 @@ #include "mongo/bson/bsonelement.h" #include "mongo/util/stacktrace.h" -namespace mongo::stack_trace { +namespace mongo::stack_trace_detail { /** * A utility for uint64_t <=> uppercase hex string conversions. It @@ -49,13 +49,15 @@ namespace mongo::stack_trace { */ class Hex { public: - using Buf = std::array<char, 16>; + using Buf = std::array<char, 18>; // 64/4 hex digits plus potential "0x" - static StringData toHex(uint64_t x, Buf& buf); + static StringData toHex(uint64_t x, Buf& buf, bool showBase = false); static uint64_t fromHex(StringData s); - explicit Hex(uint64_t x) : _str(toHex(x, _buf)) {} + explicit Hex(uint64_t x, bool showBase = false) : _str{toHex(x, _buf, showBase)} {} + explicit Hex(const void* x, bool showBase = false) + : Hex{reinterpret_cast<uintptr_t>(x), showBase} {} operator StringData() const { return _str; @@ -90,7 +92,7 @@ class CheapJson { public: class Value; - explicit CheapJson(Sink& sink); + explicit CheapJson(StackTraceSink& sink); // Create an empty JSON document. Value doc(); @@ -109,7 +111,7 @@ private: bool _pretty = false; int _indent = 0; - Sink& _sink; + StackTraceSink& _sink; }; /** @@ -174,4 +176,4 @@ private: StringData _sep; // Emitted upon append. Starts empty, then set to ",". }; -} // namespace mongo::stack_trace +} // namespace mongo::stack_trace_detail diff --git a/src/mongo/util/stacktrace_posix.cpp b/src/mongo/util/stacktrace_posix.cpp index f66d2ed01a9..49710cc691a 100644 --- a/src/mongo/util/stacktrace_posix.cpp +++ b/src/mongo/util/stacktrace_posix.cpp @@ -70,25 +70,18 @@ #endif namespace mongo { -namespace stack_trace { +namespace stack_trace_detail { namespace { -constexpr int kFrameMax = 100; constexpr size_t kSymbolMax = 512; constexpr StringData kUnknownFileName = "???"_sd; -class OstreamJsonSink : public Sink { -public: - explicit OstreamJsonSink(std::ostream& os) : _os(os) {} - -private: - void doWrite(StringData v) override { - _os << v; - } - std::ostream& _os; -}; +// Answer might be negative, but that should be a peculiar case. +ptrdiff_t offsetFromBase(uintptr_t base, uintptr_t addr) { + return addr - base; +} -struct StackTraceOptions { +struct Options { bool withProcessInfo = true; bool withHumanReadable = true; bool trimSoMap = true; // only include the somap entries relevant to the backtrace @@ -103,20 +96,6 @@ StringData getBaseName(StringData path) { return path.substr(lastSlash + 1); } -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{}; -}; - class IterationIface { public: enum Flags { @@ -127,156 +106,128 @@ public: virtual ~IterationIface() = default; virtual void start(Flags f) = 0; virtual bool done() const = 0; - virtual const AddressMetadata& deref() const = 0; + virtual const StackTraceAddressMetadata& deref() const = 0; virtual void advance() = 0; }; -// World's dumbest "vector". Doesn't allocate. -template <typename T, size_t N> -struct ArrayAndSize { - using iterator = typename std::array<T, N>::iterator; - using reference = typename std::array<T, N>::reference; - using const_reference = typename std::array<T, N>::const_reference; - - auto begin() { - return _arr.begin(); - } - auto end() { - return _arr.begin() + _n; - } - reference operator[](size_t i) { - return _arr[i]; - } - - auto begin() const { - return _arr.begin(); - } - auto end() const { - return _arr.begin() + _n; - } - const_reference operator[](size_t i) const { - return _arr[i]; - } - - void push_back(const T& v) { - _arr[_n++] = v; - } - - std::array<T, N> _arr; - size_t _n = 0; -}; - /** - * Iterates through the stacktrace to extract the bases addresses for each address in the - * stacktrace. Returns a sorted, unique sequence of these base addresses. + * Iterates through the stacktrace to extract the base for each address in the + * stacktrace. Returns a sorted, unique sequence of these bases. */ -ArrayAndSize<uint64_t, kFrameMax> uniqueBases(IterationIface& source) { - ArrayAndSize<uint64_t, kFrameMax> bases; - for (source.start(source.kSymbolic); !source.done(); source.advance()) { - const auto& f = source.deref(); - if (f.soFile) { - uintptr_t base = f.soFile->base; - // Push base into bases keeping it sorted and unique. - auto ins = std::lower_bound(bases.begin(), bases.end(), base); - if (ins != bases.end() && *ins == base) { - continue; - } else { - bases.push_back(base); - std::rotate(ins, bases.end() - 1, bases.end()); - } +size_t uniqueBases(IterationIface& iter, uintptr_t* bases, size_t capacity) { + auto basesEndMax = bases + capacity; + auto basesEnd = bases; + for (iter.start(iter.kSymbolic); basesEnd < basesEndMax && !iter.done(); iter.advance()) { + const auto& f = iter.deref(); + if (f.file()) { + // Add the soFile base into bases, keeping it sorted and unique. + auto base = f.file().base(); + auto position = std::lower_bound(bases, basesEnd, base); + if (position != basesEnd && *position == base) + continue; // skip duplicate base + *basesEnd++ = base; + std::rotate(position, basesEnd - 1, basesEnd); } } - return bases; + return basesEnd - bases; } -void printRawAddrsLine(IterationIface& source, Sink& sink, const StackTraceOptions& options) { - for (source.start(source.kRaw); !source.done(); source.advance()) { - sink << " " << Hex(source.deref().address); +void printRawAddrsLine(IterationIface& iter, StackTraceSink& sink, const Options& options) { + for (iter.start(iter.kRaw); !iter.done(); iter.advance()) { + sink << " " << Hex(iter.deref().address()); } } -void appendJsonBacktrace(IterationIface& source, - CheapJson::Value& jsonRoot, - const StackTraceOptions& options) { +void appendJsonBacktrace(IterationIface& iter, CheapJson::Value& jsonRoot) { CheapJson::Value frames = jsonRoot.appendKey("backtrace").appendArr(); - for (source.start(source.kSymbolic); !source.done(); source.advance()) { - const auto& f = source.deref(); - uint64_t base = f.soFile ? f.soFile->base : 0; + for (iter.start(iter.kSymbolic); !iter.done(); iter.advance()) { + const auto& f = iter.deref(); + auto base = f.file().base(); CheapJson::Value frameObj = frames.appendObj(); frameObj.appendKey("b").append(Hex(base)); - frameObj.appendKey("o").append(Hex(f.address - base)); - if (f.symbol) { - frameObj.appendKey("s").append(f.symbol->name); + frameObj.appendKey("o").append(Hex(offsetFromBase(base, f.address()))); + if (f.symbol()) { + frameObj.appendKey("s").append(f.symbol().name()); + // We don't write the symbol offset for some reason. } } } /** * Most elements of `bsonProcInfo` are copied verbatim into the `jsonProcInfo` Json - * object. But if `bases` is non-null, The "somap" BSON Array is filtered to only - * include elements corresponding to the addresses in `bases`. + * object. But the "somap" BSON Array is filtered to only include elements corresponding + * to the addresses contained by the range `[bases, basesEnd)`. */ -void printJsonProcessInfoCommon(const BSONObj& bsonProcInfo, - CheapJson::Value& jsonProcInfo, - const ArrayAndSize<uint64_t, kFrameMax>* bases) { +void printJsonProcessInfoTrimmed(const BSONObj& bsonProcInfo, + CheapJson::Value& jsonProcInfo, + const uintptr_t* bases, + const uintptr_t* basesEnd) { for (const BSONElement& be : bsonProcInfo) { - if (bases && be.type() == BSONType::Array) { - if (StringData key = be.fieldNameStringData(); key == "somap") { - CheapJson::Value jsonArr = jsonProcInfo.appendKey(key).appendArr(); - for (const BSONElement& ae : be.Array()) { - BSONObj bRec = ae.embeddedObject(); - uint64_t soBase = Hex::fromHex(bRec.getStringField("b")); - if (std::binary_search(bases->begin(), bases->end(), soBase)) - jsonArr.append(ae); - } - continue; - } + StringData key = be.fieldNameStringData(); + if (be.type() != BSONType::Array || key != "somap"_sd) { + jsonProcInfo.append(be); + continue; + } + CheapJson::Value jsonSoMap = jsonProcInfo.appendKey(key).appendArr(); + for (const BSONElement& ae : be.Array()) { + BSONObj bRec = ae.embeddedObject(); + uintptr_t soBase = Hex::fromHex(bRec.getStringField("b")); + if (std::binary_search(bases, basesEnd, soBase)) + jsonSoMap.append(ae); } - jsonProcInfo.append(be); } } -void printJsonProcessInfoTrimmed(IterationIface& source, - const BSONObj& bsonProcInfo, - CheapJson::Value& jsonProcInfo) { - auto bases = uniqueBases(source); - printJsonProcessInfoCommon(bsonProcInfo, jsonProcInfo, &bases); -} - -void appendJsonProcessInfo(IterationIface& source, - CheapJson::Value& jsonRoot, - const StackTraceOptions& options) { - if (!options.withProcessInfo) - return; +template <bool isTrimmed> +void appendJsonProcessInfoImpl(IterationIface& iter, CheapJson::Value& jsonRoot) { const BSONObj& bsonProcInfo = globalSharedObjectMapInfo().obj(); CheapJson::Value jsonProcInfo = jsonRoot.appendKey("processInfo").appendObj(); - if (options.trimSoMap) { - printJsonProcessInfoTrimmed(source, bsonProcInfo, jsonProcInfo); + if constexpr (isTrimmed) { + uintptr_t bases[kStackTraceFrameMax]; + size_t basesSize = uniqueBases(iter, bases, kStackTraceFrameMax); + printJsonProcessInfoTrimmed(bsonProcInfo, jsonProcInfo, bases, bases + basesSize); } else { - printJsonProcessInfoCommon(bsonProcInfo, jsonProcInfo, nullptr); + for (const BSONElement& be : bsonProcInfo) { + jsonProcInfo.append(be); + } } } -void printHumanReadable(IterationIface& source, Sink& sink, const StackTraceOptions& options) { - for (source.start(source.kSymbolic); !source.done(); source.advance()) { - const auto& f = source.deref(); - sink << " "; - if (f.soFile) { - sink << getBaseName(f.soFile->name); - sink << "("; - if (f.symbol) { - sink << f.symbol->name << "+0x" << Hex(f.address - f.symbol->base); - } else { - // No symbol, so fall back to the `soFile` offset. - sink << "+0x" << Hex(f.address - f.soFile->base); - } - sink << ")"; +void appendJsonProcessInfo(IterationIface& iter, CheapJson::Value& jsonRoot, bool trimmed) { + if (trimmed) { + appendJsonProcessInfoImpl<true>(iter, jsonRoot); + } else { + appendJsonProcessInfoImpl<false>(iter, jsonRoot); + } +} + +void printMetadata(StackTraceSink& sink, const StackTraceAddressMetadata& meta) { + auto printOffset = [&sink](uintptr_t base, uintptr_t address) { + ptrdiff_t offset = offsetFromBase(base, address); + StringData sign = "+"_sd; + if (offset < 0) { + sign = "-"_sd; + offset = -offset; + } + sink << sign << Hex(static_cast<uint64_t>(offset), true); + }; + + sink << " "; + if (meta.file()) { + sink << getBaseName(meta.file().name()); + sink << "("; + if (meta.symbol()) { + sink << meta.symbol().name(); + printOffset(meta.symbol().base(), meta.address()); } else { - // Not even shared object information, just punt with unknown filename (SERVER-43551) - sink << kUnknownFileName; + printOffset(meta.file().base(), meta.address()); } - sink << " [0x" << Hex(f.address) << "]\n"; + sink << ")"; + } else { + // Not even shared object information, just punt with unknown filename (SERVER-43551) + sink << kUnknownFileName; } + sink << " [0x" << Hex(meta.address()) << "]\n"; } /** @@ -299,50 +250,50 @@ void printHumanReadable(IterationIface& source, Sink& sink, const StackTraceOpti * analysis tool. For example, on Linux it contains a subobject named "somap", describing * the objects referenced in the "b" fields of the "backtrace" list. */ -void printStackTraceGeneric(IterationIface& source, Sink& sink, const StackTraceOptions& options) { - // TODO(SERVER-42670): make this asynchronous signal safe. - printRawAddrsLine(source, sink, options); +void printStackTraceGeneric(StackTraceSink& sink, IterationIface& iter, const Options& options) { + printRawAddrsLine(iter, sink, options); sink << "\n----- BEGIN BACKTRACE -----\n"; { CheapJson json{sink}; CheapJson::Value doc = json.doc(); CheapJson::Value jsonRootObj = doc.appendObj(); - appendJsonBacktrace(source, jsonRootObj, options); - appendJsonProcessInfo(source, jsonRootObj, options); + appendJsonBacktrace(iter, jsonRootObj); + if (options.withProcessInfo) { + appendJsonProcessInfo(iter, jsonRootObj, options.trimSoMap); + } } sink << "\n"; if (options.withHumanReadable) { - printHumanReadable(source, sink, options); + for (iter.start(iter.kSymbolic); !iter.done(); iter.advance()) { + printMetadata(sink, iter.deref()); + } } sink << "----- END BACKTRACE -----\n"; } -void mergeDlInfo(AddressMetadata& f) { +void mergeDlInfo(StackTraceAddressMetadata& 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) { + 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.file() && dli.dli_fbase) { + f.file().assign(reinterpret_cast<uintptr_t>(dli.dli_fbase), dli.dli_fname); } - 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)}; - } + if (!f.symbol() && dli.dli_saddr) { + f.symbol().assign(reinterpret_cast<uintptr_t>(dli.dli_saddr), dli.dli_sname); } } #if MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_LIBUNWIND -class Iteration : public IterationIface { +class LibunwindStepIteration : public IterationIface { public: - explicit Iteration(Sink& sink) : _sink(sink) { + explicit LibunwindStepIteration(StackTraceSink& sink) : _sink(sink) { if (int r = unw_getcontext(&_context); r < 0) { _sink << "unw_getcontext: " << unw_strerror(r) << "\n"; _failed = true; @@ -350,8 +301,8 @@ public: } private: - void start(Flags f) override { - _flags = f; + void start(Flags flags) override { + _flags = flags; _end = false; if (_failed) { @@ -371,8 +322,8 @@ private: return _end; } - const AddressMetadata& deref() const override { - return _f; + const StackTraceAddressMetadata& deref() const override { + return _meta; } void advance() override { @@ -389,7 +340,6 @@ private: } void _load() { - _f = {}; unw_word_t pc; if (int r = unw_get_reg(&_cursor, UNW_REG_IP, &pc); r < 0) { _sink << "unw_get_reg: " << unw_strerror(r) << "\n"; @@ -400,24 +350,26 @@ private: _end = true; return; } - _f.address = pc; + _meta.reset(static_cast<uintptr_t>(pc)); if (_flags & kSymbolic) { + // `unw_get_proc_name`, with its acccess to a cursor, and to libunwind's + // dwarf reader, can generate better metadata than mergeDlInfo, so prefer it. unw_word_t offset; if (int r = unw_get_proc_name(&_cursor, _symbolBuf, sizeof(_symbolBuf), &offset); r < 0) { - _sink << "unw_get_proc_name(" << Hex(_f.address) << "): " << unw_strerror(r) + _sink << "unw_get_proc_name(" << Hex(_meta.address()) << "): " << unw_strerror(r) << "\n"; } else { - _f.symbol = NameBase{_symbolBuf, _f.address - offset}; + _meta.symbol().assign(_meta.address() - offset, _symbolBuf); } - mergeDlInfo(_f); + mergeDlInfo(_meta); } } - Sink& _sink; + StackTraceSink& _sink; Flags _flags; - AddressMetadata _f{}; + StackTraceAddressMetadata _meta; bool _failed = false; bool _end = false; @@ -427,19 +379,12 @@ private: char _symbolBuf[kSymbolMax]; }; +#endif // MONGO_STACKTRACE_BACKEND -MONGO_COMPILER_NOINLINE -void printStackTrace(Sink& sink) { - Iteration iteration(sink); - printStackTraceGeneric(iteration, sink, StackTraceOptions{}); -} - -#elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_EXECINFO - -class Iteration : public IterationIface { +class RawBacktraceIteration : public IterationIface { public: - explicit Iteration(Sink& sink) { - _n = ::backtrace(_addresses.data(), _addresses.size()); + explicit RawBacktraceIteration(StackTraceSink& sink) { + _n = rawBacktrace(_addresses.data(), _addresses.size()); if (_n == 0) { int err = errno; sink << "Unable to collect backtrace addresses (errno: " << Dec(err) << " " @@ -449,18 +394,21 @@ public: } private: - void start(Flags f) override { - _flags = f; + void start(Flags flags) override { + _flags = flags; _i = 0; if (!done()) _load(); } + bool done() const override { return _i >= _n; } - const AddressMetadata& deref() const override { - return _f; + + const StackTraceAddressMetadata& deref() const override { + return _meta; } + void advance() override { ++_i; if (!done()) @@ -468,50 +416,72 @@ private: } void _load() { - _f = {}; - _f.address = reinterpret_cast<uintptr_t>(_addresses[_i]); + _meta.reset(reinterpret_cast<uintptr_t>(_addresses[_i])); if (_flags & kSymbolic) { - mergeDlInfo(_f); + mergeDlInfo(_meta); } } Flags _flags; - AddressMetadata _f; + StackTraceAddressMetadata _meta; - std::array<void*, kFrameMax> _addresses; + std::array<void*, kStackTraceFrameMax> _addresses; size_t _n = 0; size_t _i = 0; }; -MONGO_COMPILER_NOINLINE -void printStackTrace(Sink& sink) { - Iteration iteration(sink); - printStackTraceGeneric(iteration, sink, StackTraceOptions{}); +} // namespace +} // namespace stack_trace_detail + +void StackTraceAddressMetadata::printTo(StackTraceSink& sink) const { + stack_trace_detail::printMetadata(sink, *this); } +size_t rawBacktrace(void** addrs, size_t capacity) { +#if MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_LIBUNWIND + return ::unw_backtrace(addrs, capacity); +#elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_EXECINFO + return ::backtrace(addrs, capacity); #elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_NONE - -MONGO_COMPILER_NOINLINE -void printStackTrace(Sink& sink) { - sink << "This platform does not support printing stacktraces\n"; + return 0; +#endif } -#endif // MONGO_STACKTRACE_BACKEND +const StackTraceAddressMetadata& StackTraceAddressMetadataGenerator::load(void* address) { + _meta.reset(reinterpret_cast<uintptr_t>(address)); + stack_trace_detail::mergeDlInfo(_meta); + return _meta; +} -} // namespace -} // namespace stack_trace +void printStackTrace(StackTraceSink& sink) { +#if MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_LIBUNWIND + stack_trace_detail::Options options{}; + static constexpr bool kUseUnwindSteps = true; + if (kUseUnwindSteps) { + stack_trace_detail::LibunwindStepIteration iteration(sink); + printStackTraceGeneric(sink, iteration, options); + } else { + stack_trace_detail::RawBacktraceIteration iteration(sink); + printStackTraceGeneric(sink, iteration, options); + } +#elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_EXECINFO + stack_trace_detail::Options options{}; + stack_trace_detail::RawBacktraceIteration iteration(sink); + printStackTraceGeneric(sink, iteration, options); +#elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_NONE + sink << "This platform does not support printing stacktraces\n"; +#endif +} -MONGO_COMPILER_NOINLINE void printStackTrace(std::ostream& os) { - stack_trace::OstreamJsonSink sink{os}; - stack_trace::printStackTrace(sink); + OstreamStackTraceSink sink{os}; + printStackTrace(sink); } void printStackTrace() { - // NOTE: We disable long-line truncation for the stack trace, because the JSON representation of - // the stack trace can sometimes exceed the long line limit. + // NOTE: We disable long-line truncation for the stack trace, because the JSON + // representation of the stack trace can sometimes exceed the long line limit. printStackTrace(log().setIsTruncatable(false).stream()); } - } // namespace mongo diff --git a/src/mongo/util/stacktrace_test.cpp b/src/mongo/util/stacktrace_test.cpp index 6387aedf7f7..8b3fc95ce21 100644 --- a/src/mongo/util/stacktrace_test.cpp +++ b/src/mongo/util/stacktrace_test.cpp @@ -46,6 +46,13 @@ namespace mongo { +namespace stack_trace_test_detail { +/** Needs to have linkage so we can test metadata. */ +void testFunctionWithLinkage() { + printf("..."); +} +} // namespace stack_trace_test_detail + namespace { using namespace fmt::literals; @@ -306,7 +313,6 @@ TEST(StackTrace, PosixFormat) { if (!hf.soFileName.empty()) { uintptr_t btBase = fromHex(bt["b"].String()); auto soEntryIter = soMap.find(btBase); - // TODO: (SERVER-43420) fails on RHEL6 when looking up `__libc_start_main`. // ASSERT_TRUE(soEntryIter != soMap.end()) << "not in soMap: 0x{:X}"_format(btBase); if (soEntryIter == soMap.end()) continue; @@ -394,36 +400,92 @@ TEST(StackTrace, EarlyTraceSanity) { } } -class StringSink : public stack_trace::Sink { -public: - StringSink(std::string& s) : _s{s} {} +#ifndef _WIN32 +// `MetadataGenerator::load` should fill its meta member with something reasonable. +// Only testing with functions which we expect the dynamic linker to know about, so +// they must have external linkage. +TEST(StackTrace, MetadataGenerator) { + StackTraceAddressMetadataGenerator gen; + struct { + void* ptr; + std::string fileSub; + std::string symbolSub; + } const tests[] = { + { + reinterpret_cast<void*>(&stack_trace_test_detail::testFunctionWithLinkage), + "stacktrace_test", + "testFunctionWithLinkage", + }, + { + // printf's file tricky (surprises under ASAN, Mac, ...), + // but we should at least get a symbol name containing "printf" out of it. + reinterpret_cast<void*>(&std::printf), + {}, + "printf", + }, + }; -private: - void doWrite(StringData v) override { - format_to(std::back_inserter(_s), FMT_STRING("{}"), v); + for (const auto& test : tests) { + const auto& meta = gen.load(test.ptr); + if (kSuperVerbose) { + OstreamStackTraceSink sink{std::cout}; + meta.printTo(sink); + } + ASSERT_EQUALS(meta.address(), reinterpret_cast<uintptr_t>(test.ptr)); + if (!test.fileSub.empty()) { + ASSERT_TRUE(meta.file()); + ASSERT_STRING_CONTAINS(meta.file().name(), test.fileSub); + } + + if (!test.symbolSub.empty()) { + ASSERT_TRUE(meta.symbol()); + ASSERT_STRING_CONTAINS(meta.symbol().name(), test.symbolSub); + } } +} - std::string& _s; -}; +TEST(StackTrace, MetadataGeneratorFunctionMeasure) { + // Measure the size of a C++ function as a test of metadata retrieval. + // Load increasing addresses until the metadata's symbol name changes. + StackTraceAddressMetadataGenerator gen; + void* fp = reinterpret_cast<void*>(&stack_trace_test_detail::testFunctionWithLinkage); + const auto& meta = gen.load(fp); + if (!meta.symbol()) + return; // No symbol for `fp`. forget it. + std::string savedSymbol{meta.symbol().name()}; + uintptr_t fBase = meta.symbol().base(); + ASSERT_EQ(fBase, reinterpret_cast<uintptr_t>(fp)) + << "function pointer should match its symbol base"; + size_t fSize = 0; + for (; true; ++fSize) { + auto& m = gen.load(reinterpret_cast<void*>(fBase + fSize)); + if (!m.symbol() || m.symbol().name() != savedSymbol) + break; + } + // Place some reasonable expectation on the size of the tiny test function. + ASSERT_GT(fSize, 0); + ASSERT_LT(fSize, 512); +} +#endif // _WIN32 class CheapJsonTest : public unittest::Test { public: using unittest::Test::Test; + using CheapJson = stack_trace_detail::CheapJson; + using Hex = stack_trace_detail::Hex; + using Dec = stack_trace_detail::Dec; }; TEST_F(CheapJsonTest, Appender) { - using Dec = stack_trace::Dec; - using Hex = stack_trace::Hex; std::string s; - StringSink sink{s}; + StringStackTraceSink sink{s}; sink << "Hello" << ":" << Dec(0) << ":" << Hex(255) << ":" << Dec(1234567890); ASSERT_EQ(s, "Hello:0:FF:1234567890"); } TEST_F(CheapJsonTest, Hex) { - using Hex = stack_trace::Hex; - ASSERT_EQ(StringData(Hex(0)), "0"); + ASSERT_EQ(StringData(Hex(static_cast<void*>(0))), "0"); ASSERT_EQ(StringData(Hex(0xffff)), "FFFF"); ASSERT_EQ(Hex(0xfff0), "FFF0"); ASSERT_EQ(Hex(0x8000'0000'0000'0000), "8000000000000000"); @@ -432,15 +494,15 @@ TEST_F(CheapJsonTest, Hex) { ASSERT_EQ(Hex::fromHex("FFFFFFFFFFFFFFFF"), 0xffff'ffff'ffff'ffff); std::string s; - StringSink sink{s}; + StringStackTraceSink sink{s}; sink << Hex(0xffff); ASSERT_EQ(s, R"(FFFF)"); } TEST_F(CheapJsonTest, DocumentObject) { std::string s; - StringSink sink{s}; - stack_trace::CheapJson env{sink}; + StringStackTraceSink sink{s}; + CheapJson env{sink}; auto doc = env.doc(); ASSERT_EQ(s, ""); { @@ -452,8 +514,8 @@ TEST_F(CheapJsonTest, DocumentObject) { TEST_F(CheapJsonTest, ScalarStringData) { std::string s; - StringSink sink{s}; - stack_trace::CheapJson env{sink}; + StringStackTraceSink sink{s}; + CheapJson env{sink}; auto doc = env.doc(); doc.append(123); ASSERT_EQ(s, R"(123)"); @@ -461,8 +523,8 @@ TEST_F(CheapJsonTest, ScalarStringData) { TEST_F(CheapJsonTest, ScalarInt) { std::string s; - StringSink sink{s}; - stack_trace::CheapJson env{sink}; + StringStackTraceSink sink{s}; + CheapJson env{sink}; auto doc = env.doc(); doc.append("hello"); ASSERT_EQ(s, R"("hello")"); @@ -470,8 +532,8 @@ TEST_F(CheapJsonTest, ScalarInt) { TEST_F(CheapJsonTest, ObjectNesting) { std::string s; - StringSink sink{s}; - stack_trace::CheapJson env{sink}; + StringStackTraceSink sink{s}; + CheapJson env{sink}; auto doc = env.doc(); { auto obj = doc.appendObj(); @@ -486,8 +548,8 @@ TEST_F(CheapJsonTest, ObjectNesting) { TEST_F(CheapJsonTest, Arrays) { std::string s; - StringSink sink{s}; - stack_trace::CheapJson env{sink}; + StringStackTraceSink sink{s}; + CheapJson env{sink}; auto doc = env.doc(); { auto obj = doc.appendObj(); @@ -505,8 +567,8 @@ TEST_F(CheapJsonTest, Arrays) { TEST_F(CheapJsonTest, AppendBSONElement) { std::string s; - StringSink sink{s}; - stack_trace::CheapJson env{sink}; + StringStackTraceSink sink{s}; + CheapJson env{sink}; { auto obj = env.doc().appendObj(); for (auto& e : fromjson(R"({"a":1,"arr":[2,123],"emptyO":{},"emptyA":[]})")) @@ -517,8 +579,8 @@ TEST_F(CheapJsonTest, AppendBSONElement) { TEST_F(CheapJsonTest, Pretty) { std::string s; - StringSink sink{s}; - stack_trace::CheapJson env{sink}; + StringStackTraceSink sink{s}; + CheapJson env{sink}; env.pretty(); auto doc = env.doc(); { diff --git a/src/mongo/util/stacktrace_windows.cpp b/src/mongo/util/stacktrace_windows.cpp index 89fbb4ffcbe..7ad19c931c0 100644 --- a/src/mongo/util/stacktrace_windows.cpp +++ b/src/mongo/util/stacktrace_windows.cpp @@ -234,12 +234,12 @@ struct TraceItem { /** - * Print stack trace (using a specified stack context) to "os" + * Print stack trace (using a specified stack context) to `sink`> * - * @param context CONTEXT record for stack trace - * @param os ostream& to receive printed stack backtrace + * @param context execution state that produces the stack trace + * @param sink receives printed stack backtrace */ -void printWindowsStackTrace(CONTEXT& context, std::ostream& os) { +void printWindowsStackTrace(CONTEXT& context, StackTraceSink& sink) { auto& symbolHandler = SymbolHandler::instance(); stdx::lock_guard<SymbolHandler> lk(symbolHandler); @@ -281,7 +281,7 @@ void printWindowsStackTrace(CONTEXT& context, std::ostream& os) { TraceItem traceItem; size_t moduleWidth = 0; size_t sourceWidth = 0; - for (size_t i = 0; i < stack_trace::kFrameMax; ++i) { + for (size_t i = 0; i < kStackTraceFrameMax; ++i) { BOOL ret = StackWalk64(imageType, symbolHandler.getHandle(), GetCurrentThread(), @@ -315,32 +315,42 @@ void printWindowsStackTrace(CONTEXT& context, std::ostream& os) { ++sourceWidth; size_t frameCount = traceList.size(); for (size_t i = 0; i < frameCount; ++i) { - os << traceList[i].moduleName << ' '; + sink << traceList[i].moduleName << " "; size_t width = traceList[i].moduleName.length(); while (width < moduleWidth) { - os << ' '; + sink << " "; ++width; } - os << traceList[i].sourceAndLine << ' '; + sink << traceList[i].sourceAndLine << " "; width = traceList[i].sourceAndLine.length(); while (width < sourceWidth) { - os << ' '; + sink << " "; ++width; } - os << traceList[i].symbolAndOffset << '\n'; + sink << traceList[i].symbolAndOffset << "\n"; } } +void printWindowsStackTrace(CONTEXT& context, std::ostream& os) { + OstreamStackTraceSink sink{os}; + printWindowsStackTrace(context, sink); +} + void printWindowsStackTrace(CONTEXT& context) { printWindowsStackTrace(context, log(logger::LogComponent::kDefault).stream()); } -void printStackTrace(std::ostream& os) { +void printStackTrace(StackTraceSink& sink) { CONTEXT context; memset(&context, 0, sizeof(context)); context.ContextFlags = CONTEXT_CONTROL; RtlCaptureContext(&context); - printWindowsStackTrace(context, os); + printWindowsStackTrace(context, sink); +} + +void printStackTrace(std::ostream& os) { + OstreamStackTraceSink sink{os}; + printStackTrace(sink); } void printStackTrace() { diff --git a/src/mongo/util/stacktrace_windows.h b/src/mongo/util/stacktrace_windows.h index 28a54bc7ee7..dbbe2345e89 100644 --- a/src/mongo/util/stacktrace_windows.h +++ b/src/mongo/util/stacktrace_windows.h @@ -34,11 +34,13 @@ #include <iosfwd> #include "mongo/platform/windows_basic.h" // for CONTEXT +#include "mongo/util/stacktrace.h" namespace mongo { -// Print stack trace (using a specified stack context) to "os", default to the -// LogComponent::kControl stream. +// Print a stack trace (using a specified stack context) to a sink. +// If sink is unspecified, it defaults to the `LogComponent::kControl` stream. +void printWindowsStackTrace(CONTEXT& context, StackTraceSink& sink); void printWindowsStackTrace(CONTEXT& context, std::ostream& os); void printWindowsStackTrace(CONTEXT& context); |