Beruflich Dokumente
Kultur Dokumente
Michael Gong, Vinod Muthusamy, and Hans-Arno Jacobsen University of Toronto, September 2006 (with changes after initial publication).
Abstract.
This tutorial aims at introducing the key concepts of AspeCt-oriented C. Many examples draw from the context of operating systems. The tutorial targets undergraduate students taking an introductory-level operating systems course. However, all examples are of general use and the transfer to other contexts is straight forward. Note, for acc V 0.5 and higher the default file suffix is .acc. In ECE344 (Jan-April, 2007) we are using a previous version of the compiler, which still accepts .ac suffixed files as default. The below tutorial assumes .ac as default.
AspeCt-oriented C Tutorial Abstract Aspect-oriented Programming and AspeCt-oriented C General Structure of AspeCt-oriented C Programs The AspeCt-oriented C "Hello World" Program Explanation: o A Reusable Aspect for Memory Allocation Checking Problem Solution with AOP Explanation Benefits o A Reusable Aspect for Memory Profiling Problem Solution with AOP Explanation Benefits Note o Matching mechanism: Simple character matching: Wildcard character matching: Pointcut Matching o Using the AspeCt-oriented C Compiler: acc Command line compilation A typical makefile o Semantic checking and debugging information o AspeCt-oriented C Identifiers and Keywords o AspeCt-oriented C Examples o The AspeCt-oriented C Compiler Compilation process Generated files Debugging AspeCt-oriented C programs
o o o o
is they crosscut the system implementation. Common examples of concerns that often crosscut a system are system call tracing, integrity constraint checking, pre and post condition checking, synchronization, accounting, and security. Aspect-orientation complements existing programming paradigms, such as the object-oriented programming or the imperative style of programming (a.k.a. the procedural paradigm of which the language C is a popular example.) Typical examples of aspects are logging, debugging, error handling, security policy checking and enforcement, transaction support, and synchronisation. Note, however, that it will depend on a specific system implementation whether any of these concerns are indeed crosscutting and can therefore be classified genuinely as aspects. It does not make sense to say "X is an aspect," without referring to the context, that is, the system, X is part of. Traditional programming paradigms, such as the object-oriented or the procedural programming, cannot cleanly modularise crosscutting concerns. AOP aims to complement these paradigms to help increase the modularity of a system by offering programming language-level support to isolate and represent aspects in a program. This isolation is crucial, as it allows the developer to maintain, test, and evolve the aspect without having to go to all the locations in the source code where the aspect is present in performing maintenance tasks. AspeCt-oriented C is a research project applying AOP concepts to the C programming language to enable aspect-orientation for Cbased systems. AspeCt -oriented offers an aspect-oriented extension to C and a compiler implementing the language extension. Many developer resources for aspect-oriented development with Java can be found on the [[http://www.aosd.net Aspect-oriented Software Developer]] (AOSD) community portal. A production-strength implementation of aspect-oriented programming for Java is the AspectJ compiler. As reference, the original designers of AOP described and defined the paradigm as follows: "An aspect is a concern that cross-cuts the primary modularisation of a software system."([AOP]) "Aspect-oriented programming" (AOP) is invented to modularise aspects by extending existing programming languages with constructs for facilitating programming aspects in a modular fashion. "Such constructs can localise the implementation of cross-cutting concerns (aspects) in a small number of special program modules, rather than spreading ... throughout the primary program modules."([AOP]).
Explanation:
The core program prints the first part of the message. The aspect program defines an advice, which specifies:
when to execute: after the execution of the function, whose prototype is "int what to execute: print out the second part of the message.
main()" and
The AspeCt-oriented C compiler processes the advice declaration from the aspect file and the core program from the core file and generates C sources that contain information from both files. This step is referred to as aspect compilation. That is the advice specified in the aspect file is woven into the core to result in a program that reflects both programs' intends.
Problem
It is common practice to check the return value after memory allocation to ensure the return value is non null. This code often looks as follows:
... int *x ; x = (int *)malloc(sizeof(int) * 4); if (x == NULL) { /* rountine to handle the case when memory allocation failed */ <--- dynamic memory allocation
...
This check should be conducted after each call to
allocation, the above code fragment is almost identically scatter across the whole system. Furthermore, similar checks apply for other memory allocation calls, such as calloc() and realloc(). This is an example of an aspect. The implementation of this memory allocation checking concern is scattered throughout the entire program. While this is a very important check that should most definitely be performed, the code unnecessarily distracts from the principal program logic. This makes it difficult to focus on the essence of the program and understand the logic. Moreover, updating the involved checking concern code would mean that many lines of code scattered throughout the code base may have to change. Such pervasive changes are certainly possible, but are tedious to perform manually. Conventional decomposition techniques (e.g., object-orientation and imperative programming) can not completely isolate crosscutting concern from the system.
after(void * s) : (call($ malloc(...)) || call($ calloc(...)) || call($ realloc(...))) && result(s) { char * result = (char *)(s); if (result == NULL) { /* routine to handle the case when memory allocation fails */ } }
Now, the core program looks as follows:
... int *x ; x = (int *)malloc(sizeof(int) * 4); /* routine for handling the normal case */ <--- dynamic memory allocation
... Explanation
The advice incorporates the following two pieces of information:
when to execute: after each call to functions whose name is either "malloc", "calloc", or "realloc" and whose return type is "void *". what to execute: the check of the value for "s". If it is null, the failure case is invoked. The value of "s" is checked through the context exposure feature in AspeCt-oriented C, which passes the return value of a function call to "s").
Benefits
1. 2. 3. 4. The aspect program is reusable. It can be applied to any C program using the above memory allocation and checking mechanism. The system is easier to understand. The core program and the memory allocation checking concern are not entangled anymore. The entire memory allocation checking concern is modularised in one file. This gives programmers a focused view of how it works. The system is more flexible. The memory allocation checking concern can be left out by simply not including it in the aspect compilation stage. Note, in this particular case, it is unlikely that a designer would opt to exclude the checking concern, but rather use the aspect to substitute different checking, tracing, or debugging logic. The code is less error-prone. The code resides in one file, where it is easier and quicker to find errors. In contrast, similar code pieces are often copy-and-pasted, which is often a source of bugs. The code is robust. Newly added dynamic memory allocation functionality is also encompassed by the checking aspect, without requireing special precautions.
5. 6.
Problem
Developers are often interested in the dynamic memory use of a program. Similarly, the number of calls to malloc(), calloc() or realloc() maybe of interest. Traditionally, this problem is solved by adding macros, linking against different memory allocation libraries, or instrumenting the code. While these are all viable solutions, each comes with its on set of advantages and disadvantages. A solution that unobtrusively adds profiling calls and removes or disables them again when the code goes into production is a desirable goal.
#include <stdlib.h>
size_t totalMemoryAllocated; variables to account for profiling information int totalAllocationFuncCalled; int totalFreeFuncCalled;
void );
printf("total memory allocation function called = %d \n", totalAllocationFuncCalled); printf("total memory free function called = %d\n", totalFreeFuncCalled); }
before(): execution(int main()) { advice 1 initProfiler(); } after(): execution(int main()) { advice 2 printProfiler(); }
<--
<--
<--
totalMemoryAllocated += s; totalAllocationFuncCalled ++; } before(size_t n, size_t s): call($ calloc(...)) && args(n, s) { advice 4 totalMemoryAllocated += n * s; totalAllocationFuncCalled ++; } <--
before(size_t s): call($ realloc(...)) && args(void *, s) { advice 5 totalMemoryAllocated += s; totalAllocationFuncCalled ++; }
<--
<--
advice 1 and 2 are responsible for initialising the profiler and to output the results. Typically, a C program starts and finishing its life by executing the main function body. We therefore weave advice 1 and 2 into the execution of main(). advice 3, 4, 5, and 6 are straightforward: they update the profiling information whenever a function of interest is called. By using the context exposure feature of AspeCt-oriented C, the information about the amount of memory allocated is passed as advice parameters by using args() in the pointcut declaration.
Benefits
The benefits are similar to the benefits demonstrated for the memory allocation checking aspect above.
Note
The above memory profiling aspect is not thread-safe. It is left as an exercise to the reader to make it thread-safe. Is thread-safety an aspect?
Matching mechanism:
[back] Two matching mechansims are used: simple character matching and wildcard character matching.
int foo(int);
2. The pointcut "args(int, char)" picks out any call or execution of a function having any name, accepting an "int", and a "char" as parameters, and returning any value (including "void," no value.) Among others, it matches the following function prototypes:
void foo(int, char); int foo2(int, char); char * foo3(int, char); double x2(int, char);
2. The pointcut "args(int, ..., char))" picks out any call or execution of a function having any name, accepting a parameter list starting with "int" and ending with "char", and returning any value (including void.) Among others, it matches the following function prototypes:
void foo(int, char); void foo1(int, char); int foo2(int, char *, char); char * foo3(int, struct A * , char); double x2(int, int, int, char , double, char);
Pointcut Matching
[back] For advice code to apply at a join point, the pointcut associated with the advice must match the join point. AspeCt-oriented C performs the matching of function prototype join points in the program against the pointcut signatures specified in the aspect code. Pointcut matching is based on this information. Example: Suppose two advice declarations are as follows:
/* both foo1 and foo2 are defined in other files */ /* only foo1's prototype is given here */
void foo1();
In this example, only the foo1() call is matched by advice 1 and the foo2() call is not matched because its prototype is unknown to the AspeCt-oriented C compiler.
> acc hello.mc world.ac <-- acc will generate hello.c and world.c
> cp hello.mc hello_temp1.c <-- copy the original files to have ".c" suffix, because gcc does not recognize ".mc" and ".ac". > cp world.ac world_temp1.c
> gcc -E hello_temp1.c > hello_temp2.mc <-- preprocess files, and save them to be ".mc" and ".ac" files. > gcc -E world_temp1.c > world_temp2.ac
> acc hello_temp2.mc world_temp2.ac <-- acc will generate hello_temp2.c and world_temp2.c
A typical makefile
A typical Makefile example involving acc looks as follows:
a.out: hello.o world.o <-- a.out is comprised by 2 modules: hello.o and world.o gcc hello.o world.o
<-- hello.o depends on hello.mc and world.ac <-- acc generates hello.c and world.c
Starting with acc V 0.4 semantic checking of aspect code is supported. Starting with acc V 0.5 debugging information is generated. This serves a debugger in visualizing the original source code when stepping through aspect code, for instance. This also serves the ANSI-C compiler in pointing to the line number in the original source file when reporting errors.
To aid the developer in debugging it is recommended that the generated C sources are passed through the on Unix platforms often available indent program, which pretty-prints the input C source file. This helps understand the woven aspect logic and identify errors.
The following keywords are defined by AspeCt-oriented C. after An advice type declarator, it represents the advice code that should be run after the matched join point(s). args A pointcut type declarator, it represents the join points whose argument types are matched by the pointcut specified. around An advice type declarator, it represents the advice code that should be run and the matched join point(s), that in the absence of proceed(), are skipped. before An advice type declarator, it represents the advice code that should be run before the matched join point(s). call A pointcut type declarator, it represents the join points of calling a function whose prototype is matched by the pointcut specified. cflow A pointcut type declarator, it represents all the join points under the control flow of the specified pointcut. execution A pointcut type declarator, it represents the join points of executing a function matched by the pointcut specified. infile A pointcut type declarator, it represents the join points which exist in the specified file. infunc A pointcut type declarator, it represents the join points which exist in the specified function. pointcut Associates a name to a pointcut definition. The name can then be used in advice declarations to refer to the named pointcut declaration. proceed Used inside an around advice function, where it identifies that the original join point should be executed. result A pointcut type declarator, it represents the join points whose return type is matched by the pointcut specified. this Is used inside an advice functions. It is a pointer to a struct and allows advice code to access context information of the matched join points. Through it, advice code can access the function name via "this->funcName" and the join point type via "this->kind".
AspeCt-oriented C Examples
[back] The following are AspeCt -oriented C examples in the context of the OS/161 operating system kernel. Example: after For basic Example of after(). Please see Example 1. after() can also be used in more complex form such as the following Description: After calling the boot() function inside int
after(): call($ boot()) && infunc(kmain) { printf("aspect: after foo call in kmain function\n"); }
Example: args Please see Example 4 for a more detailed example. Description: Before calling a function which takes a pointer to a struct semaphore, the advice checks the pointer value to ensure it is not null.
before(struct semaphore * x): call($ $(...)) && args(x) { if(x == NULL) { printf("aspect: call function with null pointer\n"); } }
Example: around Please see Example 2 for a basic and detailed explanation. Description: the advice replaces the body of function null_fsync() with a message.
int around(): execution(int null_fsync(struct vnode *)) { printf("aspect: skip null_fsync function, do nothing.\n"); return 0; }
Example: before See example for "args" and Example 1. Example: call See examples for "after", "args" and Example 2. Example: cflow Please see Example 8 and Example 13. Example: execution See example for "around". There is also a plenty of example of execution in Examples. Example: infile Note: Currently, infile is not fully functional.
Description: The advice uses a message to replace the body of all functions whose name start with " null_" and which are defined in a file having a name starting with "device".
int around(): execution(int null_$(...)) && infile("device$.c") { printf("aspect: skip all null_ functions from device.c file.\n"); return 0; }
Example: infunc See example for "after" and Example 3. Example: pointcut For basic Example of pointcut. Please see Example 6. Description: After the name "CallMalloc" is associated with a call pointcut to function kmalloc(), the name can be used wherever a pointcut can be used to refer to the definition of the named pointcut, such as in a before or after advice declaration.
Example: proceed For basic Example of proceed(), please see Example 5. Description: kmalloc() calls are replaced by around advice. However, the original
around advice. If the memory allocation returns a null pointer, the kernel exits with an error message.
Example: result For basic Example of result(), please see Example 4. Description: After each call to kmalloc(), the advice checks the returned value. If it is a null pointer, the system exits with an error message. The example has the same effect as theproceed() example above.
after(void * s): call($ kmalloc(...)) && result(s) { char * result = (char *)s; if(result == NULL) { printf("aspect: out of memory\n"); } }
Example: this For basic Example of "this", please see Example 4 and Example 18A . Description: After a call to kmalloc(), the advice checks the returned value. If it is a null pointer, the system exits with an error message. Furthermore, the advice prints out in which function the failed kmalloc() occured.
after(void * s): call($ kmalloc(...)) && result(s) { char * result = (char *)s; if(result == NULL) { printf("aspect: out of memory, when calling kmalloc in function %s\n", this->funcName); } }
Compilation process
The AspeCt-oriented C compiler is a source-to-source translator. As input it processes AspeCt-oriented C and C source files and produces ANSI-C compliant files as output. The output files can be complied by an ANSI-C compliant compiler, such as gcc.
Generated files
The following table describes the files and the by the AspeCt-oriented C compiler expected file suffixes. Description core program aspect program input file .mc e.g., hello.mc .ac e.g., world.ac generated file .c e.g., hello.c .c e.g., world.c
>gdb a.out <-- launch GDB debugger GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) Copyright 2003 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details.
Breakpoint 1 at 0x8048338: file hello.c, line 10. (gdb) run <-- run a.out to the breakpoint
Breakpoint 1, main () at hello.c:10 10 printf("Hello "); <-- show source, it is the generated file, not the
{int retValue_acc;
(gdb) next 12 world$1(); <-- step into the "world$1" function call <-- inside world.c, which is generated from
(gdb) next 15 }
(gdb) next 0x42015704 in __libc_start_main () from /lib/tls/libc.so.6 (gdb) continue Continuing. <-- continue the program to the end