Dbg:Add CallableTracer for callables post()ed to boost.asio

This class and its macro allow us to trace the invocation of
callbacks as they're invoked by Boost.asio.
This commit is contained in:
2025-11-06 21:40:32 -04:00
parent eeb057effd
commit 457d0f9345
14 changed files with 212 additions and 35 deletions
+8
View File
@@ -61,6 +61,14 @@ if(ENABLE_DEBUG_LOCKS)
set(CONFIG_ENABLE_DEBUG_LOCKS TRUE) set(CONFIG_ENABLE_DEBUG_LOCKS TRUE)
endif() 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 # Set the world thread variable for config.h
if(WORLD_USE_BODY_THREAD) if(WORLD_USE_BODY_THREAD)
set(CONFIG_WORLD_USE_BODY_THREAD TRUE) set(CONFIG_WORLD_USE_BODY_THREAD TRUE)
+3
View File
@@ -3,6 +3,9 @@
# Enable debug locking features # Enable debug locking features
option(ENABLE_DEBUG_LOCKS "Enable debug features for locking system" ON) 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 # Qutex deadlock detection configuration
# Always define the variable in cache so it appears in ccmake # Always define the variable in cache so it appears in ccmake
set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING
+3 -2
View File
@@ -6,6 +6,7 @@
#include <exception> #include <exception>
#include <componentThread.h> #include <componentThread.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <asynchronousContinuationChainLink.h> #include <asynchronousContinuationChainLink.h>
@@ -141,10 +142,10 @@ public:
.callbackFn) .callbackFn)
{ {
caller->getIoService().post( caller->getIoService().post(
std::bind( STC(std::bind(
AsynchronousContinuation<OriginalCbFnT>::originalCallback AsynchronousContinuation<OriginalCbFnT>::originalCallback
.callbackFn, .callbackFn,
std::forward<Args>(args)...)); std::forward<Args>(args)...)));
} }
} }
+138
View File
@@ -0,0 +1,138 @@
#ifndef CALLABLE_TRACER_H
#define CALLABLE_TRACER_H
#include <config.h>
#include <string>
#include <functional>
#include <iostream>
#include <cstdint>
#include <opts.h>
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<typename CallableT>
explicit CallableTracer(
const char* callerFuncName,
int callerLine,
void* returnAddr0,
void* returnAddr1,
CallableT&& callable)
: callerFuncName(callerFuncName),
callerLine(callerLine),
returnAddr0(returnAddr0),
returnAddr1(returnAddr1),
callable(std::forward<CallableT>(callable))
{}
void operator()()
{
if (OptionParser::getOptions().traceCallables)
{
std::cout << "" << __func__ << ": On thread "
<< (ComponentThread::tlsInitialized()
? ComponentThread::getSelf()->name : "<TLS un-init'ed>")
<< ": 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<void()> 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 <intrin.h>
#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
+3
View File
@@ -22,6 +22,9 @@
#cmakedefine CONFIG_ENABLE_DEBUG_LOCKS #cmakedefine CONFIG_ENABLE_DEBUG_LOCKS
#cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@ #cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@
/* Debug callable tracing configuration */
#cmakedefine CONFIG_DEBUG_TRACE_CALLABLES
/* Cross-compilation configuration */ /* Cross-compilation configuration */
#cmakedefine CMAKE_CROSSCOMPILING #cmakedefine CMAKE_CROSSCOMPILING
+5 -4
View File
@@ -3,6 +3,7 @@
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <asynchronousLoop.h> #include <asynchronousLoop.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <body/body.h> #include <body/body.h>
#include <componentThread.h> #include <componentThread.h>
#include <mind.h> #include <mind.h>
@@ -158,9 +159,9 @@ void Body::initializeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
parent, mrntt, callback); parent, mrntt, callback);
thread->getIoService().post( thread->getIoService().post(
std::bind( STC(std::bind(
&InitializeReq::initializeReq1_posted, &InitializeReq::initializeReq1_posted,
request.get(), request)); request.get(), request)));
} }
void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback) void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
@@ -187,9 +188,9 @@ void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
parent, mrntt, callback); parent, mrntt, callback);
thread->getIoService().post( thread->getIoService().post(
std::bind( STC(std::bind(
&FinalizeReq::finalizeReq1_posted, &FinalizeReq::finalizeReq1_posted,
request.get(), request)); request.get(), request)));
} }
} // namespace body } // namespace body
+18 -12
View File
@@ -8,6 +8,7 @@
#include <opts.h> #include <opts.h>
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <mind.h> #include <mind.h>
#include <mindManager/mindManager.h> #include <mindManager/mindManager.h>
#include <componentThread.h> #include <componentThread.h>
@@ -33,6 +34,11 @@ void MindThread::initializeTls(void)
thisComponentThread = shared_from_this(); thisComponentThread = shared_from_this();
} }
bool ComponentThread::tlsInitialized(void)
{
return thisComponentThread != nullptr;
}
const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void) const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
{ {
if (!thisComponentThread) if (!thisComponentThread)
@@ -240,9 +246,9 @@ void MindThread::joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
mrntt, target, callback); mrntt, target, callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::joltThreadReq1_posted, &ThreadLifetimeMgmtOp::joltThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
// Thread management method implementations // Thread management method implementations
@@ -253,9 +259,9 @@ void MindThread::startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::startThreadReq1_posted, &ThreadLifetimeMgmtOp::startThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
@@ -265,14 +271,14 @@ void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted, &ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted,
request.get(), request)); request.get(), request)));
pause_io_service.post( pause_io_service.post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted, &ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted,
request.get(), request)); request.get(), request)));
} }
void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
@@ -288,9 +294,9 @@ void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::pauseThreadReq1_posted, &ThreadLifetimeMgmtOp::pauseThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
@@ -307,9 +313,9 @@ void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
pause_io_service.post( pause_io_service.post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::resumeThreadReq1_posted, &ThreadLifetimeMgmtOp::resumeThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
// CPU management method implementations // CPU management method implementations
+5 -4
View File
@@ -9,6 +9,7 @@
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <serializedAsynchronousContinuation.h> #include <serializedAsynchronousContinuation.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <componentThread.h> #include <componentThread.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
#include <deviceManager/deviceReattacher.h> #include <deviceManager/deviceReattacher.h>
@@ -626,10 +627,10 @@ void DeviceManager::attachAllUnattachedDevicesFromReq(
specs->size(), specs, caller, std::move(cb)); specs->size(), specs, caller, std::move(cb));
mrntt::mrntt.thread->getIoService().post( mrntt::mrntt.thread->getIoService().post(
std::bind( STC(std::bind(
&AttachAllUnattachedDevicesFromReq &AttachAllUnattachedDevicesFromReq
::attachAllUnattachedDevicesFromReq1_posted, ::attachAllUnattachedDevicesFromReq1_posted,
request.get(), request)); request.get(), request)));
} }
void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq( void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq(
@@ -807,9 +808,9 @@ void DeviceManager::detachAllAttachedDeviceRoles(
caller, std::move(cb)); caller, std::move(cb));
mrntt::mrntt.thread->getIoService().post( mrntt::mrntt.thread->getIoService().post(
std::bind( STC(std::bind(
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted, &DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted,
request.get(), request)); request.get(), request)));
} }
void DeviceManager::initializeDeviceReattacher() void DeviceManager::initializeDeviceReattacher()
+1
View File
@@ -49,6 +49,7 @@ public:
boost::asio::io_service& getIoService(void) { return io_service; } boost::asio::io_service& getIoService(void) { return io_service; }
static const std::shared_ptr<ComponentThread> getSelf(void); static const std::shared_ptr<ComponentThread> getSelf(void);
static bool tlsInitialized(void);
static std::shared_ptr<MarionetteThread> getMrntt(); static std::shared_ptr<MarionetteThread> getMrntt();
typedef void (mainFn)(ComponentThread &self); typedef void (mainFn)(ComponentThread &self);
+3 -2
View File
@@ -11,7 +11,8 @@
class OptionParser class OptionParser
{ {
public: public:
OptionParser() : verbose(false), printUsage(false) {} OptionParser() : verbose(false), printUsage(false), traceCallables(false)
{}
~OptionParser() = default; ~OptionParser() = default;
void parseArguments(int argc, char *argv[], char **envp); void parseArguments(int argc, char *argv[], char **envp);
@@ -38,7 +39,7 @@ public:
std::vector<std::string> senseApiLibs; std::vector<std::string> senseApiLibs;
std::string dapSpecs; std::string dapSpecs;
std::vector<std::string> dapSpecFiles; std::vector<std::string> dapSpecFiles;
bool verbose, printUsage; bool verbose, printUsage, traceCallables;
static struct option longOptions[]; static struct option longOptions[];
}; };
+5 -4
View File
@@ -2,6 +2,7 @@
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <asynchronousLoop.h> #include <asynchronousLoop.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <component.h> #include <component.h>
#include <componentThread.h> #include <componentThread.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
@@ -131,9 +132,9 @@ void MarionetteComponent::initializeReq(
*this, mrntt, callback); *this, mrntt, callback);
mrntt->getIoService().post( mrntt->getIoService().post(
std::bind( STC(std::bind(
&MrnttLifetimeMgmtOp::initializeReq1_posted, &MrnttLifetimeMgmtOp::initializeReq1_posted,
request.get(), request)); request.get(), request)));
} }
void MarionetteComponent::finalizeReq( void MarionetteComponent::finalizeReq(
@@ -151,9 +152,9 @@ void MarionetteComponent::finalizeReq(
*this, mrntt, callback); *this, mrntt, callback);
mrntt->getIoService().post( mrntt->getIoService().post(
std::bind( STC(std::bind(
&MrnttLifetimeMgmtOp::finalizeReq1_posted, &MrnttLifetimeMgmtOp::finalizeReq1_posted,
request.get(), request)); request.get(), request)));
} }
} // namespace mrntt } // namespace mrntt
+5 -4
View File
@@ -3,6 +3,7 @@
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <asynchronousLoop.h> #include <asynchronousLoop.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <mind.h> #include <mind.h>
#include <componentThread.h> #include <componentThread.h>
#include <director/director.h> #include <director/director.h>
@@ -215,9 +216,9 @@ void Mind::initializeReq(Callback<mindLifetimeMgmtOpCbFn> callback)
*this, caller, callback); *this, caller, callback);
mrntt::mrntt.thread->getIoService().post( mrntt::mrntt.thread->getIoService().post(
std::bind( STC(std::bind(
&MindLifetimeMgmtOp::initializeReq1_posted, &MindLifetimeMgmtOp::initializeReq1_posted,
request.get(), request)); request.get(), request)));
} }
void Mind::finalizeReq(Callback<mindLifetimeMgmtOpCbFn> callback) void Mind::finalizeReq(Callback<mindLifetimeMgmtOpCbFn> callback)
@@ -227,9 +228,9 @@ void Mind::finalizeReq(Callback<mindLifetimeMgmtOpCbFn> callback)
*this, caller, callback); *this, caller, callback);
mrntt::mrntt.thread->getIoService().post( mrntt::mrntt.thread->getIoService().post(
std::bind( STC(std::bind(
&MindLifetimeMgmtOp::finalizeReq1_posted, &MindLifetimeMgmtOp::finalizeReq1_posted,
request.get(), request)); request.get(), request)));
} }
void Mind::distributeAndPinThreadsAcrossCpus() void Mind::distributeAndPinThreadsAcrossCpus()
+12 -1
View File
@@ -43,6 +43,9 @@ struct option OptionParser::longOptions[] = {
{"apipath", required_argument, 0, 'p'}, {"apipath", required_argument, 0, 'p'},
{"libpath", required_argument, 0, 'p'}, {"libpath", required_argument, 0, 'p'},
{"verbose", no_argument, 0, 'v'}, {"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'}, {"help", no_argument, 0, 'h'},
{0, 0, 0, 0} {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 optind = 1; // Reset optind to 1 before parsing
opterr = 0; opterr = 0;
while ((opt = getopt_long( 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) switch (opt)
{ {
@@ -91,6 +94,9 @@ void OptionParser::parseArguments(int argc, char *argv[], char **envp)
case 'v': case 'v':
verbose = true; verbose = true;
break; break;
case 't':
traceCallables = true;
break;
case 'h': case 'h':
throw JustPrintUsageNoError(*this); throw JustPrintUsageNoError(*this);
case '?': case '?':
@@ -108,6 +114,7 @@ std::string OptionParser::getUsage() const
"[-a|--api-lib|--apilib|--api|--lib <filename>] " "[-a|--api-lib|--apilib|--api|--lib <filename>] "
"[-p|--api-lib-path|--apipath|--libpath <directory>] " "[-p|--api-lib-path|--apipath|--libpath <directory>] "
"[-v|--verbose] " "[-v|--verbose] "
"[-t|--trace-callables|--call-trace|--calltrace] "
"[-h|--help]\n\n" "[-h|--help]\n\n"
"Example DAP spec:\n" "Example DAP spec:\n"
" -s '+edev|my-cam|video-qualeiface|v4l2-video(fps-hz=30)|v4l2()|/dev/video0'"; " -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; oss << "Verbose mode is on" << std::endl;
} }
if (traceCallables) {
oss << "Callable tracing is enabled" << std::endl;
}
oss << "Cmdline DAP Specs: " << dapSpecs << std::endl; oss << "Cmdline DAP Specs: " << dapSpecs << std::endl;
oss << "DAP Spec Files: "; oss << "DAP Spec Files: ";
@@ -17,6 +17,7 @@
#include <asynchronousLoop.h> #include <asynchronousLoop.h>
#include <asynchronousBridge.h> #include <asynchronousBridge.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <spinLock.h> #include <spinLock.h>
#include "ioUringAssemblyEngine.h" #include "ioUringAssemblyEngine.h"
#include "pcloudStimulusBuffer.h" #include "pcloudStimulusBuffer.h"
@@ -514,9 +515,9 @@ void IoUringAssemblyEngine::assembleFrameReq(
*this, caller, std::move(cb)); *this, caller, std::move(cb));
parent.device->componentThread->getIoService().post( parent.device->componentThread->getIoService().post(
std::bind( STC(std::bind(
&AssembleFrameReq::assembleFrameReq1_posted, &AssembleFrameReq::assembleFrameReq1_posted,
request.get(), request)); request.get(), request)));
} }
void IoUringAssemblyEngine::onEventfdRead( void IoUringAssemblyEngine::onEventfdRead(