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 :

Inclure de l'ASM dans du C [FAQ]


Sujet :

x86 32-bits / 64-bits Assembleur

  1. #1
    Futur Membre du Club
    Inscrit en
    Mai 2004
    Messages
    6
    Détails du profil
    Informations forums :
    Inscription : Mai 2004
    Messages : 6
    Points : 7
    Points
    7
    Par défaut Inclure de l'ASM dans du C
    Bonjour, je suis confronté à une difficulté. Mon projet comporte à la fois des fichiers en langage C ainsi que des fichiers mis à part en ASM (où cetaines fonctions ont leurs corps et sont ensuite appelées dans le C). Ce que je voudrais faire c'est inclure ce code ASM dans mon programme mais je ne peux pas utiliser le code suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    asm { 
    (là j'ajoute mon code) 
    }
    car il y a trop de code (de plus je crois qu'on ne peut utiliser cela que pour quelques lignes en ASM).

    Je travaille sur Borland C++ compiler 5.02.
    Si quelqu'un sait comment ajouter ce type de code ASM ou bien si je peux inclure mes fichiers ASM en tant que bibliothèques (équivalent des fichiers .h), merci de m'en informer.

  2. #2
    mat.M
    Invité(e)
    Par défaut
    Bonjout , ce n'est pas possible de compiler le fichier .asm avec TASM et d'en obtenir un fichier .obj objet de compilation ??
    Ce fichier objet sera lié ensuite au projet en C

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Juillet 2002
    Messages
    842
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juillet 2002
    Messages : 842
    Points : 696
    Points
    696
    Par défaut
    Bonsoir,

    Je n'ai jamais eu ce genre d'erreur avec ce compilateur si l'on parle bien du même. Aurais-tu l'erreur exacte ?

    Théoriquement, et je l'ai déjà fait, on peut ajouter autant de lignes assembleur que l'ont veut, presque là où on veut :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    int main()
    {
      asm mov eax, 0
      asm ret
    }
    ou bien :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
      asm
      {
        mov eax, 0
        ret
      }
    }

  4. #4
    Futur Membre du Club
    Inscrit en
    Mai 2004
    Messages
    6
    Détails du profil
    Informations forums :
    Inscription : Mai 2004
    Messages : 6
    Points : 7
    Points
    7
    Par défaut
    Pour ceux que ca interesse Blustuff avait raison on peut ajouter autatn de code ASM voulu et l'on est donc pas limiter
    Merci bcp pour l'aide apportée

  5. #5
    Membre averti Avatar de Pierre Maurette
    Profil pro
    Inscrit en
    Juillet 2002
    Messages
    283
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France

    Informations forums :
    Inscription : Juillet 2002
    Messages : 283
    Points : 390
    Points
    390
    Par défaut
    Bonjour,
    Quelques précisions, concernant l'écriture de fonctions 100% assembleur, ce qui peut intéresser Condor7 dans le cadre de son travail, puisque c'est une bonne solution intermédiaire entre l'assemblage/lien de modules assembleur et les simples blocs asm{/*quelques instructions*/}.
    Constatations faites sur les versions 5.5 et 5.6 du compilateur Borland, différentes de 5.02, et dispo gratuitement sur le site Borland.
    L'assembleur en ligne de Borland C++ est peu documenté. Il n'est pas explicitement prévu de fonctions 100% assembleur (contrairement à Delphi/Pascal). Pas de directive "naked" non plus à ma connaissance, celle-ci permettant de demander au compilateur de ne pas générer de code de prologue et d'épilogue. Néanmoins, les codes suivants fonctionnent :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    byte __fastcall Test1(byte valeur)
    {
      asm shl al, 1
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    byte __fastcall Test2(byte valeur)
    {
      asm
      {
        mov dl, valeur
        shl dl, 1
        mov al, dl
      }
    }
    Ces deux fonctions décalent "valeur" d'un bit vers la gauche, donc multiplient par 2 un entier positif.. Elles renvoient bien un résultat dans AL, et l'appel fonctionne comme attendu, ce qui n'empêche pas le compilateur d'avertir (pas de return).

    L'assembleur BASM de Delphi est mieux sur ce plan, il offre par exemple une variable symbolique @Result.

    Il faut être prudent avec les conventions d'appel : si les paramètres sont passés par la pile, voir qui doit maintenir la pile. En fait, il est difficile de se passer d'un débogueur en CPU pour voir ce qui se passe EFFECTIVEMENT au moment de l'appel. Eventuellement, sauvegarder les registres en début de fonction et les restaurer avant lme retour (sauf EAX !). Jamais eu à le faire sur mes tests.

    Enfin, les appels de fonctions (qui restent AMHA des fonctions C) sont "peut-être" coûteux. Donc, si certaines fonctions du source asm original ne sont appelées que par du code asm, il est peut-être judicieux de les inclure dans une seule fonction C 100% assembleur.

    Pierre

  6. #6
    Membre éclairé
    Profil pro
    Inscrit en
    Juillet 2002
    Messages
    842
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juillet 2002
    Messages : 842
    Points : 696
    Points
    696
    Par défaut
    C'est un peu barbare, mais il existe un moyen, je ne sais pas sur quelle versions, mais ca marchait sur toutes celles que j'ai essayée, de supprimer les prologues épilogues en utilisant :

    __declspec(naked)

    Category

    Modifiers, C++Builder keyword extensions, Storage class specifiers

    Syntax

    __declspec( naked ) declarator

    Use of the naked argument suppresses the prolog/epilog code. Be aware when using __declspec(naked) that it does not set up a normal stack frame. A function with __declspec(naked) will not preserve the register values that are normally preserved. It is the programmer's responsibility to conform to whatever conventions the caller of the function expects.
    You can use this feature to write your own prolog/epilog code using inline assembler code. Naked functions are particularly useful in writing virtual device drivers.

    The naked attribute is relevant only to the definition of a function and is not a type modifier.
    Example
    This code defines a function with the naked attribute:

    // Example of the naked attribute
    __declspec( naked ) int func( formal_parameters )
    {
    // Function body
    }
    Mais alors j'ai eu des surprises, et ca à changé d'un compilateur sur l'autre il me sembe. Pas d'épilogues, ca veut dire pas de ret en fin de fonction, même si return était donné explicitement en C. Donc, la première fois ip s'était barré dans les choux, je me demande comment il arrivait à la fin du programme, mais ca donnait une erreur difficile à trouver, enfin c'est de ma faute.

    Pour les conventions d'appel pareil, on peut les spécifier : __cdecl, __stdcall, __pascal, __fastcall. Mais enfin c'est perrilleux, car, si on précise naked pour la fonction, le compilateur n'y voit que du feu, et ne veut pas adresser les variables par esp, il continue de les adresser par ebp. Même sans naked encore, j'ai remarqué qu'en demandant au compilateur d'optimiser la compilation, il gèrait la pile un peu différement, et l'asm inline avait des problème pour les adressages spécifiés par des symboles.

    Enfin, ces problèmes sont peut être corrigés dans des patchs.

    Sinon, j'ai moi aussi une question, pourquoi les fonctions qui contiennent de l'asm inline ne peuvent pas être développée inline quand la déclaration de la fonction est dans le même fichier ?

  7. #7
    Membre averti Avatar de Pierre Maurette
    Profil pro
    Inscrit en
    Juillet 2002
    Messages
    283
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France

    Informations forums :
    Inscription : Juillet 2002
    Messages : 283
    Points : 390
    Points
    390
    Par défaut
    C'est un peu barbare, mais il existe un moyen, je ne sais pas sur quelle versions, mais ca marchait sur toutes celles que j'ai essayée, de supprimer les prologues épilogues en utilisant :
    En fait, j'avais vu le truc, puis oublié. Aide en français (C++Builder6):

    Catégorie

    Modificateurs, Extensions de C++Builder, Spécificateurs de classe de stockage

    Syntaxe

    __declspec( naked ) déclarateur

    L'utilisation de l'argument naked permet de supprimer le code prologue et épilogue. Veillez à ce que l'utilisation de __declspec(naked) ne définisse pas un cadre de pile normal. Une fonction avec __declspec(naked) ne conserve pas les valeurs de registre habituellement conservées. Il revient au programmeur de respecter les conventions attendues par l'appelant de la fonction.
    Vous pouvez utiliser cette fonctionnalité pour écrire votre propre code prologue et épilogue à l'aide de code assembleur en ligne. Les fonctions naked sont particulièrement utiles pour l'écriture de pilotes de périphériques virtuels.

    L'attribut naked ne concerne que la définition d'une fonction et n'est pas un modificateur de type.
    Exemple
    Le code suivant définit une fonction à l'aide de l'attribut naked :

    // Exemple d'attribut naked
    __declspec( naked ) int func( formal_parameters )
    {
    // corps de la fonction
    }
    Mais alors j'ai eu des surprises, et ca à changé d'un compilateur sur l'autre il me sembe. Pas d'épilogues, ca veut dire pas de ret en fin de fonction, même si return était donné explicitement en C. Donc, la première fois ip s'était barré dans les choux, je me demande comment il arrivait à la fin du programme, mais ca donnait une erreur difficile à trouver, enfin c'est de ma faute.

    Pour les conventions d'appel pareil, on peut les spécifier : __cdecl, __stdcall, __pascal, __fastcall. Mais enfin c'est perrilleux, car, si on précise naked pour la fonction, le compilateur n'y voit que du feu, et ne veut pas adresser les variables par esp, il continue de les adresser par ebp. Même sans naked encore, j'ai remarqué qu'en demandant au compilateur d'optimiser la compilation, il gèrait la pile un peu différement, et l'asm inline avait des problème pour les adressages spécifiés par des symboles.

    Enfin, ces problèmes sont peut être corrigés dans des patchs.
    Il y en aurait pour des pages. Il faut être très prudent avec les options de compilation (stack frames -k, calling convention -px). Par exemple, comme vous le dites et contrairement à Delphi, le nom d'un paramètre est toujours traduit dans la pile (/EBP), même en appel registre (le code de prologue empile EAX). Et cette traduction se fait bêtement, tout comme en Delphi d'ailleurs, à la manière d'un #define valide dans toute la fonction. Et ce point répond certainement en partie à votre question finale.
    De plus, le compilateur doit respecter la norme quand aux instructions C ou C++. Avec quelques règles, on arrive plus ou moins à prévoir ce qui va se passer:
    - return N; est traité "le plus souvent" comme mov eax, (int)N. le RET fait partie de l'épilogue.
    - foo(a, b); est traité comme un empilement des paramètres ou une copie dans les registres, suivi d'un CALL.
    - lval = foo(a, b); est traité comme foo(a, b); suivi "le plus souvent" d'un mov lval, eax.

    Si on déclare :
    __declspec(naked) __fastcall int Test4(int valeur)
    dans le but que l'appel en C/C++ passe le paramètre dans eax, ça fonctionne, mais on remarque que "mov [ebp - 4], eax" est ajouté en début de fonction, et que "valeur" est développé une fois encore en "dword ptr [ebp - 4]". De plus, ce "mov [ebp - 4], eax" non géré écrase les variables locales de la fonction appelante. Not good.
    Peut-être une option m'aura échappé, mais il me semble qu'il faut éviter les naked + __fastcall (douteux de toutes façons) et que le moyen le plus sût d'appeler une fonction naked est de le faire en assembleur. Code de mes tests :

    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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    #include <stdio.h>
     
    int __fastcall Test1(int valeur)
    {
      asm shl eax, 2
    }
     
    int __fastcall Test2(int valeur)
    {
      asm
      {
        mov edx, valeur
        shl edx, 2
        mov eax, edx
      }
    }
     
    __declspec(naked) int Test3(int valeur)
    {
      asm
      {
        push ebp
        mov ebp, esp
        mov eax, valeur
        shl eax, 2
        pop ebp
        ret
      }
    }
     
    __declspec(naked) __fastcall int Test4(int valeur)// Caca
    {
      asm
      {
        shl eax, 2
        ret
      }
    }
     
     
     
    __declspec(naked) Test5()
    {
    #define _Argument [esp + 4]
      asm
      {
        mov eax, _Argument
        shl eax, 2
        ret
      }
    }
     
     
    int main()
    {
      int i, j;
      for(i = 0; i < 10; i++)
      {
        j = Test1(i);
        printf("%d\t", j);
     
        j = Test2(i);
        printf("%d\t", j);
     
        j = Test3(i);
        printf("%d\t", j);
     
        asm
        {
          push i
          call Test3
          mov j, eax
          pop ecx
        }
        printf("%d\t", j);
     
        j = Test4(i);
        printf("%d\t", j);
     
        asm
        {
          mov eax, i
          call Test4
          mov j, eax
        }
        printf("%d\t", j);
     
        asm
        {
          push i
          call Test5
          mov j, eax
          pop eax
        }
        printf("%d\t", j);
     
        asm push i
        Test5();
        asm mov j, eax
        asm pop eax
        printf("%d\n", j);
      }
      return 0;
    }
    Sinon, j'ai moi aussi une question, pourquoi les fonctions qui contiennent de l'asm inline ne peuvent pas être développée inline quand la déclaration de la fonction est dans le même fichier ?
    Ce n'est pas refusé effectivement par d'autres compilateurs, mais ça me paraît logique. En fait, l'inline n'est d'après la norme (C++ et C99) qu'une suggestion, et Borland aurait pu ne pas générer d'erreur et se contenter de ne pas inliner, mais je préfère le refus.
    Je vois intuitivement une série de raisons, que j'ai du mal à expliquer clairement :
    - Rappel: le sens de "inline" dans la norme n'est pas de forcer au remplacement du nom de la fonction par son code, c'est une suggestion d'optimisation et une autorisation de négliger l'appel effectif. Son utilité principale est en C++ de cumuler la sécurité d'une variable privée et la vitesse d'accès d'une variable publique, par l'écriture de getters/setters inline.
    - Le code asm n'est pas, ou très peu, analysé par le compilateur. Hors, en connaître le sens est nécessaire pour le développer intelligemment inline.
    - L'accès aux variables est déjà un problème en asm normal. Il me semble (à confirmer) qu'une fonction peut être inlinée différemment selon le contexte :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    inline int mul2(int i) {return 2 * i;}
    ...
    volatile int a = 5;
    const int b = 3;
    int c, d, e;
    ...
    c = mul2( a);// génère une multiplication, en fait un décalage
    d = mul2( b);// génère d = 6
    e = mul2(12);// génère e = 24
    Avec de l'assembleur, ça devient ingérable.
    - "inline" demande au compilo de prendre toute initiative pour optimiser, "asm" lui dit: cherche pas à comprendre, je sais ce que je fais, tu me compiles ça comme je l'écris. C'est clairement incompatible. On peut aller plus loin, un code comportant de l'asm ne pourra pratiquement être optimisé dans la portion précédent cet asm. Pour VC++7.1 (de VS .NET), qui pourtant optimise violemment :

    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
    int main()
    {
      int c = 2;
      int b = 3;
      return 0;
    }
     
    int main()
    {
      int c = 2;
      int b = 3;
      c = 3;
      _asm mov eax, c
      _asm mov c, eax
      return 0;
    }
    La première version ne génère rien (xor eax, eax; ret. La seconde ignore b, mais crée un c sur la pile, directement avec la valeur 3, avant de coder les lignes asm pourtant inutiles. On peut retenir que VC++7.1 attribue un coté "volatile" aux opérations asm (il effectue tout), calcule les variables nommément citées dans l'asm juste avant l'asm (et non sur chaque opération comme si elles étaient volatile).

    Donc, et crotte aux grincheux, la bonne façon d'inliner de l'assembleur est la macro (au vin blanc, ou aux aromates). En la définissant juste sur le scope nécessaire, je ne vois pas de danger:

    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
    #define CHRONO_DEB asm{ pushad; \
                            xor eax, eax; \
                            cpuid; \
                            rdtsc; \
                            mov eax, 10;\
                            mov edx, 100; \
                            mov DWORD PTR [Chrono + 0], eax; \
                            mov DWORD PTR [Chrono + 4], edx; \
                            popad;}
     
     
    #define CHRONO_FIN asm{ pushad; \
                            xor eax, eax; \
                            cpuid; \
                            rdtsc; \
                            mov eax, 15;\
                            mov edx, 100; \
                            sub eax, DWORD PTR [Chrono + 0]; \
                            sbb edx, DWORD PTR [Chrono + 4]; \
                            mov DWORD PTR [Chrono + 0], eax; \
                            mov DWORD PTR [Chrono + 4], edx; \
                            popad;}
    ...                        
      __int64 Chrono;
      CHRONO_DEB
      {
      // Code à tester 
      }
      CHRONO_FIN
      printf("%I64d\n", Chrono);
    Pierre

  8. #8
    Membre éclairé
    Profil pro
    Inscrit en
    Juillet 2002
    Messages
    842
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juillet 2002
    Messages : 842
    Points : 696
    Points
    696
    Par défaut
    Oui, effectivement merci ^^

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Inclure un bloc style dans le body
    Par zoullou dans le forum Mise en page CSS
    Réponses: 5
    Dernier message: 29/11/2004, 11h00
  2. problème xsl : inclure une donnée xml dans une balise html
    Par djodjo dans le forum XSL/XSLT/XPATH
    Réponses: 3
    Dernier message: 03/01/2003, 09h24
  3. Coloration syntaxique ASM dans un RichEdit
    Par Crick dans le forum Composants VCL
    Réponses: 5
    Dernier message: 20/12/2002, 01h53
  4. [TP]code asm dans une procedure
    Par M.Dlb dans le forum Turbo Pascal
    Réponses: 3
    Dernier message: 17/08/2002, 20h43

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