Escolar Documentos
Profissional Documentos
Cultura Documentos
Systems Development
A White Paper
Markus Heiling
Software Development Engineer, C/C++ Compiler Development
Summary of Contents: This document discusses runtime checks for C applications with special
regard to the requirements of embedded systems development.
Introduction
Runtime checks are features provided by a C/C++ compiler and/or a C-Runtime Library to help the
developer track down problems at the source code line level. Runtime checks are performed when
the program is executed. Runtime checks should not be confused with static checks or analysis,
which is performed at compilation time. The checks and analysis performed by a C/C++ compiler
are a valid help in development, but they are not capable of solving the more complex, hidden
problems which can be easily introduced during an application’s implementation and coding phase.
Runtime checks are performed when an application is executed. To execute the application, the
C/C++ compiler must be able to implement the application code with a noticeable amount of
additional code. While this code will not be visible to the developer at the source code level, it will
increase the amount of binary code created and the application’s execution time. However, this
increase of precious resources, especially in regards to an embedded system, is only temporary.
1
Runtime checks are used only in the debugging phase of an application and are not recompiled
when the application is built for release. The increase of code size and execution time during the
debugging phase is a required tradeoff for the time used to debug the application and isolate the
problem.
Practice shows that a decrease in the time needed to find the problem is more important and cost-
effective than trying to debug an application which is built for release. When a developer must find
the problem location in a release application, the developer must implement the application code
manually. Manual implementation also changes the original application. In many cases, manual
implementation cannot be avoided. Since manual implementation cannot be avoided, the developer
can use the automatic implementation of the C/C++ compiler to cover more error checking
scenarios and transparently inject the application code, thus saving development time.
Code Implementation
This section explains how the C/C++ compiler implements the application code by examining a
small example.
The following code example performs a read access to the global array 'numbers' by using the
array index 'index' passed to the 'readNumber' function.
int numbers[10];
int readNumber(unsigned int index)
{
return numbers[index];
}
This code will function without flaws provided that the array index is within the valid range of 0-9.
If the 'readNumber' function is invoked with a value outside the range of 0-9, the code will have
unpredictable results. Depending on how the result of the 'readNumber' function is further used
within the application, this flaw may lead to a serious problem. It is important to note that such
flaws may pass manual and automatic testing of an application because of the nature of this flaw.
The result returned by 'readNumber' depends on the index value (assuming that it is out of range)
as well as the memory contents which are actually read. Invalid indexes may not be detected by test
suites. Invalid array indexes are detected by implemented code.
To help the developer find the problem, the C/C++ compiler analyzes and implements the code at
runtime to verify that the array index is valid.
2
Everything between the START and END comment marks is inserted by the C/C++ compiler.
Analyzing the code at compilation time reveals that the application wants to access an array
variable whose size is known. Thus the read access to that array can be implemented to secure the
access and trigger an error condition and output the corresponding line of source code.
If the C/C++ compiler cannot perform the code implementation, the developer has to achieve the
same accurate result by manually adding similar code fragments. Adding similar code fragments
increases the source code base of the application and makes the code base more difficult to
maintain. Most importantly the C/C++ compiler automatically detects each statement and
expression in the C source code where this kind of implementation is required. This is a time
consuming task for the developer but relatively easy for the C/C++ compiler.
Conditions of Implementation
Beyond the check of array access bounds shown above, the following list of runtime checks
features can be used to ease problem tracking in an application.
Assignment Bounds. Storing a value into a bit field variable that is too small.
3
int div(int a, int b) {
return a / b;
}
The C/C++ compiler implements the application code to check that the divisor is not equal to zero.
The application dereferences a pointer, 'ptrNumbers', whose base object is not known to the
C/C++ compiler. Since the C/C++ compiler has no chance to determine the validity of a memory
range for a pointer, it does not use the "Array Bounds" runtime check. The C/C++ compiler
implements the code with a NULL-pointer check. The NULL-pointer check is part of the "Array
Bounds" check.
4
The implementation of the code for this type of check is more complicated than the zero divisibility
or NULL pointer reference checks. For each pointer, a small local or global data structure is created,
which stores the base address and the size of the pointer’s object.
char array[12];
char *ptr = array;
For the object ‘array’ a data structure is not necessary since the ‘array’ variable is the object and
the C/C++ compiler knows the base address and the size. When a pointer assignment is done ('ptr
= array' in this example), the internal data structure for the pointer ‘ptr’ is created and initialized
with the base address and the size as follows:
ptr_start = &array;
ptr_size = 12;
When the pointer, ‘ptr,’ is dereferenced, it is verified that the pointer is within the range [ptr_start,
ptr_start+ptr_size]. If the pointer is not within this range, an error reporting function is called
from the runtime system.
As a side effect of this technique, the mechanism automatically detects NULL-pointer dereferences.
Thus there is no need to insert additional code to check explicitly for NULL pointers.
The pre and post stack frame verification blocks are filled with a test pattern which can be verified
with an according function from the runtime system. The most common stack corruptions occur
when overwriting stack frames within a function can be detected. The pattern used to fill these
blocks is the current value of the stack pointer at function entry. This will also point out problems
in function recursions which are using a corrupt stack frame.
The stack frame will be filled with a test pattern, such as 0xCC, which can be used to detect access
to uninitialized memory.
At function exit, another function from the runtime system is called. The function verifies that the
pre and post stack frame validation blocks were not modified within the function and the stack
pointer is the same as it was at the function’s entry point.
5
As a side effect, the stack frame corruption runtime check feature can be used to also perform the
stack overflow checking, thus no extra implementation code is required.
void foo(int i)
{
switch (i) {
case 1: /* do something */ break;
case 2: /* do something */ break;
// START: RTC-Instrumentation
default: rtc_unhandledSwitchCase(__FILE__, lineOfSwitch);
// END: RTC-Instrumentation
}
}
6
Conclusion
Using a C/C++ compiler capable of instrumenting code with additional runtime checks can
noticeably reduce the efforts of finding application errors during the development phase.
Additionally, this technique offers the possibility of verifying the application in the testing phase
for the correct use of resources and assumptions made in development phase.
Contact:
Mentor Graphics 739 North University Blvd.
Embedded Systems Division Mobile, Alabama 36608
Phone: 251.208.3400
Fax: 251.343.7074
Toll free: 800.468.6853
Email: embedded_info@mentor.com
Web: http://www.mentor.com/embedded