summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBilly Donahue <billy.donahue@mongodb.com>2019-10-09 17:47:00 +0000
committerevergreen <evergreen@mongodb.com>2019-10-09 17:47:00 +0000
commit00ad16093a6e6468d4d2ff1f46d8e5c08fcca21b (patch)
tree2526c845157d4aad88fded5509b0bc5f2c543164
parent6e136efbd50551965c17116d00e1f3179c039770 (diff)
downloadmongo-00ad16093a6e6468d4d2ff1f46d8e5c08fcca21b.tar.gz
SERVER-41654 trim somap to relevant libs on stacktrace
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/util/stacktrace.h20
-rw-r--r--src/mongo/util/stacktrace_json.cpp176
-rw-r--r--src/mongo/util/stacktrace_json.h141
-rw-r--r--src/mongo/util/stacktrace_posix.cpp302
-rw-r--r--src/mongo/util/stacktrace_somap.cpp19
-rw-r--r--src/mongo/util/stacktrace_somap.h7
-rw-r--r--src/mongo/util/stacktrace_test.cpp130
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