Initial release

This commit is contained in:
Martino Ferrari
2026-05-29 13:29:59 +02:00
commit 617b5bd712
110 changed files with 29234 additions and 0 deletions
@@ -0,0 +1,590 @@
#ifndef DEBUGBROKERWRAPPER_H
#define DEBUGBROKERWRAPPER_H
#include "BrokerI.h"
#include "DataSourceI.h"
#include "DebugServiceI.h"
#include "FastPollingMutexSem.h"
#include "HighResolutionTimer.h"
#include "MemoryMapBroker.h"
#include "ObjectBuilder.h"
#include "ObjectRegistryDatabase.h"
#include "Threads.h"
// Original broker headers
#include "MemoryMapAsyncOutputBroker.h"
#include "MemoryMapAsyncTriggerOutputBroker.h"
#include "MemoryMapInputBroker.h"
#include "MemoryMapInterpolatedInputBroker.h"
#include "MemoryMapMultiBufferInputBroker.h"
#include "MemoryMapMultiBufferOutputBroker.h"
#include "MemoryMapOutputBroker.h"
#include "MemoryMapSynchronisedInputBroker.h"
#include "MemoryMapSynchronisedMultiBufferInputBroker.h"
#include "MemoryMapSynchronisedMultiBufferOutputBroker.h"
#include "MemoryMapSynchronisedOutputBroker.h"
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<ReferenceContainer *>(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.
*/
class DebugBrokerHelper {
public:
// Evaluate a break condition against the current live value of a signal.
// Returns true if the condition is met and execution should be paused.
// Only called when signal->breakOp != BREAK_OFF; reads memoryAddress directly.
static bool EvaluateBreak(const DebugSignalInfo *s) {
if (s->memoryAddress == NULL_PTR(void *)) return false;
float64 val = 0.0;
const TypeDescriptor &t = s->type;
if (t == Float64Bit) val = *static_cast<const float64 *>(s->memoryAddress);
else if (t == Float32Bit) val = *static_cast<const float32 *>(s->memoryAddress);
else if (t == UnsignedInteger32Bit) val = *static_cast<const uint32 *>(s->memoryAddress);
else if (t == SignedInteger32Bit) val = *static_cast<const int32 *>(s->memoryAddress);
else if (t == UnsignedInteger64Bit) val = static_cast<float64>(*static_cast<const uint64 *>(s->memoryAddress));
else if (t == SignedInteger64Bit) val = static_cast<float64>(*static_cast<const int64 *>(s->memoryAddress));
else if (t == UnsignedInteger16Bit) val = *static_cast<const uint16 *>(s->memoryAddress);
else if (t == SignedInteger16Bit) val = *static_cast<const int16 *>(s->memoryAddress);
else if (t == UnsignedInteger8Bit) val = *static_cast<const uint8 *>(s->memoryAddress);
else if (t == SignedInteger8Bit) val = *static_cast<const int8 *>(s->memoryAddress);
else return false; // unsupported type — skip
const float64 thr = s->breakThreshold;
switch (s->breakOp) {
case BREAK_GT: return val > thr;
case BREAK_LT: return val < thr;
case BREAK_EQ: return val == thr;
case BREAK_GEQ: return val >= thr;
case BREAK_LEQ: return val <= thr;
case BREAK_NEQ: return val != thr;
default: return false;
}
}
// Spin-wait point for output brokers — called from Execute() AFTER Process().
// Spins while paused, then consumes one step counter tick (if stepping).
// Input brokers must NOT call this — they complete normally to avoid blocking
// cross-thread EventSem posts (RealTimeThreadSynchBroker would time out).
static void OutputPauseAndStep(DebugServiceI *service, const char8 *gamName) {
if (service == NULL_PTR(DebugServiceI *)) return;
// Fast path: nothing to do when neither paused nor stepping.
// Avoids Threads::Name() lookup (and its mutex) on every 1000 Hz cycle.
if (!service->IsPaused() && !service->IsStepPending()) return;
// Wait if already paused (manual PAUSE or breakpoint from a previous cycle)
while (service->IsPaused()) Sleep::MSec(10);
// Pass the OS thread name so per-thread step filtering works.
const char8 *tName = Threads::Name(Threads::Id());
service->ConsumeStepIfNeeded(gamName, tName);
while (service->IsPaused()) Sleep::MSec(10);
}
static void Process(DebugServiceI *service,
DebugSignalInfo **signalInfoPointers,
Vector<uint32> &activeIndices, Vector<uint32> &activeSizes,
FastPollingMutexSem &activeMutex,
volatile bool *anyBreakFlag,
Vector<uint32> *breakIndices) {
if (service == NULL_PTR(DebugServiceI *))
return;
// NOTE: No spin here. Spinning for paused state is handled in Execute() of
// OUTPUT brokers only (see OutputPauseAndStep). Input brokers must not block
// because that prevents cross-thread EventSem posts from completing.
activeMutex.FastLock();
uint32 n = activeIndices.GetNumberOfElements();
if (n > 0 && signalInfoPointers != NULL_PTR(DebugSignalInfo **)) {
// Capture timestamp ONCE per broker cycle for lowest impact
uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1.0e9);
for (uint32 i = 0; i < n; i++) {
uint32 idx = activeIndices[i];
uint32 size = activeSizes[i];
DebugSignalInfo *s = signalInfoPointers[idx];
if (s != NULL_PTR(DebugSignalInfo *)) {
service->ProcessSignal(s, size, ts);
}
}
}
// FIX #3: copy break indices under lock into a local stack array, then
// release activeMutex BEFORE evaluating. EvaluateBreak reads signal
// memory which can stall (cache miss); holding activeMutex during that
// stall would block UpdateBrokersBreakStatus() in the Server thread and
// cause unnecessary priority inversion on the RT path.
bool shouldCheckBreak = (*anyBreakFlag && !service->IsPaused() &&
breakIndices != NULL_PTR(Vector<uint32> *) &&
signalInfoPointers != NULL_PTR(DebugSignalInfo **));
static const uint32 MAX_BREAK_INDICES = 64u;
uint32 localBreakIdx[MAX_BREAK_INDICES];
uint32 nb = 0u;
if (shouldCheckBreak) {
nb = breakIndices->GetNumberOfElements();
if (nb > MAX_BREAK_INDICES) nb = MAX_BREAK_INDICES;
for (uint32 i = 0; i < nb; i++) {
localBreakIdx[i] = (*breakIndices)[i];
}
}
activeMutex.FastUnLock();
// Evaluate break conditions outside the lock — safe because
// EvaluateBreak only reads signalInfoPointers[idx]->memoryAddress,
// which is RT data-bus memory and is never freed during the RT cycle.
if (shouldCheckBreak && nb > 0u) {
for (uint32 i = 0; i < nb; i++) {
uint32 idx = localBreakIdx[i];
DebugSignalInfo *s = signalInfoPointers[idx];
if (s != NULL_PTR(DebugSignalInfo *) && s->breakOp != BREAK_OFF &&
EvaluateBreak(s)) {
service->SetPaused(true);
break;
}
}
}
}
// Pass numCopies explicitly so we can mock it
static void
InitSignals(BrokerI *broker, DataSourceI &dataSourceIn,
DebugServiceI *&service, DebugSignalInfo **&signalInfoPointers,
uint32 numCopies, MemoryMapBrokerCopyTableEntry *copyTable,
const char8 *functionName, SignalDirection direction,
volatile bool *anyActiveFlag, Vector<uint32> *activeIndices,
Vector<uint32> *activeSizes, FastPollingMutexSem *activeMutex,
volatile bool *anyBreakFlag, Vector<uint32> *breakIndices) {
if (numCopies > 0) {
signalInfoPointers = new DebugSignalInfo *[numCopies];
for (uint32 i = 0; i < numCopies; i++)
signalInfoPointers[i] = NULL_PTR(DebugSignalInfo *);
}
// Use the singleton registered by DebugService::Initialise() — no ORD
// search on the Init path, and no dependency on a concrete type.
service = DebugServiceI::GetInstance();
if (service && (copyTable != NULL_PTR(MemoryMapBrokerCopyTableEntry *))) {
StreamString dsPath;
DebugServiceI::GetFullObjectName(dataSourceIn, dsPath);
fprintf(stderr, ">> %s broker for %s [%d]\n",
direction == InputSignals ? "Input" : "Output", dsPath.Buffer(),
numCopies);
MemoryMapBroker *mmb = dynamic_cast<MemoryMapBroker *>(broker);
if (mmb == NULL_PTR(MemoryMapBroker *)) {
fprintf(stderr, ">> Impossible to get broker pointer!!\n");
}
for (uint32 i = 0; i < numCopies; i++) {
void *addr = copyTable[i].dataSourcePointer;
TypeDescriptor type = copyTable[i].type;
uint32 dsIdx = i;
if (mmb != NULL_PTR(MemoryMapBroker *)) {
dsIdx = mmb->GetDSCopySignalIndex(i);
}
StreamString signalName;
if (!dataSourceIn.GetSignalName(dsIdx, signalName))
signalName = "Unknown";
fprintf(stderr, ">> registering %s.%s [%p]\n", dsPath.Buffer(),
signalName.Buffer(), mmb);
uint8 dims = 0;
uint32 elems = 1;
(void)dataSourceIn.GetSignalNumberOfDimensions(dsIdx, dims);
(void)dataSourceIn.GetSignalNumberOfElements(dsIdx, elems);
// Register canonical name
StreamString dsFullName;
dsFullName.Printf("%s.%s", dsPath.Buffer(), signalName.Buffer());
service->RegisterSignal(addr, type, dsFullName.Buffer(), dims, elems);
// Register alias
if (functionName != NULL_PTR(const char8 *)) {
StreamString gamFullName;
const char8 *dirStr =
(direction == InputSignals) ? "InputSignals" : "OutputSignals";
const char8 *dirStrShort = (direction == InputSignals) ? "In" : "Out";
// 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 =
FindByNameRecursive(ObjectRegistryDatabase::Instance(),
functionName);
fprintf(stderr, ">> GAM lookup '%s': %s\n", functionName,
gamRef.IsValid() ? "FOUND" : "NOT FOUND");
if (gamRef.IsValid()) {
StreamString absGamPath;
DebugServiceI::GetFullObjectName(*(gamRef.operator->()), absGamPath);
// Register short path (In/Out) for GUI compatibility
gamFullName.Printf("%s.%s.%s", absGamPath.Buffer(), dirStrShort,
signalName.Buffer());
signalInfoPointers[i] =
service->RegisterSignal(addr, type, gamFullName.Buffer(), dims, elems);
} else {
// Fallback to short form
gamFullName.Printf("%s.%s.%s", functionName, dirStrShort,
signalName.Buffer());
signalInfoPointers[i] =
service->RegisterSignal(addr, type, gamFullName.Buffer(), dims, elems);
}
} else {
signalInfoPointers[i] =
service->RegisterSignal(addr, type, dsFullName.Buffer(), dims, elems);
}
}
// Register broker in DebugService for optimized control
bool isOutputBroker = (direction == OutputSignals);
service->RegisterBroker(signalInfoPointers, numCopies, mmb, anyActiveFlag,
activeIndices, activeSizes, activeMutex,
anyBreakFlag, breakIndices,
(functionName != NULL_PTR(const char8 *)) ? functionName : dsPath.Buffer(),
isOutputBroker);
}
}
};
/**
* @brief Template class to instrument any MARTe2 Broker.
*/
template <typename BaseClass> class DebugBrokerWrapper : public BaseClass {
public:
DebugBrokerWrapper() : BaseClass() {
service = NULL_PTR(DebugServiceI *);
signalInfoPointers = NULL_PTR(DebugSignalInfo **);
numSignals = 0;
anyActive = false;
anyBreakActive = false;
isOutput = false;
gamName[0] = '\0';
}
virtual ~DebugBrokerWrapper() {
if (signalInfoPointers)
delete[] signalInfoPointers;
}
virtual bool Execute() {
bool ret = BaseClass::Execute();
if (ret && (anyActive || anyBreakActive)) {
DebugBrokerHelper::Process(service, signalInfoPointers, activeIndices,
activeSizes, activeMutex,
&anyBreakActive, &breakIndices);
}
// Output brokers are the safe pause point: base Execute has already
// committed data / posted any cross-thread EventSems.
if (ret && isOutput) {
DebugBrokerHelper::OutputPauseAndStep(service, gamName);
}
return ret;
}
virtual bool Init(SignalDirection direction, DataSourceI &ds,
const char8 *const name, void *gamMem) {
bool ret = BaseClass::Init(direction, ds, name, gamMem);
fprintf(stderr, ">> INIT BROKER %s %s\n", name,
direction == InputSignals ? "In" : "Out");
if (ret) {
numSignals = this->GetNumberOfCopies();
isOutput = (direction == OutputSignals);
StringHelper::CopyN(gamName, name, 255u);
DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers,
numSignals, this->copyTable, name,
direction, &anyActive, &activeIndices,
&activeSizes, &activeMutex,
&anyBreakActive, &breakIndices);
}
return ret;
}
virtual bool Init(SignalDirection direction, DataSourceI &ds,
const char8 *const name, void *gamMem, const bool optim) {
bool ret = BaseClass::Init(direction, ds, name, gamMem, false);
fprintf(stderr, ">> INIT optimized BROKER %s %s\n", name,
direction == InputSignals ? "In" : "Out");
if (ret) {
numSignals = this->GetNumberOfCopies();
isOutput = (direction == OutputSignals);
StringHelper::CopyN(gamName, name, 255u);
DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers,
numSignals, this->copyTable, name,
direction, &anyActive, &activeIndices,
&activeSizes, &activeMutex,
&anyBreakActive, &breakIndices);
}
return ret;
}
DebugServiceI *service;
DebugSignalInfo **signalInfoPointers;
uint32 numSignals;
volatile bool anyActive;
volatile bool anyBreakActive;
bool isOutput;
char8 gamName[256];
Vector<uint32> activeIndices;
Vector<uint32> activeSizes;
Vector<uint32> breakIndices;
FastPollingMutexSem activeMutex;
};
template <typename BaseClass>
class DebugBrokerWrapperNoOptim : public BaseClass {
public:
DebugBrokerWrapperNoOptim() : BaseClass() {
service = NULL_PTR(DebugServiceI *);
signalInfoPointers = NULL_PTR(DebugSignalInfo **);
numSignals = 0;
anyActive = false;
anyBreakActive = false;
isOutput = false;
gamName[0] = '\0';
}
virtual ~DebugBrokerWrapperNoOptim() {
if (signalInfoPointers)
delete[] signalInfoPointers;
}
virtual bool Execute() {
bool ret = BaseClass::Execute();
if (ret && (anyActive || anyBreakActive)) {
DebugBrokerHelper::Process(service, signalInfoPointers, activeIndices,
activeSizes, activeMutex,
&anyBreakActive, &breakIndices);
}
if (ret && isOutput) {
DebugBrokerHelper::OutputPauseAndStep(service, gamName);
}
return ret;
}
virtual bool Init(SignalDirection direction, DataSourceI &ds,
const char8 *const name, void *gamMem) {
bool ret = BaseClass::Init(direction, ds, name, gamMem);
if (ret) {
numSignals = this->GetNumberOfCopies();
isOutput = (direction == OutputSignals);
StringHelper::CopyN(gamName, name, 255u);
DebugBrokerHelper::InitSignals(this, ds, service, signalInfoPointers,
numSignals, this->copyTable, name,
direction, &anyActive, &activeIndices,
&activeSizes, &activeMutex,
&anyBreakActive, &breakIndices);
}
return ret;
}
DebugServiceI *service;
DebugSignalInfo **signalInfoPointers;
uint32 numSignals;
volatile bool anyActive;
volatile bool anyBreakActive;
bool isOutput;
char8 gamName[256];
Vector<uint32> activeIndices;
Vector<uint32> activeSizes;
Vector<uint32> breakIndices;
FastPollingMutexSem activeMutex;
};
class DebugMemoryMapAsyncOutputBroker : public MemoryMapAsyncOutputBroker {
public:
DebugMemoryMapAsyncOutputBroker() : MemoryMapAsyncOutputBroker() {
service = NULL_PTR(DebugServiceI *);
signalInfoPointers = NULL_PTR(DebugSignalInfo **);
numSignals = 0;
anyActive = false;
anyBreakActive = false;
gamName[0] = '\0';
}
virtual ~DebugMemoryMapAsyncOutputBroker() {
if (signalInfoPointers)
delete[] signalInfoPointers;
}
virtual bool Execute() {
bool ret = MemoryMapAsyncOutputBroker::Execute();
if (ret && (anyActive || anyBreakActive)) {
DebugBrokerHelper::Process(service, signalInfoPointers, activeIndices,
activeSizes, activeMutex,
&anyBreakActive, &breakIndices);
}
// Async output brokers are always output direction
if (ret) {
DebugBrokerHelper::OutputPauseAndStep(service, gamName);
}
return ret;
}
virtual bool InitWithBufferParameters(const SignalDirection direction,
DataSourceI &dataSourceIn,
const char8 *const functionName,
void *const gamMemoryAddress,
const uint32 numberOfBuffersIn,
const ProcessorType &cpuMaskIn,
const uint32 stackSizeIn) {
bool ret = MemoryMapAsyncOutputBroker::InitWithBufferParameters(
direction, dataSourceIn, functionName, gamMemoryAddress,
numberOfBuffersIn, cpuMaskIn, stackSizeIn);
if (ret) {
numSignals = this->GetNumberOfCopies();
StringHelper::CopyN(gamName, (functionName != NULL_PTR(const char8 *)) ? functionName : "", 255u);
DebugBrokerHelper::InitSignals(
this, dataSourceIn, service, signalInfoPointers, numSignals,
this->copyTable, functionName, direction, &anyActive, &activeIndices,
&activeSizes, &activeMutex, &anyBreakActive, &breakIndices);
}
return ret;
}
DebugServiceI *service;
DebugSignalInfo **signalInfoPointers;
uint32 numSignals;
volatile bool anyActive;
volatile bool anyBreakActive;
char8 gamName[256];
Vector<uint32> activeIndices;
Vector<uint32> activeSizes;
Vector<uint32> breakIndices;
FastPollingMutexSem activeMutex;
};
class DebugMemoryMapAsyncTriggerOutputBroker
: public MemoryMapAsyncTriggerOutputBroker {
public:
DebugMemoryMapAsyncTriggerOutputBroker()
: MemoryMapAsyncTriggerOutputBroker() {
service = NULL_PTR(DebugServiceI *);
signalInfoPointers = NULL_PTR(DebugSignalInfo **);
numSignals = 0;
anyActive = false;
anyBreakActive = false;
gamName[0] = '\0';
}
virtual ~DebugMemoryMapAsyncTriggerOutputBroker() {
if (signalInfoPointers)
delete[] signalInfoPointers;
}
virtual bool Execute() {
bool ret = MemoryMapAsyncTriggerOutputBroker::Execute();
if (ret && (anyActive || anyBreakActive)) {
DebugBrokerHelper::Process(service, signalInfoPointers, activeIndices,
activeSizes, activeMutex,
&anyBreakActive, &breakIndices);
}
if (ret) {
DebugBrokerHelper::OutputPauseAndStep(service, gamName);
}
return ret;
}
virtual bool InitWithTriggerParameters(
const SignalDirection direction, DataSourceI &dataSourceIn,
const char8 *const functionName, void *const gamMemoryAddress,
const uint32 numberOfBuffersIn, const uint32 preTriggerBuffersIn,
const uint32 postTriggerBuffersIn, const ProcessorType &cpuMaskIn,
const uint32 stackSizeIn) {
bool ret = MemoryMapAsyncTriggerOutputBroker::InitWithTriggerParameters(
direction, dataSourceIn, functionName, gamMemoryAddress,
numberOfBuffersIn, preTriggerBuffersIn, postTriggerBuffersIn, cpuMaskIn,
stackSizeIn);
if (ret) {
numSignals = this->GetNumberOfCopies();
StringHelper::CopyN(gamName, (functionName != NULL_PTR(const char8 *)) ? functionName : "", 255u);
DebugBrokerHelper::InitSignals(
this, dataSourceIn, service, signalInfoPointers, numSignals,
this->copyTable, functionName, direction, &anyActive, &activeIndices,
&activeSizes, &activeMutex, &anyBreakActive, &breakIndices);
}
return ret;
}
DebugServiceI *service;
DebugSignalInfo **signalInfoPointers;
uint32 numSignals;
volatile bool anyActive;
volatile bool anyBreakActive;
char8 gamName[256];
Vector<uint32> activeIndices;
Vector<uint32> activeSizes;
Vector<uint32> breakIndices;
FastPollingMutexSem activeMutex;
};
template <typename T> class DebugBrokerBuilder : public ObjectBuilder {
public:
virtual Object *Build(HeapI *const heap) const { return new (heap) T(); }
};
typedef DebugBrokerWrapper<MemoryMapInputBroker> DebugMemoryMapInputBroker;
// LCOV_EXCL_START
typedef DebugBrokerWrapper<MemoryMapOutputBroker> DebugMemoryMapOutputBroker;
typedef DebugBrokerWrapper<MemoryMapSynchronisedInputBroker>
DebugMemoryMapSynchronisedInputBroker;
typedef DebugBrokerWrapper<MemoryMapSynchronisedOutputBroker>
DebugMemoryMapSynchronisedOutputBroker;
typedef DebugBrokerWrapperNoOptim<MemoryMapInterpolatedInputBroker>
DebugMemoryMapInterpolatedInputBroker;
typedef DebugBrokerWrapper<MemoryMapMultiBufferInputBroker>
DebugMemoryMapMultiBufferInputBroker;
typedef DebugBrokerWrapper<MemoryMapMultiBufferOutputBroker>
DebugMemoryMapMultiBufferOutputBroker;
typedef DebugBrokerWrapper<MemoryMapSynchronisedMultiBufferInputBroker>
DebugMemoryMapSynchronisedMultiBufferInputBroker;
typedef DebugBrokerWrapper<MemoryMapSynchronisedMultiBufferOutputBroker>
DebugMemoryMapSynchronisedMultiBufferOutputBroker;
// LCOV_EXCL_STOP
typedef DebugBrokerBuilder<DebugMemoryMapInputBroker>
DebugMemoryMapInputBrokerBuilder;
// LCOV_EXCL_START
typedef DebugBrokerBuilder<DebugMemoryMapOutputBroker>
DebugMemoryMapOutputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapSynchronisedInputBroker>
DebugMemoryMapSynchronisedInputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapSynchronisedOutputBroker>
DebugMemoryMapSynchronisedOutputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapInterpolatedInputBroker>
DebugMemoryMapInterpolatedInputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapMultiBufferInputBroker>
DebugMemoryMapMultiBufferInputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapMultiBufferOutputBroker>
DebugMemoryMapMultiBufferOutputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapSynchronisedMultiBufferInputBroker>
DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapSynchronisedMultiBufferOutputBroker>
DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapAsyncOutputBroker>
DebugMemoryMapAsyncOutputBrokerBuilder;
typedef DebugBrokerBuilder<DebugMemoryMapAsyncTriggerOutputBroker>
DebugMemoryMapAsyncTriggerOutputBrokerBuilder;
// LCOV_EXCL_STOP
} // namespace MARTe
#endif
@@ -0,0 +1,188 @@
#ifndef DEBUGCORE_H
#define DEBUGCORE_H
#include "CompilerTypes.h"
#include "TypeDescriptor.h"
#include "StreamString.h"
#include <string.h>
namespace MARTe {
// Break condition operators stored in DebugSignalInfo::breakOp
enum BreakOp {
BREAK_OFF = 0,
BREAK_GT = 1, // >
BREAK_LT = 2, // <
BREAK_EQ = 3, // ==
BREAK_GEQ = 4, // >=
BREAK_LEQ = 5, // <=
BREAK_NEQ = 6 // !=
};
struct DebugSignalInfo {
void* memoryAddress;
TypeDescriptor type;
StreamString name;
uint8 numberOfDimensions;
uint32 numberOfElements;
volatile bool isTracing;
volatile bool isForcing;
uint8 forcedValue[1024];
uint8 forcedMask[32]; // bit e set → element e is forced; supports up to 256 elements
uint32 internalID;
volatile uint32 decimationFactor;
volatile uint32 decimationCounter;
// Conditional break fields (zero-cost when breakOp == BREAK_OFF)
volatile uint8 breakOp; // BreakOp enum value
float64 breakThreshold; // comparison threshold
};
#pragma pack(push, 1)
struct TraceHeader {
uint32 magic; // 0xDA7A57AD
uint32 seq; // Sequence number
uint64 timestamp; // HighRes timestamp
uint32 count; // Number of samples in payload
};
#pragma pack(pop)
/**
* @brief Ring buffer for high-frequency signal tracing.
* @details New format per sample: [ID:4][Timestamp:8][Size:4][Data:N]
*/
class TraceRingBuffer {
public:
TraceRingBuffer() {
bufferSize = 0;
buffer = NULL_PTR(uint8*);
readIndex = 0;
writeIndex = 0;
}
~TraceRingBuffer() {
if (buffer != NULL_PTR(uint8*)) {
delete[] buffer;
}
}
bool Init(uint32 size) {
if (buffer != NULL_PTR(uint8*)) {
delete[] buffer;
}
bufferSize = size;
buffer = new uint8[bufferSize];
readIndex = 0;
writeIndex = 0;
return (buffer != NULL_PTR(uint8*));
}
bool Push(uint32 signalID, uint64 timestamp, void* data, uint32 size) {
uint32 packetSize = 4 + 8 + 4 + size; // ID + TS + Size + Data
uint32 read = readIndex;
uint32 write = writeIndex;
uint32 available = 0;
if (read <= write) {
available = bufferSize - (write - read) - 1;
} else {
available = read - write - 1;
}
if (available < packetSize) return false;
uint32 tempWrite = write;
WriteToBuffer(&tempWrite, &signalID, 4);
WriteToBuffer(&tempWrite, &timestamp, 8);
WriteToBuffer(&tempWrite, &size, 4);
WriteToBuffer(&tempWrite, data, size);
writeIndex = tempWrite;
return true;
}
bool Pop(uint32 &signalID, uint64 &timestamp, void* dataBuffer, uint32 &size, uint32 maxSize) {
uint32 read = readIndex;
uint32 write = writeIndex;
if (read == write) return false;
uint32 tempRead = read;
uint32 tempId = 0;
uint64 tempTs = 0;
uint32 tempSize = 0;
ReadFromBuffer(&tempRead, &tempId, 4);
ReadFromBuffer(&tempRead, &tempTs, 8);
ReadFromBuffer(&tempRead, &tempSize, 4);
if (tempSize > maxSize) {
// FIX #5: Skip only the current entry rather than discarding the
// entire ring buffer. tempRead is already past the 16-byte header;
// advancing by tempSize lands at the start of the next entry.
//
// Safety fallback: if tempSize >= bufferSize the stored size field
// is corrupt (it can never be that large). In that case we cannot
// locate the next entry safely, so fall back to discarding everything
// to avoid reading garbage as sample headers on future Pop() calls.
if (tempSize >= bufferSize) {
readIndex = write; // corrupt ring — discard all
} else {
readIndex = (tempRead + tempSize) % bufferSize;
}
return false;
}
ReadFromBuffer(&tempRead, dataBuffer, tempSize);
signalID = tempId;
timestamp = tempTs;
size = tempSize;
readIndex = tempRead;
return true;
}
uint32 Count() {
uint32 read = readIndex;
uint32 write = writeIndex;
if (write >= read) return write - read;
return bufferSize - (read - write);
}
private:
void WriteToBuffer(uint32 *idx, void* src, uint32 count) {
uint32 current = *idx;
uint32 spaceToEnd = bufferSize - current;
if (count <= spaceToEnd) {
memcpy(&buffer[current], src, count);
*idx = (current + count) % bufferSize;
} else {
memcpy(&buffer[current], src, spaceToEnd);
uint32 remaining = count - spaceToEnd;
memcpy(&buffer[0], (uint8*)src + spaceToEnd, remaining);
*idx = remaining;
}
}
void ReadFromBuffer(uint32 *idx, void* dst, uint32 count) {
uint32 current = *idx;
uint32 spaceToEnd = bufferSize - current;
if (count <= spaceToEnd) {
memcpy(dst, &buffer[current], count);
*idx = (current + count) % bufferSize;
} else {
memcpy(dst, &buffer[current], spaceToEnd);
uint32 remaining = count - spaceToEnd;
memcpy((uint8*)dst + spaceToEnd, &buffer[0], remaining);
*idx = remaining;
}
}
volatile uint32 readIndex;
volatile uint32 writeIndex;
uint32 bufferSize;
uint8 *buffer;
};
}
#endif
@@ -0,0 +1,618 @@
#include "BasicTCPSocket.h"
#include "ConfigurationDatabase.h"
#include "DebugService.h"
#include "GlobalObjectsDatabase.h"
#include "HighResolutionTimer.h"
#include "LoggerService.h"
#include "Message.h"
#include "ObjectRegistryDatabase.h"
#include "ReferenceT.h"
#include "Sleep.h"
#include "StreamString.h"
#include "StringHelper.h"
#include "Threads.h"
#include "TimeoutType.h"
#include "UDPSProtocol.h"
namespace MARTe {
CLASS_REGISTER(DebugService, "1.0")
// C++98 ODR definitions for static constants
const uint32 DebugService::CMD_RATE_LIMIT;
const uint32 DebugService::CLIENT_IDLE_TIMEOUT_MS;
const uint32 DebugService::INPUT_BUFFER_MAX;
// ---------------------------------------------------------------------------
// Constructor / Destructor
// ---------------------------------------------------------------------------
DebugService::DebugService()
: DebugServiceBase(),
EmbeddedServiceMethodBinderI(),
binderServer(this, ServiceBinder::ServerType),
binderStreamer(this, ServiceBinder::StreamerType),
threadService(binderServer),
streamerService(binderStreamer) {
controlPort = 0u;
streamPort = 8081u;
logPort = 8082u;
streamIP = "127.0.0.1";
isServer = false;
suppressTimeoutLogs = true;
activeClient = NULL_PTR(BasicTCPSocket *);
cmdCountInWindow = 0u;
cmdWindowStartMs = 0u;
lastDataTimeMs = 0u;
inputBuffer = "";
// UDPS members
udpsNumSlots = 0u;
udpsDataPayload = NULL_PTR(uint8 *);
udpsDataPayloadSize = 0u;
udpsPacketCounter = 0u;
udpsConfigPending = false;
}
DebugService::~DebugService() {
if (DebugServiceI::GetInstance() == this) {
DebugServiceI::SetInstance(NULL_PTR(DebugServiceI *));
}
threadService.Stop();
streamerService.Stop();
tcpServer.Close();
udpSocket.Close();
if (activeClient != NULL_PTR(BasicTCPSocket *)) {
activeClient->Close();
delete activeClient;
activeClient = NULL_PTR(BasicTCPSocket *);
}
if (udpsDataPayload != NULL_PTR(uint8 *)) {
delete[] udpsDataPayload;
udpsDataPayload = NULL_PTR(uint8 *);
}
// signals owned by DebugServiceBase destructor
}
// ---------------------------------------------------------------------------
// Initialise
// ---------------------------------------------------------------------------
bool DebugService::Initialise(StructuredDataI &data) {
if (!ReferenceContainer::Initialise(data)) return false;
uint32 port = 0u;
if (data.Read("ControlPort", port)) {
controlPort = (uint16)port;
} else {
(void)data.Read("TcpPort", port);
controlPort = (uint16)port;
}
if (controlPort > 0u) {
isServer = true;
DebugServiceI::SetInstance(this);
}
port = 8081u;
if (data.Read("StreamPort", port)) {
streamPort = (uint16)port;
} else {
(void)data.Read("UdpPort", port);
streamPort = (uint16)port;
}
port = 8082u;
if (data.Read("LogPort", port)) {
logPort = (uint16)port;
} else {
(void)data.Read("TcpLogPort", port);
logPort = (uint16)port;
}
StreamString tempIP;
if (data.Read("StreamIP", tempIP)) {
streamIP = tempIP;
} else {
streamIP = "127.0.0.1";
}
uint32 suppress = 1u;
if (data.Read("SuppressTimeoutLogs", suppress)) {
suppressTimeoutLogs = (suppress == 1u);
}
// Capture only the local subtree — do NOT call MoveToRoot() on the shared CDB.
(void)data.Copy(fullConfig);
if (isServer) {
if (!traceBuffer.Init(8 * 1024 * 1024)) return false;
PatchRegistry();
ConfigurationDatabase threadData;
threadData.Write("Timeout", (uint32)1000);
threadService.Initialise(threadData);
streamerService.Initialise(threadData);
if (!tcpServer.Open()) return false;
if (!tcpServer.Listen(controlPort)) return false;
if (!udpSocket.Open()) return false;
// Note: do NOT bind udpSocket to streamPort here. The Go client
// must own that port to receive streamed data. The socket sends
// via an ephemeral source port, which is fine for UDP.
if (threadService.Start() != ErrorManagement::NoError) return false;
if (streamerService.Start() != ErrorManagement::NoError) return false;
// Send initial (empty) CONFIG so clients know the schema version
SendUDPSConfig();
}
return true;
}
// ---------------------------------------------------------------------------
// Transport config hook (called by RebuildConfigFromRegistry in base)
// ---------------------------------------------------------------------------
void DebugService::RebuildTransportConfig() {
const char8 *myName = GetName();
if (myName != NULL_PTR(const char8 *)) {
if (fullConfig.MoveRelative(myName)) {
(void)fullConfig.Write("ControlPort", static_cast<uint32>(controlPort));
(void)fullConfig.Write("UdpPort", static_cast<uint32>(streamPort));
(void)fullConfig.Write("LogPort", static_cast<uint32>(logPort));
if (streamIP.Size() > 0u)
(void)fullConfig.Write("StreamIP", streamIP.Buffer());
(void)fullConfig.MoveToAncestor(1u);
}
}
}
// ---------------------------------------------------------------------------
// SERVICE_INFO hook
// ---------------------------------------------------------------------------
void DebugService::GetServiceInfo(StreamString &out) {
out.Printf("OK SERVICE_INFO TCP_CTRL:%u UDP_STREAM:%u TCP_LOG:%u STATE:%s\n",
controlPort, streamPort, logPort,
isPaused ? "PAUSED" : "RUNNING");
}
// ---------------------------------------------------------------------------
// Execute / HandleMessage (framework boilerplate)
// ---------------------------------------------------------------------------
ErrorManagement::ErrorType DebugService::Execute(ExecutionInfo &info) {
(void)info;
return ErrorManagement::FatalError;
}
ErrorManagement::ErrorType DebugService::HandleMessage(ReferenceT<Message> &data) {
(void)data;
return ErrorManagement::NoError;
}
// ---------------------------------------------------------------------------
// TraceSignal override — sets config-pending flag after base handling
// ---------------------------------------------------------------------------
uint32 DebugService::TraceSignal(const char8 *name, bool enable, uint32 decimation) {
uint32 ret = DebugServiceBase::TraceSignal(name, enable, decimation);
udpsConfigPending = true;
return ret;
}
// ---------------------------------------------------------------------------
// InjectTcpLoggerIfNeeded
// ---------------------------------------------------------------------------
void DebugService::InjectTcpLoggerIfNeeded() {
if (logPort == 0u) return;
Reference existing = ObjectRegistryDatabase::Instance()->Find("LoggerService");
if (existing.IsValid()) {
ReferenceContainer *rc =
dynamic_cast<ReferenceContainer *>(existing.operator->());
if (rc != NULL_PTR(ReferenceContainer *)) {
for (uint32 i = 0u; i < rc->Size(); i++) {
Reference child = rc->Get(i);
// Check by class name to avoid a direct symbol dependency on TcpLogger.so
if (child.IsValid()) {
const ClassProperties *cp = child->GetClassProperties();
if (cp != NULL_PTR(const ClassProperties *)) {
if (StringHelper::Compare(cp->GetName(), "TcpLogger") == 0) {
return; // already has a TcpLogger
}
}
}
}
}
}
ConfigurationDatabase lsCdb;
(void)lsCdb.Write("Class", "LoggerService");
uint32 cpus = 1u;
(void)lsCdb.Write("CPUs", cpus);
if (lsCdb.CreateRelative("+DebugConsumer")) {
(void)lsCdb.Write("Class", "TcpLogger");
uint32 p = static_cast<uint32>(logPort);
(void)lsCdb.Write("Port", p);
(void)lsCdb.MoveToAncestor(1u);
}
(void)lsCdb.MoveToRoot();
ReferenceT<LoggerService> ls(
"LoggerService", GlobalObjectsDatabase::Instance()->GetStandardHeap());
if (!ls.IsValid()) return;
ls->SetName("LoggerService");
if (!ls->Initialise(lsCdb)) return;
(void)ObjectRegistryDatabase::Instance()->Insert(ls);
}
// ---------------------------------------------------------------------------
// Server thread
// ---------------------------------------------------------------------------
ErrorManagement::ErrorType DebugService::Server(ExecutionInfo &info) {
if (info.GetStage() == ExecutionInfo::TerminationStage)
return ErrorManagement::NoError;
if (info.GetStage() == ExecutionInfo::StartupStage) {
serverThreadId = Threads::Id();
Sleep::MSec(500u);
InjectTcpLoggerIfNeeded();
return ErrorManagement::NoError;
}
uint64 nowMs = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000.0);
if (activeClient == NULL_PTR(BasicTCPSocket *)) {
BasicTCPSocket *newClient = tcpServer.WaitConnection(TimeoutType(100));
if (newClient != NULL_PTR(BasicTCPSocket *)) {
clientMutex.FastLock();
activeClient = newClient;
clientMutex.FastUnLock();
cmdCountInWindow = 0u;
cmdWindowStartMs = nowMs;
lastDataTimeMs = nowMs;
}
} else {
if (nowMs - lastDataTimeMs > CLIENT_IDLE_TIMEOUT_MS) {
REPORT_ERROR_STATIC(ErrorManagement::Warning,
"Server: TCP client idle for >%u ms — closing connection.",
CLIENT_IDLE_TIMEOUT_MS);
inputBuffer = "";
clientMutex.FastLock();
activeClient->Close();
delete activeClient;
activeClient = NULL_PTR(BasicTCPSocket *);
clientMutex.FastUnLock();
cmdCountInWindow = 0u;
} else if (!activeClient->IsConnected()) {
inputBuffer = "";
clientMutex.FastLock();
activeClient->Close();
delete activeClient;
activeClient = NULL_PTR(BasicTCPSocket *);
clientMutex.FastUnLock();
} else {
char buffer[1024];
uint32 size = 1024u;
if (activeClient->Read(buffer, size)) {
if (size > 0u) {
lastDataTimeMs = nowMs;
if (nowMs - cmdWindowStartMs >= 1000u) {
cmdWindowStartMs = nowMs;
cmdCountInWindow = 0u;
}
if (inputBuffer.Size() + (uint32)size > INPUT_BUFFER_MAX) {
REPORT_ERROR_STATIC(ErrorManagement::Warning,
"Server: input buffer overflow (>%u bytes without newline) "
"— disconnecting.", INPUT_BUFFER_MAX);
inputBuffer = "";
clientMutex.FastLock();
activeClient->Close();
delete activeClient;
activeClient = NULL_PTR(BasicTCPSocket *);
clientMutex.FastUnLock();
cmdCountInWindow = 0u;
} else {
(void)inputBuffer.Seek(inputBuffer.Size());
uint32 writeSize = (uint32)size;
inputBuffer.Write(buffer, writeSize);
const char8 *raw = inputBuffer.Buffer();
uint32 total = (uint32)inputBuffer.Size();
uint32 lineStart = 0u;
bool rateLimitExceeded = false;
for (uint32 pos = 0u; pos < total && !rateLimitExceeded; pos++) {
if (raw[pos] != '\n') continue;
uint32 len = pos - lineStart;
if (len > 0u && raw[lineStart + len - 1u] == '\r') len--;
if (len > 0u) {
cmdCountInWindow++;
if (cmdCountInWindow > CMD_RATE_LIMIT) {
REPORT_ERROR_STATIC(ErrorManagement::Warning,
"Server: client exceeded rate limit (%u cmd/s) "
"— disconnecting.", CMD_RATE_LIMIT);
rateLimitExceeded = true;
break;
}
StreamString command;
uint32 cmdLen = len;
command.Write(raw + lineStart, cmdLen);
// Dispatch via base HandleCommand, write response to socket.
StreamString out;
HandleCommand(command, out);
if (out.Size() > 0u) {
const char8 *wPtr = out.Buffer();
uint32 remaining = (uint32)out.Size();
lastDataTimeMs = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000.0);
while (remaining > 0u) {
uint32 wrote = remaining;
if (!activeClient->Write(wPtr, wrote) || wrote == 0u) {
break;
}
wPtr += wrote;
remaining -= wrote;
lastDataTimeMs = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000.0);
}
}
}
lineStart = pos + 1u;
}
if (rateLimitExceeded) {
inputBuffer = "";
clientMutex.FastLock();
activeClient->Close();
delete activeClient;
activeClient = NULL_PTR(BasicTCPSocket *);
clientMutex.FastUnLock();
cmdCountInWindow = 0u;
} else {
StreamString newInputBuffer;
if (lineStart < total) {
uint32 remLen = total - lineStart;
newInputBuffer.Write(raw + lineStart, remLen);
}
inputBuffer = newInputBuffer;
}
}
}
} else {
inputBuffer = "";
clientMutex.FastLock();
activeClient->Close();
delete activeClient;
activeClient = NULL_PTR(BasicTCPSocket *);
clientMutex.FastUnLock();
}
}
}
return ErrorManagement::NoError;
}
// ---------------------------------------------------------------------------
// Streamer thread — UDPS binary telemetry
// ---------------------------------------------------------------------------
ErrorManagement::ErrorType DebugService::Streamer(ExecutionInfo &info) {
if (info.GetStage() == ExecutionInfo::TerminationStage) {
if (udpsDataPayload != NULL_PTR(uint8 *)) {
delete[] udpsDataPayload;
udpsDataPayload = NULL_PTR(uint8 *);
}
return ErrorManagement::NoError;
}
if (info.GetStage() == ExecutionInfo::StartupStage) {
streamerThreadId = Threads::Id();
return ErrorManagement::NoError;
}
// Set UDP destination
InternetHost dest(streamPort, streamIP.Buffer());
(void)udpSocket.SetDestination(dest);
// a) If config has changed, rebuild and send CONFIG packet
if (udpsConfigPending) {
SendUDPSConfig();
udpsConfigPending = false;
}
// b) Drain traceBuffer — pack each sample into udpsDataPayload
bool anyData = false;
uint32 id, size;
uint64 ts;
uint8 udpsSampleBuf[UDPS_MAX_SAMPLE_BYTES];
while (traceBuffer.Pop(id, ts, udpsSampleBuf, size, UDPS_MAX_SAMPLE_BYTES)) {
// Find matching slot by internalID
for (uint32 i = 0u; i < udpsNumSlots; i++) {
if (udpsSlots[i].internalID == id) {
if ((udpsDataPayload != NULL_PTR(uint8 *)) &&
(8u + udpsSlots[i].wireOffset + udpsSlots[i].wireSize <= udpsDataPayloadSize)) {
uint32 copySize = size;
if (copySize > udpsSlots[i].wireSize) copySize = udpsSlots[i].wireSize;
memcpy(udpsDataPayload + 8u + udpsSlots[i].wireOffset, udpsSampleBuf, copySize);
udpsSlots[i].everFilled = true;
}
anyData = true;
break;
}
}
}
// c) If we have data, stamp with HRT and send
if (anyData && udpsNumSlots > 0u && udpsDataPayload != NULL_PTR(uint8 *)) {
uint64 hrt = HighResolutionTimer::Counter();
memcpy(udpsDataPayload, &hrt, 8u);
SendUDPSFragmented(UDPS_TYPE_DATA, udpsDataPayload, udpsDataPayloadSize);
udpsPacketCounter++;
}
if (!anyData) {
Sleep::MSec(1u);
}
return ErrorManagement::NoError;
}
// ---------------------------------------------------------------------------
// SendUDPSConfig — build and send a UDPS CONFIG packet
// ---------------------------------------------------------------------------
bool DebugService::SendUDPSConfig() {
// Snapshot traced signals under mutex
mutex.FastLock();
// Count traced signals
uint32 tracedCount = 0u;
for (uint32 i = 0u; i < signals.GetNumberOfElements(); i++) {
if (signals[i]->isTracing) {
tracedCount++;
}
}
// Build CONFIG payload into udpsTxBuf starting at UDPS_HEADER_SIZE offset
uint8 *payload = udpsTxBuf + UDPS_HEADER_SIZE;
// Write numTraced (uint32 LE)
memcpy(payload, &tracedCount, 4u);
uint32 payloadOffset = 4u;
// Build slot table in parallel
uint32 currentOffset = 0u;
uint32 newNumSlots = 0u;
uint32 totalWireBytes = 0u;
for (uint32 i = 0u; i < signals.GetNumberOfElements(); i++) {
if (!signals[i]->isTracing) continue;
if (newNumSlots >= UDPS_MAX_SLOTS) break;
uint8 typeCode = UDPSTypeDescriptorToCode(signals[i]->type);
if (typeCode == UDPS_TYPECODE_UNKNOWN) {
// Skip unsupported types
continue;
}
uint32 numElements = signals[i]->numberOfElements;
if (numElements == 0u) numElements = 1u;
uint32 numRows, numCols;
if (signals[i]->numberOfDimensions >= 2u) {
// For matrix: approximate square root
numCols = numElements;
numRows = 1u;
} else if (signals[i]->numberOfDimensions == 1u) {
numRows = numElements;
numCols = 1u;
} else {
numRows = 1u;
numCols = 1u;
}
uint32 wireSize = UDPSTypeCodeByteSize(typeCode) * numElements;
// Write signal descriptor (136 bytes) to payload
if (payloadOffset + UDPS_SIGNAL_DESC_SIZE <= sizeof(udpsTxBuf) - UDPS_HEADER_SIZE - 1u) {
UDPSSignalDescriptor *desc = reinterpret_cast<UDPSSignalDescriptor *>(payload + payloadOffset);
memset(desc, 0, UDPS_SIGNAL_DESC_SIZE);
if (signals[i]->name.Size() > 0u) {
strncpy(desc->name, signals[i]->name.Buffer(), UDPS_MAX_SIGNAL_NAME - 1u);
}
desc->typeCode = typeCode;
desc->quantType = UDPS_QUANT_NONE;
desc->numDimensions = signals[i]->numberOfDimensions;
desc->numRows = numRows;
desc->numCols = numCols;
desc->rangeMin = 0.0;
desc->rangeMax = 0.0;
desc->timeMode = UDPS_TIMEMODE_PACKET;
desc->samplingRate = 0.0;
desc->timeSignalIdx = UDPS_NO_TIME_SIGNAL;
// unit stays zero
payloadOffset += UDPS_SIGNAL_DESC_SIZE;
}
// Fill slot table entry
udpsSlots[newNumSlots].internalID = signals[i]->internalID;
udpsSlots[newNumSlots].wireOffset = currentOffset;
udpsSlots[newNumSlots].wireSize = wireSize;
udpsSlots[newNumSlots].everFilled = false;
newNumSlots++;
currentOffset += wireSize;
totalWireBytes += wireSize;
}
// Write publish mode byte
if (payloadOffset < sizeof(udpsTxBuf) - UDPS_HEADER_SIZE) {
payload[payloadOffset] = UDPS_PUBLISH_STRICT;
payloadOffset++;
}
udpsNumSlots = newNumSlots;
mutex.FastUnLock();
// Reallocate data payload buffer
if (udpsDataPayload != NULL_PTR(uint8 *)) {
delete[] udpsDataPayload;
udpsDataPayload = NULL_PTR(uint8 *);
}
udpsDataPayloadSize = 8u + totalWireBytes; // 8 bytes HRT + signal data
if (udpsDataPayloadSize > 0u) {
udpsDataPayload = new uint8[udpsDataPayloadSize];
memset(udpsDataPayload, 0, udpsDataPayloadSize);
}
// Send CONFIG packet
SendUDPSFragmented(UDPS_TYPE_CONFIG, payload, payloadOffset);
return true;
}
// ---------------------------------------------------------------------------
// SendUDPSFragmented — fragment and send a UDPS packet
// ---------------------------------------------------------------------------
bool DebugService::SendUDPSFragmented(uint8 type, const uint8 *payload, uint32 payloadSize) {
if (payloadSize <= UDPS_MAX_PAYLOAD) {
// Single datagram
UDPSBuildHeader(udpsTxBuf, type, udpsPacketCounter, 0u, 1u, payloadSize);
if (payload != (udpsTxBuf + UDPS_HEADER_SIZE)) {
memcpy(udpsTxBuf + UDPS_HEADER_SIZE, payload, payloadSize);
}
uint32 toWrite = UDPS_HEADER_SIZE + payloadSize;
(void)udpSocket.Write(reinterpret_cast<char8 *>(udpsTxBuf), toWrite);
} else {
// Fragmented
uint32 numFrags = (payloadSize + UDPS_MAX_PAYLOAD - 1u) / UDPS_MAX_PAYLOAD;
uint32 offset = 0u;
for (uint32 i = 0u; i < numFrags; i++) {
uint32 chunkSize = UDPS_MAX_PAYLOAD;
if (offset + chunkSize > payloadSize) {
chunkSize = payloadSize - offset;
}
UDPSBuildHeader(udpsTxBuf, type, udpsPacketCounter,
static_cast<uint16>(i),
static_cast<uint16>(numFrags),
chunkSize);
memcpy(udpsTxBuf + UDPS_HEADER_SIZE, payload + offset, chunkSize);
uint32 toWrite = UDPS_HEADER_SIZE + chunkSize;
(void)udpSocket.Write(reinterpret_cast<char8 *>(udpsTxBuf), toWrite);
offset += chunkSize;
}
}
return true;
}
} // namespace MARTe
@@ -0,0 +1,183 @@
#ifndef DEBUGSERVICE_H
#define DEBUGSERVICE_H
#include "BasicTCPSocket.h"
#include "BasicUDPSocket.h"
#include "DebugServiceBase.h"
#include "EmbeddedServiceMethodBinderI.h"
#include "MessageI.h"
#include "ReferenceT.h"
#include "SingleThreadService.h"
#include "UDPSProtocol.h"
namespace MARTe {
/**
* @brief TCP/UDP implementation of DebugServiceI (via DebugServiceBase).
*
* Signal tracing now uses the UDPS binary protocol (same wire format as
* UDPStreamer) so that the same Go web client can consume both sources.
*
* Flow:
* 1. The Go client listens on udpPort.
* 2. DebugService binds the same port on its end (or uses the client's
* configured IP:port as destination for the old push model).
* 3. On startup and on every TRACE change, DebugService sends a CONFIG
* packet listing the currently-traced signals.
* 4. For each RT drain cycle that contains new data, DebugService sends
* one DATA packet (Strict mode) with the most-recent value per signal.
*/
class DebugService : public DebugServiceBase,
public MessageI,
public EmbeddedServiceMethodBinderI {
public:
friend class DebugServiceTest;
CLASS_REGISTER_DECLARATION()
DebugService();
virtual ~DebugService();
virtual bool Initialise(StructuredDataI &data);
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);
virtual ErrorManagement::ErrorType HandleMessage(ReferenceT<Message> &data);
/**
* @brief Override TraceSignal to trigger a CONFIG re-send after each change.
*/
virtual uint32 TraceSignal(const char8 *name, bool enable, uint32 decimation = 1u);
protected:
virtual void GetServiceInfo(StreamString &out);
virtual void RebuildTransportConfig();
private:
void InjectTcpLoggerIfNeeded();
ErrorManagement::ErrorType Server (ExecutionInfo &info);
ErrorManagement::ErrorType Streamer(ExecutionInfo &info);
/**
* @brief Build and transmit a CONFIG packet for the current traced-signal set.
* @details Iterates over all registered signals, selects those with
* isTracing == true, builds UDPSSignalDescriptor array, and sends
* a (possibly fragmented) UDPS CONFIG packet. Also rebuilds the
* udpsSlots table and udpsWirePayload buffer.
* @return true on success.
*/
bool SendUDPSConfig();
/**
* @brief Fragment and send a payload as one or more UDPS datagrams.
* @param type Packet type (UDPS_TYPE_CONFIG or UDPS_TYPE_DATA).
* @param payload Pointer to fully-built payload.
* @param payloadSize Byte count of payload.
* @return true if all datagrams were written without error.
*/
bool SendUDPSFragmented(uint8 type, const uint8 *payload, uint32 payloadSize);
// -----------------------------------------------------------------------
// TCP/UDP transport configuration
// -----------------------------------------------------------------------
uint16 controlPort;
uint16 streamPort;
uint16 logPort;
StreamString streamIP;
bool isServer;
bool suppressTimeoutLogs;
BasicTCPSocket tcpServer;
BasicUDPSocket udpSocket;
// -----------------------------------------------------------------------
// Server-thread helper class
// -----------------------------------------------------------------------
class ServiceBinder : public EmbeddedServiceMethodBinderI {
public:
enum ServiceType { ServerType, StreamerType };
ServiceBinder(DebugService *parent, ServiceType type)
: parent(parent), type(type) {}
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info) {
if (type == StreamerType) return parent->Streamer(info);
return parent->Server(info);
}
private:
DebugService *parent;
ServiceType type;
};
ServiceBinder binderServer;
ServiceBinder binderStreamer;
SingleThreadService threadService;
SingleThreadService streamerService;
ThreadIdentifier serverThreadId;
ThreadIdentifier streamerThreadId;
FastPollingMutexSem clientMutex;
BasicTCPSocket *activeClient;
// -----------------------------------------------------------------------
// TCP server rate limiting and idle detection
// -----------------------------------------------------------------------
static const uint32 CMD_RATE_LIMIT = 100u;
static const uint32 CLIENT_IDLE_TIMEOUT_MS = 120000u;
static const uint32 INPUT_BUFFER_MAX = 8192u;
uint32 cmdCountInWindow;
uint64 cmdWindowStartMs;
uint64 lastDataTimeMs;
StreamString inputBuffer;
// -----------------------------------------------------------------------
// UDPS streaming state (replaces the old custom-format streamer buffers)
// -----------------------------------------------------------------------
/**
* @brief Per-slot descriptor for DATA packet assembly.
* One slot per currently-traced signal; index mirrors CONFIG order.
*/
struct UDPSSlot {
uint32 internalID; ///< DebugSignalInfo::internalID
uint32 wireOffset; ///< Byte offset in udpsDataPayload (after 8-byte HRT)
uint32 wireSize; ///< Bytes occupied by this signal in the DATA payload
bool everFilled; ///< True once at least one sample has been placed
};
/** Maximum number of simultaneously traced signals. */
static const uint32 UDPS_MAX_SLOTS = 512u;
/** Maximum payload per UDP datagram (signal data bytes, excluding header). */
static const uint32 UDPS_MAX_PAYLOAD = 1400u;
/** Hard cap on a single signal's data: 65535 - header(17) - HRT(8). */
static const uint32 UDPS_MAX_SAMPLE_BYTES = 65510u;
UDPSSlot udpsSlots[UDPS_MAX_SLOTS]; ///< Slot table (valid for [0, udpsNumSlots))
uint32 udpsNumSlots; ///< Number of active slots
/** Heap-allocated DATA payload buffer: [HRT:8][sig0_data]...[sigN_data].
* Re-allocated by SendUDPSConfig(). NULL when no signals are traced. */
uint8 *udpsDataPayload;
uint32 udpsDataPayloadSize;
/** Staging buffer: a single sample popped from traceBuffer. */
uint8 udpsSampleBuf[65535u];
/** General-purpose TX buffer for CONFIG and DATA packet assembly. */
uint8 udpsTxBuf[65535u];
/** Packet sequence counter (incremented per DATA/CONFIG datagram group). */
uint32 udpsPacketCounter;
/**
* @brief Set to true by TraceSignal() to request a CONFIG re-send.
* Read and cleared by the Streamer thread.
*/
volatile bool udpsConfigPending;
};
} // namespace MARTe
#endif // DEBUGSERVICE_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,214 @@
#ifndef DEBUGSERVICEBASE_H
#define DEBUGSERVICEBASE_H
/**
* @file DebugServiceBase.h
* @brief Shared base class for DebugService and WebDebugService.
*
* Extracts all signal-management, command-handling and config logic that is
* common to both the TCP/UDP transport (DebugService) and the HTTP/SSE
* transport (WebDebugService), eliminating the previous code duplication.
*/
#include "ConfigurationDatabase.h"
#include "DataSourceI.h"
#include "DebugServiceI.h"
#include "FastPollingMutexSem.h"
#include "ReferenceContainer.h"
#include "ReferenceT.h"
#include "StreamString.h"
namespace MARTe {
/**
* @brief Intermediate base class that holds all shared debugging logic.
*
* Inherits from ReferenceContainer (to be a MARTe2 Object) and DebugServiceI
* (to expose the RT-path / control-path interface). Transport subclasses
* (DebugService, WebDebugService) inherit from this class and add only their
* transport-specific threading and socket logic.
*/
class DebugServiceBase : public ReferenceContainer, public DebugServiceI {
public:
friend class DebugServiceTest;
DebugServiceBase();
virtual ~DebugServiceBase();
// =========================================================================
// DebugServiceI RT-path overrides (shared implementation)
// =========================================================================
virtual DebugSignalInfo *RegisterSignal(void *memoryAddress,
TypeDescriptor type,
const char8 *name,
uint8 numberOfDimensions = 0,
uint32 numberOfElements = 1);
virtual void ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
uint64 timestamp);
virtual void RegisterBroker(DebugSignalInfo **signalPointers,
uint32 numSignals,
MemoryMapBroker *broker,
volatile bool *anyActiveFlag,
Vector<uint32> *activeIndices,
Vector<uint32> *activeSizes,
FastPollingMutexSem *activeMutex,
volatile bool *anyBreakFlag,
Vector<uint32> *breakIndices,
const char8 *gamName = NULL_PTR(const char8 *),
bool isOutput = false);
virtual bool IsPaused() const { return isPaused; }
virtual void SetPaused(bool paused) { isPaused = paused; }
virtual bool IsStepPending() const { return stepRemaining > 0u; }
virtual void ConsumeStepIfNeeded(const char8 *gamName,
const char8 *threadName = NULL_PTR(const char8 *));
// =========================================================================
// DebugServiceI control-path overrides (shared implementation)
// =========================================================================
virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr);
virtual uint32 UnforceSignal(const char8 *name);
virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1);
virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold);
virtual uint32 ClearBreak (const char8 *name);
virtual bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable);
virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs);
virtual uint32 UnmonitorSignal (const char8 *path);
// =========================================================================
// Shared control-path methods (used by transport subclasses)
// =========================================================================
/**
* @brief Parse and dispatch a debug command; write text response to @p out.
*
* Handles: TRACE, FORCE, UNFORCE, BREAK, PAUSE, RESUME, STEP, STEP_STATUS,
* VALUE, DISCOVER, TREE, INFO, LS, CONFIG, MONITOR, UNMONITOR,
* SERVICE_INFO, MSG.
*
* SERVICE_INFO delegates to GetServiceInfo() so each transport can fill in
* its own port/state information.
*/
void HandleCommand(const StreamString &cmdIn, StreamString &out);
void GetStepStatus(StreamString &out);
void GetSignalValue(const char8 *name, StreamString &out);
void Discover (StreamString &out);
void InfoNode (const char8 *path, StreamString &out);
void ListNodes (const char8 *path, StreamString &out);
void ServeConfig (StreamString &out);
void SetFullConfig(ConfigurationDatabase &config);
void RebuildConfigFromRegistry();
// =========================================================================
// Struct shared by both transports
// =========================================================================
struct MonitoredSignal {
ReferenceT<DataSourceI> dataSource;
uint32 signalIdx;
uint32 internalID;
uint32 periodMs;
uint64 lastPollTime;
uint32 size;
StreamString path;
};
// =========================================================================
// Constants
// =========================================================================
static const uint32 GET_VALUE_MAX_ELEMENTS = 256u;
/**
* @brief Pre-build DISCOVER and TREE response caches.
*
* Called automatically by SetFullConfig() once the application is fully
* initialised. After this point, DISCOVER and TREE are served from the
* pre-built string with no mutex contention and no JSON generation cost.
* Both caches are invalidated whenever a new signal is registered (which
* normally only happens during broker init, before SetFullConfig).
*/
void BuildDiscoverCache();
void BuildTreeCache();
// Number of signals per DISCOVER_PART TCP chunk. Responses larger than
// this are split so the Go client can start parsing immediately.
static const uint32 DISCOVER_CHUNK_SIGNALS = 256u;
protected:
// =========================================================================
// Virtual hooks for transport subclasses
// =========================================================================
/**
* @brief Fill the SERVICE_INFO response text (transport-specific).
*
* Called by HandleCommand when the SERVICE_INFO command is received.
* The base implementation writes nothing; subclasses override to append
* e.g. "OK SERVICE_INFO TCP_CTRL:8080 UDP_STREAM:8081 STATE:RUNNING\n".
*/
virtual void GetServiceInfo(StreamString &out) = 0;
/**
* @brief Write back transport-specific config keys after RebuildConfigFromRegistry().
*
* Called at the end of RebuildConfigFromRegistry() so that port numbers
* and other transport parameters that were read in Initialise() are
* reflected back into fullConfig (ExportData on ReferenceContainers
* doesn't re-emit config-file parameters).
*/
virtual void RebuildTransportConfig() {}
// =========================================================================
// Shared protected helpers
// =========================================================================
void Step(uint32 n, const char8 *threadName = NULL_PTR(const char8 *));
void UpdateBrokersActiveStatus();
void UpdateBrokersBreakStatus();
void PatchRegistry();
uint32 ExportTree(ReferenceContainer *container, StreamString &json,
const char8 *pathPrefix);
void ExportTreeNode(const char8 *path, StreamString &out);
void EnrichWithConfig(const char8 *path, StreamString &json);
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
// =========================================================================
// Shared data members
// =========================================================================
Vector<DebugSignalInfo *> signals;
Vector<SignalAlias> aliases;
Vector<BrokerInfo> brokers;
Vector<MonitoredSignal> monitoredSignals;
FastPollingMutexSem mutex;
FastPollingMutexSem tracePushMutex;
TraceRingBuffer traceBuffer;
volatile bool isPaused;
volatile uint32 stepRemaining;
StreamString pausedAtGam;
StreamString stepThreadFilter;
ConfigurationDatabase fullConfig;
bool manualConfigSet;
// Pre-built response caches. Guarded by mutex (brief lock for swap,
// none needed for reads once cacheValid is true and construction is done).
StreamString discoverCache; // full chunked DISCOVER_PART+DISCOVER payload
StreamString treeCache; // full TREE payload including sentinel
volatile bool discoverCacheValid;
volatile bool treeCacheValid;
};
} // namespace MARTe
#endif // DEBUGSERVICEBASE_H
@@ -0,0 +1,188 @@
#ifndef DEBUGSERVICEI_H
#define DEBUGSERVICEI_H
/**
* @file DebugServiceI.h
* @brief Abstract interface for the MARTe2 debug/instrumentation service.
*
* All broker wrappers (DebugBrokerWrapper) depend solely on this interface,
* decoupling them from the concrete TCP/UDP implementation. Alternative
* transports — TTY, WebSocket, shared-memory ring, etc. — can be plugged in
* by providing a new concrete implementation without touching any broker or
* application code.
*
* The interface is split into two logical groups:
*
* RT-path API
* Called from broker threads on every RT cycle. Implementations must
* keep these methods as cheap as possible; the only permissible
* synchronisation primitive is a fast spinlock (FastPollingMutexSem).
*
* Control-path API
* Called from server / command-handler threads (TCP, TTY, Web …).
* Latency here is acceptable; correctness and thread-safety matter.
*
* Concrete implementations register themselves during Initialise() via
* SetInstance(). Broker wrappers retrieve the active instance via
* GetInstance(); there is no ORD search on the hot path.
*/
#include "DebugCore.h"
#include "FastPollingMutexSem.h"
#include "Object.h"
#include "StreamString.h"
#include "TypeDescriptor.h"
namespace MARTe {
// Forward declarations — concrete types are only needed in the implementation.
class MemoryMapBroker;
struct SignalAlias {
StreamString name;
uint32 signalIndex;
};
struct BrokerInfo {
DebugSignalInfo **signalPointers;
uint32 numSignals;
MemoryMapBroker *broker;
volatile bool *anyActiveFlag;
Vector<uint32> *activeIndices;
Vector<uint32> *activeSizes;
FastPollingMutexSem *activeMutex;
volatile bool *anyBreakFlag;
Vector<uint32> *breakIndices;
StreamString gamName;
bool isOutput;
};
/**
* @brief Abstract debug-service interface.
*/
class DebugServiceI {
public:
// -------------------------------------------------------------------------
// Static instance registry
// -------------------------------------------------------------------------
/**
* @brief Return the currently registered debug-service instance, or NULL.
*
* Called on every broker Init() path and from OutpautPauseAndStep().
* Returns NULL when no debug service has been initialised, in which case
* all instrumentation is a no-op.
*/
static DebugServiceI *GetInstance() { return instance; }
/**
* @brief Register @p inst as the global debug-service.
*
* Concrete implementations call this from their Initialise() method.
* Passing NULL deregisters the current instance (called from the
* destructor so dangling pointers are never visible to broker threads).
*/
static void SetInstance(DebugServiceI *inst) { instance = inst; }
virtual ~DebugServiceI() {}
// =========================================================================
// RT-path API (called from broker execute threads every cycle)
// =========================================================================
/**
* @brief Register a signal memory region with the debug service.
*
* Called once per signal during broker Init(). Returns a pointer to the
* internal DebugSignalInfo that the broker caches for use on the hot path.
* Thread-safe; must not be called after the RT loop has started.
*/
virtual DebugSignalInfo *RegisterSignal(void *memoryAddress,
TypeDescriptor type,
const char8 *name,
uint8 numberOfDimensions = 0,
uint32 numberOfElements = 1) = 0;
/**
* @brief Process one signal on the RT path.
*
* Applies forced values (memcpy into signal memory) and, when tracing is
* enabled and the decimation counter fires, pushes a sample to the trace
* ring buffer. Called under the broker's activeMutex; implementations
* must not acquire any lock that is also held by the Server thread.
*/
virtual void ProcessSignal(DebugSignalInfo *signalInfo,
uint32 size,
uint64 timestamp) = 0;
/**
* @brief Register a broker so the service can push active/break index
* updates to it without iterating every signal.
*/
virtual void RegisterBroker(DebugSignalInfo **signalPointers,
uint32 numSignals,
MemoryMapBroker *broker,
volatile bool *anyActiveFlag,
Vector<uint32> *activeIndices,
Vector<uint32> *activeSizes,
FastPollingMutexSem *activeMutex,
volatile bool *anyBreakFlag,
Vector<uint32> *breakIndices,
const char8 *gamName = NULL_PTR(const char8 *),
bool isOutput = false) = 0;
/** @brief Return true if the RT loop is currently held at a pause/breakpoint. */
virtual bool IsPaused() const = 0;
/** @brief Set or clear the paused state (called by break-condition logic). */
virtual void SetPaused(bool paused) = 0;
/** @brief Return true if a step count is pending (stepRemaining > 0). */
virtual bool IsStepPending() const = 0;
/**
* @brief Consume one step credit for the current output-broker cycle.
*
* Called by every output broker after Execute(). No-op when stepRemaining
* is zero (the common case); only acquires a mutex when stepping is active.
*/
virtual void ConsumeStepIfNeeded(
const char8 *gamName,
const char8 *threadName = NULL_PTR(const char8 *)) = 0;
// =========================================================================
// Control-path API (called from server / command-handler threads)
// =========================================================================
virtual uint32 ForceSignal (const char8 *name, const char8 *valueStr) = 0;
virtual uint32 UnforceSignal(const char8 *name) = 0;
virtual uint32 TraceSignal (const char8 *name, bool enable, uint32 decimation = 1) = 0;
virtual uint32 SetBreak (const char8 *name, uint8 op, float64 threshold) = 0;
virtual uint32 ClearBreak (const char8 *name) = 0;
virtual bool IsInstrumented(const char8 *fullPath,
bool &traceable, bool &forcable) = 0;
virtual uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs) = 0;
virtual uint32 UnmonitorSignal (const char8 *path) = 0;
// =========================================================================
// Utility (implementation-agnostic, defined in DebugService.cpp)
// =========================================================================
/**
* @brief Resolve the fully-qualified ORD path of @p obj into @p fullPath.
*
* Static so it can be called without a service instance. All concrete
* implementations (and InitSignals in DebugBrokerWrapper.h) use this
* to build canonical signal names. The definition lives in
* DebugService.cpp alongside FindPathInContainer().
*/
static bool GetFullObjectName(const Object &obj, StreamString &fullPath);
protected:
/** Pointer to the single active debug-service instance. */
static DebugServiceI *instance;
};
} // namespace MARTe
#endif // DEBUGSERVICEI_H
@@ -0,0 +1 @@
include Makefile.inc
@@ -0,0 +1,36 @@
#############################################################
# MARTe2 Integrated Components — DebugService
#############################################################
OBJSX=DebugService.x DebugServiceBase.x
PACKAGE=Components/Interfaces
ROOT_DIR=../../../../
MAKEDEFAULTDIR=$(MARTe2_DIR)/MakeDefaults
include $(MAKEDEFAULTDIR)/MakeStdLibDefs.$(TARGET)
# Shared UDPS protocol header (from Common/)
INCLUDES += -I$(ROOT_DIR)/Common/UDP
INCLUDES += -I$(ROOT_DIR)/Source/Components/Interfaces/TCPLogger
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L0Types
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L1Portability
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L2Objects
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L3Streams
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L4Messages
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L4Logger
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L4Configuration
INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L5GAMs
INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L1Portability
INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L3Services
INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4Messages
INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4LoggerService
INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L5GAMs
INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability
INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams
all: $(OBJS) $(SUBPROJ) \
$(BUILD_DIR)/DebugService$(LIBEXT) \
$(BUILD_DIR)/DebugService$(DLLEXT)
echo $(OBJS)
include $(MAKEDEFAULTDIR)/MakeStdLibRules.$(TARGET)