Solutions and Tools
We mentioned different types of problems that might occur during migration from 32-bit to 64-bit architectures. Below we suggest various approaches, define some rules and show how those rules can be enforced with static analysis tools. Here we will describe solutions that can be enforced through static analysis. The implementation of the rules may vary across different static analysis tools.
Integer Types Discrepancies. While some of these defects may be found by compilers and produce warnings, you can write a rule that catches all explicit and implicit casts (conversions) flagging those which are subject to change between two systems. Specifically, all casts to long may be reported as architecture-dependent. In most of the cases using appropriate types defined in system headers, would produce more stable and consistent code regardless of the architecture where the code is compiled.
Casting Between Pointers and Integers. Previously, we recommended avoiding these casts as much as possible and even offered safe macros that get access to a pointer’s numeric value strictly through pointer arithmetic. If this technique is accepted, all size discrepancies will be reduced to integer conversions described in the paragraph above.
If conversion to new style macros is not feasible, we can still rely on compiler’s abilities to catch casts between integers and pointers. Since various compilers differ in their ability to catch problematic conversions, commercial static analysis tools also can be used to catch this type of errors.
Structures for Inter-program Communications. Once again, the goal here is to make sure that when the same structures are compiled on two different architectures, the displacement of each field stays the same regardless of the architecture. Essentially this means that each field in the structure has exactly the same size regardless of whether it is a 32-bit or 64-bit architecture.
There are two major approaches to reach this goal:
- Use preselected types with explicit size, like uint8_t, uint16_t, uint32_t, uint64_t, etc. While each of those types might be typedef-ed to different base types, each of those types is guaranteed to have the same size within the structure.
- Make sure all types are typedef'd only to architecture-independent basic types, like char, short, int and long long, but not long.
- In all structures passed to predefined I/O functions, check for all prohibited typedef-s. This would normally include size_t, uid_t and others that have different sizes on different architectures. All usages of such elements within I/O structures are flagged as non-portable.
- Check for all allowed typedef-s. This includes uint8_t, uint16_t, etc. Those are trusted typedef-s and they are unconditionally allowed.
- For any other integer type we find its base type and flag it if it is architecture-dependent (like long).
Most of the potential problems with migration to 64-bit architecture are well known and understood. Various tools may be used to automatically detect those defects. Here we list a few of the rules that we have developed that have helped companies detect problems statically. Using static analysis enables companies to find problems early and to detect them fast and comprehensively throughout the entire codebase.
- All unguarded integer truncations where a longer integer type is assigned (casted) to a shorter one without range checks.
- All implicit casts from unsigned integer to signed integer.
- Casts between pointers and integers (except for constant 0).
- Checking preselected structures or structures passed to preselected I/O functions for fields that are unsafe to migrate.
- Checking structures alignment for consistency.
Some companies defined certain naming conventions and private types for their codebases. Sometimes it is needed to get into the root of those conventions and extend them in the way that they encompass rules to accommodate 32-bit to 64-bit migration. It might be different for a given company, but there is a field-tested methodology that allows achieving smooth migration from 32-bit to 64-bit architecture.