Preparing for Porting to 64-Bit Platforms
September 30, 2010
3 min read
By finding and fixing C/C++ memory errors before you port to 64-bit, you can reduce the risk of failures on the new platform and architecture, streamline the porting process, and make the original application more robust and reliable.
Here’s a process you can apply to prepare your application for the 64-bit porting process:
- Perform mutation testing via runtime memory error detection
- Use static analysis to identify error-prone and non-portable code
- Repeat runtime memory error detection
- Perform unit testing (Optional)
Step 1: Perform Mutation Testing via Runtime Memory Error Detection
Before you start porting, you should rid your original code of problems that will plague you on the 64-bit processor; for example, memory corruption and memory leaks. One of the most effective ways to expose the types of pointer and integer problems that will cause trouble on 64-bit processors is to leverage Mutation Testing for runtime memory error detection.
Runtime memory error detection technologies such as Parasoft Insure++ allow you to leverage techniques from traditional mutation testing to uncover ambiguities that are difficult to detect through other methods or tools. Whereas traditional mutation testing attempts to create “faulty” mutants to create a more effective test suite, mutation testing for runtime memory error detection creates and executes what should be functionally equivalent mutants of the source code under test.
When one of these mutants performs differently than the original program, it indicates that the code’s functionality relies on implicit assumptions which may not always be satisfied during execution. If a mutant causes the program to crash or encounter other serious problems, it’s a sign that when the assumptions are not satisfied, serious errors will occur at runtime.
In C++, this process of creating and running equivalent mutants can uncover:
- Lack of copy constructors or bad copy constructors.
- Missing or incorrect constructors.
- Wrong order of initialization of code.
- Problems with operations of pointers.
- Dependence on undefined behavior such as order of evaluation.
Step 2: Use static analysis to identify error-prone and non-portable code
After you clean the most critical errors exposed by mutation testing, use a static analysis tool to identify code that is likely to cause trouble when it is ported to the new platform/architecture. Static code analyzers use a compiler-like front-end to build a syntactic and semantic model of the software. The syntactic model is then analyzed against a set of rules or “checkers” to see if the code is in violation. These checkers use pattern-matching algorithms to detect errors such as poor use of language constructs, use of insecure functions, and detect poor coding practices that hinder portability (to be covered in my next post) and other violations of coding guidelines.
More sophisticated checkers employ semantic analysis that uses data and control flow to detect complex bugs and security vulnerabilities. To do this, the static analyzer builds an execution model of the software, considers possible paths through the code, and evaluates use of data as it flows from source (e.g. user input) to its destination (e.g. an API call such as a system call). Analyzing every single possible condition and path would be too time consuming, so the analyzer uses heuristics to detect the most likely paths for evaluation. Types of errors detected by these checkers include null pointer deference, buffer overflows, and security vulnerabilities like command and SQL injections.
There are two main tasks to focus on while performing static analysis:
- Identify and fix code that is likely to result in an error on any platform or architecture.
- Identify and fix code that might not port well.
First, check industry-respected C/C++ coding standards that identify coding constructs, which are likely to lead to problems on any platform or architecture. By ensuring that code complies with these coding standards, you prevent errors. This translates to less debugging on the new platform or architecture and reduces the chance of having bugs that elude testing and make their way into the release.
Step 3: Repeat Runtime Memory Error Detection
Repeat runtime memory error detection to verify that the modifications you made while fixing static analysis violations did not introduce any runtime errors.
Step 4: Perform Unit Testing (Optional)
At this point, you might want to perform one more step to ensure that your code is as error free as possible before you port it. This additional step is unit testing. Unit testing is traditionally used to find errors as soon as each application unit is completed. It can also be beneficial later in the development process because, at the unit level, it is easier to design inputs that reach all of the functions. In turn, this helps you find errors faster and expose errors that you might not uncover with application-level testing.
In the next post I cover the use of static analysis to help prepare code for porting to 64-bit platforms.