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 bys
.
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:
Instantiate Debugger.
Wrap this instance in any stored::ProtocolLayer that is required for your device.
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.
See also
-
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 tolist()
.
-
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
See also
- 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 thelist
andlen
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.
See also
-
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
isnullptr
.
-
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
-
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
-
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.
See also
-
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
-
virtual bool version(ProtocolLayer &response)
Push the version string into the given response.
See also
- 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 = '?'
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 tolist()
.
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
-
using ListCallbackArg = void(char const*, DebugVariant&, void*)
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.
-
typedef DebugVariantBase base