Beginner's Guide to Linkers

[Updated March 2009 to include more information on the pecularities of linking on Windows, plus some clarification on the one definition rule.]

g++ -o test1 test1a.o test1b.o test1a.o(.text+0x18): In function `main': : undefined reference to `findmax(int, int)' collect2: ld returned 1 exit status

extern "C"

Table of Contents

Naming of Parts: What's in a C File

A definition of a variable induces the compiler to reserve some space for that variable, and possibly fill that space with a particular value.

A definition of a function induces the compiler to generate code for that function.

global variables , which exist for the whole lifetime of the program ("static extent"), and which are usually accessible in lots of different functions

, which exist for the whole lifetime of the program ("static extent"), and which are usually accessible in lots of different functions local variables, which only exist while a particular function is being executed ("local extent") and are only accessible within that function

static local variables are actually global variables, because they exist for the lifetime of the program, even though they're only visible inside a single function

local variables are actually global variables, because they exist for the lifetime of the program, even though they're only visible inside a single function likewise static global variables also count as global variables, even though they can only be accessed by the functions in the particular file where they were defined

static

malloc

new

free

delete

Code Data Global Local Dynamic Initialized Uninitialized Initialized Uninitialized Declaration int fn(int x); extern int x; extern int x; N/A N/A N/A Definition int fn(int x) { ... } int x = 1;

(at file scope) int x;

(at file scope) int x = 1;

(at function scope) int x;

(at function scope) ( int* p = malloc(sizeof(int)); )

/* This is the definition of a uninitialized global variable */ int x_global_uninit; /* This is the definition of a initialized global variable */ int x_global_init = 1; /* This is the definition of a uninitialized global variable, albeit * one that can only be accessed by name in this C file */ static int y_global_uninit; /* This is the definition of a initialized global variable, albeit * one that can only be accessed by name in this C file */ static int y_global_init = 2; /* This is a declaration of a global variable that exists somewhere * else in the program */ extern int z_global; /* This is a declaration of a function that exists somewhere else in * the program (you can add "extern" beforehand if you like, but it's * not needed) */ int fn_a(int x, int y); /* This is a definition of a function, but because it is marked as * static, it can only be referred to by name in this C file alone */ static int fn_b(int x) { return x+1; } /* This is a definition of a function. */ /* The function parameter counts as a local variable */ int fn_c(int x_local) { /* This is the definition of an uninitialized local variable */ int y_local_uninit; /* This is the definition of an initialized local variable */ int y_local_init = 3; /* Code that refers to local and global variables and other * functions by name */ x_global_uninit = fn_a(x_local, x_global_init); y_local_uninit = fn_a(x_local, y_local_init); y_local_uninit += fn_b(z_global); return (y_global_uninit + y_local_uninit); }

What The C Compiler Does

.o

.obj

code , corresponding to definitions of functions in the C file

, corresponding to definitions of functions in the C file data, corresponding to definitions of global variables in the C file (for an initialized global variable, the initial value of the variable also has to be stored in the object file).

if

while

goto

Dissecting An Object File

nm

nm

Symbols from c_parts.o: Name Value Class Type Size Line Section fn_a | | U | NOTYPE| | |*UND* z_global | | U | NOTYPE| | |*UND* fn_b |00000000| t | FUNC|00000009| |.text x_global_init |00000000| D | OBJECT|00000004| |.data y_global_uninit |00000000| b | OBJECT|00000004| |.bss x_global_uninit |00000004| C | OBJECT|00000004| |*COM* y_global_init |00000004| d | OBJECT|00000004| |.data fn_c |00000009| T | FUNC|00000055| |.text

man

A class of U indicates an undefined reference, one of the "blanks" mentioned previously. For this object, there are two: " fn_a " and " z_global ". (Some versions of nm may also print out a section , which will be *UND* or UNDEF in this case)

indicates an undefined reference, one of the "blanks" mentioned previously. For this object, there are two: " " and " ". (Some versions of may also print out a , which will be or in this case) A class of t or T indicates where code is defined; the different classes indicate whether the function is local to this file ( t ) or not ( T )i.e. whether the function was originally declared with static . Again, some systems may also show a section, something like .text

or indicates where code is defined; the different classes indicate whether the function is local to this file ( ) or not ( )i.e. whether the function was originally declared with . Again, some systems may also show a section, something like A class of d or D indicates an initialized global variable, and again the particular class indicates whether the variable is local ( d ) or not ( D ). If there's a section, it will be something like .data

or indicates an initialized global variable, and again the particular class indicates whether the variable is local ( ) or not ( ). If there's a section, it will be something like For an uninitialized global variable, we get b if it's static/local, and B or C when it's not. The section in this case will probably be something like .bss or *COM*.

What The Linker Does: Part 1

/* Initialized global variable */ int z_global = 11; /* Second global named y_global_init, but they are both static */ static int y_global_init = 2; /* Declaration of another global variable */ extern int x_global_init; int fn_a(int x, int y) { return(x+y); } int main(int argc, char *argv[]) { const char *message = "Hello, world"; return fn_a(11,12); }

ld

nm

Symbols from sample1.exe: Name Value Class Type Size Line Section _Jv_RegisterClasses | | w | NOTYPE| | |*UND* __gmon_start__ | | w | NOTYPE| | |*UND* __libc_start_main@@GLIBC_2.0| | U | FUNC|000001ad| |*UND* _init |08048254| T | FUNC| | |.init _start |080482c0| T | FUNC| | |.text __do_global_dtors_aux|080482f0| t | FUNC| | |.text frame_dummy |08048320| t | FUNC| | |.text fn_b |08048348| t | FUNC|00000009| |.text fn_c |08048351| T | FUNC|00000055| |.text fn_a |080483a8| T | FUNC|0000000b| |.text main |080483b3| T | FUNC|0000002c| |.text __libc_csu_fini |080483e0| T | FUNC|00000005| |.text __libc_csu_init |080483f0| T | FUNC|00000055| |.text __do_global_ctors_aux|08048450| t | FUNC| | |.text _fini |08048478| T | FUNC| | |.fini _fp_hw |08048494| R | OBJECT|00000004| |.rodata _IO_stdin_used |08048498| R | OBJECT|00000004| |.rodata __FRAME_END__ |080484ac| r | OBJECT| | |.eh_frame __CTOR_LIST__ |080494b0| d | OBJECT| | |.ctors __init_array_end |080494b0| d | NOTYPE| | |.ctors __init_array_start |080494b0| d | NOTYPE| | |.ctors __CTOR_END__ |080494b4| d | OBJECT| | |.ctors __DTOR_LIST__ |080494b8| d | OBJECT| | |.dtors __DTOR_END__ |080494bc| d | OBJECT| | |.dtors __JCR_END__ |080494c0| d | OBJECT| | |.jcr __JCR_LIST__ |080494c0| d | OBJECT| | |.jcr _DYNAMIC |080494c4| d | OBJECT| | |.dynamic _GLOBAL_OFFSET_TABLE_|08049598| d | OBJECT| | |.got.plt __data_start |080495ac| D | NOTYPE| | |.data data_start |080495ac| W | NOTYPE| | |.data __dso_handle |080495b0| D | OBJECT| | |.data p.5826 |080495b4| d | OBJECT| | |.data x_global_init |080495b8| D | OBJECT|00000004| |.data y_global_init |080495bc| d | OBJECT|00000004| |.data z_global |080495c0| D | OBJECT|00000004| |.data y_global_init |080495c4| d | OBJECT|00000004| |.data __bss_start |080495c8| A | NOTYPE| | |*ABS* _edata |080495c8| A | NOTYPE| | |*ABS* completed.5828 |080495c8| b | OBJECT|00000001| |.bss y_global_uninit |080495cc| b | OBJECT|00000004| |.bss x_global_uninit |080495d0| B | OBJECT|00000004| |.bss _end |080495d4| A | NOTYPE| | |*ABS*

There's also a fair number of complicating details cluttering up the output, but if you filter out anything starting with an underscore it gets a lot simpler.

Duplicate Symbols

In C++, the situation is straightforward.

For C, things are slightly less clear. There has to be exactly one definition of any functions or initialized global variables, but the definition of an uninitialized global variable can be treated as a tentative definition. C then allows (or at least does not forbid) different source files to have tentative definitions for the same object.

the one definition rule

(This model is sometimes known as the "common model" of linking, after the Fortran COMMON keyword.)

(this is sometimes known as the "relaxed ref/def model" of linking).

--work-properly

-fno-common

What The Operating System Does

local variables are allocated on a piece of memory known as the stack , which grows and shrinks as different functions are called and complete

, which grows and shrinks as different functions are called and complete dynamically allocated memory is taken from an area known as the heap, and the malloc function keeps track of where all the available space in this area is.

What The Linker Does: Part 2

(Technical aside: This section completely skips a major feature of the linker: relocation. Different programs will be different sizes, so when the shared library gets mapped into the address space of different programs, it will be at different addresses. This in turn means that all of the functions and variables in the library are in different places. Now, if all of the ways of referring to addresses are relative ("the value +1020 bytes from here") rather than absolute ("the value at 0x102218BF") this is less of a problem, but this isn't always possible. If not, then all of these absolute addresses need to have a suitable offset added to themthis is relocation. I'm not going to mention this topic again, though, because it's almost always invisible to the C/C++ programmerit's very rare that a linking issue is because of relocation problems)

Static Libraries

ar

.a

lib

-lfred

libfred.a

(Historically, a program called ranlib also used to be needed for static libraries, in order to build an index of symbols at the start of the library. Nowadays the ar tool tends to do this itself.)

On Windows, static libraries have a .LIB extension and are produced by the LIB tool, but this can be confusing as the same extension is also used for an "import library", which just holds a list of the things available in a DLL—see the section on Windows DLLs.

a.o

b.o

-lx

-ly

File a.o b.o libx.a liby.a Object a.o b.o x1.o x2.o x3.o y1.o y2.o y3.o Definitions a1, a2, a3 b1, b2 x11, x12, x13 x21, x22, x23 x31, x32 y11, y12 y21, y22 y31, y32 Undefined references b2, x12 a3, y22 x23, y12 y11 y21 x31

a.o

b.o

b2

a3

x12

y22

libx.a

x1.o

x12

x23

y12

y22

x23

y12

libx.a

x23

x2.o

libx.a

y11

y22

y12

y11

libx.a

liby.a

y1.o

y2.o

y21

y2.o

b.o

y32

libx.a

liby.a

y3.o

x31

libx.a

x3.o

(By the way, this example has a cyclic dependency between the two libraries libx.a and liby.a ; this is typically a Bad Thing, particularly on Windows)

Shared Libraries

libc

printf

fopen

A slightly less obvious disadvantage is that once a program has been statically linked, the code in it is fixed forever. If someone finds and fixes a bug in printf , then every program has to be linked again in order to pick up the fixed code.

To get around these and other problems, shared libraries were introduced (normally indicated by a .so extension, or .dll on Windows machines and .dylib on Mac OS X). For these kinds of libraries, the normal command line linker doesn't necessarily join up all of the dots. Instead, the regular linker takes a kind of "IOU" note, and defers the payment of that note until the moment when the program is actually run.

What this boils down to is this: if the linker finds that the definition for a particular symbol is in a shared library, then it doesn't include the definition of that symbol in the final executable. Instead, the linker records the name of symbol and which library it is supposed to come from in the executable file instead.

When the program is run, the operating system arranges that these remaining bits of linking are done "just in time" for the program to run. Before the main function is run, a smaller version of the linker (often called ld.so ) goes through these promissory notes and does the last stage of the link there and thenpulling in the code of the library and joining up all of the dots.

This means that none of the executable files have a copy of the code for printf . If a new, fixed, version of printf is available, it can be slipped in just by changing libc.so it'll get picked up the next time any program runs.

There's another big difference with how shared libraries work compared to static libraries, and that shows up in the granularity of the link. If a particular symbol is pulled in from a particular shared library (say printf in libc.so ), then the whole of that shared library is mapped into the address space of the program. This is very different from the behavior of a static library, where only the particular objects that held undefined symbols got pulled in.

Put another way, a shared library is itself produced as a result of a run of the linker (rather than just forming a big pile of objects like ar does), with references between objects in the same library getting resolved. Once again, nm is a useful tool for illustrating this: for the example libraries above it will produce sets of results for the individual object files when run on a static version of the library, but for the shared version of the library, liby.so has only x31 as an undefined symbol. Also, for the library-ordering example at the end of the previous section, there wouldn't be a problem: adding a reference to y32 into b.c would make no difference, as all of the contents of y3.o and x3.o are already pulled in anyway.

As an aside, another useful tool is ldd ; on Unix platforms this shows the set of shared libraries that an executable (or a shared library) depends on, together with an indication of where those libraries are likely to be found. For the program to run successfully, the loader needs to be able to find all of these libraries, together with all of their dependencies in turn. (Typically, the loader looks for libraries in the list of directories held in the LD_LIBRARY_PATH environment variable.) /usr/bin:ldd xeyes linux-gate.so.1 => (0xb7efa000) libXext.so.6 => /usr/lib/libXext.so.6 (0xb7edb000) libXmu.so.6 => /usr/lib/libXmu.so.6 (0xb7ec6000) libXt.so.6 => /usr/lib/libXt.so.6 (0xb7e77000) libX11.so.6 => /usr/lib/libX11.so.6 (0xb7d93000) libSM.so.6 => /usr/lib/libSM.so.6 (0xb7d8b000) libICE.so.6 => /usr/lib/libICE.so.6 (0xb7d74000) libm.so.6 => /lib/libm.so.6 (0xb7d4e000) libc.so.6 => /lib/libc.so.6 (0xb7c05000) libXau.so.6 => /usr/lib/libXau.so.6 (0xb7c01000) libxcb-xlib.so.0 => /usr/lib/libxcb-xlib.so.0 (0xb7bff000) libxcb.so.1 => /usr/lib/libxcb.so.1 (0xb7be8000) libdl.so.2 => /lib/libdl.so.2 (0xb7be4000) /lib/ld-linux.so.2 (0xb7efb000) libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0xb7bdf000)

The reason for this larger granularity is because modern operating systems are clever enough that you can save more than just the duplicate disk space that happens with static libraries; different running processes that use the same shared library can also share the code segment (but not the data/bss segmentstwo different processes could be in different places for their strtok after all). In order to do this, the whole library has to be mapped in one go, so that the internal references all line up to the same placesif one process pulled in a.o and c.o and another pulled in b.o and c.o , there wouldn't be any commonality for the OS to leverage.

Windows DLLs

Although the general principles of shared libraries are roughly similar on Unix platforms and Windows, that are a few details that can catch out the unwary. Exporting Symbols The most major difference between the two is that symbols are not automatically exported by Windows libraries. On Unix, all of the symbols from all of the object files that were linked into the shared library are visible to users of the library. On Windows, the programmer has to explicitly choose to make particular symbols visible—i.e. to export them. There are three ways to export a symbol from a Windows DLL (and all three ways can be mixed together in the same library). In the source code, declare the symbol as __declspec(dllexport) , thusly:

__declspec(dllexport) int my_exported_function(int x, double y);

, thusly: On the invocation of the linker, use the /export: symbol_to_export option to LINK.EXE .

LINK.EXE /dll /export:my_exported_function

option to . Get the linker to pull in a module definition ( .DEF ) file (by using the /DEF:def_file linker option), and in that file include an EXPORTS section that contains the symbols you want to export. EXPORTS my_exported_function my_other_exported_function Once C++ is added in to the mix, the first of these options is the easiest because the compiler takes care of the name mangling for you. .LIB and Other Library-Related Files This neatly leads on to the second complication with Windows libraries: the information about exported symbols that the linker needs to join things up is not held in the DLL itself. Instead, this information is held in a corresponding .LIB file. The .LIB file associated with a DLL describes what (exported) symbols are present in the DLL, together with their locations. Any other binary that uses the DLL needs to look in the .LIB file so that it can join up the symbols correctly. To confuse things, the .LIB extension is also used for static libraries. In fact, there are a wide variety of different files that can be relevant for Windows libraries. As well as the .LIB file and the (optional) .DEF file mentioned in the previous section, you might see all of the following files associated with your Windows library. Link output files: library .DLL : The library code itself; this is needed (at run-time) by any executable that uses the library. library .LIB : An "import library" file which describes what symbols are where in the output DLL. This file is only produced if the DLL exports some symbols; if no symbols are exported, there is no point in having the .LIB file. This file is needed at link-time by anything that uses this library. library .EXP : An "export file" for the library being linked, which is needed when linking binaries with circular dependencies. library .ILK : If the /INCREMENTAL option was specified to the linker so that incremental linking is enabled, this file holds the status of the incremental linking. Needed for any future incremental linking of this library. library .PDB : If the /DEBUG option was specified to the linker, this file is a program database containing debugging information for the library. library .MAP : If the /MAP option was specified to the linker, this file holds a description of the internal layout of the library.

Link input files: library .LIB : An "import library" file which describes what symbols are where in any other DLLs that are needed by the thing being linked. library .LIB : A static library file which contains a collection of object files that are needed by the thing being linked. Note the ambiguous use of the .LIB extension. library .DEF : A "module definition" file which allows control of various details of the linked library, including the export of symbols. library .EXP : An "export file" for the library being linked, which can indicate that a previous run of LIB.EXE for the library has already created the .LIB file for the library. Relevant when linking binaries with circular dependencies. library .ILK : Incremental linking status file; see above. library .RES : Resource file that contains information about the various GUI widgets that the executable uses; these are included in the final binary file.

This is contrast to Unix, where most of the information held in these extra files is (usually) just included in the library itself. Importing Symbols As well as requiring DLLs to explicitly declare which symbols they export, Windows also allows binaries that use library code to explicitly declare which symbols they import. This is optional, but gives a speed optimization due to some historical features of 16-bit windows. To do this, declare the symbol as __declspec(dllimport) in the source code, thusly:

__declspec(dllimport) int function_from_some_dll(int x, double y); __declspec(dllimport) extern int global_var_from_some_dll; It's normal good practice in C to have a single declaration for any function or global variable, held in a header file. This leads to a bit of a conundrum: the code in the DLL that holds the definition of the function/variable needs to export the symbol, but any code outside the DLL needs to import the symbol. A common way round this is to use a preprocessor macro in the header file. #ifdef EXPORTING_XYZ_DLL_SYMS #define XYZ_LINKAGE __declspec(dllexport) #else #define XYZ_LINKAGE __declspec(dllimport) #endif XYZ_LINKAGE int xyz_exported_function(int x); XYZ_LINKAGE extern int xyz_exported_variable; The C file in the DLL which defines the function and variable ensures that the preprocessor variable EXPORTING_XYZ_DLL_SYMS is #define d before it includes this header file, and so does an export of the symbols. Any other code that pulls in this header file doesn't define the symbol and so indicates an import of the symbols. Circular Dependencies One final complication with DLLs is that Windows is stricter than Unix in requiring every symbol to have a resolution at link time. On Unix, it's possible to link a shared library that contains an unresolved symbol that the linker has never seen; in this situation, any other code that pulls in this shared library must provide that symbol, or the program will fail to load. Windows doesn't allow this sort of laxity. In most systems this isn't a problem. Executables rely on high-level libraries, the high-level libraries rely on lower-level libraries, and everything gets linked in the opposite order—low-level libraries first, then higher-level libraries, and finally the executables that rely on it all. However, if there are circular dependencies between binaries, then things are trickier. If X.DLL needs a symbol from Y.DLL , and Y.DLL needs a symbol from X.DLL , then there is a chicken-and-egg problem: whichever library is linked first won't be able to find all of its symbols. Windows does provide a way around this, roughly as follows. First, fake a link of library X . Run LIB.EXE (not LINK.EXE ) to produce an X.LIB file that is the same as would have been produced by LINK.EXE . No X.DLL file is produced, but a X.EXP file does get emitted.

. Run (not ) to produce an file that is the same as would have been produced by . No file is produced, but a file does get emitted. Link library Y as normal; this pulls in the X.LIB file from the previous step, and outputs both a Y.DLL and a Y.LIB file.

as normal; this pulls in the file from the previous step, and outputs both a and a file. Finally link library X properly. This is almost the same as normal, but it additionally includes the X.EXP file created in the first step. As normal, this link will pull in the Y.LIB file from the previous step and will create a X.DLL file. Unlike normal, the link will skip the process of creating an X.LIB file, because there one already there from the first step (which is what the .EXP file indicates). Of course, a better idea is usually to re-organize the libraries so that there aren't any circular dependencies….

Adding C++ To The Picture

Function Overloading & Name Mangling

The first change that C++ allows is the ability to overload a function, so there can be different versions of the same named functions, differing in the types that the function accepts (the function's signature): int max(int x, int y) { if (x>y) return x; else return y; } float max(float x, float y) { if (x>y) return x; else return y; } double max(double x, double y) { if (x>y) return x; else return y; } This obviously gives a problem for the linker: when some other code refers to max , which one does it mean?

nm

Symbols from fn_overload.o: Name Value Class Type Size Line Section __gxx_personality_v0| | U | NOTYPE| | |*UND* _Z3maxii |00000000| T | FUNC|00000021| |.text _Z3maxff |00000022| T | FUNC|00000029| |.text _Z3maxdd |0000004c| T | FUNC|00000041| |.text

max

int

float

double

c++filt

--demangle

Symbols from fn_overload.o: Name Value Class Type Size Line Section __gxx_personality_v0| | U | NOTYPE| | |*UND* max(int, int) |00000000| T | FUNC|00000021| |.text max(float, float) |00000022| T | FUNC|00000029| |.text max(double, double) |0000004c| T | FUNC|00000041| |.text

extern "C"

extern "C"

g++ -o test1 test1a.o test1b.o test1a.o(.text+0x18): In function `main': : undefined reference to `findmax(int, int)' collect2: ld returned 1 exit status

findmax

_Z7findmaxii

findmax

By the way, note that an extern "C" linkage declaration is ignored for member functions (7.5.4 of the C++ standard).

Initialization of Statics

nm

class Fred { private: int x; int y; public: Fred() : x(1), y(2) {} Fred(int z) : x(z), y(3) {} }; Fred theFred; Fred theOtherFred(55); For this code, the (demangled) output of nm gives: Symbols from global_obj.o: Name Value Class Type Size Line Section __gxx_personality_v0| | U | NOTYPE| | |*UND* __static_initialization_and_destruction_0(int, int)|00000000| t | FUNC|00000039| |.text Fred::Fred(int) |00000000| W | FUNC|00000017| |.text._ZN4FredC1Ei Fred::Fred() |00000000| W | FUNC|00000018| |.text._ZN4FredC1Ev theFred |00000000| B | OBJECT|00000008| |.bss theOtherFred |00000008| B | OBJECT|00000008| |.bss global constructors keyed to theFred |0000003a| t | FUNC|0000001a| |.text

.gnu.linkonce.t.stuff

Templates

max

max_template.h

max

template <class T> T max(T x, T y) { if (x>y) return x; else return y; } and include this header file in C++ code to use the templated function: #include "max_template.h" int main() { int a=1; int b=2; int c; c = max(a,b); // Compiler automatically figures out that max<int>(int,int) is needed double x = 1.1; float y = 2.2; double z; z = max<double>(x,y); // Compiler can't resolve, so force use of max (double,double) return 0; }

max<int>(int,int)

max<double>(double,double)

max<float>(float,float)

max<MyFloatingPointClass>(MyFloatingPointClass,MyFloatingPointClass)

Symbols from max_template.o: Name Value Class Type Size Line Section __gxx_personality_v0| | U | NOTYPE| | |*UND* double max<double>(double, double) |00000000| W | FUNC|00000041| |.text._Z3maxIdET_S0_S0_ int max<int>(int, int) |00000000| W | FUNC|00000021| |.text._Z3maxIiET_S0_S0_ main |00000000| T | FUNC|00000073| |.text

max<int>(int,int)

max<double>(double,double)

Dynamically Loaded Libraries

dlopen

dlsym

LoadLibrary

GetProcAddress

dlopen

The dlopen also allows the choice of whether to resolve all of these references at the instant that the library is loaded ( RTLD_NOW ), or one by one as each undefined reference is hit ( RTLD_LAZY ). The first way means that the dlopen call takes much longer, but the second way involves the slight risk that sometime later the program will discover that there is an undefined reference that can't be resolvedat which point, the program will be terminated.

dlsym

NULL

Interaction with C++ Features

dlsym

extern "C"

dlsym

dlopen

In Unix these are functions called _init and _fini , or for more recent systems using the GNU toolchain, these are any functions marked with __attribute__((constructor)) or __attribute__((destructor)) . In Windows, the relevant function is DllMain with a reason parameter or DLL_PROCESS_ATTACH or DLL_PROCESS_DETACH .

More Details

Copyright (c) 2004-2005,2009-2010 David Drysdale

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is available here.

Back to Home Page

Contact me