Hello and welcome to the Modern Embedded Systems Programming course. My name is Miro Samek, and in this lesson, I'll continue the subject of assertions and Design by Contract. Today, you'll learn how to apply them in embedded systems. To quickly summarize what happened so far, in the previous lesson #47, you learned what assertions are and how they are implemented in the standard C facility . You've also learned about Design by Contract (DBC) and the proper use of assertions to avert errors and NOT to handle exceptional conditions. Today, you'll see how to apply assertions and DBC in embedded C. You'll also consider several aspects of the implementation and deployment of assertions in embedded systems. Embedded Assertsions So, let's see how to implement assertions in an embedded-systems-friendly way. Here, I will quickly review the implementation consisting of a single header file, "qassert.h," located in the qpc framework. This is a self-contained file and you can use it outside the QPC framework in your own projects. Actually, you've been already using these assertions on multiple occasions, for example, in the lessons about the RTOS. Usually, I would review the raw file, but today I'd like to show you a review of the documentation generated by doxygen because it's often an easier way to explore the code. The documentation is located in the QP online manual that can be found on the state-machine.com website through the Products/QPC-Manual menu. To find quickly what you need, type "qassert" into the search box. The first documented macro is Q_ASSERT_ID(), which is a general-purpose assertion used later for all other assertion variants. The macro takes two parameters: the ID number, which should be unique within a given module, and the Boolean expression to check. The macro itself is defined by means of the ternary operator, which is standard practice. For example, here is the standard header file from the ARM-KEIL compiler-6 that you've been using through uVision IDE>. The ternary operator is used to define all versions of the assert() macro in that file. A significant advantage of defining assert as an expression as opposed to a statement, is that it can be safely used inside if-else control-flow. If you defined assert() as an if-statement, you could run into the dangling-else problems. The else branch from your code could be misunderstood as else from the assertion if-statement. But going back to the Q_ASSERT_ID() definition, the boolean expression "expr_" is evaluated, and if it's true, the ternary operator returns zero cast on void, just like the standard assert. Otherwise, the ternary operator calls the Q_onAssert() function, which, as you can expect, will be your custom error handler. To uniquely identify the specific assertion, the Q_onAssert() callback function takes the ID parameter and the `Q_this_module_` string. This is a departure from the standard practice of identifying assertions with the __LINE__ number and the __FILE__ string. Why depart from the beaten path? Well, __LINE__ is quite an unstable ID for an assertion because adding or removing lines changes all the following line numbers. As I will discuss in a minute, testing assertions requires a *stable* way of identifying them. Similarly, the string generated by __FILE__ often depends on how the file is specified to the compiler. If the file is specified with the path name, __FILE__ expands accordingly. Also, if you use multiple assertions in a given file, some embedded compilers might generate multiple copies of the file string, which is wasteful. To avoid all such problems, the qassert header file provides a macro Q_DEFINE_THIS_MODULE(), where you can define your own module name string, typically at the top of each module. That static and const pointer will be subsequently used in all assertions in a given file. Going back to the Q_onAssert() function, it is defined as never returning with the Q_NORETURN macro. If that macro is not defined elsewhere, the default definition uses the C99 _Noreturn specifier. This can help the compiler to perform optimizations as execution paths that violate asserted conditions are unreachable. Additionally, if you use static analysis tools, the Q_onAssert() function should be given the "no return" semantics, like it is done in the QPC framework for the PC-Lint-Plus static analysis tool. This helps the tool to better understand your code and avoid diagnostics for asserted conditions. Now, qassert.h provides many other assertion variants, such as Q_REQUIRE(), Q_ENSURE(), and Q_INVARIANT(). These correspond to preconditions, postconditions, and invariants, respectively. The names of the macros are direct loans from the Eiffel programming language that natively supports Design by Contract. The most useful and frequently used are preconditions, which you designate with the macro Q_REQUIRE_ID(), or Q_REQUIRE(). Preconditions are specified inside the functions, but they spell out contractual obligations for the callers of these functions. So, when a precondition assertion fails, you know the problem is not with the function but rather an incorrect call to that function. Using a precondition assertion, as opposed to just a generic assertion, is an excellent way of documenting a function and providing a hint as to where the problem might be. In that way, preconditions should be an integral part of the function's documentation, just like the list of parameters, because the callers of the function should know and satisfy the preconditions. The other variants of assertions, like postconditions and invariants, are less frequently used but also provide valuable documentation as to the purpose of these assertions. Another frequently used assertion variant is Q_ERROR(), which simply calls Q_onAssert(). This one is useful for incorrect paths through the code, such as unwanted events in a state machine. The Assertion Handler Regarding the Q_onAssert() handler, in the traditional use of assertions only during debugging, you might get away with a simplistic implementation, such as an endless loop. That way, when an assertion happens... you can break into the program with a debugger and find it conveniently spinning inside Q_onAssert(). You can then inspect the call stack to see where the assertion fired. But you can also leave assertions enabled in the final product, which I will strongly advocate in a minute. In that case, the assertion handler becomes critically important as your last line of defense after detecting an error. To provide real protection, however, the assertion handler must be carefully designed to perform damage control and corrective actions for your specific system. Please remember that the assertion handler should not return, so most of the time, it should end with resetting the system. For that, the CMSIS provides the function NVIC_SystemReset(). Also, please remember that the assertion handler runs after your system has already been compromised. For that reason, the assertion handler is NOT your "usual code" and must be carefully tested under various fault conditions, such as stack overflow, interrupt preemption, etc. For such testing, I highly recommend the fault-injection technique you saw in several lessons of this video course. For example, you can intentionally change the stack pointer (SP) register to simulate stack overflow and then cause an assertion failure. Assert Handler and Exceptions In programming languages that support throwing and catching exceptions, such as C++, assertion handlers often throw exceptions. In my opinion, this is a mistake for a couple of reasons. First, throwing an exception makes sense only if that exception can be caught and reasonably handled with the intent to continue program execution. None of this applies if assertions are used correctly. And second, throwing an exception involves unwinding the stack, which implicitly assumes that the stack hasn't been compromised. The main purpose of the fuse analogy was to recognize that the power of assertions derives from their simplicity. You should keep the assertion handler as simple as possible because any complications, such as throwing exceptions, only increase the chance of failures while already in a compromised situation, thus defeating the purpose of effective damage control. Hardware Assertions So far, this lesson was only about software assertions placed explicitly in the source code. But there are also several failure conditions detected in hardware. For example, in the lessons about the startup code, you've seen so-called fault exceptions, such as HardFault, MemManage, BusFault, and UsageFault. Traditionally, these conditions haven't been seen as related to assertions, but for all intents and purposes, the hardware fault handlers and the assertion handler have the same function. Unlike software assertions, you cannot disable hardware faults in the final product, so you cannot get away with fault handlers coded as endless loops (unless, that is, the users of your device can tolerate permanent "denial of service"). Therefore, since you must implement a robust fault handler anyway, you might as well develop just one common handler for both hardware and software assertions. This is precisely how the startup code that you've been using in most of the lessons in this course is structured. As you can see, the fault handlers branch to assert_fail(), which carefully resets the stack pointer, in case of stack overflow, and then it calls the familiar Q_onAssert() handler. Impacts of Assertions As I confessed in the introduction, assertions helped me more than any other programming technique. For example, if you search the Quantum Leaps Free Support Forum for "assert," you will find 23 pages of posts. Many start with an assertion failure report and end with an observation of how difficult the bug would be to find and fix without the particular assertion. But you don't need to take only my word for it. One of the rules for developing safety-critical code by NASA JPL Laboratory for Reliable Software is not only to use assertions but to achieve a sufficient density of about two assertions per function. Also, Microsoft Research published a paper showing studies of commercial software projects where modules with a low density of assertions had much higher post-release bug rates than those seeded with many assertions. By the way, Links to all papers and resources that I mention will be provided in the video description. As Bertrand Meyer remarked: "The use of assertions will completely change your approach to software construction and in particular your view of errors." Working with software programmed "offensively" with assertions feels completely different than with software programmed "defensively." With the recommended density of assertions, the software stops producing undesired behavior, denial of service, or crashes. All bugs manifest themselves as assertion failures. This effect is truly amazing. The integrity checks embodied in assertions prevent the code from "wandering around." Even hardware failures and broken builds don't crash and burn, but end up in the assertion handler. Once an assertion fires, dismissing the problem as an intermittent "glitch" is much harder, so all errors require attention. You also have a record in the form of the unique assertion-id to start your investigations. This makes most bugs more transparent. In contrast, testing defensively programmed code is inconclusive. You might be running tests for days and nights, but you can never be sure if the runs were truly successful or perhaps the program silently produced garbage all night long. Disabling Assertions As I already mentioned, software assertions can be disabled, and the standard practice is to use them only during debugging but disable them entirely in the final product. For example, here is what I heard just yesterday from one engineer working on a medical device: "We don't have assertions enabled for the released version of our codebase. The goal is to detect and fix assert failures during development." I absolutely agree with the second sentence: to do everything in our power to fix all bugs during development and never fail assertions in the final product. But once assertions have been used and tested, I don't really understand the logic behind disabling assertions. All the analogies for forming the proper mental model of assertions also show that disabling assertions is, frankly, ridiculous. If you think of assertions as fuses, would you replace all the fuses with nails, coins, and paper clips for real use? How would you feel if you discovered a fuse box "fixed" like that is a car you are about to drive or an airplane you are about to fly? It wouldn't inspire much confidence in your safety. Would it? Yet, this is precisely what disabling assertions does to the code. If you think of assertions as guardrails, would you prefer to drive on a mountain road with or without tem? I mean, you never intend to hit a guardrail anyway, so you don't need it, right? Finally, if you view assertions as an insurance policy, would you cancel it right before a fire season? I mean, you don't really want to cash such a policy, do you? But if your house burns down anyway, it sure helps to have a damage control mechanism in place. I hope you get the picture... This concludes the lesson about assertions and Design by Contract for embedded systems. If you like this channel, please give this video a like and subscribe to stay tuned. You can also visit state-machine.com/video-course for the class notes and project file downloads. Finally, all the projects are also available on GitHub in the Quantum Leaps repository "modern embedded programming course." Thanks for watching!