diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fc25fe..3293130 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,14 @@ if(ENABLE_DEBUG_LOCKS) set(CONFIG_ENABLE_DEBUG_LOCKS TRUE) endif() +# Set the debug trace callables variable for config.h +if(ENABLE_DEBUG_TRACE_CALLABLES) + set(CONFIG_DEBUG_TRACE_CALLABLES TRUE) + # Suppress frame-address warnings when using __builtin_return_address() + # with values above 0 (See callableTracer.h). + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-frame-address") +endif() + # Set the world thread variable for config.h if(WORLD_USE_BODY_THREAD) set(CONFIG_WORLD_USE_BODY_THREAD TRUE) diff --git a/cmake/DebugOpts.cmake b/cmake/DebugOpts.cmake index 4e8dcc0..b2c5981 100644 --- a/cmake/DebugOpts.cmake +++ b/cmake/DebugOpts.cmake @@ -3,6 +3,9 @@ # Enable debug locking features option(ENABLE_DEBUG_LOCKS "Enable debug features for locking system" ON) +# Enable callable tracing for debugging boost::asio post operations +option(ENABLE_DEBUG_TRACE_CALLABLES "Enable callable tracing for debugging boost::asio post operations" OFF) + # Qutex deadlock detection configuration # Always define the variable in cache so it appears in ccmake set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING diff --git a/include/asynchronousContinuation.h b/include/asynchronousContinuation.h index 8a51c85..a01a39b 100644 --- a/include/asynchronousContinuation.h +++ b/include/asynchronousContinuation.h @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -141,10 +142,10 @@ public: .callbackFn) { caller->getIoService().post( - std::bind( + STC(std::bind( AsynchronousContinuation::originalCallback .callbackFn, - std::forward(args)...)); + std::forward(args)...))); } } diff --git a/include/callableTracer.h b/include/callableTracer.h new file mode 100644 index 0000000..1b6ea70 --- /dev/null +++ b/include/callableTracer.h @@ -0,0 +1,138 @@ +#ifndef CALLABLE_TRACER_H +#define CALLABLE_TRACER_H + +#include +#include +#include +#include +#include +#include + +namespace smo { + +/** + * @brief CallableTracer - Wraps callables with metadata for debugging + * + * This class wraps any callable object with metadata (caller function name, + * line number, and return addresses) to help debug cases where callables + * posted to boost::asio::io_service have gone out of scope. The metadata + * can be accessed from the callable's address when debugging. + */ +class CallableTracer +{ +public: + /** + * @brief Constructor that wraps a callable with metadata + * @param callerFuncName The name of the function that created this callable + * @param callerLine The line number where this callable was created + * @param returnAddr0 The return address of the direct caller + * @param returnAddr1 The return address of the caller before that + * @param callable The callable object to wrap + */ + template + explicit CallableTracer( + const char* callerFuncName, + int callerLine, + void* returnAddr0, + void* returnAddr1, + CallableT&& callable) + : callerFuncName(callerFuncName), + callerLine(callerLine), + returnAddr0(returnAddr0), + returnAddr1(returnAddr1), + callable(std::forward(callable)) + {} + + void operator()() + { + if (OptionParser::getOptions().traceCallables) + { + std::cout << "" << __func__ << ": On thread " + << (ComponentThread::tlsInitialized() + ? ComponentThread::getSelf()->name : "") + << ": Calling callable posted by:\n" + << "\t" << callerFuncName << "\n\tat line " << (int)callerLine + << " return addr 0: " << returnAddr0 + << ", return addr 1: " << returnAddr1 + << std::endl; + } + callable(); + } + +public: + /// Name of the function that created this callable + std::string callerFuncName; + /// Line number where this callable was created + int callerLine; + /// Return address of the direct caller + void* returnAddr0; + /// Return address of the caller before that + void* returnAddr1; + +private: + /// The wrapped callable (type-erased using std::function) + std::function callable; +}; + +} // namespace smo + +/** + * @brief STC - SMO Traceable Callable macro + * + * When CONFIG_DEBUG_TRACE_CALLABLES is defined, wraps the callable with + * CallableTracer to store metadata (caller function name, line number, + * and return addresses). When not defined, returns the callable directly + * with no overhead. + * + * Uses compiler-specific macros to get fully qualified function names: + * - GCC/Clang: __PRETTY_FUNCTION__ (includes full signature with namespace/class) + * - MSVC: __FUNCSIG__ (includes full signature) + * - Fallback: __func__ (unqualified function name only) + * + * Uses compiler-specific builtins to get return addresses: + * - GCC/Clang: __builtin_return_address(0) and __builtin_return_address(1) + * - MSVC: _ReturnAddress() (only one level available) + * - Fallback: nullptr for return addresses + * + * Usage: + * thread->getIoService().post( + * STC(std::bind(&SomeClass::method, this, arg1, arg2))); + */ +#ifdef CONFIG_DEBUG_TRACE_CALLABLES + #if defined(__GNUC__) || defined(__clang__) + // GCC/Clang: __PRETTY_FUNCTION__ gives full signature + // e.g., "void smo::SomeClass::method(int, int)" + // __builtin_return_address(0) = direct caller + // __builtin_return_address(1) = caller before that + #define STC(arg) smo::CallableTracer( \ + __PRETTY_FUNCTION__, \ + __LINE__, \ + __builtin_return_address(0), \ + __builtin_return_address(1), \ + arg) + #elif defined(_MSC_VER) + // MSVC: __FUNCSIG__ gives full signature + // e.g., "void __cdecl smo::SomeClass::method(int, int)" + // _ReturnAddress() = direct caller (only one level available) + #include + #define STC(arg) smo::CallableTracer( \ + __FUNCSIG__, \ + __LINE__, \ + _ReturnAddress(), \ + nullptr, \ + arg) + #else + // Fallback to standard __func__ (unqualified name only) + // No return address support + #define STC(arg) smo::CallableTracer( \ + __func__, \ + __LINE__, \ + nullptr, \ + nullptr, \ + arg) + #endif +#else +#define STC(arg) arg +#endif + +#endif // CALLABLE_TRACER_H diff --git a/include/config.h.in b/include/config.h.in index 8ab57c6..ed7e3ff 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -22,6 +22,9 @@ #cmakedefine CONFIG_ENABLE_DEBUG_LOCKS #cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@ +/* Debug callable tracing configuration */ +#cmakedefine CONFIG_DEBUG_TRACE_CALLABLES + /* Cross-compilation configuration */ #cmakedefine CMAKE_CROSSCOMPILING diff --git a/smocore/body/body.cpp b/smocore/body/body.cpp index 4ec254f..45d2add 100644 --- a/smocore/body/body.cpp +++ b/smocore/body/body.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -158,9 +159,9 @@ void Body::initializeReq(Callback callback) parent, mrntt, callback); thread->getIoService().post( - std::bind( + STC(std::bind( &InitializeReq::initializeReq1_posted, - request.get(), request)); + request.get(), request))); } void Body::finalizeReq(Callback callback) @@ -187,9 +188,9 @@ void Body::finalizeReq(Callback callback) parent, mrntt, callback); thread->getIoService().post( - std::bind( + STC(std::bind( &FinalizeReq::finalizeReq1_posted, - request.get(), request)); + request.get(), request))); } } // namespace body diff --git a/smocore/componentThread.cpp b/smocore/componentThread.cpp index 829f482..0542294 100644 --- a/smocore/componentThread.cpp +++ b/smocore/componentThread.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,11 @@ void MindThread::initializeTls(void) thisComponentThread = shared_from_this(); } +bool ComponentThread::tlsInitialized(void) +{ + return thisComponentThread != nullptr; +} + const std::shared_ptr ComponentThread::getSelf(void) { if (!thisComponentThread) @@ -240,9 +246,9 @@ void MindThread::joltThreadReq(Callback callback) mrntt, target, callback); this->getIoService().post( - std::bind( + STC(std::bind( &ThreadLifetimeMgmtOp::joltThreadReq1_posted, - request.get(), request)); + request.get(), request))); } // Thread management method implementations @@ -253,9 +259,9 @@ void MindThread::startThreadReq(Callback callback) caller, shared_from_this(), callback); this->getIoService().post( - std::bind( + STC(std::bind( &ThreadLifetimeMgmtOp::startThreadReq1_posted, - request.get(), request)); + request.get(), request))); } void MindThread::exitThreadReq(Callback callback) @@ -265,14 +271,14 @@ void MindThread::exitThreadReq(Callback callback) caller, shared_from_this(), callback); this->getIoService().post( - std::bind( + STC(std::bind( &ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted, - request.get(), request)); + request.get(), request))); pause_io_service.post( - std::bind( + STC(std::bind( &ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted, - request.get(), request)); + request.get(), request))); } void MindThread::pauseThreadReq(Callback callback) @@ -288,9 +294,9 @@ void MindThread::pauseThreadReq(Callback callback) caller, shared_from_this(), callback); this->getIoService().post( - std::bind( + STC(std::bind( &ThreadLifetimeMgmtOp::pauseThreadReq1_posted, - request.get(), request)); + request.get(), request))); } void MindThread::resumeThreadReq(Callback callback) @@ -307,9 +313,9 @@ void MindThread::resumeThreadReq(Callback callback) caller, shared_from_this(), callback); pause_io_service.post( - std::bind( + STC(std::bind( &ThreadLifetimeMgmtOp::resumeThreadReq1_posted, - request.get(), request)); + request.get(), request))); } // CPU management method implementations diff --git a/smocore/deviceManager/deviceManager.cpp b/smocore/deviceManager/deviceManager.cpp index 6380638..c15d293 100644 --- a/smocore/deviceManager/deviceManager.cpp +++ b/smocore/deviceManager/deviceManager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -626,10 +627,10 @@ void DeviceManager::attachAllUnattachedDevicesFromReq( specs->size(), specs, caller, std::move(cb)); mrntt::mrntt.thread->getIoService().post( - std::bind( + STC(std::bind( &AttachAllUnattachedDevicesFromReq ::attachAllUnattachedDevicesFromReq1_posted, - request.get(), request)); + request.get(), request))); } void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq( @@ -807,9 +808,9 @@ void DeviceManager::detachAllAttachedDeviceRoles( caller, std::move(cb)); mrntt::mrntt.thread->getIoService().post( - std::bind( + STC(std::bind( &DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted, - request.get(), request)); + request.get(), request))); } void DeviceManager::initializeDeviceReattacher() diff --git a/smocore/include/componentThread.h b/smocore/include/componentThread.h index 181f759..584839d 100644 --- a/smocore/include/componentThread.h +++ b/smocore/include/componentThread.h @@ -49,6 +49,7 @@ public: boost::asio::io_service& getIoService(void) { return io_service; } static const std::shared_ptr getSelf(void); + static bool tlsInitialized(void); static std::shared_ptr getMrntt(); typedef void (mainFn)(ComponentThread &self); diff --git a/smocore/include/opts.h b/smocore/include/opts.h index 4602678..647cd55 100644 --- a/smocore/include/opts.h +++ b/smocore/include/opts.h @@ -11,7 +11,8 @@ class OptionParser { public: - OptionParser() : verbose(false), printUsage(false) {} + OptionParser() : verbose(false), printUsage(false), traceCallables(false) + {} ~OptionParser() = default; void parseArguments(int argc, char *argv[], char **envp); @@ -38,7 +39,7 @@ public: std::vector senseApiLibs; std::string dapSpecs; std::vector dapSpecFiles; - bool verbose, printUsage; + bool verbose, printUsage, traceCallables; static struct option longOptions[]; }; diff --git a/smocore/marionette/lifetime.cpp b/smocore/marionette/lifetime.cpp index 5d8fa72..b3ab6b6 100644 --- a/smocore/marionette/lifetime.cpp +++ b/smocore/marionette/lifetime.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -131,9 +132,9 @@ void MarionetteComponent::initializeReq( *this, mrntt, callback); mrntt->getIoService().post( - std::bind( + STC(std::bind( &MrnttLifetimeMgmtOp::initializeReq1_posted, - request.get(), request)); + request.get(), request))); } void MarionetteComponent::finalizeReq( @@ -151,9 +152,9 @@ void MarionetteComponent::finalizeReq( *this, mrntt, callback); mrntt->getIoService().post( - std::bind( + STC(std::bind( &MrnttLifetimeMgmtOp::finalizeReq1_posted, - request.get(), request)); + request.get(), request))); } } // namespace mrntt diff --git a/smocore/mind.cpp b/smocore/mind.cpp index 497bc63..7b4ea31 100644 --- a/smocore/mind.cpp +++ b/smocore/mind.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -215,9 +216,9 @@ void Mind::initializeReq(Callback callback) *this, caller, callback); mrntt::mrntt.thread->getIoService().post( - std::bind( + STC(std::bind( &MindLifetimeMgmtOp::initializeReq1_posted, - request.get(), request)); + request.get(), request))); } void Mind::finalizeReq(Callback callback) @@ -227,9 +228,9 @@ void Mind::finalizeReq(Callback callback) *this, caller, callback); mrntt::mrntt.thread->getIoService().post( - std::bind( + STC(std::bind( &MindLifetimeMgmtOp::finalizeReq1_posted, - request.get(), request)); + request.get(), request))); } void Mind::distributeAndPinThreadsAcrossCpus() diff --git a/smocore/opts.cpp b/smocore/opts.cpp index b54c14e..308e022 100644 --- a/smocore/opts.cpp +++ b/smocore/opts.cpp @@ -43,6 +43,9 @@ struct option OptionParser::longOptions[] = { {"apipath", required_argument, 0, 'p'}, {"libpath", required_argument, 0, 'p'}, {"verbose", no_argument, 0, 'v'}, + {"trace-callables", no_argument, 0, 't'}, + {"call-trace", no_argument, 0, 't'}, + {"calltrace", no_argument, 0, 't'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; @@ -58,7 +61,7 @@ void OptionParser::parseArguments(int argc, char *argv[], char **envp) optind = 1; // Reset optind to 1 before parsing opterr = 0; while ((opt = getopt_long( - argc, argv, "s:d:a:p:vh?", longOptions, &optionIndex)) != -1) + argc, argv, "s:d:a:p:vht?", longOptions, &optionIndex)) != -1) { switch (opt) { @@ -91,6 +94,9 @@ void OptionParser::parseArguments(int argc, char *argv[], char **envp) case 'v': verbose = true; break; + case 't': + traceCallables = true; + break; case 'h': throw JustPrintUsageNoError(*this); case '?': @@ -108,6 +114,7 @@ std::string OptionParser::getUsage() const "[-a|--api-lib|--apilib|--api|--lib ] " "[-p|--api-lib-path|--apipath|--libpath ] " "[-v|--verbose] " + "[-t|--trace-callables|--call-trace|--calltrace] " "[-h|--help]\n\n" "Example DAP spec:\n" " -s '+edev|my-cam|video-qualeiface|v4l2-video(fps-hz=30)|v4l2()|/dev/video0'"; @@ -121,6 +128,10 @@ std::string OptionParser::stringifyOptions(void) const oss << "Verbose mode is on" << std::endl; } + if (traceCallables) { + oss << "Callable tracing is enabled" << std::endl; + } + oss << "Cmdline DAP Specs: " << dapSpecs << std::endl; oss << "DAP Spec Files: "; diff --git a/stimBuffApis/livoxGen1/ioUringAssemblyEngine.cpp b/stimBuffApis/livoxGen1/ioUringAssemblyEngine.cpp index 193919f..59925be 100644 --- a/stimBuffApis/livoxGen1/ioUringAssemblyEngine.cpp +++ b/stimBuffApis/livoxGen1/ioUringAssemblyEngine.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "ioUringAssemblyEngine.h" #include "pcloudStimulusBuffer.h" @@ -514,9 +515,9 @@ void IoUringAssemblyEngine::assembleFrameReq( *this, caller, std::move(cb)); parent.device->componentThread->getIoService().post( - std::bind( + STC(std::bind( &AssembleFrameReq::assembleFrameReq1_posted, - request.get(), request)); + request.get(), request))); } void IoUringAssemblyEngine::onEventfdRead(