modm API documentation
|
Functions | |
void | modm_abandon (const modm::AssertionInfo &info) modm_weak |
void | modm_assert (bool condition, const char *name, const char *description, uintptr_t context=uintptr_t (-1)) |
bool | modm_assert_continue_fail (bool condition, const char *name, const char *description, uintptr_t context=uintptr_t (-1)) |
bool | modm_assert_continue_ignore (bool condition, const char *name, const char *description, uintptr_t context=uintptr_t (-1)) |
bool | modm_assert_continue_fail_debug (bool condition, const char *name, const char *description, uintptr_t context=uintptr_t (-1)) |
bool | modm_assert_continue_ignore_debug (bool condition, const char *name, const char *description, uintptr_t context=uintptr_t (-1)) |
#define | MODM_ASSERTION_INFO_HAS_DESCRIPTION 0/1 |
Declares whether or not AssertionInfo has a description field. | |
#define | MODM_ASSERTION_HANDLER(handler) |
#define | MODM_ASSERTION_HANDLER_DEBUG(handler) |
enum | modm::Abandonment : uint8_t { modm::Abandonment::DontCare = Bit0, modm::Abandonment::Ignore = Bit1, modm::Abandonment::Fail = Bit2, modm::Abandonment::Debug = Bit7 } |
Describes abandonment type of assertions. More... | |
using | modm::AbandonmentBehavior = Flags8< Abandonment > |
using | modm::AssertionHandler = Abandonment (*)(const AssertionInfo &info) |
Signature of the assertion handlers. | |
lbuild module: modm:architecture:assert
This module provides a way to define and report assertions, that act as a low-cost replacement for C++ exceptions and as a low-cost customization point for errors raised in asynchronous code.
Assertions are called with or without a context:
modm_assert(condition, name, descr);
modm_assert(condition, name, descr, context);
They have the following arguments:
bool condition
: The assertion fails when this condition evaluated to false.const char *name
: A short and unique assertion name.const char *description
: A detailed description of the failure.uintptr_t context = -1
: Optional context.The condition is evaluated at most once by a (C-style) cast to bool.
The name format is not enforced, however, it is recommended to either use what
for top-level failures, like malloc
for heap troubles, or scope.what
for failures that may not be unique, like can.rx
vs. uart.rx
for when their respective receive buffers overflow.
The description can be as detailed as necessary, since it is only included in the firmware if the with_description
option is set to true, which also defines MODM_ASSERTION_INFO_HAS_DESCRIPTION
to 1 or 0. You can either find the detailed description in your code via its name, or if you prefer a stand-alone solution and your target has enough memory, include all strings in your binary.
The context is of pointer size, and anything passed to it is cast to uintptr_t
. Otherwise all bits are set via uintptr_t(-1)
.
Assertions are implemented as macros and expand to this pseudo-code equivalent:
If you like to know the technical details, you can read here about the original assertions in xpcc.
Assertions may also be recoverable if the call site allows for it. For example if the CAN receive buffer overflows, you may want to simply discard the input. If malloc fails to allocate it just returns NULL and the caller is responsible to deal with that. But maybe you want to enable an additional assertion in debug mode just to double-check.
When an assertion fails, the runtime calls any number of user-defined handlers, registered using MODM_ASSERTION_HANDLER(handler)
. The handlers must return a modm::Abandonment
value, specifying whether they want to continue with the execution with Abandonment::Ignore
, or abandon execution with Abandonment::Fail
leading to a call to modm_abandon(info)
, or delegate the decision with Abandonment::DontCare
.
For example, this neutral handler logs the failed assertion's name, but delegates all further decisions to others:
You may register specialized handlers anywhere in your code, for example for ignoring the mentioned CAN receive buffer overflow in your connectivity code:
You may define any number of handlers, which are called in random order by the runtime and their results are accumulated as follows:
Abandonment::Fail
execution abandons.Abandonment::Ignore
execution resumes.Abandonment::DontCare
, the assertion type determines the outcome.modm::atomic::Lock
inside your assertion handler.The call site of the assertion decides whether an assertion can be recovered from or not. For example, if the CAN receive buffer has overflowed, but execution continues, then code to discard the message must be in place.
In case no handlers are registered or they all delegate the abandonment decision away, the call site must decide what the default behavior is. For this purpose the following assertions are available:
void modm_assert()
: Always abandons execution when failed.bool modm_assert_continue_fail()
: Abandons execution unless overwritten.bool modm_assert_continue_ignore()
: Resumes execution unless overwritten.Assertions that can resume execution return the evaluated boolean condition to be used to branch to cleanup code:
Additionally, these assertions are only active in profile=debug
(more about profiles: scons, make). Of course they still evaluate and return the condition in profile=release
, so you can use them just as above:
Alternatively, you can guard the entire assertion statement with the MODM_BUILD_DEBUG
macro if you only want to execute the check and branch in debug profile:
Here are some guidelines for choosing the best assertion type:
modm_assert()
.modm_assert_continue_{fail|ignore}()
: a. Abort by default, if the failure runs contrary to expected behavior. b. Resume by default, if the failure is expected and its behavior is well documented.modm_assert_continue_{fail|ignore}_debug()
.Let's illustrate these with a few examples:
exit()
is called. There is no sensible fallback, since there is no operating system to return control back to, so use modm_assert()
.modm_assert_continue_fail(..., irq_number)
.modm_assert_continue_ignore(..., &message)
with a pointer to the message to inform the developer.modm_assert_continue_fail_debug()
here is warranted, helping the developer find potential issues faster, and then ignoring this assert for debug builds by registering a handler via MODM_ASSERTION_HANDLER_DEBUG()
.modm_assert_continue_ignore_debug()
to give the developer a way to log the failure frequency without having to provide a special API. This can help diagnose a problem perhaps with the bus connection faster.If execution is abandoned modm_abandon(const AssertionInfo &info)
is called, which is a weak and empty function by default.
The function is meant to be overwritten by the application on embedded targets for example to disable relevant hardware for safety, log the failure via UART and perhaps blink some LEDs wildly to get the user's attention.
After returning from that function, the runtime resets the chip on Cortex-M, or loops forever on AVR, or calls abort()
on hosted. You may of course, implement your own abandoning behavior instead of returning from modm_abandon()
.
modm_abandon()
may be called inside high-priority interrupts! You can try to lower the active IRQ priority to let UART work, however, in the worst case you're called from within the HardFault or even NMI handlers which have the highest fixed priority.Places the full description of a modm_assert()
into the firmware image instead of only into the ELF file. This makes printing assertion information a simple standalone feature, fully independent of any additional script for decoding logging output, however, it may increase binary size considerably!
Generated with: debug in [debug, off, release]
#define MODM_ASSERTION_HANDLER |
This adds a function to the list of assertion handlers to execute on assertion failure. Note that this macro does not give you any influence over the order of handler execution on assertion failure. Do not write assertion handlers that depend on any ordered execution!
handler | A function of signature AssertionHandler . |
#define MODM_ASSERTION_HANDLER_DEBUG |
This adds an assertion handler only for debug mode.
handler | A function of signature AssertionHandler . |
|
strong |
void modm_abandon | ( | const modm::AssertionInfo & | info | ) |
Overwriteable abandonment handler for all targets.
You should overwrite this handler for custom failure behaviour like blinking LEDs and logging the failure via a serial connection.
void modm_assert | ( | bool | condition, |
const char * | name, | ||
const char * | description, | ||
uintptr_t | context = uintptr_t (-1) |
||
) |
Always abandons execution
bool modm_assert_continue_fail | ( | bool | condition, |
const char * | name, | ||
const char * | description, | ||
uintptr_t | context = uintptr_t (-1) |
||
) |
Abandons execution, unless overwritten by assertion handlers to resume.
bool modm_assert_continue_fail_debug | ( | bool | condition, |
const char * | name, | ||
const char * | description, | ||
uintptr_t | context = uintptr_t (-1) |
||
) |
Abandons execution, unless overwritten by assertion handlers to resume.
bool modm_assert_continue_ignore | ( | bool | condition, |
const char * | name, | ||
const char * | description, | ||
uintptr_t | context = uintptr_t (-1) |
||
) |
Resumes execution, unless overwritten by assertion handlers to abandon.
bool modm_assert_continue_ignore_debug | ( | bool | condition, |
const char * | name, | ||
const char * | description, | ||
uintptr_t | context = uintptr_t (-1) |
||
) |
Resumes execution, unless overwritten by assertion handlers to abandon.