Defensive programming mitigates unforeseen problems
What works in the lab doesn't always work in the field, often due to unanticipated interactions and undiscovered errors. Defensive programming can help make designs more resilient, but developing embedded software that can handle unforeseen circumstances is not a trivial matter; it requires discipline and forethought. Here are five tips to help developers become better defensive programmers.
Tip #1 – Checksum or CRC data
The use of checksums and cyclic redundancy check (CRC) algorithms is a great way for a developer to verify that data being sent across a serial link is indeed correct. Embedded systems once vetted will always behave as expected in the controlled environment of the test bench. Once a system is released into the wild however, the environment in which the system is operating becomes a great unknown. A noisy environment could create communication noise that results in bit flips and misread data. The best hope for detecting this corrupted data is to have a sanity check attached to the data through the use of a checksum or CRC.
Tip #2 – Design by contract
Design by contract is an approach for developing software that results in a highly defined software interface where each function is associated with well-defined pre-conditions and post-conditions. The idea is that if an application is going to call a particular function, the calling application must meet the function's pre-conditions in order to get a valid response or action. Design by contract can be a powerful tool for developers because it explicitly specifies what a function is expecting to receive and what the guaranteed output will be with valid pre-conditions. Since the expectations aren’t "read between the lines," a developer calling the function knows exactly what the expectations are in order to use the function.
Tip #3 – Use assert
The assert macro is a great way for developers to verify their assumptions about their application at a given point within an application. The use of assertions can be very powerful for catching bugs and unexpected behavior in a program the moment that the bug occurs. Assertions can even be used in a Design by Contract environment to verify that the pre-conditions and post-conditions of the contract have been met. For an introduction to the use of assertions in embedded software consider reading 8 Tips for squashing bugs using assert in C and When to assert or not assert.
Tip #4 – Check pointers and buffers
Pointers and buffers are two places where developers always seem to get themselves into trouble. When developing an embedded system in C, it is really easy to accidentally de-reference a NULL pointer or overflow a buffer. Defensive programmers should be checking the validity of a pointer before de-referencing it. Is the pointer NULL? Don’t de-eference it! Is the value stored in the pointer a valid value? If so then de-reference.
Pointer arithmetic and the use of arrays can be quite dangerous as well. Developers should be adding boundary checks to buffer and pointer arithmetic operations to ensure that the results remain within the memory spaces they are supposed to. Accidentally overwriting memory by just a single byte could have catastrophic consequences for the embedded system and, more importantly, its users.
Tip #5 – Use a stack monitor
Performing a worst-case stack analysis and properly sizing the stack is a difficult endeavor. Usually the size of the stack is either left at the compilers default settings or the developer scribbles a few possible values on a piece of paper and uses the "eeny meeny miny moe" technique. Neither technique is sufficient and the worst-case situation that the stack will overflow becomes a real possibility.
Developers can help guard against a stack overflow by monitoring for such an event. Most real-time operating systems have a stack monitor built into them. Enabling the stack monitor is nothing more than adjusting a macro with the configuration for the RTOS. In a bare-metal system, a developer needs to be a bit more pro-active and either write a stack monitor themselves or use one of many available stack monitors that can be found on the internet. To learn more about stack monitors consider reading Create a stack monitor in seven easy steps and Improving code integrity with a stack guard.
These five tips are just a few examples of how developers can improve their embedded software through defensive programming. There are many additional techniques such as writing secure code and encrypting data that can help to improve the chances that an embedded system will continue to function even when unforeseen circumstances strike.
Jacob Beningo is a Certified Software Development Professional (CSDP) whose expertise is in embedded software. He works with companies to decrease costs and time to market while maintaining a quality and robust product. Feel free to contact him at firstname.lastname@example.org, at his website www.beningo.com, and sign-up for his monthly Embedded Bytes Newsletter here.
Join over 2,000 technical professionals and embedded systems hardware, software, and firmware developers at ESC Minneapolis November 4-5, 2015 and learn about the latest techniques and tips for reducing time, cost, and complexity in the embedded development process.
The Embedded Systems Conference and EDN are owned by UBM Canon.