diff options
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/util/stacktrace.h | 20 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_json.cpp | 176 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_json.h | 141 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_posix.cpp | 302 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_somap.cpp | 19 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_somap.h | 7 | ||||
-rw-r--r-- | src/mongo/util/stacktrace_test.cpp | 130 |
8 files changed, 679 insertions, 117 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index e0bb62f04d3..51695b49167 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -153,6 +153,7 @@ baseEnv.Library( 'util/signal_handlers_synchronous.cpp', 'util/stacktrace.cpp', 'util/stacktrace_${TARGET_OS_FAMILY}.cpp', + 'util/stacktrace_json.cpp', 'util/stacktrace_somap.cpp', 'util/str.cpp', 'util/system_clock_source.cpp', diff --git a/src/mongo/util/stacktrace.h b/src/mongo/util/stacktrace.h index be94d5e7865..dcb0b29b2f9 100644 --- a/src/mongo/util/stacktrace.h +++ b/src/mongo/util/stacktrace.h @@ -39,8 +39,28 @@ #include "mongo/platform/windows_basic.h" // for CONTEXT #endif +#include "mongo/base/string_data.h" + namespace mongo { +/** Abstract sink onto which stacktrace is piecewise emitted. */ +class StackTraceSink { +public: + StackTraceSink& operator<<(StringData v) { + doWrite(v); + return *this; + } + + StackTraceSink& operator<<(uint64_t v) { + doWrite(v); + return *this; + } + +private: + virtual void doWrite(StringData v) = 0; + virtual void doWrite(uint64_t v) = 0; +}; + // Print stack trace information to "os", default to the log stream. void printStackTrace(std::ostream& os); void printStackTrace(); diff --git a/src/mongo/util/stacktrace_json.cpp b/src/mongo/util/stacktrace_json.cpp new file mode 100644 index 00000000000..bec3b22cb5f --- /dev/null +++ b/src/mongo/util/stacktrace_json.cpp @@ -0,0 +1,176 @@ +/** + * 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/util/stacktrace_json.h" + +#include <cctype> + +#include "mongo/bson/bsonobj.h" +#include "mongo/util/assert_util.h" + +namespace mongo::stacktrace_detail { +namespace { +constexpr StringData kHexDigits = "0123456789ABCDEF"_sd; + +/** + * Wrapper that streams a string-like object to a StackTraceSink, surrounded by double + * quotes. + */ +template <typename T> +class Quoted { +public: + explicit Quoted(const T& v) : _v(v) {} + + friend StackTraceSink& operator<<(StackTraceSink& sink, const Quoted& q) { + return sink << kQuote << q._v << kQuote; + } + +private: + static constexpr StringData kQuote = "\""_sd; + const T& _v; +}; +} // namespace + +StringData Hex::toHex(uint64_t x, Buf& buf) { + char* data = buf.data(); + size_t nBuf = buf.size(); + char* p = data + nBuf; + if (!x) { + *--p = '0'; + } else { + for (int d = 0; d < 16; ++d) { + if (!x) + break; + *--p = kHexDigits[x & 0xf]; + x >>= 4; + } + } + return StringData(p, data + nBuf - p); +} + +uint64_t Hex::fromHex(StringData s) { + uint64_t x = 0; + for (char c : s) { + char uc = std::toupper(static_cast<unsigned char>(c)); + if (size_t pos = kHexDigits.find(uc); pos == std::string::npos) { + return x; + } else { + x <<= 4; + x += pos; + } + } + return x; +} + +CheapJson::Value::Value(CheapJson* env, Kind k) : _env(env), _kind(k) { + if (_kind == kObj) { + _env->_sink << "{"; + } else if (_kind == kArr) { + _env->_sink << "["; + } +} + +CheapJson::Value::~Value() { + if (_kind == kObj) { + _env->_sink << "}"; + } else if (_kind == kArr) { + _env->_sink << "]"; + } +} + +auto CheapJson::Value::appendObj() -> Value { + _next(); + return Value{_env, kObj}; +} + +auto CheapJson::Value::appendArr() -> Value { + _next(); + return Value{_env, kArr}; +} + +auto CheapJson::Value::appendKey(StringData k) -> Value { + fassert(_kind == kObj, "appendKey requires this to be kObj"); + _next(); + _env->_sink << Quoted(k) << ":"; + return Value{_env, kNop}; +} + +void CheapJson::Value::append(StringData v) { + _next(); + _env->_sink << Quoted(v); +} + +void CheapJson::Value::append(uint64_t v) { + _next(); + _env->_sink << v; +} + +void CheapJson::Value::append(const BSONElement& be) { + if (_kind == kObj) + appendKey(be.fieldNameStringData())._copyBsonElementValue(be); + else + _copyBsonElementValue(be); +} + +void CheapJson::Value::_copyBsonElementValue(const BSONElement& be) { + switch (be.type()) { + case BSONType::String: + append(be.valueStringData()); + break; + case BSONType::NumberInt: + append(be.Int()); + break; + case BSONType::Object: { + Value sub = appendObj(); + for (const BSONElement& e : be.Obj()) + sub.append(e); + } break; + case BSONType::Array: { + Value sub = appendArr(); + for (const BSONElement& e : be.Array()) + sub.append(e); + } break; + default: + // warning() << "unknown type " << be.type() << "\n"; + break; + } +} + +void CheapJson::Value::_next() { + _env->_sink << _sep; + _sep = ","_sd; +} + +auto CheapJson::doc() -> Value { + return Value(this); +} + +CheapJson::CheapJson(StackTraceSink& sink) : _sink(sink) {} + +} // namespace mongo::stacktrace_detail diff --git a/src/mongo/util/stacktrace_json.h b/src/mongo/util/stacktrace_json.h new file mode 100644 index 00000000000..0ad322d20f5 --- /dev/null +++ b/src/mongo/util/stacktrace_json.h @@ -0,0 +1,141 @@ +/** + * 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 <array> +#include <ostream> + +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonelement.h" +#include "mongo/util/stacktrace.h" + +namespace mongo::stacktrace_detail { + +/** + * A utility for uint64_t <=> uppercase hex string conversions. It + * can be used to produce a StringData. + * + * sink << Hex(x).str(); // as a temporary + * + * Hex hx(x); + * StringData sd = hx.str() // sd storage is in `hx`. + */ +class Hex { +public: + using Buf = std::array<char, 16>; + + static StringData toHex(uint64_t x, Buf& buf); + + static uint64_t fromHex(StringData s); + + explicit Hex(uint64_t x) : _str(toHex(x, _buf)) {} + + StringData str() const { + return _str; + } + +private: + Buf _buf; + StringData _str; +}; + +/** An append-only, async-safe, malloc-free Json emitter. */ +class CheapJson { +public: + class Value; + + explicit CheapJson(StackTraceSink& sink); + + // Create an empty JSON document. + Value doc(); + +private: + StackTraceSink& _sink; +}; + +/** + * A Json Value node being emitted. Emits {}/[] braces, keyval ":" separators, commas, + * and quotes. To use this, make a Value for the root document and call `append*` + * members, adding a nested structure of objects, arrays, and scalars to the active + * Value. + * + * The constructor emits any appropriate opening brace, and the destructor emits any + * appropriate closing brace. Keeps an internal state so that a comma is emitted on the + * second and subsequent append call. + */ +class CheapJson::Value { +public: + /** The empty root document, which emits no braces. */ + explicit Value(CheapJson* env) : Value(env, kNop) {} + + /** Emit the closing brace if any. */ + ~Value(); + + /** Begin a Json Array. Returns a Value that can be used to append elements to it. */ + Value appendArr(); + + /** Begin a Json Object. Returns a Value that can be used to append elements to it. */ + Value appendObj(); + + /** Append key `k` to this Json Object. Returns the empty Value mapped to `k`. */ + Value appendKey(StringData k); + + /** Append string `v`, surrounded by doublequote characters. */ + void append(StringData v); + + /** Append integer `v`, in decimal. */ + void append(uint64_t v); + + /** + * Append the elements of `be` to this Object or Array. + * Behavior depends on the kind of Value this is. + * - If object: append `be` keys and values. + * - If array: append `be` values only. + */ + void append(const BSONElement& be); + +private: + enum Kind { + kNop, // A blank Value, not an aggregate, emits no punctuation. Can emit one element. + kObj, // Object: can emit multiple key-value pairs: {k1:v1, k2:v2, ...} + kArr, // Array: can emit multiple scalars [v1, v2, ...] + }; + + /* Emit the opening brace corresponding to the specified `k`. */ + Value(CheapJson* env, Kind k); + void _copyBsonElementValue(const BSONElement& be); + void _next(); + + CheapJson* _env; + Kind _kind; + StringData _sep; // Emitted upon append. Starts empty, then set to ",". +}; + +} // namespace mongo::stacktrace_detail diff --git a/src/mongo/util/stacktrace_posix.cpp b/src/mongo/util/stacktrace_posix.cpp index 7a63e6c26f0..c354a2d787a 100644 --- a/src/mongo/util/stacktrace_posix.cpp +++ b/src/mongo/util/stacktrace_posix.cpp @@ -48,6 +48,7 @@ #include "mongo/platform/compiler_gcc.h" #include "mongo/util/log.h" #include "mongo/util/scopeguard.h" +#include "mongo/util/stacktrace_json.h" #include "mongo/util/version.h" #define MONGO_STACKTRACE_BACKEND_LIBUNWIND 1 @@ -70,16 +71,33 @@ namespace mongo { namespace stacktrace_detail { +namespace { constexpr int kFrameMax = 100; constexpr size_t kSymbolMax = 512; constexpr StringData kUnknownFileName = "???"_sd; +class OstreamJsonSink : public StackTraceSink { +public: + explicit OstreamJsonSink(std::ostream& os) : _os(os) {} + +private: + void doWrite(StringData v) override { + _os << v; + } + void doWrite(uint64_t v) override { + _os << v; + } + std::ostream& _os; +}; + struct StackTraceOptions { bool withProcessInfo = true; bool withHumanReadable = true; + bool trimSoMap = true; // only include the somap entries relevant to the backtrace }; + // E.g., for "/foo/bar/my.txt", returns "my.txt". StringData getBaseName(StringData path) { size_t lastSlash = path.rfind('/'); @@ -116,19 +134,163 @@ public: virtual void advance() = 0; }; -template <typename T> -struct Quoted { - explicit Quoted(const T& v) : v(v) {} +// 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; - friend std::ostream& operator<<(std::ostream& os, const Quoted& q) { - return os << "\"" << q.v << "\""; + auto begin() { + return _arr.begin(); + } + auto end() { + return _arr.begin() + _n; + } + reference operator[](size_t i) { + return _arr[i]; } - const T& v; + 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; }; /** - * Prints a stack backtrace for the current thread to the specified ostream. + * Iterates through the stacktrace to extract the bases addresses for each address in the + * stacktrace. Returns a sorted, unique sequence of these base addresses. + */ +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()); + } + } + } + return bases; +} + +void printRawAddrsLine(IterationIface& source, + StackTraceSink& sink, + const StackTraceOptions& options) { + for (source.start(source.kRaw); !source.done(); source.advance()) { + sink << " " << Hex(source.deref().address).str(); + } +} + +void appendJsonBacktrace(IterationIface& source, + CheapJson::Value& jsonRoot, + const StackTraceOptions& options) { + 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; + CheapJson::Value frameObj = frames.appendObj(); + frameObj.appendKey("b").append(Hex(base).str()); + frameObj.appendKey("o").append(Hex(f.address - base).str()); + if (f.symbol) { + frameObj.appendKey("s").append(f.symbol->name); + } + } +} + +/** + * 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`. + */ +void printJsonProcessInfoCommon(const BSONObj& bsonProcInfo, + CheapJson::Value& jsonProcInfo, + const ArrayAndSize<uint64_t, kFrameMax>* bases) { + 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; + } + } + 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; + auto bsonSoMap = globalSharedObjectMapInfo(); + if (!bsonSoMap) + return; + const BSONObj& bsonProcInfo = bsonSoMap->obj(); + CheapJson::Value jsonProcInfo = jsonRoot.appendKey("processInfo").appendObj(); + if (options.trimSoMap) { + printJsonProcessInfoTrimmed(source, bsonProcInfo, jsonProcInfo); + } else { + printJsonProcessInfoCommon(bsonProcInfo, jsonProcInfo, nullptr); + } +} + +void printHumanReadable(IterationIface& source, + StackTraceSink& 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).str(); + } else { + // No symbol, so fall back to the `soFile` offset. + sink << "+0x" << Hex(f.address - f.soFile->base).str(); + } + sink << ")"; + } else { + // Not even shared object information, just punt with unknown filename (SERVER-43551) + sink << kUnknownFileName; + } + sink << " [0x" << Hex(f.address).str() << "]\n"; + } +} + +/** + * Prints a stack backtrace for the current thread to the specified sink. * * The format of the backtrace is: * @@ -148,83 +310,23 @@ struct Quoted { * the objects referenced in the "b" fields of the "backtrace" list. */ void printStackTraceGeneric(IterationIface& source, - std::ostream& os, + StackTraceSink& sink, const StackTraceOptions& options) { - // TODO: make this signal-safe. - os << std::hex << std::uppercase; - 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 + // TODO(SERVER-42670): make this asynchronous signal safe. + printRawAddrsLine(source, sink, options); + sink << "\n----- BEGIN BACKTRACE -----\n"; { - 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 << "}"; + CheapJson json{sink}; + CheapJson::Value doc = json.doc(); + CheapJson::Value jsonRootObj = doc.appendObj(); + appendJsonBacktrace(source, jsonRootObj, options); + appendJsonProcessInfo(source, jsonRootObj, options); } - os << "\n"; - // Display the human-readable trace + sink << "\n"; 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 { - // Not even shared object information, just punt with unknown filename. - os << kUnknownFileName; - } - os << " [0x" << f.address << "]\n"; - } + printHumanReadable(source, sink, options); } - os << "----- END BACKTRACE -----\n"; + sink << "----- END BACKTRACE -----\n"; } void mergeDlInfo(AddressMetadata& f) { @@ -252,7 +354,8 @@ void mergeDlInfo(AddressMetadata& f) { class Iteration : public IterationIface { public: - explicit Iteration(std::ostream& os, bool fromSignal) : _os(os), _fromSignal(fromSignal) { + explicit Iteration(StackTraceSink& sink, bool fromSignal) + : _sink(sink), _fromSignal(fromSignal) { if (int r = unw_getcontext(&_context); r < 0) { _os << "unw_getcontext: " << unw_strerror(r) << std::endl; _failed = true; @@ -270,7 +373,7 @@ private: } 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; + _sink << "unw_init_local2: " << unw_strerror(r) << "\n"; _end = true; return; } @@ -289,7 +392,7 @@ private: int r = unw_step(&_cursor); if (r <= 0) { if (r < 0) { - _os << "error: unw_step: " << unw_strerror(r) << std::endl; + _sink << "error: unw_step: " << unw_strerror(r) << "\n"; } _end = true; } @@ -302,7 +405,7 @@ private: _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; + _sink << "unw_get_reg: " << unw_strerror(r) << "\n"; _end = true; return; } @@ -315,7 +418,7 @@ private: 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; + _sink << "unw_get_proc_name(" << _f.address << "): " << unw_strerror(r) << "\n"; } else { _f.symbol = NameBase{_symbolBuf, _f.address - offset}; } @@ -323,7 +426,7 @@ private: } } - std::ostream& _os; + StackTraceSink& _sink; bool _fromSignal; Flags _flags; @@ -339,21 +442,21 @@ private: }; MONGO_COMPILER_NOINLINE -void printStackTrace(std::ostream& os, bool fromSignal) { - Iteration iteration(os, fromSignal); - printStackTraceGeneric(iteration, os, StackTraceOptions{}); +void printStackTrace(StackTraceSink& sink, bool fromSignal) { + Iteration iteration(sink, fromSignal); + printStackTraceGeneric(iteration, sink, StackTraceOptions{}); } #elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_EXECINFO class Iteration : public IterationIface { public: - explicit Iteration(std::ostream& os, bool fromSignal) { + explicit Iteration(StackTraceSink& sink, 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; + sink << "Unable to collect backtrace addresses (errno: " << err << " " << strerror(err) + << ")\n"; return; } } @@ -394,30 +497,33 @@ private: }; MONGO_COMPILER_NOINLINE -void printStackTrace(std::ostream& os, bool fromSignal) { - Iteration iteration(os, fromSignal); - printStackTraceGeneric(iteration, os, StackTraceOptions{}); +void printStackTrace(StackTraceSink& sink, bool fromSignal) { + Iteration iteration(sink, fromSignal); + printStackTraceGeneric(iteration, sink, StackTraceOptions{}); } #elif MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_NONE MONGO_COMPILER_NOINLINE -void printStackTrace(std::ostream& os, bool fromSignal) { - os << "This platform does not support printing stacktraces" << std::endl; +void printStackTrace(StackTraceSink& sink, bool fromSignal) { + sink << "This platform does not support printing stacktraces\n"; } #endif // MONGO_STACKTRACE_BACKEND +} // namespace } // namespace stacktrace_detail MONGO_COMPILER_NOINLINE void printStackTrace(std::ostream& os) { - stacktrace_detail::printStackTrace(os, false); + stacktrace_detail::OstreamJsonSink sink{os}; + stacktrace_detail::printStackTrace(sink, false); } MONGO_COMPILER_NOINLINE void printStackTraceFromSignal(std::ostream& os) { - stacktrace_detail::printStackTrace(os, true); + stacktrace_detail::OstreamJsonSink sink{os}; + stacktrace_detail::printStackTrace(sink, true); } } // namespace mongo diff --git a/src/mongo/util/stacktrace_somap.cpp b/src/mongo/util/stacktrace_somap.cpp index ef278df9498..75ccf94eb63 100644 --- a/src/mongo/util/stacktrace_somap.cpp +++ b/src/mongo/util/stacktrace_somap.cpp @@ -136,12 +136,13 @@ void processNoteSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONO 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))) + + ElfW(Ehdr) eHeader; + if (phdr.p_memsz < sizeof(eHeader)) 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)); + memcpy(&eHeader, (char*)(info.dlpi_addr + phdr.p_vaddr), sizeof(eHeader)); std::string quotedFileName = "\"" + str::escape(info.dlpi_name) + "\""; @@ -196,9 +197,6 @@ void processLoadSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONO * 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))); @@ -207,8 +205,8 @@ int outputSOInfo(dl_phdr_info* info, size_t sz, void* data) { for (ElfW(Half) i = 0; i < info->dlpi_phnum; ++i) { const ElfW(Phdr) & phdr(info->dlpi_phdr[i]); - if (!isSegmentMappedReadable(phdr)) - continue; + if (!(phdr.p_flags & PF_R)) + continue; // skip non-readable segments switch (phdr.p_type) { case PT_NOTE: processNoteSegment(*info, phdr, &soInfo); @@ -324,14 +322,13 @@ MONGO_INITIALIZER(ExtractSOMap)(InitializerContext*) { soMap << "compiledModules" << vii.modules(); addOSComponentsToSoMap(&soMap); - _globalSharedObjectMapInfo = new SharedObjectMapInfo(soMap.done()); + _globalSharedObjectMapInfo = new SharedObjectMapInfo(soMap.obj()); return Status::OK(); } } // namespace -SharedObjectMapInfo::SharedObjectMapInfo(BSONObj obj) - : _obj(std::move(obj)), _json(_obj.jsonString(Strict)) {} +SharedObjectMapInfo::SharedObjectMapInfo(BSONObj obj) : _obj(std::move(obj)) {} SharedObjectMapInfo* globalSharedObjectMapInfo() { return _globalSharedObjectMapInfo; diff --git a/src/mongo/util/stacktrace_somap.h b/src/mongo/util/stacktrace_somap.h index a392c2838cd..9e2db532cc8 100644 --- a/src/mongo/util/stacktrace_somap.h +++ b/src/mongo/util/stacktrace_somap.h @@ -37,15 +37,12 @@ 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; + const BSONObj& obj() const { + return _obj; } private: BSONObj _obj; - std::string _json; }; // Available after the MONGO_INITIALIZER has run. diff --git a/src/mongo/util/stacktrace_test.cpp b/src/mongo/util/stacktrace_test.cpp index b2ba29b045d..ee8d510cdec 100644 --- a/src/mongo/util/stacktrace_test.cpp +++ b/src/mongo/util/stacktrace_test.cpp @@ -38,10 +38,11 @@ #include <sstream> #include <vector> +#include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/unittest/unittest.h" #include "mongo/util/stacktrace.h" - +#include "mongo/util/stacktrace_json.h" namespace mongo { @@ -258,9 +259,9 @@ TEST(StackTrace, PosixFormat) { if (kSuperVerbose) { for (auto& elem : jsonObj["backtrace"].Array()) { - tlog() << " btelem=" << LogJson(elem.Obj()); + tlog() << " btelem=\n" << LogJson(elem.Obj()); } - tlog() << " processInfo=" << LogJson(jsonObj["processInfo"].Obj()); + tlog() << " processInfo=\n" << LogJson(jsonObj["processInfo"].Obj()); } ASSERT_TRUE(jsonObj["processInfo"].Obj().hasField("somap")); @@ -357,5 +358,128 @@ TEST(StackTrace, WindowsFormat) { << "of trace: `" << trace << "`"; } +class StringSink : public StackTraceSink { +public: + StringSink(std::string& s) : _s{s} {} + +private: + void doWrite(StringData v) override { + format_to(std::back_inserter(_s), FMT_STRING("{}"), v); + } + + void doWrite(uint64_t v) override { + format_to(std::back_inserter(_s), FMT_STRING("{:d}"), v); + } + + std::string& _s; +}; + +class CheapJsonTest : public unittest::Test { +public: + using unittest::Test::Test; +}; + +TEST_F(CheapJsonTest, Appender) { + std::string s; + StringSink sink{s}; + sink << "Hello" + << ":" << 0 << ":" << 255 << ":" << 1234567890; + ASSERT_EQ(s, "Hello:0:255:1234567890"); +} + +TEST_F(CheapJsonTest, Hex) { + using Hex = stacktrace_detail::Hex; + ASSERT_EQ(Hex(0).str(), "0"); + ASSERT_EQ(Hex(0xffff).str(), "FFFF"); + ASSERT_EQ(Hex(0xfff0).str(), "FFF0"); + ASSERT_EQ(Hex(0x8000'0000'0000'0000).str(), "8000000000000000"); + ASSERT_EQ(Hex::fromHex("FFFF"), 0xffff); + ASSERT_EQ(Hex::fromHex("0"), 0); + ASSERT_EQ(Hex::fromHex("FFFFFFFFFFFFFFFF"), 0xffff'ffff'ffff'ffff); + + std::string s; + StringSink sink{s}; + sink << Hex(0xffff).str(); + ASSERT_EQ(s, R"(FFFF)"); +} + +TEST_F(CheapJsonTest, DocumentObject) { + std::string s; + StringSink sink{s}; + stacktrace_detail::CheapJson env{sink}; + auto doc = env.doc(); + ASSERT_EQ(s, ""); + { + auto obj = doc.appendObj(); + ASSERT_EQ(s, "{"); + } + ASSERT_EQ(s, "{}"); +} + +TEST_F(CheapJsonTest, ScalarStringData) { + std::string s; + StringSink sink{s}; + stacktrace_detail::CheapJson env{sink}; + auto doc = env.doc(); + doc.append(123); + ASSERT_EQ(s, R"(123)"); +} + +TEST_F(CheapJsonTest, ScalarInt) { + std::string s; + StringSink sink{s}; + stacktrace_detail::CheapJson env{sink}; + auto doc = env.doc(); + doc.append("hello"); + ASSERT_EQ(s, R"("hello")"); +} + +TEST_F(CheapJsonTest, ObjectNesting) { + std::string s; + StringSink sink{s}; + stacktrace_detail::CheapJson env{sink}; + auto doc = env.doc(); + { + auto obj = doc.appendObj(); + obj.appendKey("k").append(255); + { + auto inner = obj.appendKey("obj").appendObj(); + inner.appendKey("innerKey").append("hi"); + } + } + ASSERT_EQ(s, R"({"k":255,"obj":{"innerKey":"hi"}})"); +} + +TEST_F(CheapJsonTest, Arrays) { + std::string s; + StringSink sink{s}; + stacktrace_detail::CheapJson env{sink}; + auto doc = env.doc(); + { + auto obj = doc.appendObj(); + obj.appendKey("k").append(0xFF); + { obj.appendKey("empty").appendArr(); } + { + auto arr = obj.appendKey("arr").appendArr(); + arr.append(240); + arr.append(241); + arr.append(242); + } + } + ASSERT_EQ(s, R"({"k":255,"empty":[],"arr":[240,241,242]})"); +} + +TEST_F(CheapJsonTest, AppendBSONElement) { + std::string s; + StringSink sink{s}; + stacktrace_detail::CheapJson env{sink}; + { + auto obj = env.doc().appendObj(); + for (auto& e : fromjson(R"({"a":1,"arr":[2,123],"emptyO":{},"emptyA":[]})")) + obj.append(e); + } + ASSERT_EQ(s, R"({"a":1,"arr":[2,123],"emptyO":{},"emptyA":[]})"); +} + } // namespace } // namespace mongo |