IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

x86 32-bits / 64-bits Assembleur Discussion :

Allocation mémoire pour exécution de code généré


Sujet :

x86 32-bits / 64-bits Assembleur

  1. #1
    Membre éclairé Avatar de mchk0123
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    816
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Janvier 2007
    Messages : 816
    Points : 844
    Points
    844
    Par défaut Allocation mémoire pour exécution de code généré
    Voilà j'ai un grand nombre de DLL mathématiques compilées ainsi que les fichiers "*.h" (en C) décrivants les signatures de chaque fonction de chaque DLL. Pour ne pas arranger les choses, la liste des DLL doit pouvoir évoluer (sorte de plugins).

    Donc ce que je veux faire, c'est, à l'exécution, demander à l'utilisateur le nom d'une DLL, puis d'une fonction, puis des valeurs de chaque paramètre.
    Ensuite, je fais les étapes suivantes :

    - charger la DLL dont le nom n'est connu qu'à l'exécution (je sais faire)
    - pointer vers la fonction dont le nom n'est connu qu'à l'exécution (je sais faire)
    - convertir chaque paramètre depuis une chaîne de caractères vers le bon type (connu aprés analyse des fichiers "*.h") (je sais faire)
    - appeller la fonction (c'est là ou ça coinçe)

    Grand pb. s'il en est !

    Donc mon idée est d'allouer à l'exécution une zone mémoire exécutable
    et d'y écrire (à la brutos) les prologues & épilogues ASM ainsi qu'un simple call vers la fonction de la DLL ; et ce pour chaque fonction, une fois que je connais le nom de la DLL.

    En gros c'est le problème classique d'appel de fonctions entre un monde "interprétée" et un monde "compilée" (mais je veux une solution légère et simple à mettre en oeuvre).

    Je penses que ma méthode est viable, et je ne vois pas comment faire mieux. Mais est-ce que je risque des "General Protection Fault" (comment rendre mon code plus sur ?). En effet, est-ce que cela ne me posera des pbs. si les DLLs utilisent des technos avancées comme C++ mangling, COM+, exceptions (C++, structurées), ... Quand est-il des calling conventions, ... ?

    Je penses qu'un simple lien vers une présentation des prologue & épilogues "classiques" (par convention d'appel) pour proc x486 serait d'un grand secours.

  2. #2
    Rédacteur
    Avatar de Neitsa
    Homme Profil pro
    Chercheur sécurité informatique
    Inscrit en
    Octobre 2003
    Messages
    1 041
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Chercheur sécurité informatique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 041
    Points : 1 956
    Points
    1 956
    Par défaut
    Bonjour,

    Première question : N'y-a-t-il pas un moyen de standardiser le système de plug-ins ? Si les clients (ceux qui implémentent les DLLs de plug-in) dispose d'un interface de plug-in cela éviterait pas mal de problème...

    A moins que les DLLs soient disparates (pas d'interface de plug-in possible) et que leur but soit uniquement d'apporter des fonctions mathématiques supplémentaires à ton programme (dans ce cas là, ma première question est caduque). Ceci dit, cela peut poser un problème de sécurité : il suffirait de mettre une DLL malicieuse et de faire croire au programme que c'est un plug-in.

    Je penses que ma méthode est viable, et je ne vois pas comment faire mieux. Mais est-ce que je risque des "General Protection Fault" (comment rendre mon code plus sur ?). En effet, est-ce que cela ne me posera des pbs. si les DLLs utilisent des technos avancées comme C++ mangling, COM+, exceptions (C++, structurées), ... Quand est-il des calling conventions, ... ?
    Depuis l'assembleur, on ne peut pas appeler de fonction décorées ne serait-ce que par le fait que les schèmes de mangling sont différents entre compilateurs (par exemple, dans quel registre doit on passer 'this', comment s'occuper correctement des appels aux fonctions virtuelles, etc.)

    Le but du jeu est alors de créer un wrapper en C pour appeler du C++. A ce sujet voir : http://www.cs.nmsu.edu/please/ddl/cppredirection.php

    Au sujet des exceptions, on peut les gérer en assembleur, mais le code devient vraiment complexe... Comme tu souhaites générer dynamiquement l'appel vers une fonction il devient difficile de s'occuper correctement de la gestion de ces exceptions.

    Pour les conventions d'appel, il faudra évidemment générer des prologues d'appel différent suivant la convention d'appel de la fonction dans la DLL. Le tout devant être générer au niveau des opcodes...

    En gros il te faudra une fonction par convention d'appel qui prépare (à l'exécution) l'appel :

    - génération du nombre de PUSH adéquat (cdecl, stdcall, pascall)
    - génération de registres adéquat (fastcall, sachant que chaque compilateur utilise son propre schéma de registres utilisés, ça ce complique sévèrement)
    - génération du CALL adéquat
    - nettoyage de la pile (cdecl)


    Pour les différentes conventions d'appel tu peux regarder cet excellent document d'Agner Fog (il ya aussi un chapitre sur le name mangling) : http://www.agner.org/optimize/calling_conventions.pdf

    Donc mon idée est d'allouer à l'exécution une zone mémoire exécutable
    et d'y écrire (à la brutos) les prologues & épilogues ASM ainsi qu'un simple call vers la fonction de la DLL ; et ce pour chaque fonction, une fois que je connais le nom de la DLL.
    Si j'ai bien compris, quelque chose comme ça :

    Pour synthétiser, un exemple en pseudo-code C, préparant un appel vers une fonction (stdcall) dans un DLL :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
     
    //sachant que le bloc mémoire pour générer l'appel s'appelle pCall
     
    //allocation du bloc mémoire pour les arguments et le CALL
    char* pCall = VirtualAlloc();
     
     
    int PrepareStdCall(void* FuncPointer, int NumOfArgs, int[] pParams)
    {
     
    //génération des PUSH
    for (int i = 0; i < NumOfArgs; i++)
    {
    // générer l'opcode du PUSH (0x68) et le mettre dans pCall
    pCall[i*5] = 0x68;
     
    // A la suite, mettre le pointeur vers param numéro i
    (int)pCall[i+1*5] = pParams[i];
    }
     
    //génération du call (0xE8 02000000)
     
    //génération du RET (0xC3)
     
    // génération du JMP (0xFF25 xxxxxxxx) où xx.. est le pointeur de la fonction à appeler
     
     
    //appel de notre bloc mémoire pour appeler la fonction
    __asm
    {
         jmp pCall;
    }
    }
    C'est bien cela ?

    En tout cas ça me paraît vraiment compliqué, mais faire un système de plug-ins sans interface, on en est réduit, si on utilise pas de langage avec RTTI ou réflexion, à ce genre de manipulation. Je ne vois pas d'autres manières de procéder... Peut être quelqu'un d'autre aura une meilleure idée.

  3. #3
    Membre éclairé Avatar de mchk0123
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    816
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Janvier 2007
    Messages : 816
    Points : 844
    Points
    844
    Par défaut
    Ton pseudo code est exactement ce que j'avais en tête. Ca me rassure de savoir que je me suis exprimer clairement.

    En fait, dsl, le terme "plugin" est mal adapté à ma situation. Je n'ai pas du tout la main pour pouvoir imposer un framework ou une API pour les DLLs.
    Il s'agit plutôt de DLLs qui sont toutes en vrac (non uniformisées). Et en plus je veux rester suffisament ouvert pour permettre l'incorporation d'une nouvelle DLL sans avoir à recompiler.

    Question sécurité, c'est vrai que là j'ouvre une sacrée porte. Mais comment font donc par exemple "ctypes" pour Python, ou le langage D ?

    Pour les prologues et épilogues, les conventions d'appels (merci des 2 liens, ça va vraiment m'aider : je connaissait le guide d'optimisation Agner grâçe à RadAsm, mais pas le site Web) je devrait y arriver.

    Par contre j'ai plus d'inquiétudes en ce qui concerne :
    - les cadres de piles (ça je ne pas fermer les yeux là dessus) (à moins qu'absents en cas de compilation "release" des DLLs ?)
    - le mangling C++ (normalement pas trop dur, cryptique, mais pas trop dur)
    - le passage du this (j'ai aucune info là dessus)
    - les exceptions (C++ & C structurées) (... " ...)

    Ce dont je ne devrais pas avoir à me soucier :
    - les cadres de pointeurs (tableaux)
    - les cadres de mémoire allouée dans le tas

    Par contre là ou j'attendrais (voir laisser tomber complètement) c'est pour :
    - les informations de debug
    - les informations de profiling
    - les enrobages comme COM+

    Citation Envoyé par Neitsa
    En tout cas ça me paraît vraiment compliqué, mais faire un système de plug-ins sans interface, on en est réduit, si on utilise pas de langage avec RTTI ou réflexion, à ce genre de manipulation. Je ne vois pas d'autres manières de procéder...
    J'esperes avoir bien compris ce que tu dis : en fait tout ce passe comme si je gérait la réflexion par moi même. Avant d'appeller la DLL, je parse les fichiers "*.h" pour construire des listes d'informations (comme le nb. de params, leur type, ... etc.). Du coup je pourrais passer autre chose que des "ints" (comme dans le pseudo-code).

    Pour info. mon projet est un interpreteur "light" d'expressions mathématiques.
    Et les DLLs contiennent plein de fonctions mathématiques (C et hélas aussi C++). L'interpréteur est stable, le parseur de "*.h" est écrit, il ne me manque plus que l'appel effectif vers les DLLs pour terminer le "bridge".

  4. #4
    Rédacteur
    Avatar de Neitsa
    Homme Profil pro
    Chercheur sécurité informatique
    Inscrit en
    Octobre 2003
    Messages
    1 041
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Chercheur sécurité informatique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 041
    Points : 1 956
    Points
    1 956
    Par défaut
    Citation Envoyé par mchk0123
    Ton pseudo code est exactement ce que j'avais en tête. Ca me rassure de savoir que je me suis exprimer clairement.
    On s'est compris alors

    En fait, dsl, le terme "plugin" est mal adapté à ma situation. Je n'ai pas du tout la main pour pouvoir imposer un framework ou une API pour les DLLs.
    Il s'agit plutôt de DLLs qui sont toutes en vrac (non uniformisées). Et en plus je veux rester suffisament ouvert pour permettre l'incorporation d'une nouvelle DLL sans avoir à recompiler.
    D'accord, je comprend bien ce que tu veux faire. Prendre des DLLs d'horizons divers et les utiliser pour ton propre programme.

    Question sécurité, c'est vrai que là j'ouvre une sacrée porte. Mais comment font donc par exemple "ctypes" pour Python, ou le langage D ?
    Justement, bonne remarque, ca tombe pile dans ce que tu cherches à faire. Exploiter des DLLs sans avoir à s'occuper du langage dans lequel elles sont implémentée.

    ctypes comme d'autres, utilise le FFI (Foreign Function Interface ; cf. http://en.wikipedia.org/wiki/Foreign_function_interface ). Mais ça reste plus une sorte de Design Pattern qu'une implémentation concrète...

    Tu peux jeter un oeil du coté de SWIG (Simplified Wrapper and Interface Generator ; cf. http://www.swig.org/ ) qui permet de générer des interfaces, via un langage de script, pour appeler du code d'un langage vers un autre.

    De l'extérieur ça ressemble à une boite noire (c'est d'ailleurs le but), le problème est que tu devra directement regarder au niveau des sources (de SWIG ou d'un FFI) pour voir comment s'opère les bindings entre langages... Beaucoup de boulot en perspective

    Par contre j'ai plus d'inquiétudes en ce qui concerne :
    - les cadres de piles (ça je ne pas fermer les yeux là dessus) (à moins qu'absents en cas de compilation "release" des DLLs ?)
    Pour les "stack frames", ça n'est pas vraiment problématique, les fonctions s'occupent elles-même de nettoyer leur pile sauf en cdecl ou c'est toi (l'appelant) qui devra le faire. Mais en réalité c'est simple, tu soustrais de la pile le nombre d'arguments passés à la fonction multiplié par 4 :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    push 00000030 ; exemple d'un char (ASCII : '0')
    push 00000004 ; exemple d'int
    push 00401000  ; exemple d'un pointeur sur chaîne
    call xxxxxxxx ; appel de fonction avec convention cdecl 
    sub esp, 0x0C    ; nettoie la pile de  3 * 4 octets (enlève les trois PUSHs)
    - le mangling C++ (normalement pas trop dur, cryptique, mais pas trop dur)
    - le passage du this (j'ai aucune info là dessus)
    A mon humble avis, c'est vraiment le gros problème...

    Je n'ai jamais essayé de charger un pointeur de fonction C++ depuis de l'assembleur, peut être qu'il suffit juste de passer le nom décoré à la fonction chargé de trouver un pointeur (GetProcAdress() par exemple) ?

    Par contre, appeler du C++ sachant qu'il n'existe aucune convention entre compilateur et qu'aucune ABI (Apllication Binary Interface) ne stipule concrètement, par exemple, ou doit être passé le pointeur 'this'... (D'après Fog et quelques tests personnels il semble que ce soit le registre ECX sous Windows, mais il faudrait tester avec beaucoup de compilateur pour être sûr)....

    C'est sûrement possible, mais ça me semble nettement plus compliqué que d'appeler du C

    - les exceptions (C++ & C structurées)
    Au maximum en assembleur, sans que ce soit trop pénible, tu peux "gérer" les exceptions de façon à éviter de faire tomber le processus (crash) en cas d'exceptions graves, mais pas plus, ou alors ça devient fichtrement compliqué et il faut sortir la grosse armada (comprendre plusieurs centaines de ligne de code).

    En gros quelque chose comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    // protection via SEH (gestion exception)
    ... //code de gestion (tout au plus une 10aine de ligne en asm)
     
    push 0
    push 1
    call func
     
    // retrait du la protection
     
    ... ; si ça plante dans la fonction (func) on arrivera directement ici. En gros on sort de la fonction sans faire crasher le processus.
     
    ret ; retour à l'appelant (ton programme)


    Par contre là ou j'attendrais (voir laisser tomber complètement) c'est pour :
    - les informations de debug
    - les informations de profiling
    - les enrobages comme COM+
    Entièrement d'accord

    J'esperes avoir bien compris ce que tu dis : en fait tout ce passe comme si je gérait la réflexion par moi même. Avant d'appeller la DLL, je parse les fichiers "*.h" pour construire des listes d'informations (comme le nb. de params, leur type, ... etc.). Du coup je pourrais passer autre chose que des "ints" (comme dans le pseudo-code).
    Oui c'est bien ça.
    Néanmoins pour ce qui est de passer autre chose que des ints, n'oublie pas que sous les O.S 32 bits, tout se passe en interne comme si on ne poussait que des entiers (ou par registre si convention fastcall) sur la pile avant un appel de fonction.(j'entends ici, taille d'un entier = 32 bits).

    Que ce soit un 'char' ou un 'short' un un pointeur, tout fait 32 bits. Tout ce qui est susceptible de dépasser 32 bits (chaînes, 'long long' et consorts) est passé par pointeur, donc on ne passe au final que "quelque chose" qui fait de toute manière 32 bits.

    C'est pour cette raison que dans mon pseudo-code je passais les paramètres par un tableau d'int.

    Ca simplifie derechef le passage d'arguments à ta fonction qui fait le binding entre ton programme et la fonction de la DLL appelée, tu peux tout transtyper en int (ou unsigned int).

    Pour info. mon projet est un interpreteur "light" d'expressions mathématiques.
    Et les DLLs contiennent plein de fonctions mathématiques (C et hélas aussi C++). L'interpréteur est stable, le parseur de "*.h" est écrit, il ne me manque plus que l'appel effectif vers les DLLs pour terminer le "bridge".
    Ce qui simplifierait le processus, ce serait de pouvoir recompiler les fonctions de toute tes DLLs avec la même convention d'appel et en C... Encore faut-il que tu ais accès aux sources et que le passage du C++ vers le C puisse ce limiter à un simple "extern C {}"... (pas si sûr )

    Dommage que le 64 bits ne soit pas plus implanter que cela, au moins le 64 bits ne propose plus qu'une seul convention d'appel et qui plus est très codifiée, ce qui t'aurais éviter tous ces soucis.

    Bon courage

  5. #5
    Membre éclairé Avatar de mchk0123
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    816
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Janvier 2007
    Messages : 816
    Points : 844
    Points
    844
    Par défaut
    Citation Envoyé par Neitsa
    Pour les "stack frames", ça n'est pas vraiment problématique, les fonctions s'occupent elles-même de nettoyer leur pile sauf en cdecl ou c'est toi (l'appelant) qui devra le faire.
    Arf ... je crois qu'on ne parle pas de la même chose. Pour moi les cadres de piles, c'est du code compilé en + par l'utilisation d'une option passée au compilateur.

    Le principe (en simplifiant) étant d'encadrer la pile par une zone mémoire de "padding" pour détecter les écrasement exceptionnels en dehors de la plage normale ou il est "safe" d'écrire. Il existe à ma connaissance la même chose pour les zones mémoires allouée sur le tas, ainsi que la mémoire allouée pour chaque processus. C'est par ce mécanisme (enfin je crois), que par exemple, Windows détecte les "General Protection Fault". Surement qu'il existe quelque chose d'équivalent sous Unix.

    'oublie pas que sous les O.S 32 bits, tout se passe en interne comme si on ne poussait que des entiers
    Ca c'est une super bonne nouvelle ! C'est marrant pourtant de mémoire, il me semblait (du temps jadis ou j'avais touché un peu à l'ASM, c'est à dire des lustres ...), que certaines fonction renvoyant leur valeur de retour dans AL ou AX ou EAX selon la circonstance ?

    quelques tests personnels il semble que ce soit le registre ECX sous Windows, mais il faudrait tester avec beaucoup de compilateur pour être sûr
    Utiliser le C++ depuis l'assembleur ça peut faire peur. Mais je penses que ça reste jouable. Les spécifications du C++ sont énormément complexes (sans parler des templates & virtualité), mais au final le code généré ce rapproche beaucoup du C. Par exemple le paramètre this, n'est ni plus ni moins qu'un pointeur 32 bits sur une vtbl. Et de là on trouve tout ce qu'on veux (par contre attention à l'alignement).

    Dommage que le 64 bits ne soit pas plus implanter que cela, au moins le 64 bits ne propose plus qu'une seul convention d'appel et qui plus est très codifiée, ce qui t'aurais éviter tous ces soucis.
    Arf x2 ... Justement je suis sur 64 bits (en dév. et en release), dont peut être que je ne vais pas me géner Par contre trouver un compilo C++ qui compile en opcode x64 ... ? A part Intel et VS2005/PSDK je ne vois pas.

  6. #6
    Rédacteur
    Avatar de Neitsa
    Homme Profil pro
    Chercheur sécurité informatique
    Inscrit en
    Octobre 2003
    Messages
    1 041
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Chercheur sécurité informatique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 1 041
    Points : 1 956
    Points
    1 956
    Par défaut
    Citation Envoyé par mchk0123
    Arf ... je crois qu'on ne parle pas de la même chose. Pour moi les cadres de piles, c'est du code compilé en + par l'utilisation d'une option passée au compilateur.

    Le principe (en simplifiant) étant d'encadrer la pile par une zone mémoire de "padding" pour détecter les écrasement exceptionnels en dehors de la plage normale ou il est "safe" d'écrire. Il existe à ma connaissance la même chose pour les zones mémoires allouée sur le tas, ainsi que la mémoire allouée pour chaque processus. C'est par ce mécanisme (enfin je crois), que par exemple, Windows détecte les "General Protection Fault". Surement qu'il existe quelque chose d'équivalent sous Unix.
    D'accord Je croyais que tu parlais du "stack frame".

    Mais dans ce que tu dis, généralement c'est la fonction qui se charge de vérifier la stabilité de sa pile (un peu comme les vérifications au runtime proposé par les compilos de Microsoft pour éviter les écrasements suspicieux menant à des shellcodes).

    Si je comprend bien tu voudrais aussi appliquer cette vérification dans le code généré à l'exécution qui va appeler la fonction ?

    Ca c'est une super bonne nouvelle ! C'est marrant pourtant de mémoire, il me semblait (du temps jadis ou j'avais touché un peu à l'ASM, c'est à dire des lustres ...), que certaines fonction renvoyant leur valeur de retour dans AL ou AX ou EAX selon la circonstance ?
    L'ABI définit que le retour doit se faire sur EAX (32 bits) [ AX en 16 bits, RAX en 64]. Après en assembleur, on peut retourner n'importe où et sur n'importe quelle taille

    Arf x2 ... Justement je suis sur 64 bits (en dév. et en release), dont peut être que je ne vais pas me géner Par contre trouver un compilo C++ qui compile en opcode x64 ... ? A part Intel et VS2005/PSDK je ne vois pas.
    Ah ok, si tu ne travaille qu'en 64 bits sous Windows, penche toi sur le document d'Agner Fog qui traite des conventions d'appel.

    Sinon regarde les spécifications de Microsoft à ce sujet :

    http://msdn2.microsoft.com/en-us/lib...86(VS.80).aspx

    Le compilo microsoft (pour x64) ne permet plus l'asm inline, il faut compiler à coté (fichier séparé) ou "hardcoder" les opcodes à la main...

  7. #7
    Membre éclairé Avatar de mchk0123
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    816
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Janvier 2007
    Messages : 816
    Points : 844
    Points
    844
    Par défaut
    Citation Envoyé par Neitsa
    D'accord Je croyais que tu parlais du "stack frame".
    Et oui je me rappelles plus exactement comment ça s'appelles.

    Citation Envoyé par Neitsa
    Si je comprend bien tu voudrais aussi appliquer cette vérification dans le code généré à l'exécution qui va appeler la fonction ?
    Oui ça serait l'idéal, car ça augmenterais de beaucoup la stabilité et la sécurité de mes appels de fonctions. Mais bon fait pas que je rêve, là dessus ça va être du "compilo-spécifique".

    Citation Envoyé par Neitsa
    Ah ok, si tu ne travaille qu'en 64 bits sous Windows, penche toi sur le document d'Agner Fog qui traite des conventions d'appel.

    Sinon regarde les spécifications de Microsoft à ce sujet :
    Merci encore pour ce lien.

    Décidément je reviendrais plus souvent sur le forum ASM, on y rencontre de vrais experts dans ce domaine.

Discussions similaires

  1. évènement onclick pour exécuter un code
    Par paolo2002 dans le forum Général JavaScript
    Réponses: 5
    Dernier message: 29/04/2008, 13h30
  2. quelques problèmes pour exécuter mes codes
    Par djimangue dans le forum Langage
    Réponses: 6
    Dernier message: 18/12/2007, 01h00
  3. Pas assez de mémoire pour exécuter un code
    Par med_ellouze dans le forum Langage
    Réponses: 6
    Dernier message: 11/08/2007, 02h51
  4. Réponses: 28
    Dernier message: 27/05/2007, 15h16
  5. Réponses: 6
    Dernier message: 24/03/2006, 18h24

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo