Overview of the AME

Introduction

In the software production life cycle, programmers often encounter a need to understand the behavior of a program. This need arises in the debugging, testing, performance tuning, or maintenance of a large program. Programmers use execution monitoring tools to help them gain more insights about a program when they cannot obtain sufficient understanding by studying the program text. Unfortunately, most programming languages are not developed with monitoring capabilities in mind in the first place, and facilities are generally integrated into programming environment as an afterthought instead.

Due to the universal problems (volume, intrusion, access) encountered when writing tools that monitor the execution of a program, development of execution monitors is very difficult. Lots of effort must be expended solving those problems whenever a monitoring tool is written. The goal of the Alamo framework is to reduce the difficulties in writing monitoring tools by constructing a platform on which monitor construction is relatively easier. Alamo's design extends earlier work developed for an interpretive language [Jeff93] by developing techniques suitable for a compiled systems programming language ANSI C.

The Alamo framework supports a range of dynamic analysis tools. It runs on Sun workstations that use the Solaris 2.x operating system. It consists of three major components: a Configurable C Instrumentation (CCI) tool [Templ96], the Alamo Monitor Executive (AME), and run-time support libraries for graphics and visualization and for target program access [Zhou96]. This document introduces the AME component.

Features of the AME

The most important feature of the framework is that it applies the microkernel approach to solve the execution monitoring problem. Instead of writing monolithic monitoring systems, it encourages users to develop specialized tools that observe specific aspects of program behavior. The following sections describe the features associated with this design choice in more detail.

Synchronous thread execution model

The first important decision in the design of Alamo is to choose an execution model from the three primary execution models used by most monitoring tools. Those primary models are the one-process model, two-process model, and thread model. Detailed descriptions of these models can be found in [Jeff93]. The Alamo framework uses the thread model. The reason for this is that the one-process model is too code and data intrusive and the two-process model is too slow in switching back and forth from target program (TP) and execution monitor (EM). The thread model lies midway in between these two models. In the thread model, the TP and EM are two different entities but they share the same address space. Context switch time between them is much less than that between two different processes and accessing the TP's information is much easier for EMs.

Since threads execute concurrently, a standard thread model would allow the execution of TP and EMs to be truly parallel on multi-processor machines. If the TP and EMs execute simultaneously, the TP might modify state while it is being accessed by EMs. In Alamo the TP and EMs are synchronized in order to ensure the consistency between them. This has the advantage of giving EMs full stable information about the behavior of the TP and equally importantly, it substantially simplifies monitor development.

AME implemented two approaches for the context switch. The first one employed the Solaris threads package. Under Solaris threads, AME assigns each loaded module a separate thread, but because of the synchronous execution, at any instant only one thread is running. Semaphores are used to synchronize the execution of multiple threads. Since Alamo's execution model is not concurrent, and semaphore operations impose additional overheads, we choose the second approach because it removes semaphores and threads and uses a more portable and simpler model.

In our second approach, a new user-level context is created for each loaded module. The new context includes a stack region allocated out of the heap space of that module. Each module has a handle to its context. Each context executes with its own control flow, stack, and a separate set of registers. Important register values (eg. sp, fp, and pc) are saved onto the stack before the control is transferred to another context. The core piece of code for the context switch, which comes from the coroutine facilities in the Icon programming language is written in assembly code and has been ported to many different machines and operating systems, such as UNIX, MS-DOS, Macintosh, VMS, etc. This approach is more efficient timewise and more portable than the Solaris threads approach.

Event-driven control of the target program

The execution of TP is driven by events. An event is defined as any change of state that has information associated with it at the source-language level. It is the smallest unit of execution behavior that is observable by a monitor [Templ96]. From the monitor's view an event has two components: an event code and an event value. The code describes what type of event has taken place and the value is a C value associated with the event. The nature of an event value depends on the corresponding event code.

Whenever a desired event occurs in the TP, execution control is transferred to the EM along with a code and a value for that event. When this occurs, an event has been reported to the EM. When an EM resumes the execution of the TP, it uses an event mask to explicitly specify what kind of events are to be reported in the execution that follows. The event mask is a bit vector with each bit representing a particular event code. When a bit is set, that means the event code corresponding to that bit is used by an EM.

AME supports dynamic event masking based on the event code. It chooses to mask events in the TP instead of filtering them in the EM. This reduces the number of context switches between the TP and EMs. Moreover, event masking allows an EM to specify what events are to be reported and to change the specification at run-time.

Dynamic loading without linking

Since multiple tools can be used to monitor the same program, users need the flexibility to choose what tools to use and how many of them to use. AME provides users with this kind of flexibility by implementing a dynamic loading facility. Dynamic loading refers to the ability to load multiple programs into a shared execution environment. This novel dynamic loading facility differs from the system's dynamic loading facilities in that the latter one dynamically links symbolic addresses as it loads programs. In the context of execution monitoring, linking is not desired. The global variables used by EMs are different from those used by the TP. Without the linking process, both the TP and EMs can use the global variables with the same name without interfering with each other.

Two alternatives for managing the heap space of loaded programs are implemented in AME. The first approach is to use a single system heap. The advantage of this approach is that the heap space is not limited for the loaded modules, since they are sharing the system's heap space. The disadvantage of it is that the heap space allocated for the TP is interleaved with the heap space allocated for EMs. If the TP is buggy, random pointers can write over EM code or data regions. Memory protection can be provided for EM code regions, but protecting EM data regions is impossible in this approach because of memory interleaving.

The second approach provides an independent heap space for each module, giving monitor writers the ability to specify memory protection on both EM code and data regions. The disadvantage of this solution is that the fixed-sized heap space represents a limitation imposed by the monitoring framework at the beginning of execution. The heap size can be set to a large number without recompilation and it is not a problem in practice.

Multiple monitors and monitor coordinators

One benefit from the dynamic loading capability of the framework is that multiple monitors can be used to monitor a single TP. Since EMs are easier to write if they do not need to be aware of each other, this leads to the construction of a monitor coordinator (MC). The MC is a special purpose monitor that monitors a TP, and provides coordination and monitoring services to additional EMs. Execution monitors receiving services from a MC need not be aware of the existence of the MC or the other execution monitors. There are three advantages in using MCs. They are modularity, specialization, and extensibility [Jeff93]. It on the other hand has its disadvantages. Using MCs introduces some additional context switches among programs. This in turn can degrade the performance while monitoring the program.

Bibliography

[Jeff93] Jeffery, C. L., "A Framework for Monitoring Program Execution", Technical Report 93-21, Department of Computer Science, University of Arizona, 1993.

[Templ96] Templer, K. S. and Jeffery, C. L., "Design of a Configurable C Instrumentation Tool", Technical Report 96-6, Division of Computer Science, University of Texas at San Antonio, February, 1996.

[Zhou96] Zhou, Wenyi and Jeffery, C. L., "Target Program State Access in Alamo Monitor Framework", Technical Report 96-5, Division of Computer Science, University of Texas at San Antonio, February, 1996.