Sie sind auf Seite 1von 19

AspeCt-oriented C Tutorial

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

Aspect-oriented Programming and AspeCt-oriented C


[back] Aspect-oriented programming (AOP) is a programming paradigm that allows the developer to modularise crosscutting concerns. A concern represents a unit of functionality present in a system such as buffer management, file access, or process management. Crosscutting concerns, are concerns that, for a given decomposition of a system, cannot be cleanly modularised in the system. That

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]).

General Structure of AspeCt-oriented C Programs


[back] A program developed with AspeCt-oriented C consists of two parts, commonly referred to as the core or base program and the aspect program. The core program is written in plain C and the aspect program is written in AspeCt-oriented C and C. To help distinguish between core and aspect program AspeCt-oriented C expects the suffix ".mc" for the core program and the suffix ".ac" for the aspect program. The AspeCt-oriented C compiler is designed as source-to-source translation, reading AspeCt-oriented C and C as input and producing C as output. AspeCt-oriented C expects ".mc" and ".ac" files and generates ANSI-C compliant C files. The resulting C files can be compiled by any C compiler, like gcc, cc, or xlc to generated the object files and executable.

The AspeCt-oriented C "Hello World" Program


[back] The "Hello World" program in AspeCt-oriented C is based on the following core program (hello.mc):

int main() { printf("Hello "); }


and the aspect program (world.ac) is:

after(): execution(int main()) { printf(" World from !AspeCt-oriented C ! \n"); }


After compilation and running the executable, the output is:

Hello World from !AspeCt-oriented C !

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.

A Reusable Aspect for Memory Allocation Checking


[back]

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

} /* routine for handling the normal case */

...
This check should be conducted after each call to

malloc(). Since malloc() is a widely used means for dynamic memory

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.

Solution with AOP


The memory allocation checking concern is a typical aspect. Its functionality is complementary to the core program logic and its use crosscuts the whole system. To better modularise the system, improve maintainability, and increase code readability, the system designers may decide to isolate the concern. This decision has to be carefully reflected. In the above case, for example, the check is absolutely necessary. For illustration purposes, we show how to represent the memory allocation checking concern with AspeCtoriented C. The checking logic would be refactored into an aspect file, as follows:

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.

A Reusable Aspect for Memory Profiling


[back]

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.

Solution with AOP


The inherent difficulty in properly modularising memory profiling is the fact that it crosscuts the whole system. Memory profiling is a classical example of an aspect in this situation. The following implementation of memory profiling as aspect can be woven into an existing program:

#include <stdlib.h>

size_t totalMemoryAllocated; variables to account for profiling information int totalAllocationFuncCalled; int totalFreeFuncCalled;

<-- use global

void initProfiler() { can contain regular C functions totalMemoryAllocated = 0; totalAllocationFuncCalled = 0; totalFreeFuncCalled = 0; }

<-- AspectC file

void );

printProfiler() { printf("total memory allocated = %d bytes\n", totalMemoryAllocated

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(); }

<--

<--

before(size_t s): call($ malloc(...)) && args(s) { advice 3

<--

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 ++; }

<--

before() : call(void free(void *)) { advice 6 totalFreeFuncCalled++; } Explanation

<--

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.

Simple character matching:


If there are no wildcard characters (i.e., wildcard characters are "$", or "...") used in the pointcut declaration, AspeCt-oriented C uses simple case-sensitive string comparison for matching against a function's prototype. For example: 1. The pointcut "call(int foo(int))" picks out any call to a function having name "foo", accepting an "int" parameter, and returning an "int". This matches the following function prototype:

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);

Wildcard character matching:


Wildcard characters includes "$" and "...". "$" represents a single string of arbitrary length, including the empty string. "..." represents a single list of any length, including the empty list. Using wildcards enhances the matching capabilities of AspeCt-oriented C. For example: 1. The pointcut "call(i$t f$oo(in$))" picks out any call to a function having a name starting with "f" and ending with "oo", accepting a parameter of type starting with "in", and returing a type starting with "i" and ending with "t". Among others, it matches the following function prototypes:

it foo(in); int foo(int); int f1oo(in); int f2oo(inxy);

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:

/* advice 1 */ before() : call (void foo1()) {...}

/* advice 2 */ before() : call (void foo2()) {...}


and the core program is as follows:

/* both foo1 and foo2 are defined in other files */ /* only foo1's prototype is given here */

void foo1();

int main() { foo1(); foo2(); }

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.

Using the AspeCt-oriented C Compiler: acc


[back]

Command line compilation


> gcc hello_temp2.c world_temp2.c
Note gcc If there are no "include" files, use acc as follows:

> acc hello.mc world.ac <-- acc will generate hello.c and world.c

> gcc hello.c world.c


If there are "include" files, the input file to acc must be preprocessed. Use acc as follows:

> 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

-E invokes the preprocessor only. It is like cpp (the C PreProcessor .)

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: hello.mc world.ac acc hello.mc world.ac

<-- hello.o depends on hello.mc and world.ac <-- acc generates hello.c and world.c

gcc -c hello.c <-- gcc generates hello.o from hello.c

world.o: world.ac acc world.ac

<-- world.o depends on world.ac <-- acc generates world.c

gcc -c world.c <-- gcc generates world.o from world.o

Semantic checking and debugging information


[back] The current AspeCt-oriented C Version 0.2 has the following limitations. As AspeCt-oriented C matures, these limitations will go away. It is therefore recommended to practice careful coding, double checking and triple checking your code.

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.

AspeCt-oriented C Identifiers and Keywords


[back] AspeCt-oriented C follows the nomenclature standards set by the emerging Aspect-oriented Software Development Community and uses the following terminology, identifiers and keywords. For a detailed language specification and the syntax of the AspeCt-oriented C language, please refer to the latest AspeCt-oriented C Specification. The following terminology is used in the presentation of AspeCt-oriented C. advice The code to execute when a join point is matched by a pointcut defined inside the code part of a pointcut declaration. join point A well-defined point in the execution context of a program. AspeCt-oriented C supports function-related join points. pointcut A language extension representing one or more join points.

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

kmain(), the advice prints out a message.

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.

pointcut CallMalloc(): call(void * kmalloc(size_t));

before(): CallMalloc() { printf("aspect: before call kmalloc\n"); }

after() : CallMalloc() { printf("aspect: after call kmalloc\n"); }

Example: proceed For basic Example of proceed(), please see Example 5. Description: kmalloc() calls are replaced by around advice. However, the original

kmalloc() is still called inside the

around advice. If the memory allocation returns a null pointer, the kernel exits with an error message.

void * around(): call(void * kmalloc(...)) { char * result;

result = (char *)proceed(); if(result == NULL) { printf("aspect: out of memory\n"); } }

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); } }

The AspeCt-oriented C Compiler


[back]

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

Debugging AspeCt-oriented C programs


AspeCt-oriented C program can be debugged just like regular C programs, because the AspeCt-oriented C compiler generates regular C files. 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. Starting with AspeCt-oriented C V 0.5 the line number and file information from the generated files and the source files are kept in sync. For versions prior to acc V 0.5 the mapping of source to generated files is not correct. When debugging the program, the developer has to step through the generated C source files. Below we show such a debugging session to illustrate how debugging may still be done. Using the aspect-oriented "Hello World" program as an example, we illustrate a debugging sessions below.

>acc hello.mc world.ac >gcc -g hello.c world.c executable

<-- compile by acc <-- compile by gcc and create debuggable

>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.

This GDB was configured as "i386-redhat-linux-gnu"...

(gdb) break main

<-- set a breakpoint

Breakpoint 1 at 0x8048338: file hello.c, line 10. (gdb) run <-- run a.out to the breakpoint

Starting program: /home/mwgong/temp/AspectC/ACC/src/working/example/a.out

Breakpoint 1, main () at hello.c:10 10 printf("Hello "); <-- show source, it is the generated file, not the

(gdb) list original hello.mc 5 6 7 8 9 10 11 12 13 14 int main()

{int retValue_acc;

printf("Hello "); { world$1(); <-- inserted by acc } return retValue_acc;

(gdb) next 12 world$1(); <-- step into the "world$1" function call <-- inside world.c, which is generated from

(gdb) step world$1 () at world.c:6 world.ac 6

printf(" World from AspectC ! \n"); } <-- back to hello.c

(gdb) next Hello

World from AspectC !

main () at hello.c:14 14 return retValue_acc; <-- this "return" is inserted by acc

(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

Program exited with code 040. (gdb)

Das könnte Ihnen auch gefallen