Created
April 26, 2024 17:06
-
-
Save haydenridd/bd5cc9d5b6a1dfa3074adbc20bf75d34 to your computer and use it in GitHub Desktop.
Non-Exception Error Handling for Embedded
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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