Debugger

Embedded Debugger message handling.

Protocol

The default set of commands that is processed by stored::Debugger is listed below. A subclass of stored::Debugger may extend the set of capabilities, for application-specific purposes.

The protocol is a request-response mechanism; for every request, there must be a response. Requests are processed in order. This is the OSI application layer of the protocol stack. For other layers, see Protocol.

Requests always start with an ASCII character, which is the command. The response can be actual data, or with ack ! or nack ?. All requests and responses are usually plain ASCII (or UTF-8 for strings), to simplify processing it over a terminal or by humans. However, it does not have to be the case.

Capabilities

Request: ?

?

Response: a list of command characters.

?rwe

This command is mandatory for every debugging target.

Echo

Request: e <any data>

eHello World

Response: <the same data>

Hello World

Read

Request: r <name of object>

(Every scope within) the name of the object may be abbreviated, as long as it is unambiguous. In case there is a alias created for the object (see Alias below), the alias character can be used instead of the name.

r/bla/asdf

Response: ? | <ASCII hex value of object>

For values with fixed length (int, float), the byte order is big/network endian. For ints, the initial zeros can be omitted. For other data, all bytes are encoded.

123abc

Write

Request: w <value in ASCII hex> <name of object>

See Read for details about the hex value and object name.

w10/b/a

Response: ! | ?

!

List

Requests a full list of all objects of all registered stores to the current Embedded Debugger.

Request: l

l

Response: ( <type byte in hex> <length in hex> <name of object> \n ) * | ?

3b4/b/i8
201/b/b

See stored::Type for the type byte. The type byte is always two hex characters. The number of characters of the length of the object depends on the value; leading zeros may be removed.

Alias

Assigns a character to a object path. An alias can be everywhere where an object path is expected. Creating aliases skips parsing the object path repeatedly, so makes debugging more efficient. If no object is specified, the alias is removed. The number of aliases may be limited. If the limit is hit, the response will be ?. The alias name can be any char in the range 0x20 () - 0x7e (~), except for 0x2f (/).

Request: a <char> ( <name of object> ) ?

a0/bla/a

Response: ! | ?

!

Macro

Saves a sequence of commands and assigns a name to it. The macro name can be any char in the range 0x20 () - 0x7e (~). In case of a name clash with an existing non-macro command, the command is executed; the macro cannot hide or replace the command.

The separator can be any char, as long as it is not used within a command of the macro definition. Using \r, \n, or \t is usually safe, as it cannot occur inside a name.

Without the definition after the macro name, the macro is removed. The system may be limited in total definition length. The macro string is reinterpreted every time it is invoked.

The responses of the commands are merged into one response frame, without separators. The Echo command can be used to inject separators in the output.

Request: m <char> ( <separator> <command> ) *

mZ r/bla/a e; r/bla/z

Response: ! | ?

!

If the Z command is now executed, the result could be something like:

123;456

Identification

Returns a fixed string that identifies the application.

Request: i

i

Response: ? | <UTF-8 encoded application name>

libstored

Version

Returns a list of versions.

Request: v

v

Response: ? | <protocol version> ( <application-specific version> ) *

2 r243+trunk beta

Read memory

Read a memory via a pointer instead of the store. Returns the number of requested bytes. If no length is specified, a word is returned.

Request: R <pointer in hex> ( <length> ) ?

R1ffefff7cc 4

Response: ? | <bytes in hex>

efbe0000

Bytes are just concatenated as they occur in memory, having the byte at the lowest address first.

Write memory

Write a memory via a pointer instead of the store.

Request: W <pointer in hex> <bytes in hex>

W1ffefff7cc 0123

Response: ? | !

!

Streams

Read all available data from a stream. Streams are application-defined sequences of bytes, like stdout and stderr. They may contain binary data. There are an arbitrary number of streams, with an arbitrary single-char name, except for ?, as it makes the response ambiguous.

To list all streams with data:

Request: s

To request all data from a stream, where the optional suffix is appended to the response:

Request: s <char> <suffix> ?

sA/

Response: ? | <data> <suffix>

Hello World!!1/

Once data has been read from the stream, it is removed. The next call will return new data. If a stream was never used, ? is returned. If it was used, but it is empty now, the stream char does not show up in the s call, but does respond with the suffix. If no suffix was provided, and there is no data, the response is empty.

The number of streams and the maximum buffer size of a stream may be limited.

Depending on stored::Config::CompressStreams, the data returned by s is compressed using heatshrink (window=8, lookahead=4). Every chunk of data returned by s is part of a single stream, and must be decompressed as such. As (de)compression is stateful, all data from the start of the stream is required for decompression. Moreover, stream data may remain in the compressor before retrievable via s.

To detect if the stream is compressed, and to forcibly flush out and reset the compressed stream, use the Flush (f) command. A flush will terminate the current stream, push out the last bit of data from the compressor’s buffers and restart the compressor’s state. So, a normal startup sequence of a debug client would be:

  • Check if f capability exists. If not, done; no compression is used on streams.

  • Flush out all streams: execute f.

  • Drop all streams, as the start of the stream is possibly missing: execute sx for every stream returned by s.

Afterwards, pass all data received from s through the heatshrink decoder.

Flush

Flush out and reset a stream (see also Streams). When this capability does not exist, streams are not compressed. If it does exist, all streams are compressed. Use this function to initialize the stream if the (de)compressing state is unknown, or to force out the last data (for example, the last trace data).

Request: f <char> ?

Response: !

The optional char is the stream name. If omitted, all streams are flushed and reset. The response is always !, regardless of whether the stream existed or had data.

The stream is blocked until it is read out by s. This way, the last data is not lost, but new data could be dropped if this takes too long. If you want an atomic flush-retrieve, use a macro.

Tracing

Executes a macro every time the application invokes stored::Debugger::trace(). A stream is filled with the macro output.

Request: t ( <macro> <stream> ( <decimate in hex> ) ? ) ?

tms64

This executes macro output of m to the stream s, but only one in every 100 calls to trace(). If the output does not fit in the stream buffer, it is silently dropped.

t without arguments disables tracing. If the decimate argument is omitted, 1 is assumed (no decimate). Only one tracing configuration is supported; another t command with arguments overwrites the previous configuration.

Response: ? | !

!

The buffer collects samples over time, which is read out by the client possibly at irregular intervals. Therefore, you probably want to know the time stamp of the sample. For this, include reading the time in the macro definition. By convention, the time is a top-level variable t with the unit between braces. It is implementation-defined what the offset is of t, which can be since the epoch or since the last boot, for example. For example, your store can have one of the following time variables:

// Nice resolution, wraps around after 500 millennia.
(uint64) t (us)
// Typical ARM systick counter, wraps around after 49 days.
(uint32) t (ms)
// Pythonic time. Watch out with significant bits.
(double) t (s)

The time is usually a function type, as it is read-only and reading it should invoke some time-keeping functions.

Macro executions are just concatenated in the stream buffer. Make sure to use the Echo command to inject proper separators to allow parsing the stream content afterwards.

For example, the following requests are typical to setup tracing:

# Define aliases to speed up macro processing.
at/t (us)
a1/some variable
a2/some other variable
# Save the initial start time offset to relate it to our wall clock.
rt
# Define a macro for tracing
mM rt e, r1 e, r2 e;
# Setup tracing
tMT
# Repeatedly read trace buffer
sT
sT
sT
...

Now, the returned stream buffer (after decompressing) contains triplets of time, /some variable, /some other variable, like this:

101,1,2;102,1,2;103,1,2;

Depending on the buffer size, reading the buffer may be orders of magnitude slower than the actual tracing speed.

stored::Debugger

class Debugger : public stored::ProtocolLayer

The application-layer implementation of the Embedded Debugger protocol.

To use the Debugger in your application:

By default, the Debugger provides the standard set of commands. To extend it, create a subclass and override capabilities() and process().

Subclassed by ExtendedDebugger

Public Types

using ListCallback = void(char const*, DebugVariant&)

Callback function prototype as supplied to list().

It receives the name of the object, and the corresponding stored::DebugVariant of the object.

typedef DebugStoreBase::ListCallbackArg ListCallbackArg

Callback function prototype as supplied to list().

It receives the name of the object, the corresponding stored::DebugVariant of the object, and the arg value, as passed to list().

typedef Map<char const*, DebugStoreBase*, StorePrefixComparator>::type StoreMap

The map to used by map(), which maps names to stores.

Public Functions

explicit Debugger(char const *identification = nullptr, char const *versions = nullptr)

Constructor.

See also

setVersions()

Parameters:
  • identification – the identification, that is to be returned by identification().

  • versions – the version list, that is to be processed by version().

virtual ~Debugger() noexcept override

Destructor.

virtual void capabilities(char *&caps, size_t &len, size_t reserve = 0)

Get the capabilities as supported by this Debugger.

The list is allocated on the spm(). The pointer and the length are returned through the list and len arguments.

Parameters:
  • caps – the list of capabilities

  • len – the size of the buffer of list

  • reserve – when allocating memory for list, add this number of bytes

virtual void decode(void *buffer, size_t len) override

Decode a frame and forward the decoded frame to the upper layer.

The given buffer may be decoded in-place.

DebugVariant find(char const *name, size_t len = std::numeric_limits<size_t>::max()) const

Performs a lookup of the given object name.

Parameters:
  • name – the name to find, which can be abbreviated until unambiguous

  • len – the maximum length of name to check

Returns:

a stored::DebugVariant, which is invalid when the object was not found.

virtual char const *identification()

Returns the identification.

template<typename F>
inline list(F &&f) const

Iterates over the directory and invoke a callback for every object.

Parameters:

f – the callback to invoke, which can be any type, but must look like ListCallback

void list(ListCallbackArg *f, void *arg = nullptr) const

Iterates over the directory and invoke a callback for every object.

Parameters:
  • f – the callback to invoke

  • arg – an arbitrary argument to be passed to f

template<typename Store>
inline void map(Store &store, char const *name = nullptr)

Register a store to this Debugger.

If there is only one store registered, all objects of that store are accessible using the names as defined by the store. If multiple stores are mapped, all objects are prefixed using either the prefix supplied to map(), or using the store’s name when name is nullptr.

virtual void process(void const *frame, size_t len, ProtocolLayer &response)

Process a Embedded Debugger message.

Parameters:
  • frame – the frame to decode

  • len – the length of frame

  • response – the layer to push responses into

void setIdentification(char const *identification = nullptr)

Sets the identification.

The supplied string is not copied; just the pointer is saved, so the string must remain valid while the Debugger has its pointer. So, it is fine to supply a string literal.

See also

identification()

void setVersions(char const *versions = nullptr)

Set the versions as used by version().

The supplied string is not copied; just the pointer is saved, so the string must remain valid while the Debugger has its pointer. So, it is fine to supply a string literal.

See also

version()

StoreMap const &stores() const

Returns the mapped stores.

Stream const *stream(char s) const

Returns the stream buffer given a stream name.

Returns:

the stream or nullptr when there is no stream with the given name

Stream *stream(char s, bool alloc = false)

Returns the stream buffer given a stream name.

Parameters:
  • s – the stream name

  • alloc – when set to true, try to allocate the stream if it does not exist yet

Returns:

the stream or nullptr when there is no stream with the given name

size_t stream(char s, char const *data)

Adds a zero-terminated string to the given stream.

size_t stream(char s, char const *data, size_t len)

Adds a buffer to the given stream.

This function does not block when the buffer is full.

Parameters:
  • s – the stream to append to, which is created if it does not exist yet

  • data – the data to be appended (which may include 0)

  • len – the length of data

Returns:

The length that was appended. On success, this equals len, but may be less if the buffer was full.

char const *streams(void const *&buffer, size_t &len)

Gets the existing streams.

Parameters:
  • buffer – an spm() allocated buffer with stream names

  • len – the length of the resulting buffer

Returns:

0-terminated string of stream names (which equals buffer)

void trace()

Executes the trace macro and appends the output to the trace buffer.

The application should always call this function as often as required for tracing. For example, if 1 kHz tracing is to be supported, make sure to call this function at (about) 1 kHz. Depending on the configuration, decimation is applied. This call will execute the macro, when required, so the call is potentially expensive.

The output of the macro is either completely or not at all put in the stream buffer; it is not truncated.

bool tracing() const

Checks if tracing is currently enabled and configured.

void unmap(char const *name)

Unmaps a store from this Debugger.

Note that if after unmapping there is only one mapped store left, the prefixes are automatically dropped from all names.

See also

map()

virtual bool version(ProtocolLayer &response)

Push the version string into the given response.

See also

setVersions()

Returns:

true if the version is pushed, false if not available

Public Static Attributes

static char const Ack = '!'
static char const CmdAlias = 'a'
static char const CmdCapabilities = '?'
static char const CmdEcho = 'e'
static char const CmdFlush = 'f'
static char const CmdIdentification = 'i'
static char const CmdList = 'l'
static char const CmdMacro = 'm'
static char const CmdRead = 'r'
static char const CmdReadMem = 'R'
static char const CmdStream = 's'
static char const CmdTrace = 't'
static char const CmdVersion = 'v'
static char const CmdWrite = 'w'
static char const CmdWriteMem = 'W'
static char const Nack = '?'
struct StorePrefixComparator

Helper class to sort StoreMap based on name.

Public Functions

inline bool operator()(char const *lhs, char const *rhs) const noexcept

stored::DebugStoreBase

class DebugStoreBase

Wrapper for a store, that hides the store’s template parameters.

This is a type-independent base class of stored::DebugStore. For stored::Debugger, this is the interface to access a specific store.

See also

stored::DebugStore

Subclassed by stored::DebugStore< Store >

Public Types

using ListCallbackArg = void(char const*, DebugVariant&, void*)

Callback function prototype as supplied to list().

It receives the name of the object, the corresponding stored::DebugVariant of the object, and the arg value, as passed to list().

Public Functions

virtual ~DebugStoreBase() = default

Destructor.

virtual DebugVariant find(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept = 0

Performs a lookup of the given object name.

Parameters:
  • name – the name to find, which can be abbreviated until unambiguous

  • len – the maximum length of name to check

Returns:

a stored::DebugVariant, which is invalid when the object was not found.

virtual void list(ListCallbackArg *f, void *arg = nullptr, char const *prefix = nullptr) const = 0

Iterates over the directory and invoke a callback for every object.

Parameters:
  • f – the callback to invoke

  • arg – an arbitrary argument to be passed to f

  • prefix – a prefix applied to the name passed to f

virtual char const *name() const noexcept = 0

Returns the name of this store.

Returns:

the name, which cannot be nullptr

stored::DebugVariant

class DebugVariant : public stored::DebugVariantBase

A wrapper for any type of object in a store.

This is a template-type-independent container for a stored::DebugVariantTyped.

Even though the DebugVariantTyped uses virtual functions, inheritance, and templates, the allocation is done within this object. This object is small, efficient, default-copyable and default-assignable, and can therefore be used as a value in a standard container.

Public Types

typedef DebugVariantBase base

Public Functions

inline DebugVariant()

Constructor for an invalid stored::Variant wrapper.

template<typename Container>
inline explicit DebugVariant(Variant<Container> const &variant)

Constructor for a stored::Variant wrapper.

inline virtual size_t get(void *dst, size_t len = 0) const final

Retrieve data from the object.

Parameters:
  • dst – the destination buffer

  • len – the size of dst. If 0, it defaults to size().

Returns:

the number of bytes written into dst

inline bool operator!=(DebugVariant const &rhs) const
inline bool operator==(DebugVariant const &rhs) const
inline virtual size_t set(void const *src, size_t len = 0) final

Set data to the buffer.

Parameters:
  • src – the data to be written

  • len – the length of src. If 0, it defaults to size().

Returns:

the number of bytes consumed

inline virtual size_t size() const final

The size of this object.

inline virtual Type::type type() const final

The type of this object.

inline virtual bool valid() const final

Returns if this wrapper points to a valid object.