summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBilly Donahue <billy.donahue@mongodb.com>2019-11-19 14:38:59 +0000
committerevergreen <evergreen@mongodb.com>2019-11-19 14:38:59 +0000
commit4722a18440d6645a24f83def678f7cf7a6a290fe (patch)
treeaf89db80122ea3f879f47f51d8b068bb20346703 /src
parent26fb6b0fc1edb6f6c42310232cbea0a7605162e7 (diff)
downloadmongo-4722a18440d6645a24f83def678f7cf7a6a290fe.tar.gz
SERVER-42406 stacktrace API (revised)
Diffstat (limited to 'src')
-rw-r--r--src/mongo/util/stacktrace.h166
-rw-r--r--src/mongo/util/stacktrace_json.cpp29
-rw-r--r--src/mongo/util/stacktrace_json.h16
-rw-r--r--src/mongo/util/stacktrace_posix.cpp382
-rw-r--r--src/mongo/util/stacktrace_test.cpp120
-rw-r--r--src/mongo/util/stacktrace_windows.cpp34
-rw-r--r--src/mongo/util/stacktrace_windows.h6
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);