File indexing completed on 2026-09-20 08:22:19
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsPlugins/FpeMonitoring/FpeMonitor.hpp"
0010
0011 #include <algorithm>
0012 #include <cfenv>
0013 #include <csignal>
0014 #include <cstddef>
0015 #include <cstdint>
0016 #include <cstdlib>
0017 #include <iostream>
0018 #include <iterator>
0019 #include <memory>
0020 #include <mutex>
0021 #include <vector>
0022
0023 #include <boost/stacktrace/frame.hpp>
0024 #include <boost/stacktrace/safe_dump_to.hpp>
0025 #include <boost/stacktrace/stacktrace.hpp>
0026 #include <boost/stacktrace/stacktrace_fwd.hpp>
0027
0028 #include "FpeMonitorPlatform.hpp"
0029
0030 namespace ActsPlugins {
0031
0032 namespace {
0033
0034 bool canMergeFpeInfo(const FpeMonitor::Result::FpeInfo &existing, FpeType type,
0035 std::uintptr_t location,
0036 const boost::stacktrace::stacktrace &st) {
0037 if (existing.type != type) {
0038 return false;
0039 }
0040
0041 if (location != 0 && existing.location != 0) {
0042 return location == existing.location;
0043 }
0044
0045 const auto &existingFrame = *existing.st->begin();
0046 const auto &candidateFrame = *st.begin();
0047 return boost::stacktrace::hash_value(existingFrame) ==
0048 boost::stacktrace::hash_value(candidateFrame);
0049 }
0050
0051 }
0052
0053 FpeMonitor::Result::FpeInfo::~FpeInfo() = default;
0054
0055 FpeMonitor::Result::FpeInfo::FpeInfo(
0056 std::size_t countIn, FpeType typeIn,
0057 std::shared_ptr<const boost::stacktrace::stacktrace> stIn,
0058 std::uintptr_t locationIn)
0059 : count{countIn}, type{typeIn}, st{std::move(stIn)}, location{locationIn} {}
0060
0061 FpeMonitor::Result FpeMonitor::Result::merged(const Result &with) const {
0062 Result result{};
0063
0064 for (unsigned int i = 0; i < m_counts.size(); i++) {
0065 result.m_counts[i] = m_counts[i] + with.m_counts[i];
0066 }
0067
0068 std::ranges::copy(with.m_stackTraces,
0069 std::back_inserter(result.m_stackTraces));
0070 std::ranges::copy(m_stackTraces, std::back_inserter(result.m_stackTraces));
0071
0072 result.deduplicate();
0073
0074 return result;
0075 }
0076
0077 void FpeMonitor::Result::merge(const Result &with) {
0078 for (unsigned int i = 0; i < m_counts.size(); i++) {
0079 m_counts[i] = m_counts[i] + with.m_counts[i];
0080 }
0081
0082 std::ranges::copy(with.m_stackTraces, std::back_inserter(m_stackTraces));
0083
0084 deduplicate();
0085 }
0086
0087 void FpeMonitor::Result::add(FpeType type, void *stackPtr,
0088 std::size_t bufferSize, std::uintptr_t location) {
0089 auto st = std::make_unique<boost::stacktrace::stacktrace>(
0090 boost::stacktrace::stacktrace::from_dump(stackPtr, bufferSize));
0091
0092 for (auto &el : m_stackTraces) {
0093 if (canMergeFpeInfo(el, type, location, *st)) {
0094 el.count += 1;
0095 return;
0096 }
0097 }
0098
0099 m_stackTraces.emplace_back(1, type, std::move(st), location);
0100 }
0101
0102 bool FpeMonitor::Result::contains(const FpeInfo &info) const {
0103 return std::ranges::any_of(m_stackTraces, [&](const FpeInfo &el) {
0104 return canMergeFpeInfo(el, info.type, info.location, *info.st);
0105 });
0106 }
0107
0108 FpeMonitor::Result &FpeMonitor::result() {
0109 consumeRecorded();
0110 return m_result;
0111 }
0112
0113 void FpeMonitor::consumeRecorded() {
0114 if (m_recorded.empty()) {
0115 return;
0116 }
0117
0118 for (const auto &recorded : m_recorded) {
0119 m_result.add(recorded.type, recorded.stackPtr, recorded.bufferSize,
0120 recorded.location);
0121 }
0122
0123 m_buffer.reset();
0124 m_recorded.clear();
0125 }
0126
0127 unsigned int FpeMonitor::Result::count(FpeType type) const {
0128 return m_counts.at(static_cast<std::uint32_t>(type));
0129 }
0130
0131 unsigned int FpeMonitor::Result::numStackTraces() const {
0132 return m_stackTraces.size();
0133 }
0134
0135 const std::vector<FpeMonitor::Result::FpeInfo> &
0136 FpeMonitor::Result::stackTraces() const {
0137 return m_stackTraces;
0138 }
0139
0140 bool FpeMonitor::Result::encountered(FpeType type) const {
0141 return count(type) > 0;
0142 }
0143
0144 void FpeMonitor::Result::summary(std::ostream &os, std::size_t depth) const {
0145 os << "FPE result summary:\n";
0146 static const std::vector<FpeType> types = {
0147 FpeType::INTDIV, FpeType::INTOVF, FpeType::FLTDIV, FpeType::FLTOVF,
0148 FpeType::FLTUND, FpeType::FLTRES, FpeType::FLTINV, FpeType::FLTSUB};
0149
0150 for (auto type : types) {
0151 os << "- " << type << ": " << count(type) << "\n";
0152 }
0153
0154 os << "\nStack traces:\n";
0155 for (const auto &info : stackTraces()) {
0156 os << "- " << info.type << ": (" << info.count << " times)\n";
0157
0158 os << stackTraceToString(*info.st, depth);
0159 }
0160 os << std::endl;
0161 }
0162
0163 void FpeMonitor::Result::deduplicate() {
0164 std::vector<FpeInfo> copy = std::move(m_stackTraces);
0165 m_stackTraces.clear();
0166 m_stackTraces.reserve(copy.size());
0167
0168 for (const auto &info : copy) {
0169 const auto mergeTarget =
0170 std::ranges::find_if(m_stackTraces, [&](const FpeInfo &existing) {
0171 return canMergeFpeInfo(existing, info.type, info.location, *info.st);
0172 });
0173
0174 if (mergeTarget != m_stackTraces.end()) {
0175 mergeTarget->count += info.count;
0176 } else {
0177 m_stackTraces.push_back(info);
0178 }
0179 }
0180 }
0181
0182 FpeMonitor::FpeMonitor()
0183 : m_excepts{FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW} {
0184 enable();
0185 }
0186
0187 FpeMonitor::FpeMonitor(int excepts) : m_excepts(excepts) {
0188 enable();
0189 }
0190
0191 FpeMonitor::~FpeMonitor() {
0192 disable();
0193 }
0194
0195 void FpeMonitor::signalHandler(int signal, siginfo_t *si, void *ctx) {
0196 if (stack().empty()) {
0197 return;
0198 }
0199
0200 FpeMonitor &fpe = *stack().top();
0201 auto type = detail::decodeFpeType(signal, si, ctx);
0202 if (!type.has_value()) {
0203 if (detail::shouldFailFastOnUnknownSignal()) {
0204
0205
0206 std::_Exit(EXIT_FAILURE);
0207 }
0208 return;
0209 }
0210 fpe.m_result.m_counts.at(static_cast<std::uint32_t>(*type))++;
0211 std::uintptr_t location =
0212 si != nullptr ? reinterpret_cast<std::uintptr_t>(si->si_addr) : 0;
0213
0214 try {
0215 auto [buffer, remaining] = fpe.m_buffer.next();
0216 using NativeFramePtr = boost::stacktrace::frame::native_frame_ptr_t;
0217 std::size_t stored =
0218 detail::captureStackFromSignalContext(ctx, buffer, remaining);
0219 if (stored == 0) {
0220 std::size_t depth = boost::stacktrace::safe_dump_to(
0221 detail::safeDumpSkipFrames(), buffer, remaining);
0222 stored = depth * sizeof(NativeFramePtr);
0223 }
0224 if (stored > 0) {
0225 fpe.m_buffer.pushOffset(stored);
0226 fpe.m_recorded.emplace_back(
0227 *type, buffer, stored,
0228 location);
0229 }
0230
0231 } catch (const std::bad_alloc &e) {
0232 std::cout << "Unable to collect stack trace due to memory limit"
0233 << std::endl;
0234 }
0235
0236 detail::maskTrapsInSignalContext(ctx, *type);
0237 }
0238
0239 void FpeMonitor::enable() {
0240 if (!detail::isRuntimeSupported()) {
0241 return;
0242 }
0243 ensureSignalHandlerInstalled();
0244
0245
0246 detail::clearPendingExceptions(m_excepts);
0247
0248 if (!stack().empty()) {
0249
0250 detail::disableExceptions(stack().top()->m_excepts);
0251 }
0252
0253 detail::enableExceptions(m_excepts);
0254
0255 stack().push(this);
0256 }
0257
0258 void FpeMonitor::rearm() {
0259 consumeRecorded();
0260 if (!detail::isRuntimeSupported()) {
0261 return;
0262 }
0263 detail::clearPendingExceptions(m_excepts);
0264 detail::enableExceptions(m_excepts);
0265 }
0266
0267 void FpeMonitor::ensureSignalHandlerInstalled() {
0268 auto &state = globalState();
0269 if (state.isSignalHandlerInstalled) {
0270 return;
0271 }
0272
0273 std::lock_guard lock{state.mutex};
0274 if (state.isSignalHandlerInstalled) {
0275 return;
0276 }
0277
0278 detail::installSignalHandlers(&signalHandler);
0279
0280 state.isSignalHandlerInstalled = true;
0281 }
0282
0283 void FpeMonitor::disable() {
0284 if (!detail::isRuntimeSupported()) {
0285 return;
0286 }
0287 detail::clearPendingExceptions(m_excepts);
0288 assert(!stack().empty() && "FPE stack shouldn't be empty at this point");
0289 stack().pop();
0290
0291 detail::disableExceptions(m_excepts);
0292 if (!stack().empty()) {
0293
0294 detail::clearPendingExceptions(stack().top()->m_excepts);
0295 detail::enableExceptions(stack().top()->m_excepts);
0296 }
0297 }
0298
0299 std::stack<FpeMonitor *> &FpeMonitor::stack() {
0300 static thread_local std::stack<FpeMonitor *> monitors;
0301 return monitors;
0302 }
0303
0304 FpeMonitor::GlobalState &FpeMonitor::globalState() {
0305 static GlobalState state{};
0306 return state;
0307 }
0308
0309 std::ostream &operator<<(std::ostream &os, FpeType type) {
0310 #define CASE(x) \
0311 case FpeType::x: \
0312 os << #x; \
0313 break;
0314
0315 switch (type) {
0316 CASE(INTDIV)
0317 CASE(INTOVF)
0318 CASE(FLTDIV)
0319 CASE(FLTOVF)
0320 CASE(FLTUND)
0321 CASE(FLTRES)
0322 CASE(FLTINV)
0323 CASE(FLTSUB)
0324 }
0325 #undef CASE
0326
0327 return os;
0328 }
0329
0330 std::string FpeMonitor::stackTraceToString(
0331 const boost::stacktrace::stacktrace &st, std::size_t depth) {
0332 return boost::stacktrace::detail::to_string(st.as_vector().data(),
0333 std::min(depth, st.size()));
0334 }
0335
0336 std::string FpeMonitor::getSourceLocation(
0337 const boost::stacktrace::frame &frame) {
0338 return frame.source_file() + ":" + std::to_string(frame.source_line());
0339 }
0340
0341 bool FpeMonitor::canSymbolize() {
0342 #if defined(BOOST_STACKTRACE_USE_NOOP)
0343 return false;
0344 #else
0345 return true;
0346 #endif
0347 }
0348
0349 bool FpeMonitor::isSupported() {
0350 return detail::isRuntimeSupported();
0351 }
0352
0353 }