Differences

This shows you the differences between two versions of the page.

Link to this comparison view

so:laboratoare:laborator-05 [2018/05/04 11:09]
sandu.dorogan [Alocarea/Dealocarea memoriei]
so:laboratoare:laborator-05 [2022/03/02 09:32] (current)
teodor_stefan.dutu [Exercițiul 0 - GSOC]
Line 1: Line 1:
 ====== Laborator 05 - Gestiunea memoriei ====== ====== Laborator 05 - Gestiunea memoriei ======
    
-===== Materiale ajutătoare ===== 
  
-  *[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab05-slides.pdf | lab05-slides.pdf]] 
-  *[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab05-refcard.pdf | lab05-refcard.pdf]] 
  
 ==== Nice to read ==== ==== Nice to read ====
  
   * TLPI - Chapter 7, ''​Memory Allocation''​   * TLPI - Chapter 7, ''​Memory Allocation''​
 +
 +===== Link-uri către secțiuni utile =====
 +
 +  * [[#​Gestiunea memoriei]]
 +  * [[#Spațiul de adresă al unui proces]]
 +  * [[#​Alocarea/​Dealocarea memoriei]]
 +  * [[#Lucru cu memoria - Probleme]]
 +    * [[#GDB - Detectarea zonei de acces nevalid de tip page fault]]
 +    * [[#mcheck - verificarea consistenței heap-ului]]
 +  * [[#Leak-uri de memorie]]
 +    * [[#​Valgrind]]
 +    * [[#mtrace]]
 +    * [[#Dublă dealocare]]
 +  ​
  ===== Gestiunea memoriei =====  ===== Gestiunea memoriei =====
  
Line 23: Line 34:
  
 Este, în consecință,​ fundamentală cunoașterea contextului în care acționează subsistemul de gestiune a memoriei și înțelegerea interfeței puse la dispoziție programatorului de către sistemul de operare. Este, în consecință,​ fundamentală cunoașterea contextului în care acționează subsistemul de gestiune a memoriei și înțelegerea interfeței puse la dispoziție programatorului de către sistemul de operare.
 +
 ===== Spațiul de adresă al unui proces ===== ===== Spațiul de adresă al unui proces =====
  
Line 249: Line 261:
 int *p = (int *)malloc(10 * sizeof(int));​ int *p = (int *)malloc(10 * sizeof(int));​
 </​code>​ </​code>​
-malloc întoarce //void *// care în C este automat convertit la tipul dorit. Mai mult, dacă se face cast, iar headerul ''​stdlib.h''​ necesar pentru funcția malloc nu este inclus, nu se va genera eroare! Pe anumite arhitecturi,​ acest caz poate conduce la un comportament nedefinit. Spre deosebire de C, în C++ este nevoie de cast. Mai multe detalii despre această problemă: ​ ​[[http://​www.cprogramming.com/​faq/​cgi-bin/​smartfaq.cgi?​answer=1047673478&​id=1043284351 | aici ]]+malloc întoarce //void *// care în C este automat convertit la tipul dorit. Mai mult, dacă se face cast, iar headerul ''​stdlib.h''​ necesar pentru funcția malloc nu este inclus, nu se va genera eroare! Pe anumite arhitecturi,​ acest caz poate conduce la un comportament nedefinit. Spre deosebire de C, în C++ este nevoie de cast. Discutia este elaborata ​ ​[[http://​www.cprogramming.com/​faq/​cgi-bin/​smartfaq.cgi?​answer=1047673478&​id=1043284351 | aici ]].
  
 Mai multe informații despre funcțiile de alocare găsiți în [[http://​www.gnu.org/​software/​libc/​manual/​html_node/​Unconstrained-Allocation.html#​Unconstrained-Allocationl| manualul bibliotecii standard C]] și în pagina de manual **man malloc**. Mai multe informații despre funcțiile de alocare găsiți în [[http://​www.gnu.org/​software/​libc/​manual/​html_node/​Unconstrained-Allocation.html#​Unconstrained-Allocationl| manualul bibliotecii standard C]] și în pagina de manual **man malloc**.
Line 317: Line 329:
  DIE (mat == NULL, "​HeapAlloc"​);​  DIE (mat == NULL, "​HeapAlloc"​);​
      
- for (i = 0; i < n; i++) {+ for (i = 0; i < m; i++) {
  mat[i] = HeapAlloc(processHeap,​ 0, n * sizeof(**mat));​  mat[i] = HeapAlloc(processHeap,​ 0, n * sizeof(**mat));​
  if (mat[i] == NULL) {  if (mat[i] == NULL) {
Line 842: Line 854:
 ====== Exerciții ====== ====== Exerciții ======
  
-===== Exercițiul 0 Joc interactiv (2p) =====+<note important>​ 
 +În cadrul laboratoarelor vom folosi repository-ul de git al materiei SO - https://​github.com/​systems-cs-pub-ro/​so. Va trebui sa clonați repository-ul pe masinile virtuale folosind comanda: ''​git clone https://​github.com/​systems-cs-pub-ro/​so''​. Dacă doriți să descărcați repositoryul în altă locație, folosiți comanda ''​git clone https://​github.com/​systems-cs-pub-ro/so ${target}''​.
  
-  * Detalii desfășurare [[http://​ocw.cs.pub.ro/courses/​so/​meta/​notare#​joc_interactiv|joc]].+Pentru a actualiza repository-ul,​ folosiți comanda ''​git pull origin master''​ din interiorul directorului în care se află repository-ulRecomandarea este să îl actualizați cât mai frecvent, înainte să începeți lucrul, pentru a vă asigura că aveți versiunea cea mai recentăÎn cazul în care gitul detectează conflicte la nivelul vreunui fişier, folosiți următoarele comenzi pentru a vă păstra modificările:​ 
 +<​code>​ 
 +git stash 
 +git pull origin master 
 +git stash pop 
 +</code>
  
 +Pentru mai multe informații despre folosirea utilitarului git, urmați ghidul de la https://​gitimmersion.com.
 +</​note>​
  
 +<note tip>​Pentru a vă ajuta la implementarea exercițiilor din laborator, în directorul ''​utils''​ din arhivă există un fișier ''​utils.h''​ cu funcții utile.
 +</​note>​
  
 +==== Exercițiul 0 - GSOC ====
  
-===== Linux (9p=====+Google Summer of Code este un program de vară în care studenții 
 +(indiferent de anul de studiusunt implicați în proiecte Open Source 
 +pentru a își dezvolta skill-urile de programare, fiind răsplătiți cu o 
 +bursă a cărei valoare [[https://​developers.google.com/​open-source/​gsoc/​help/​student-stipends|depinde de țară]] 
 +([[https://​developers.google.com/​open-source/​gsoc|pagină principală GSOC]]).
  
-<note important>​În rezolvarea laboratorului folosiți arhiva de sarcini [[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab05-tasks.zip | lab05-tasks.zip]]</​note>​+UPB se află în top ca număr de studenți acceptați; în fiecare an fiind 
 +undeva la aprox30-40 de studenți acceptați. 
 +Vă încurajăm să aplicați!
  
-<note tip>​Pentru a vă ajuta la implementarea exercițiilor din laborator, în directorul ''​utils''​ din arhivă există un fișier ''​utils.h''​ cu funcții utile. +===== Linux =====
-</​note>​ +
-==== Exercițiul 1 - Zone de stocare a variabilelor (0.5p) ​====+
  
-Intrați în directorul ''​1-counter''​ și implementați funcția ''​inc()''​ care întoarce ​de fiecare dată un întreg reprezentând numărul de apeluri până în momentul respectiv al funcției ''​inc''​ (**nu** aveți voie să folosiți variabile globale).+==== Exercițiul 1 - Zone de stocare a variabilelor ====
  
 +Intrați în directorul ''​1-counter''​ și implementați funcția ''​inc()''​ care întoarce de fiecare dată un întreg reprezentând numărul de apeluri până în momentul respectiv al funcției ''​inc''​ (**nu** aveți voie să folosiți variabile globale).
  
-==== Exercițiul 2 - Spațiul de adresă al unui proces ​(1p) ====+==== Exercițiul 2 - Spațiul de adresă al unui proces ====
   ​   ​
 Intrați în directorul ''​2-adr_space''​ și deschideți sursa ''​adr_space.c''​. În alt terminal compilați și rulați programul. Observați zonele de memorie din executabil în care sunt salvate variabilele,​ folosind comanda:<​code bash> Intrați în directorul ''​2-adr_space''​ și deschideți sursa ''​adr_space.c''​. În alt terminal compilați și rulați programul. Observați zonele de memorie din executabil în care sunt salvate variabilele,​ folosind comanda:<​code bash>
Line 878: Line 906:
 </​note>​ </​note>​
  
-==== Exercițiul 3 - Alocarea, realocarea și dezalocarea memoriei ​(1p) ====+==== Exercițiul 3 - Alocarea, realocarea și dezalocarea memoriei ====
  
 Intrați în directorul ''​3-alloc'',​ compilați și rulați programul ''​alloc''​. Intrați în directorul ''​3-alloc'',​ compilați și rulați programul ''​alloc''​.
Line 890: Line 918:
 Revedeți secțiunile [[#​valgrind|Valgrind]] și [[#alocarea memoriei in linux|Alocarea memoriei în Linux]] din laborator.</​note>​ Revedeți secțiunile [[#​valgrind|Valgrind]] și [[#alocarea memoriei in linux|Alocarea memoriei în Linux]] din laborator.</​note>​
  
-==== Exercițiul 4 - Rezolvarea unei probleme de tip Segmentation Fault (1p) ====+==== Exercițiul 4 - Rezolvarea unei probleme de tip Segmentation Fault ====
  
-Intrați în directorul ''​4-gdb''​ și inspectați sursa. Programul ar trebui să citescă un mesaj de la ''​stdin''​ și să-l afișeze. Compilați și rulați sursa. Rulați încă o dată programul din ''​gdb''​ (revedeți [[so:​laboratoare-2013:​resurse:​gdb#​rulare gdb|rularea unui program din gdb]]).+Intrați în directorul ''​4-gdb''​ și inspectați sursa. Programul ar trebui să citescă un mesaj de la ''​stdin''​, să îi transforme fiecare literă în majusculă ​și apoi să-l afișeze. Compilați și rulați sursa.
  
-Pentru a identifica exact unde crapă programul ​folosiți comanda ​[[http://​inside.mines.edu/​fs_home/​lwiencke/​elab/​gdb/​gdb_42.html ​backtrace]]. Pentru detalii despre comenzile ​din gdb folosițcomanda ''​help''​:<code bash> +Rulați încă o dată programul ​din ''​gdb''​ (revedeți [[so:​laboratoare-2013:​resurse:gdb#rulare gdb|rularea unui program ​din gdb]] ș ​[[#​acces nevalid | detectarea unui acces nevalid de tip page fault]]): 
-(gdb) help +<code bash> 
-</code>+(gdb) run 
 +Starting program: ​/home/​student/​lab05/​skel/​lin/​4-gdb/​fault  
 +Give input string:text
  
-Schimbațframe-ul curent cu frame-ul funcției ''​main''​ (revedeți [[#acces nevalid | detectarea unui acces nevalid de tip page fault]]):<code bash> +Program received signal SIGSEGV, Segmentation fault. 
-(gdb) frame main+0x0000555555554809 in upper_string (msg=0x0) at fault.c:​27 
 +27              while (msg[i] != '\0') { 
 +(gdb) bt 
 +#0  0x0000555555554809 in upper_string (msg=0x0) at fault.c:27 
 +#1  0x0000555555554835 in main (at fault.c:40 
 +(gdb) 
 </​code>​ </​code>​
  
-Inspectați valoarea variabilei ''​buf'':<​code bash> 
-(gdb) print buf 
-</​code>​ 
  
-Acum dorim să vedem de ce este ''​buf = NULL'',​ urmărind pașii:+Acum dorim să vedem de ce este ''​msg = NULL'',​ urmărind pașii:
    * Omorâți actualul proces:<​code bash>    * Omorâți actualul proces:<​code bash>
 (gdb) kill (gdb) kill
 </​code>​ </​code>​
-   * Puneți un breakpoint la începutul funcției ''​main'':<​code bash> +   * Puneți un breakpoint la începutul funcției ''​read_message'':<​code bash> 
-(gdb) break main+(gdb) break read_message
 </​code>​ </​code>​
-   ​* ​Rulați programul și inspectați valoarea lui ''​buf'' ​înainte și după apelul funcției ​''​malloc'' ​(folosiți ''​next''​ pentru a trece la instrucțiunea următoare, fără a urmări apelul ​de funcție).+   ​* ​Finalizați funcția:<​code bash> 
 +(gdb) finish 
 +Run till exit from #0  read_message () at fault.c:​14 
 +Give input string:​text 
 +0x0000555555554825 in main () at fault.c:​38 
 +38              message = read_message();​ 
 +Value returned is $1 = 0x0 
 +</​code>​ 
 + 
 +   * După cum puteți vedea, ​''​read_message'' ​returneză ''​0x0'' ​în loc de adresa mesajului citit.
    * Explicați sursa erorii, apoi rezolvați-o.    * Explicați sursa erorii, apoi rezolvați-o.
-==== Exercițiul 5 - Lucru cu memoria - Valgrind ​(1p) ====+ 
 +<note tip> 
 +  * https://​stackoverflow.com/​questions/​40290049/​why-does-gcc-return-0-instead-of-the-address-of-a-stack-allocated-variable 
 +  * https://​github.com/​gcc-mirror/​gcc/​commit/​f22a2cb7500755d69ee5965985d8621c735579c0 
 +</​note>​ 
 +==== Exercițiul 5 - Lucru cu memoria - Valgrind ====
  
 Intrați în directorul ''​5-struct''​ și completați fișierul ''​struct.c''​ conform comentariilor marcate cu ''​TODO''​. Intrați în directorul ''​5-struct''​ și completați fișierul ''​struct.c''​ conform comentariilor marcate cu ''​TODO''​.
Line 925: Line 971:
    * Revedeți secțiunea [[#​valgrind|Valgrind]] din laborator. ​    * Revedeți secțiunea [[#​valgrind|Valgrind]] din laborator. ​
  
-==== Exercițiul 6 - Stack overflow ​(2p) ====+==== Exercițiul 6 - Stack overflow ====
  
 Intrați în directorul 6-stack și inspectați sursa și completați problemele marcate cu //TODO1// astfel: Intrați în directorul 6-stack și inspectați sursa și completați problemele marcate cu //TODO1// astfel:
Line 931: Line 977:
       * în funcția ''​take_snapshot''​ salvați în structura de date ce reține imaginea stivei câmpurile adresă și valoare.       * în funcția ''​take_snapshot''​ salvați în structura de date ce reține imaginea stivei câmpurile adresă și valoare.
  
-Ce reține structura ''​stack_elements''?​+Ce reține structura ''​stack_element''?​
  
 Funcția ''​f2''​ pune pe stivă un vector de 3 întregi. În ce ordine sunt puse elementele vectorului pe stivă? Funcția ''​f2''​ pune pe stivă un vector de 3 întregi. În ce ordine sunt puse elementele vectorului pe stivă?
Line 938: Line 984:
  
 <note tip> <note tip>
-Dezasamblați executabilul. Observați că înainte de call ''​f2''​ se pune pe stivă ''​instruction pointer-ul(eip)''​ care este adresa primului byte de după call. La intrarea în funcție controlul s-a transmis de la caller la callee. Acesta din urmă salvează vechiul ''​base pointer(ebp)''​ iar ebp va conține adresa vârfului stivei.+Dezasamblați executabilul. Observați că înainte de call ''​f2''​ se pune pe stivă ''​instruction pointer-ul(eip) / rip (x86-64)''​ care este adresa primului byte de după call. La intrarea în funcție controlul s-a transmis de la caller la callee. Acesta din urmă salvează vechiul ''​base pointer(ebp) / rbp (x86-64)''​ iar ebp va conține adresa vârfului stivei.
 </​note>​ </​note>​
  
-Folosiți gdb pentru a afla informații despre cum este pus array-ul pe stivă ​și ce index trebuie suprascris: <code bash>+Folosiți gdb pentru a afla informații despre cum este pus array-ul pe stivă: <code bash>
 (gdb) break f2 (gdb) break f2
 (gdb) run (gdb) run
Line 947: Line 993:
 </​code>​ </​code>​
  
-Aveți grijă la proctectori ​de stivă [[https://​mudongliang.github.io/​2016/​05/​24/​stack-protector.html|Stack protectors]].+Aveți grijă la protectori ​de stivă [[https://​mudongliang.github.io/​2016/​05/​24/​stack-protector.html|Stack protectors]].
  
-In funcția ''​f2''​ bufferul ''​v''​ se află pe stivă sub adresa de return a funcției (IP-ul la care se întoarce programul dupa ce execută ''​f2''​). Scriind în bufferul ''​v''​ mai multe elemente decat are acesta alocate pe stivă, vom putea suprascrie adresa de return a lui ''​f2''​ cu o alta adresă (aici, adresa funcției ''​show_message''​). Atenție, după adresa de return este salvat pe stivă base pointerul și abia apoi găsim și bufferul ''​v''​.+In funcția ''​f2''​ bufferul ''​v''​ se află pe stivă sub adresa de return a funcției (IP-ul la care se întoarce programul dupa ce execută ''​f2''​). Scriind în bufferul ''​v''​ mai multe elemente decat are acesta alocate pe stivă, vom putea suprascrie adresa de return a lui ''​f2''​ cu o altă adresă (aici, adresa funcției ''​show_message''​). Atenție, după adresa de return este salvat pe stivă base pointerul și abia apoi găsim și bufferul ''​v''​.
  
 Folosindu-vă de vectorul ''​v''​ **fortați execuția** funcției ''​show_message''​ **fără** a o apela explicit. Astfel, după apelul funcției ''​f2'',​ fluxul programului nu se va mai întoarce în funcția ''​f1'',​ ci va executa ''​show_message''​. Urmăriți comentariile marcate cu //TODO2// (revedeți partea din laborator referitoare la [[#stiva | stivă ]]) Folosindu-vă de vectorul ''​v''​ **fortați execuția** funcției ''​show_message''​ **fără** a o apela explicit. Astfel, după apelul funcției ''​f2'',​ fluxul programului nu se va mai întoarce în funcția ''​f1'',​ ci va executa ''​show_message''​. Urmăriți comentariile marcate cu //TODO2// (revedeți partea din laborator referitoare la [[#stiva | stivă ]])
 +Folosiți-vă de cum arată stiva pentru a afla indexul în cadrul vectorului ''​vv''​ la care se află IP-ul.
 +
  
 <note tip> <note tip>
Line 957: Line 1005:
 </​note>​ </​note>​
  
-==== Exercițiul 7 - Detectare probleme de lucru cu memoria ​(1p) ====+==== Exercițiul 7 - Detectare probleme de lucru cu memoria ====
  
 În directorul ''​7-trim''​ analizați programul ''​trim.c'',​ compilați și rulați executabilul ''​trim''​. În directorul ''​7-trim''​ analizați programul ''​trim.c'',​ compilați și rulați executabilul ''​trim''​.
Line 963: Line 1011:
 Încercați să detectați problema folosind gdb (revedeți tehnicile folosite la exercițiul 3). Încercați să detectați problema folosind gdb (revedeți tehnicile folosite la exercițiul 3).
 Care este problema? Cum o rezolvați? Care este problema? Cum o rezolvați?
-==== Exercițiul 8 - Endianess ​(1p) ====+ 
 +==== Exercițiul 8 - Endianess ====
  
 Intrați în directorul ''​8-endian''​ și inspectați sursa ''​endian.c''​. Folosindu-vă de variabila ''​w''​ afișați ​ numărul ''​n=0xDEADBEEF''​. Intrați în directorul ''​8-endian''​ și inspectați sursa ''​endian.c''​. Folosindu-vă de variabila ''​w''​ afișați ​ numărul ''​n=0xDEADBEEF''​.
Line 970: Line 1019:
  
  
-==== Exercițiul 9 - Lucrul cu stiva (0.5p) ​====+<​hidden>​==== Exercițiul 9 - Lucrul cu stiva ====
  
 Intrați în directorul ''​9-bad_stack''​ și analizați fișierul ''​bad_stack.c''​. Compilați și rulați programul. Intrați în directorul ''​9-bad_stack''​ și analizați fișierul ''​bad_stack.c''​. Compilați și rulați programul.
Line 981: Line 1030:
  
 <note tip>​Indicație:​ Mutați variabila ''​lab_so''​ din funcția ''​my_fun()''​ într-o altă zonă de memorie.</​note>​ <note tip>​Indicație:​ Mutați variabila ''​lab_so''​ din funcția ''​my_fun()''​ într-o altă zonă de memorie.</​note>​
-===== Exerciții BONUS (3 SO Karma) ​===== +</​hidden>​ 
 +===== Exerciții BONUS ===== 
  
 ==== BONUS Windows ==== ==== BONUS Windows ====
  
-=== 1 so karma - Realizarea unui wrapper pentru funcțiile malloc și free ===+=== Realizarea unui wrapper pentru funcțiile malloc și free ===
  
 Deschideți proiectul Visual Studio din directorul ''​malloc-wrapper''​ și inspectați cele două fișiere existente: ''​xmalloc.c''​ și ''​xmalloc.h''​. Deschideți proiectul Visual Studio din directorul ''​malloc-wrapper''​ și inspectați cele două fișiere existente: ''​xmalloc.c''​ și ''​xmalloc.h''​.
Line 995: Line 1045:
 De ce este mai dificil să se realizeze o funcție ''​xfree''​ care să realizeze aceleași operații? De ce este mai dificil să se realizeze o funcție ''​xfree''​ care să realizeze aceleași operații?
  
-=== 1 so karma - Program de test pentru wrapperul xmalloc ===+=== Program de test pentru wrapperul xmalloc ===
  
 Analizați fișierul ''​test.c''​ și implementați funcțiile ''​tensor_alloc'',​ respectiv ''​tensor_free''​ care alocă/​dealocă un vector tridimensional (tensor). Folosiți funcțiile ''​xmalloc''​ și ''​xfree''​ implementate în cadrul exercițiului anterior (urmăriți comentariile marcate cu ''​TODO''​). Analizați fișierul ''​test.c''​ și implementați funcțiile ''​tensor_alloc'',​ respectiv ''​tensor_free''​ care alocă/​dealocă un vector tridimensional (tensor). Folosiți funcțiile ''​xmalloc''​ și ''​xfree''​ implementate în cadrul exercițiului anterior (urmăriți comentariile marcate cu ''​TODO''​).
Line 1001: Line 1051:
 ==== BONUS Linux ==== ==== BONUS Linux ====
  
-=== 1 so karma - Realizarea unei implementări sumare a funcției malloc ===+=== Realizarea unei implementări sumare a funcției malloc ===
  
 Urmăriți în ''​man''​ specificarea apelurilor [[http://​linux.die.net/​man/​2/​brk | brk]] și [[http://​linux.die.net/​man/​2/​sbrk | sbrk]]. Folosind acest apel de sistem, completați implementarea funcției [[http://​linux.die.net/​man/​3/​malloc | malloc ]] din sursa ''​my_malloc.c''​. Va trebui întâi să extindeți limita curentă a heap-ului (program break) cu valoarea cerută pentru alocare. Urmăriți în ''​man''​ specificarea apelurilor [[http://​linux.die.net/​man/​2/​brk | brk]] și [[http://​linux.die.net/​man/​2/​sbrk | sbrk]]. Folosind acest apel de sistem, completați implementarea funcției [[http://​linux.die.net/​man/​3/​malloc | malloc ]] din sursa ''​my_malloc.c''​. Va trebui întâi să extindeți limita curentă a heap-ului (program break) cu valoarea cerută pentru alocare.
Line 1011: Line 1061:
 <note tip>​Pentru rularea programului de test, nu uitați să exportați ''​LD_LIBRARY_PATH''​ (revedeți secțiunea de [[so:​laboratoare-2013:​laborator-01#​biblioteci in linux | biblioteci partajate din laboratorul 1]])</​note>​ <note tip>​Pentru rularea programului de test, nu uitați să exportați ''​LD_LIBRARY_PATH''​ (revedeți secțiunea de [[so:​laboratoare-2013:​laborator-01#​biblioteci in linux | biblioteci partajate din laboratorul 1]])</​note>​
  
-===== Soluții ===== 
- 
-[[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab05-sol.zip | lab05-sol.zip]] 
  
 ===== Resurse utile ===== ===== Resurse utile =====
so/laboratoare/laborator-05.txt · Last modified: 2022/03/02 09:32 by teodor_stefan.dutu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0