From: Proceedings of the Workshop on Reflective Programming in C++ and Java (Vancouver-Canada), Oct 1998.
Using a Reflective Architecture to Validate Object-oriented Applications by Fault Injection
Amanda Cibele Apolinário Rosa Institute of Computing State University of Campinas (Unicamp) P.O Box 6176 Campinas - 13083-970 SP Brazil
[email protected]
Eliane Martins Institute of Computing State University of Campinas (Unicamp) P.O Box 6176 Campinas - 13083-970 SP Brazil
[email protected]
ABSTRACT
This text presents FIRE, a tool developed to support reflective fault injection in order to validate objectoriented applications. FIRE was implemented using OpenC++1.2, and its aim is to validate C++ and OpenC++1.2 applications. Some issues about the use of FIRE in fault injection experiments are shown here. Keywords
Software-implemented fault injection, object-oriented systems, reflective programming , metaobjects. INTRODUCTION
The objective of software fault injection techniques is to modify the hardware/software state of the system under software control, thus causing the system to behave as if any fault was present [5]. These techniques are being increasingly used to validate dependability properties of software systems, so several tools have been developed. Such tools present several injection methods, which vary according to the fault injection destination, (fault-tolerant mechanisms, application, operating system, or communication channels) and the difficulties or facilities offered by the operational or development environment of the target system. According to [4] those methods can be categorized according to injection time: during the compile-time or during runtime.
In compile-time injection, the original program instructions are substituted for alternate ones which inject faults. During runtime, two elements are necessary: an extra software to inject faults and some mechanism to trigger the injectors, i.e., the application must be prepared to fault injection experiments. Compile-time injection presents low intrusiveness, since it is not necessary to control the experiments during runtime and no instrumentation is introduced into the target system. That approach, however, presents low controllability and it is neither easy to know if a fault was activated, nor to monitor its effects. Runtime injection presents larger control and observation facilities, but greater intrusiveness unless the injector and the monitor use some hardware device [6] or special characteristics of the target system environment [1] and [7]. In another context, object-oriented paradigm has been increasingly used as an effective programming model because it organizes better the system’s components. Particularly, object-oriented programming is being increasingly used to implement fault-tolerant applications. Therefore, it is very important to develop tools to validate those applications. In our work we propose the use of computational reflection to minimize the perturbation to the target application when testing object-oriented applications by runtime software fault injection. Reflection was chosen because it is a simple way to separate the instrumentation functionality from those of the target application. Our method was implemented in a reflective fault injection
tool (FIRE) for applications developed in C++ or OpenC++1.2 [2]. The remaining text is organized in the following way: section 2 presents our reflective approach for reflective instrumentation and its advantages to software fault injection. Section 3 gives a brief presentation of FIRE. Section 4 presents some of experiment’s results and section 5 concludes this text. A REFLECTIVE APPROACH FOR INSTRUMENTATION
As previously mentioned, one of the most important drawbacks of software injection techniques during runtime is the target system’s structure and behavior disturbance caused by instrumentation. The structural disturbance happens because the injection and monitoring aspects are generally mixed together with application’s functionality. Behavioral disturbance is caused by the execution of additional software introduced on the target system in order to trigger injectors and to inject faults. A possibility to decrease the structural disturbance in the target system is to prepare the system in order to reduce the alteration to its original structure, guaranteeing the separation of concerns. Behavioral disturbance cannot be totally avoided without using some hybrid approach (combining hardware and software) or some special features of the underlying platform, like in Fiesta [7] or Xception [1]. The idea of using reflection for fault injection appears then, as a viable solution to reduce the intrusiveness problem, at least with respect to the structural disturbance. In reflective programming the system architecture is divided into interrelated subsystems: a target subsystem (base-level), that makes computations on an external domain, and a reflective subsystem (meta-level) that makes computations on the target subsystem. The baselevel in this study is used to implement application’s objects. The instrumentation for fault injection and for monitoring, then, can be introduced as a reflective subsystem which observes and manipulates data and actions executed on the target subsystem injecting faults and observing its behavior. Reflection and object-orientation advantages to software fault injection:
present
several
•
Reduction of the disturbance in the target application’ structure. The fault injection and monitoring requirements (encapsulated in metaobjects) are separated from the requirements associated to the purpose of the application (encapsulated in objects).
•
Reusability of components. The separation of domains allows the aspects of fault injection and monitoring to be developed independently of the application, propitiating the reusability of application’s objects as well as of metaobjects.
•
Flexibility in the fault injection. Using metaobjects allows to inject different types of faults in different objects of the base-level, according to its characteristics.
•
Easy handling. Metaobjects of fault injection and monitoring can be easily incorporated and removed from the application under test.
FIRE: A REFLECTIVE FAULT INJECTION TOOL
FIRE (Fault Injection using a Reflective Architecture) was developed using the C++ language and the pre-processor OpenC++1.2. Reflection was used to implement an injection and monitoring library, which is linked to the target application. In this section we present the following aspects of FIRE: the reflective architecture, the trigger mechanism, the injection method, the fault model and the experiment control. Reflective architecture for fault injection
The reflective architecture for software fault injection proposed in this study is shown in Figure 1. Its main components are: an application, representing the target system application implementing the fault-tolerance mechanisms to be validated; a meta-level library, and a controller. The application is an object-oriented fault-tolerant application, which must be submitted to a preparation phase before testing. In this phase the clauses declaring the reflective components (classes, methods, or attributes) are introduced into the source code. In OpenC++1.2 instances of a reflective class can be reflective or not. In this phase, it is also necessary to declare which objects will be reflective and which will not. In the following the reflective objects will be called as injectable objects. The meta-level library contains metaobjects called schedulers1 each composed of an injector and a sensor. An injector is responsible for the introduction of prespecified faults into an injectable object, and a sensor is responsible for readout collection. As a metaobject, a scheduler is created when its associated injectable object is instantiated. Its function will be further explained in 3.2. The controller controls the experiments: it starts the application, controls injectors and sensors and stores results. The controller is composed of three objects: the injection manager, the monitor and user interface. The injection manager reads the pre-specified faults from a Fault file and transfers them to schedulers, which transfer them to injectors at injection time. The monitor receives and stores the collected data in the History file. The user
1
The scheduler is a composite object created because of a limitation of OpenC++1.2: each base-level object can be associated with one and only one meta-object. Separation of injection and monitoring functions is necessary for modularity and reusability concerns, thus, the need of a composite metaobject.
interface helps users to observe and control the execution of experiments at runtime.
User FIRE Controller Interface history
Monitor
Meta-level
faults
Injection Manager
Scheduler = Injector + Sensor
Base-level Target Application
Figure 1 A reflective architecture for fault injection and monitoring. Trigger mechanism
The trigger mechanisms used by most of the tools are based in the spatial activation (when the execution researches a specified address [5], and/or certain data is used [1]) or in the temporal activation (after interval of predetermined time). FIRE presents a new activation mechanism: message interception. Message interception is the characteristic provided by the OpenC++1.2 metaobject protocol to transfer the flow of execution from base-level to the meta-level, and it can occur either when a reflective method is called or when a reflective attribute is accessed. The message interception mechanism of OpenC++1.2 offers a simple and efficient way to trigger the injection/monitoring modules. In FIRE the injectors and sensors activation is done indirectly through the schedulers activation. The interception of messages is used in order to “wake up” a scheduler. In the meta-level, the scheduler, when necessary, activates an injector and/or a sensor. Category_names are associated with reflective methods and attributes to identify the action to be taken (injected and/or monitored); that way, a scheduler recognizes that the reflective method or attribute has a category and changes method invocations and attribute access accordingly. Injection method
The injection is done by corrupting values of attributes and/or arguments of reflective methods. Those values are available in the meta-level through container objects of the class ArgPac, which represents a stack. The scheduler knows the stack’s top and it informs this address to the injector. The injector applies the mask and the operation
specified in the fault instance to the content of that address added to a displacement informed by the user. Fault Model
In our approach to testing we are doing a parallel with hardware chips by considering objects as software IC’s, as proposed in [3]. When considering objects as software IC´s (integrated circuits), we can use the same correspondence as Hoffman; that is, each public method (or attribute) can correspond to a pin, or to a related set of pins. Therefore, a parallel with hardware fault injection can be made, and we can have the introduction of internal faults2 (affecting an object’s private features) or external faults (affecting an object’s interface). Another dimension of fault classification is based on the repetition pattern; that is, faults can be transient (never repeated), intermittent (repeated based on activation injector number; i.e. a fault can be reinjected between N injector invocation) or permanent (always repeated). The proposed tool can inject transient, intermittent and permanent faults. Experiment Control
When an automatic fault injection experiment is initiated, FIRE asks the user about: prepared target application name, fault_file name and history_file name. Afterwards, FIRE: I.
Reads a fault from a given fault_file and writes it into a fault_buffer;
2
Although OpenC++1.2 does not allow private features of an object to be reflected, internal faults can still be considered for protected features.
II. Starts the application process and waits it to terminate (during the application run, the fault in the fault_buffer is read by the first instanced scheduler, this fault is injected by injectors at proper moments, and data are collected by sensors).
CONCLUSIONS
This procedure is repeated for every fault in the fault_file. At the end of every experiment all data collected by sensors are retrieved by the monitor and stored in a history_file.
The tool uses the facilities offered by the protocol of metaobjects of OpenC++1.2 to inject faults and monitor their effects. The original structure of the target application is not altered, just some directives (C++ comments) and some indications are introduced; trap instructions are not inserted, it is not necessary to execute the application in the trace mode, nor special hardware or development environment characteristics of the target application are used, as in other software fault injection tools. Up to now, FIRE can inject faults in methods, through the corruption of values of arguments, in functions, altering values of arguments and results, and in attributes, altering values during the reading or assigning.
OUR EXPERIMENTS USING FIRE
In this section, we present some issues concerning the use of FIRE. Our objective in these experiments was not to validate the dependability proprieties of the used target applications, but to investigate the applicability of FIRE. In a first moment, we used FIRE in verification experiments of RobustStack [8], a distributed objectoriented application that implements a fault-tolerant stack. This application uses two optional mechanisms to tolerate software faults: recovery blocks and N-versions. Three variants of stack are used. In the experiments external faults were injected by corrupting arguments of public methods. It were injected transient, intermittent and permanent faults. The experiments were very useful because they demonstrate the capability of FIRE to reveal implementation faults on the target application. But, we came across some limitations to prepare the target application: •
OpenC++1.2 didn’t accept some include’s used in the target application (to solve this problem we removed the problematic include’s, we used OpenC++ to produce the reflect characteristics and before the application compilation we put the include’s back to the code);
•
Template classes could not be instrumented because the g++ compiler detected some syntax errors in the OpenC++ generated source;
•
Sometimes we wanted to inject faults and/or monitor internal characteristics of objects, but the MOP of OpenC++1.2 don’t permit to do that.
After doing the experiments using RobustStack, we did some tests using simple reflective programs to verify the applicability of FIRE on reflective applications. In theses cases we observed the following: •
As the application already had a meta-level, the injection and monitoring features were introduced like a meta-meta-level. Because in the reflective architecture, one level controls the level immediately bellow it, our instrumentation was introduced into the application meta-level. To corrupt the base-level data, the scheduler must find those data into the meta-level data reified to the meta-meta-level.
This summary presented FIRE, an injection tool for software fault injection. FIRE is implemented in C++ and OpenC++1.2, and is aimed to validate applications implemented in C++ or OpenC++1.2.
It would be interesting for software fault injection at execution time: •
That a MOP provided a larger flexibility in the relationship between object and metaobject. It would be interesting, for example, that a reflective object could be associated to two metaobjects: one to inject faults and another to monitor their effects. Concerning reflective applications, that a fault injection metaobject could be associated with a base-level object, instead of its metaobject;
•
That a metaobject had access to the maximum of information on the object with which it is associated, i.e., totally breaking the barrier of the object encapsulation.
Regarding the use of reflection to software fault injection at compile time, we think that the meta-class model is also a viable way to inject faults through code mutation, because it can substitute the original class structure by alternate faulty ones. In spite of some limitations of the OpenC++1.2 metaobject protocol, our experience in the development of FIRE revealed that the implementation of fault injection and monitoring characteristics in the meta-level decreases the disturbance in the application’s structure and increases the flexibility in the fault injection. Up to now, we can’t compare the behavioral disturbance produced by FIRE on target applications with other tools. ACKNOWLEDGMENTS
We thank FAPESP and CNPq that financed this work. REFERENCES
1. Carreira, J.; Madeira, H.; Silva, J.G. “Xception: a software fault injection and monitoring in processor functional units”. 5th IFIP International Working Conference on Dependable Computing for Critical
Applications, Urbana-Champaign, Illinois, USA, 1995, pp. 135 -149. 2. Chiba S., “Open-C++ Release 1.2 Programmer’s Guide”, Technical Report no 93-3, Dept. Information Science, University of Tokyo, 1993. 3. Hoffman D., “Hardware Testing and Software IC’s”, Proc. Pacific NW Software Quality Conference. Portland, Oregon, Sept. 1989. 4. Hsueh, M.C.; Tsai, T.K.; Iyer, R.K. “Fault Injection Techniques and Tools”. IEEE Computer, Apr/1997, pp. 75-82. 5. Kanawati, N.; Kanawati, G.; Abraham, J. “FERRARI: A Tool for the Validation of System Dependability Properties”. Proc. FTCS-22, IEEE CS Press, Los Alamitos, Calif., 1992, pp. 336-344.
6. Kanawati, N.; Kanawati, G.; Abraham, J. “Dependability evaluation using hybrid fault/error injection”. IEEE Transactions on Computer, Feb/1995, pp. 224-233. 7. Krishnamurthy N., Jhaveri V., Abraham J. “A Design Methodology for Software Fault Injection in Embedded Systems”. Proc of the 1998 IFIP International Workshop on Dependable Computing and its Applications, Johannesburg, South Africa, Jan, 12 - 14, 98, pp237 - 248. 8. Piubeli D. Prado. “Fault-tolerant System Implementation Using Object-Oriented Programming”. Master Thesis, Institute of Computer Science, State University of Campinas, Campinas, Brazil, Jan/1998. (in Portuguese).