Skip to content

Instantly share code, notes, and snippets.

@haydenridd
Created April 26, 2024 17:06
Show Gist options
  • Save haydenridd/bd5cc9d5b6a1dfa3074adbc20bf75d34 to your computer and use it in GitHub Desktop.
Save haydenridd/bd5cc9d5b6a1dfa3074adbc20bf75d34 to your computer and use it in GitHub Desktop.
Non-Exception Error Handling for Embedded
#include "error.h"
// printf is used here as an example, but would be replaced with your specific logger (RTT, over UART, etc.)
#include <stdio.h>
#include <stdlib.h>
namespace error_handling {
void printError(Error &err) {
printf("ERROR: %s:%d: %d (%s)\n",
err.at_file,
err.at_line,
err.code,
err.text);
}
void unrecoverableError(const char *file_val, int32_t line_val)
{
printf("Unrecoverable error encountered at %s:%d\n", file_val, line_val);
// TODO: Your platform specific reset procedure, for this example
// will just call exit(1); Some valid options are infinite loop,
// force a hard fault, force a breakpoint, etc.
exit(1);
}
void unrecoverableError(const char *file_val, int32_t line_val, Error &err)
{
printf("The following error: \n");
printError(err);
printf("Led to an unrecoverable error:\n");
unrecoverableError(file_val, line_val);
}
void unrecoverableError(const char *file_val,
int32_t line_val,
const char *message)
{
printf("%s\n", message);
unrecoverableError(file_val, line_val);
}
} // namespace error_handling
#pragma once
#include <cstdint>
namespace error_handling {
constexpr int32_t OK = 0;
/**
* @brief Convenience macro for getting just root filename instead of full path
*
*/
#define __FILENAME__ \
(__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 \
: __FILE__)
#define SET_ERROR3(err, value, text_val) \
do { \
(err).code = (value); \
(err).text = text_val; \
(err).at_file = __FILENAME__; \
(err).at_line = __LINE__; \
} while (0)
#define SET_ERROR(err, value) SET_ERROR3(err, value, #value)
#define SET_ERR(value) SET_ERROR(err, value)
/**
* @brief Records/Propagates error state
*
* This class represents error conditions as non-zero (typically
* negative) values of code. The actual error values are defined by
* the application and not part of the class itself.
*
* By default, new Error instances are created with a code of 0
* (zero) meaning no error.
*/
struct Error {
/**
* @brief Constructs a new Error instance with a code of 0
*/
Error()
: code{OK}
, text{nullptr}
, at_file{nullptr}
, at_line{0}
{
}
Error(int32_t code_val,
const char *text_val,
const char *file_val,
int32_t line_val)
: code{code_val}
, text{text_val}
, at_file{file_val}
, at_line{line_val}
{
}
/**
* @returns the _code directly, unwrapping the object
*/
operator int() const { return code; }
/**
* @brief combines two Errors
*
* The state of the called Error instance is preserved if it's not
* OK. If it is OK, the other error state is copied.
*/
Error &operator|=(const Error &other)
{
if (code == OK) {
*this = other;
}
return *this;
}
void clear()
{
code = OK;
text = nullptr;
at_file = nullptr;
at_line = 0;
}
int32_t code;
const char *text;
const char *at_file;
int32_t at_line;
};
/**
* @brief Performing a logical OR on two errors has the existing error take
* precedence
*/
inline Error &operator|(Error &left, Error &right)
{
return (left.code != OK) ? left : right;
}
#define ERROR(code) Error(code, #code, __FILENAME__, __LINE__)
enum : int32_t {
ERROR_BASE = -100000,
INVALID_STATE = ERROR_BASE - 1,
BAD_PARAMETER = ERROR_BASE - 2,
TIMED_OUT = ERROR_BASE - 3,
INIT_REQUIRED = ERROR_BASE - 4
};
void printError(Error &err);
/**
* @brief An error that should halt firmware execution and either pause for
* debugger access, or restart system
*/
void unrecoverableError(const char *file_val, int32_t line_val);
void unrecoverableError(const char *file_val, int32_t line_val, Error &err);
void unrecoverableError(const char *file_val,
int32_t line_val,
const char *message);
// Some useful convenience macros for triggering an unrecoverable error
#define UNRECOVERABLE_IF_FALSE(expr) \
if (!(expr)) error_handling::unrecoverableError(__FILENAME__, __LINE__);
#define UNRECOVERABLE_IF_ERROR(err) \
if (err) error_handling::unrecoverableError(__FILENAME__, __LINE__, err);
#define UNRECOVERABLE_MSG(msg) \
error_handling::unrecoverableError(__FILENAME__, __LINE__, (msg))
/**
* @brief Just a nice way of indicating a section should never be reached (like
* in the case of post kernel start or jumping to a different application), and
* if the section is reached triggering an unrecoverable error
*
*/
#define UNREACHABLE_SECTION() UNRECOVERABLE_MSG("Unreachable section!")
} // namespace error_handling
#include "error.h"
#include <stdio.h>
#include <cassert>
using error_handling::Error;
using error_handling::OK;
using error_handling::BAD_PARAMETER;
using error_handling::printError;
static void errorProneFunction(int b, Error &err) {
if(b > 5) {
SET_ERR(BAD_PARAMETER);
return;
}
}
int main()
{
Error err;
// By default, error is "OK"
assert(err == OK);
// "OK" evaluates to false, as in, "no error"
assert(!err);
// No errors
errorProneFunction(1, err);
assert(!err);
// Catch and handle an error
errorProneFunction(6, err);
if(err) {
printf("errorProneFunction errored:\n");
printError(err);
printf("... but we don't care\n");
// Errors can be reset back to "OK"
err.clear();
}
// Experience an "unrecoverable" error
errorProneFunction(12, err);
UNRECOVERABLE_IF_ERROR(err);
// Comment out UNRECOVERABLE_IF_ERROR(err) and uncomment to test out
// some other variants
// UNRECOVERABLE_IF_FALSE(1 == 2);
// UNRECOVERABLE_MSG("Stop execution stat!");
// UNREACHABLE_SECTION();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment