From b86ede99b9eb39f4b9ecb07a5dfbcc2e1abce140 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Wed, 8 Apr 2026 23:44:01 +0200 Subject: [PATCH] Fixed issues with config and tracing --- .../DebugService/DebugBrokerWrapper.h | 45 ++++-- .../Interfaces/DebugService/DebugService.cpp | 133 ++++++++++++++---- .../Interfaces/DebugService/DebugService.h | 2 + Test/Integration/IntegrationTests.cpp | 2 +- run_debug_app.sh | 41 +++++- 5 files changed, 174 insertions(+), 49 deletions(-) diff --git a/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h b/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h index 51abd06..7cfde77 100644 --- a/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h +++ b/Source/Components/Interfaces/DebugService/DebugBrokerWrapper.h @@ -26,6 +26,30 @@ namespace MARTe { +// Recursive search for any object with a given name anywhere in the registry. +// Used to find GAMs regardless of nesting level. +static Reference FindByNameRecursive(ReferenceContainer *container, + const char8 *name) { + if (container == NULL_PTR(ReferenceContainer *)) + return Reference(); + uint32 n = container->Size(); + for (uint32 i = 0; i < n; i++) { + Reference child = container->Get(i); + if (!child.IsValid()) + continue; + if (StringHelper::Compare(child->GetName(), name) == 0) + return child; + ReferenceContainer *sub = + dynamic_cast(child.operator->()); + if (sub != NULL_PTR(ReferenceContainer *)) { + Reference found = FindByNameRecursive(sub, name); + if (found.IsValid()) + return found; + } + } + return Reference(); +} + /** * @brief Helper for optimized signal processing within brokers. */ @@ -126,21 +150,14 @@ public: (direction == InputSignals) ? "InputSignals" : "OutputSignals"; const char8 *dirStrShort = (direction == InputSignals) ? "In" : "Out"; - // Try to find the GAM with different path variations + // Search recursively through the entire registry for the GAM by name. + // Direct Find("GAM1") only checks top-level; the GAM may be nested + // several levels deep inside a RealTimeApplication container. Reference gamRef = - ObjectRegistryDatabase::Instance()->Find(functionName); - if (!gamRef.IsValid()) { - // Try with "App.Functions." prefix - StreamString tryPath; - tryPath.Printf("App.Functions.%s", functionName); - gamRef = ObjectRegistryDatabase::Instance()->Find(tryPath.Buffer()); - } - if (!gamRef.IsValid()) { - // Try with "Functions." prefix - StreamString tryPath; - tryPath.Printf("Functions.%s", functionName); - gamRef = ObjectRegistryDatabase::Instance()->Find(tryPath.Buffer()); - } + FindByNameRecursive(ObjectRegistryDatabase::Instance(), + functionName); + fprintf(stderr, ">> GAM lookup '%s': %s\n", functionName, + gamRef.IsValid() ? "FOUND" : "NOT FOUND"); if (gamRef.IsValid()) { StreamString absGamPath; diff --git a/Source/Components/Interfaces/DebugService/DebugService.cpp b/Source/Components/Interfaces/DebugService/DebugService.cpp index 27af8b6..e59783f 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.cpp +++ b/Source/Components/Interfaces/DebugService/DebugService.cpp @@ -93,6 +93,7 @@ DebugService::DebugService() isServer = false; suppressTimeoutLogs = true; isPaused = false; + manualConfigSet = false; activeClient = NULL_PTR(BasicTCPSocket *); } @@ -157,18 +158,10 @@ bool DebugService::Initialise(StructuredDataI &data) { suppressTimeoutLogs = (suppress == 1); } - // Try to capture full configuration autonomously if data is a - // ConfigurationDatabase - ConfigurationDatabase *cdb = dynamic_cast(&data); - if (cdb != NULL_PTR(ConfigurationDatabase *)) { - // Save current position - StreamString currentPath; - // In MARTe2 ConfigurationDatabase there isn't a direct GetCurrentPath, - // but we can at least try to copy from root if we are at root. - // For now, we rely on explicit SetFullConfig or documentary injection. - } - - // Copy local branch as fallback + // Do NOT call MoveToRoot() on the shared CDB here — that corrupts the + // ReferenceContainer::Initialise() traversal cursor for sibling objects. + // Just capture the local subtree; full config is rebuilt lazily from the + // live ObjectRegistryDatabase when ServeConfig/EnrichWithConfig is called. (void)data.Copy(fullConfig); if (isServer) { @@ -196,6 +189,50 @@ bool DebugService::Initialise(StructuredDataI &data) { void DebugService::SetFullConfig(ConfigurationDatabase &config) { config.MoveToRoot(); config.Copy(fullConfig); + manualConfigSet = true; +} + +static void BuildCDBFromContainer(ReferenceContainer *container, + ConfigurationDatabase &cdb) { + if (container == NULL_PTR(ReferenceContainer *)) + return; + uint32 n = container->Size(); + for (uint32 i = 0u; i < n; i++) { + Reference child = container->Get(i); + if (!child.IsValid()) + continue; + const char8 *name = child->GetName(); + if (name == NULL_PTR(const char8 *)) + continue; + + bool created = cdb.CreateRelative(name); + if (!created) { + if (!cdb.MoveRelative(name)) + continue; + } + + const char8 *className = child->GetClassProperties()->GetName(); + if (className != NULL_PTR(const char8 *)) + (void)cdb.Write("Class", className); + + // Export the object's own properties (standard fields, parameters). + // Custom config-file-only fields (PVName etc.) are not available here. + (void)child->ExportData(cdb); + + ReferenceContainer *sub = + dynamic_cast(child.operator->()); + if (sub != NULL_PTR(ReferenceContainer *)) + BuildCDBFromContainer(sub, cdb); + + (void)cdb.MoveToAncestor(1u); + } +} + +void DebugService::RebuildConfigFromRegistry() { + ConfigurationDatabase newConfig; + BuildCDBFromContainer(ObjectRegistryDatabase::Instance(), newConfig); + newConfig.MoveToRoot(); + newConfig.Copy(fullConfig); } static void PatchItemInternal(const char8 *originalName, @@ -247,7 +284,7 @@ DebugSignalInfo *DebugService::RegisterSignal(void *memoryAddress, const char8 *name, uint8 numberOfDimensions, uint32 numberOfElements) { - printf(" registering: %s\n", name); + fprintf(stderr, " RegisterSignal[%p]: %s\n", (void*)this, name); mutex.FastLock(); DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *); uint32 sigIdx = 0xFFFFFFFF; @@ -785,8 +822,13 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) { void DebugService::EnrichWithConfig(const char8 *path, StreamString &json) { if (path == NULL_PTR(const char8 *)) return; + if (!manualConfigSet) { + RebuildConfigFromRegistry(); + } fullConfig.MoveToRoot(); + fprintf(stderr, "[EnrichWithConfig] path=%s\n", path); + const char8 *current = path; bool ok = true; while (ok) { @@ -801,29 +843,49 @@ void DebugService::EnrichWithConfig(const char8 *path, StreamString &json) { ok = false; } + // Normalise short direction names to both forms so we search consistently. + // Paths use "In"/"Out" (alias convention); CDBs may store either form. + bool found = false; + + // 1. Try exact match (bare name - rebuilt-from-registry CDBs use this) if (fullConfig.MoveRelative(part.Buffer())) { - // Found exact - } else { - bool found = false; + fprintf(stderr, "[EnrichWithConfig] nav exact '%s' OK\n", part.Buffer()); + found = true; + } + + // 2. Try +name (raw config-file CDBs prefix nodes with '+') + if (!found) { + StreamString prefixed; + prefixed.Printf("+%s", part.Buffer()); + if (fullConfig.MoveRelative(prefixed.Buffer())) { + fprintf(stderr, "[EnrichWithConfig] nav prefixed '%s' OK\n", prefixed.Buffer()); + found = true; + } + } + + // 3. Expand short direction aliases: In -> InputSignals / Out -> OutputSignals + if (!found) { if (part == "In") { if (fullConfig.MoveRelative("InputSignals")) { + fprintf(stderr, "[EnrichWithConfig] nav 'In'->InputSignals OK\n"); + found = true; + } else if (fullConfig.MoveRelative("+InputSignals")) { + fprintf(stderr, "[EnrichWithConfig] nav 'In'->+InputSignals OK\n"); found = true; } } else if (part == "Out") { if (fullConfig.MoveRelative("OutputSignals")) { found = true; + } else if (fullConfig.MoveRelative("+OutputSignals")) { + found = true; } } + } - if (!found) { - StreamString prefixed; - prefixed.Printf("+%s", part.Buffer()); - if (fullConfig.MoveRelative(prefixed.Buffer())) { - // Found prefixed - } else { - return; // Not found - } - } + if (!found) { + fprintf(stderr, "[EnrichWithConfig] FAILED at part '%s'\n", part.Buffer()); + fullConfig.MoveToRoot(); + return; } } @@ -886,6 +948,14 @@ void DebugService::JsonifyDatabase(ConfigurationDatabase &db, void DebugService::ServeConfig(BasicTCPSocket *client) { if (client == NULL_PTR(BasicTCPSocket *)) return; + + // If no manual config was injected (test case), rebuild from the live registry. + // In production, Initialise() only captures the DebugService subtree (to avoid + // corrupting the shared CDB cursor), so we always need to rebuild here. + if (!manualConfigSet) { + RebuildConfigFromRegistry(); + } + StreamString json; fullConfig.MoveToRoot(); JsonifyDatabase(fullConfig, json); @@ -930,6 +1000,7 @@ void DebugService::InfoNode(const char8 *path, BasicTCPSocket *client) { } EnrichWithConfig(path, json); } else { + StreamString enrichAlias; mutex.FastLock(); bool found = false; for (uint32 i = 0; i < aliases.Size(); i++) { @@ -941,13 +1012,21 @@ void DebugService::InfoNode(const char8 *path, BasicTCPSocket *client) { json.Printf("\"Name\": \"%s\", \"Class\": \"Signal\", \"Type\": " "\"%s\", \"ID\": %d", s->name.Buffer(), tname ? tname : "Unknown", s->internalID); - EnrichWithConfig(aliases[i].name.Buffer(), json); + enrichAlias = aliases[i].name; found = true; break; } } + if (!found) { + fprintf(stderr, "[InfoNode][%p] signal '%s' NOT found in %u aliases:\n", (void*)this, path, (uint32)aliases.Size()); + for (uint32 i = 0; i < aliases.Size(); i++) { + fprintf(stderr, " alias[%u] = '%s'\n", i, aliases[i].name.Buffer()); + } + } mutex.FastUnLock(); - if (!found) + if (found) + EnrichWithConfig(enrichAlias.Buffer(), json); + else json += "\"Error\": \"Object not found\""; } json += "}\nOK INFO\n"; diff --git a/Source/Components/Interfaces/DebugService/DebugService.h b/Source/Components/Interfaces/DebugService/DebugService.h index 5cc6172..12d1de4 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.h +++ b/Source/Components/Interfaces/DebugService/DebugService.h @@ -74,6 +74,7 @@ public: void ListNodes(const char8 *path, BasicTCPSocket *client); void ServeConfig(BasicTCPSocket *client); void SetFullConfig(ConfigurationDatabase &config); + void RebuildConfigFromRegistry(); struct MonitoredSignal { ReferenceT dataSource; @@ -150,6 +151,7 @@ private: BasicTCPSocket *activeClient; ConfigurationDatabase fullConfig; + bool manualConfigSet; static DebugService *instance; }; diff --git a/Test/Integration/IntegrationTests.cpp b/Test/Integration/IntegrationTests.cpp index 930e86f..6c6ddf2 100644 --- a/Test/Integration/IntegrationTests.cpp +++ b/Test/Integration/IntegrationTests.cpp @@ -80,7 +80,7 @@ int main() { Sleep::MSec(1000); printf("\n--- Test 5: Config & Metadata Enrichment ---\n"); - // TestConfigCommands(); // Skipping for now + TestConfigCommands(); Sleep::MSec(1000); printf("\n--- Test 6: TREE Command Enhancement ---\n"); diff --git a/run_debug_app.sh b/run_debug_app.sh index 03657ec..8137d69 100755 --- a/run_debug_app.sh +++ b/run_debug_app.sh @@ -11,13 +11,16 @@ fi # 2. Paths MARTE_EX="${MARTe2_DIR}/Build/${TARGET}/App/MARTeApp.ex" -DEBUG_LIB="$(pwd)/Build/libmarte_dev.so" +BUILD_DIR="$(pwd)/Build/${TARGET}" + +# Our plugin libraries +DEBUG_LIB="${BUILD_DIR}/Components/Interfaces/DebugService/libDebugService.so" +TCPLOGGER_LIB="${BUILD_DIR}/Components/Interfaces/TCPLogger/libTcpLogger.so" # Component library base search path COMPONENTS_BUILD_DIR="${MARTe2_Components_DIR}/Build/${TARGET}/Components" -# DYNAMICALLY FIND ALL COMPONENT DIRS -# MARTe2 Loader needs the specific directories containing .so files in LD_LIBRARY_PATH +# DYNAMICALLY FIND ALL COMPONENT DIRS and add to LD_LIBRARY_PATH ALL_COMPONENT_DIRS=$(find "$COMPONENTS_BUILD_DIR" -type d) for dir in $ALL_COMPONENT_DIRS; do if ls "$dir"/*.so >/dev/null 2>&1; then @@ -26,15 +29,39 @@ for dir in $ALL_COMPONENT_DIRS; do done # Ensure our build dir and core dir are included -export LD_LIBRARY_PATH="$(pwd)/Build:${MARTe2_DIR}/Build/${TARGET}/Core:${LD_LIBRARY_PATH}" +export LD_LIBRARY_PATH="${BUILD_DIR}/Components/Interfaces/DebugService:${BUILD_DIR}/Components/Interfaces/TCPLogger:${MARTe2_DIR}/Build/${TARGET}/Core:${LD_LIBRARY_PATH}" # 3. Cleanup echo "Cleaning up lingering processes..." pkill -9 MARTeApp.ex sleep 1 -# 4. Launch Application +# 4. Validate libraries exist +for lib in "$DEBUG_LIB" "$TCPLOGGER_LIB"; do + if [ ! -f "$lib" ]; then + echo "ERROR: Library not found: $lib" + echo "Run: make -f Makefile.gcc core" + exit 1 + fi +done + +# 5. Launch Application echo "Launching standard MARTeApp.ex with debug_test.cfg..." -# PRELOAD ensures our DebugService class is available to the registry early -export LD_PRELOAD="${DEBUG_LIB}" +# LD_PRELOAD ensures our classes are registered before the config is parsed. +# MARTeApp.ex does not use dlopen for plugins — preloading is the standard approach. +# All required component .so files are collected via the find loop into LD_PRELOAD too. +PRELOAD_LIBS="${DEBUG_LIB}:${TCPLOGGER_LIB}" + +# Collect any additional component .so files referenced by the config +for lib in \ + "${COMPONENTS_BUILD_DIR}/DataSources/RealTimeThreadSynchronisation/RealTimeThreadSynchronisation.so" \ + "${COMPONENTS_BUILD_DIR}/DataSources/LinuxTimer/LinuxTimer.so" \ + "${COMPONENTS_BUILD_DIR}/DataSources/LoggerDataSource/LoggerDataSource.so" \ + "${COMPONENTS_BUILD_DIR}/GAMs/IOGAM/IOGAM.so"; do + if [ -f "$lib" ]; then + PRELOAD_LIBS="${PRELOAD_LIBS}:${lib}" + fi +done + +export LD_PRELOAD="${PRELOAD_LIBS}" "$MARTE_EX" -f Test/Configurations/debug_test.cfg -l RealTimeLoader -s State1