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 :

critique d'une petite routine


Sujet :

x86 32-bits / 64-bits Assembleur

  1. #1
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut critique d'une petite routine
    Bonjour,

    Sur la voie d'apprendre l'assembleur (moderne)... J'ai réalisé une petite routine juste pour afficher un chiffre. J'aimerais bien une critique (constructive et sympathique, mais critique) ; voir code ci-dessous. Sinon, cela m'a amené à quelques questions :

    1. Paramètres octets: Comment passer un paramètre de taille inférieure au mot (en particulier un byte) sur la pile, et non dans un registre, et le retrouver facilement ? (problèmes d'endianness et de pile la tête en bas)
    Je n'arrive pas à penser ça. Quand j'essaie de le faire, ma routine affiche toujours 'n' pour une raison que je ne comprends pas. J'ai essayé de lire [esp], [esp+3], [esp-3] (logiquement mon byte devrait être à l'une de ces 3 adresses) et ça donne toujours 'n'. (Pour l'instant j'utilise un registre.)

    2. Méta-infos: Y a-t-il une façon standard de transmettre en retour une méta-info, comme un signal d'erreur, par opposition à une véritable valeur de retour ? Pour ces dernières, on utilise généralement eax; y a-t-il un registre couramment dédié aux erreurs ou autres méta-infos ? (Pour l'instant je décide d'utiliser ebx, qui est souvent libre.)

    3. Labels locaux: Y a-t-il une façon ordinaire de définir des labels locaux (aussi bien pour les données que pour les cibles de sauts) ? Je sais que certains assembleurs permettent de faire cela, mais y a-t-il une méthode simple et courante indépendamment de l'assembleur utilisé? Aussi, comment ces assembleurs réalisent-ils cela ? (Pour l'intant, je préfixe mes labels locaux avec le nom de la routine.)


    Merci,
    Denis

    PS : Désolé de vous transmettre ça en anglais, j'ai l'habitude d'échanger du code avec des gens de toute origine, et souvent anglophones.


    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
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
     
    ; asm (nasm)
     
    ; -----------------------------------------------------------------------------
    ;              write_digit
    ;
    ; This function is able to write a digit in any numeral base from 2 to 36.
    ; For bases above 10, it uses latin letters --as for ordinary hex writing.
    ; Only uppercase letters are used here (they have the size of decimal digits).
    ; Note that base 36 means digits from 0 to 35, not 36 ;-).
    ; The only parameter is the digit's numeric value, which should be passed in
    ; eax (al in fact). The function returns in ebx 0 if all is right, 1 if the
    ; value was strictly above 35, in which case '?' is written out.
    ; -----------------------------------------------------------------------------
     
     
    section .data
    ; space & newline, for testing
       space       db 32
       newline     db 10
     
    ;  static array of digits, plus error code
       digits: db "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?"
       digit_error_code equ $ - 1
     
    section .bss
     
    section .text
       global _start
     
     
    ; === write digit =============================================================
    ;     digit numeric value: in al
     
    write_digit:
       movzx ecx, al              ; pointer to digit will be in ecx for writing
       cmp ecx, 35
       ja write_digit_above_35
    ; case below or equal to 35:
       mov ebx, 0                 ; return value: ok
       add ecx, digits            ; point to digit
       jmp write_digit_write
    ; case above 35:
    write_digit_above_35:
       mov ebx, 1                 ; return value: error
       mov ecx, digit_error_code  ; point to error code '?'
    ; write digit
    write_digit_write:
       mov edx, 1                 ; weight of string to be written
      ; (pointer to byte already in ecx)
       mov ebx, 1                 ; file descriptor for stdout
       mov eax, 4                 ; code for sys_write
       int 80h
       ret
     
     
    ; === test with sample values =================================================
     
    ; --- test tool routines: write space & newline -------------------------------
    write_space:
       mov edx, 1
       mov ecx, space
       mov ebx, 1
       mov eax, 4
       int 80h
       ret
     
    write_newline:
       mov edx, 1
       mov ecx, newline
       mov ebx, 1
       mov eax, 4
       int 80h
       ret
     
    ; --- start = test ------------------------------------------------------------
    _start:
       xor eax, eax
     
       ; 0
       mov al, 0
       call write_digit
       call write_space
     
       ; 1
       mov al, 1
       call write_digit
       call write_space
     
       ; 9
       mov al, 9
       call write_digit
       call write_space
     
       ; 10
       mov al, 10
       call write_digit
       call write_space
     
       ; 15
       mov al, 15
       call write_digit
       call write_space
     
       ; 16
       mov al, 16
       call write_digit
       call write_space
     
       ; 35
       mov al, 35
       call write_digit
       call write_space
     
       ; 36
       mov al, 36
       call write_digit
       call write_space
     
       ; 255
       mov al, 255
       call write_digit
       call write_space
     
       call write_newline
       jmp _end             ; useless here, but kept for generality
     
    ; --- end ---------------------------------------------------------------------
    _end:
    	mov	ebx, 0      ; exit code 0
    	mov	eax, 1      ; code for sys_exit
    	int	80h

  2. #2
    Membre confirmé Avatar de bifur
    passe le balais et l'aspirateur
    Inscrit en
    Mars 2008
    Messages
    314
    Détails du profil
    Informations personnelles :
    Âge : 38

    Informations professionnelles :
    Activité : passe le balais et l'aspirateur

    Informations forums :
    Inscription : Mars 2008
    Messages : 314
    Points : 550
    Points
    550
    Par défaut
    je voie que tu uttilise l'intrruption 80h pour appeler tes fonctions système je suppose donc que tu uttilise linux si oui est ce que tu aurais une bonne doc sur les fonction disponible via l'int80h? ça m'aiderait a comprendre mieux (et de me mettre enfin a l'assembleur pour inux)

    dans les anciens système ont uttilisait les registres du processeur pour les arguments des sous fonctions au lieux de la pile ce qui permettait de passer des argment de la taille d'un octet

  3. #3
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par bifur Voir le message
    je voie que tu uttilise l'intrruption 80h pour appeler tes fonctions système je suppose donc que tu uttilise linux si oui est ce que tu aurais une bonne doc sur les fonction disponible via l'int80h? ça m'aiderait a comprendre mieux (et de me mettre enfin a l'assembleur pour inux)
    Oui et non. J'ai une liste complète de syscalls, avec tous les arguments de chaque appel (y compris description des typedefs et structs concernés). Un parfait aide-mémoire, donc. C'est une page web qui inclut même des liens vers les fichiers source de Linux concernés sur ton système; mais ça marche pas chez moi, apparemment les sources ne sont pas au même endroit chez moi (sous Lubuntu) que chez l'auteur. (Le jour où je comprendrai la logique de l'orga du système de fichiers Unix, les poules auront des dents.)

    PS: Je l'ai retrouvée sur internet, ça vient de là:
    http://docs.cs.up.ac.za/programming/.../syscalls.html
    (comme ça j'ai pas à te la passer en privé)

    Une liste explicative des syscalls Linux, je sais même pas si ça existe! Encore moins une classée par fonctionnalité (I/O, gestion des processus, ...). Je suis tombé sur 2-3 listes vaguement "pédagogiques" dans des tutos, mais avec juste une douzaine d'appels chacune. Si tu trouves mieux, je suis preneur.

    Après, pour les détails, tu as toujours les man pages, section 2 pour les syscalls. Exemple pour sys_write, tape dans un terminal:
    Mais il faut déjà savoir au sujet de quoi chercher la doc !

    dans les anciens système ont uttilisait les registres du processeur pour les arguments des sous fonctions au lieux de la pile ce qui permettait de passer des argment de la taille d'un octet
    Juste ce que je fais, quoi? Ca me pose pas de problème tant qu'il s'agit de sous-routines internes (à un "module", disons) et bien documentées. Mais dès que ça déborde de l'interface vers des "clients" externes, même si c'est mon propre code, je voudrais un système simple et sûr, et pour ça y a que la pile (ou alors je veux bien apprendre, but de mes questions).

    A plus long terme, j'aurais envie en fait d'écrire un petit préprocesseur qui traduit les appels et retours de fonctions en suivant une logique (calling convention) bien définie. Je lance mon préprocesseur et en sortie j'ai le code assembleur (Nasm) bien écrit, simple et sûr. Genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
        monappel f a b
     
    ==>
     
        push b
        push a
        push ebp
        sub esp, 12
        mov ebp, esp
        ....
    (pas correct probablement, j'ai pas encore intégré le truc, mais tu vois l'idée)

    Denis

  4. #4
    Membre confirmé Avatar de bifur
    passe le balais et l'aspirateur
    Inscrit en
    Mars 2008
    Messages
    314
    Détails du profil
    Informations personnelles :
    Âge : 38

    Informations professionnelles :
    Activité : passe le balais et l'aspirateur

    Informations forums :
    Inscription : Mars 2008
    Messages : 314
    Points : 550
    Points
    550
    Par défaut
    Une liste explicative des syscalls Linux, je sais même pas si ça existe! Encore moins une classée par fonctionnalité (I/O, gestion des processus, ...). Je suis tombé sur 2-3 listes vaguement "pédagogiques" dans des tutos, mais avec juste une douzaine d'appels chacune. Si tu trouves mieux, je suis preneur.
    c'est la meme chose pour moi, c'est pour ça que l'assembleur je n'ai pas pu décoller du dos pour en faire

    c'est curieux que ta fonction n'affiche que des "n" la seule erreur qui pourrait explique cela c'est que ecx ne pointe pas a l'endroit voulu. est ce que la valeur "digits" contient bien l'adresse mémoire de la chaine et qu'il n'y aurait pas un offset en plus lors de la compilation?

  5. #5
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par bifur Voir le message
    c'est curieux que ta fonction n'affiche que des "n" la seule erreur qui pourrait explique cela c'est que ecx ne pointe pas a l'endroit voulu. est ce que la valeur "digits" contient bien l'adresse mémoire de la chaine et qu'il n'y aurait pas un offset en plus lors de la compilation?
    J'ai pas été assez clair, peut-être. Elle affiche que des 'n' si j'essaie de passer la valeur numérique du chiffre sur la pile. Mais dans la version postée cette valeur est passée en registre, et tout va bien Je voudrais quand même trouver un moyen de passer un byte ou toute valeur plus "légère" qu'un mot sur la pile (et la retrouver sans difficulté ni erreur possible depuis l'intérieur de la routine).

    Denis

  6. #6
    Membre confirmé Avatar de bifur
    passe le balais et l'aspirateur
    Inscrit en
    Mars 2008
    Messages
    314
    Détails du profil
    Informations personnelles :
    Âge : 38

    Informations professionnelles :
    Activité : passe le balais et l'aspirateur

    Informations forums :
    Inscription : Mars 2008
    Messages : 314
    Points : 550
    Points
    550
    Par défaut
    j'ai réussi a modifier ton ptit programme pour passer les argument par la pile
    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
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
     
    ; === write digit =============================================================
    ;     digit numeric value: in al
     
    write_digit:
    mov ecx,[esp+4]
    and ecx,0FFh
       cmp ecx, 35
       ja write_digit_above_35
    ; case below or equal to 35:
       mov ebx, 0                 ; return value: ok
       add ecx, digits            ; point to digit
       jmp write_digit_write
    ; case above 35:
    write_digit_above_35:
       mov ebx, 1                 ; return value: error
       mov ecx, digit_error_code  ; point to error code '?'
    ; write digit
    write_digit_write:
       mov edx, 1                 ; weight of string to be written
      ; (pointer to byte already in ecx)
       mov ebx, 1                 ; file descriptor for stdout
       mov eax, 4                 ; code for sys_write
       int 80h
       ret
     
     
    ; === test with sample values =================================================
     
    ; --- test tool routines: write space & newline -------------------------------
    write_space:
       mov edx, 1
       mov ecx, space
       mov ebx, 1
       mov eax, 4
       int 80h
       ret
     
    write_newline:
       mov edx, 1
       mov ecx, newline
       mov ebx, 1
       mov eax, 4
       int 80h
       ret
     
    ; --- start = test ------------------------------------------------------------
    _start: ; You can move the start label where-ever you want in code segment.
       xor eax, eax
     
       ; 0
       mov ah, 0
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 1
       mov ah, 1
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 9
       mov ah, 9
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 10
       mov ah, 10
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 15
       mov ah, 15
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 16
       mov ah, 16
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 35
       mov ah, 35
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 36
       mov ah, 36
    push ax
    inc esp
       call write_digit
       call write_space
     
       ; 255
       mov ah, 255
    push ax
    inc esp
       call write_digit
       call write_space
     
       call write_newline
       jmp _end             ; useless here, but kept for generality
     
    ; --- end ---------------------------------------------------------------------
    _end:
    	mov	ebx, 0      ; exit code 0
    	mov	eax, 1      ; code for sys_exit
    	int	80h
    en fait il m'as suffit de lire la dernière data empilé a l'adresse esp+4 car en fait l'instruction call empile de façon implicite l'adresse de retour, 4 octet dans mon cas car on est en 32 bit d'adresssage et que le call se situe dans le même segment (le coup de l'empilement invisble je ne m'en souvenait plus vu que je ne bidouille pas souvent la pile ) donc le décalage dans la pile varie en fonction de la façon dont on appelle la sous fonction

    et pour pouvoir empiler seulement un octet je fait (pour empiler ah)

  7. #7
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Merci, Bifur !

    Tu m'as permis de trouver le moyen de faire comme je le souhaitais, bien que ce soit pas exactement comme tu le fais toi, là. Mon problème était je crois l'histoire de l'adresse de retour inscrite à esp par call, comme tu le dis.

    [Comment on fait pour insérer du code inline avec BBCode ?]

    Bien, la valeur numérique du chiffre est maintenant passée sur la pile, dans l'octet de poids faible d'un mot. Je le lis là, à esp+4 et non esp, donc, en remplissant le reste du mot de zéros grâce à l'instruction movzx. Et voilà !

    J'ai modifié 2, 3 autres petits trucs ici et là, ci-dessous la nouvelle version au cas où ça intéresse quiconque. Entretemps j'ai écrit la routine qui utilise celle-ci : écriture d'un nombre naturel en base quelconque de 2 à 36 ; mais c'est pas prêt, elle bugge:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Exception en point flottant (core dumped)
    Première fois que je vois un langage de prog me causer en français, c'est troublant, non ? Surtout que j'utilise ni registre, ni instruction float. Donc, soit le traitement est erronné, soit c'est la traduc' qu'est mal placée (je penche pour le second cas).

    Merci encore,
    Denis

    PS: Je cherche maintenant une liste des instructions x86 (32/64), avec spécification exacte et explications claires, si possible. Je viens de tomber sur enter/leave que je connaissais pas.

    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
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    ; asm (nasm)
    ; -----------------------------------------------------------------------------
    ;
    ;              write_digit
    ;
    ; This function is able to write a digit in any numeral base from 2 to 36.
    ; For bases above 10, it uses latin letters --as for ordinary hex writing.
    ; Only uppercase letters are used here (they have the size of decimal digits).
    ; Note that base 36 means digits from 0 to 35, not 36 ;-).
    ; 
    ; The only parameter is the digit's *numeric* value, which should be passed
    ; on the stack, in the least-significant byte of a word.
    ; The function returns a validity check code in ebx: 0 if all is right, 1
    ; if the digit value was strictly above 35, in which case '?' is written out.
    ;
    ; -----------------------------------------------------------------------------
     
     
    section .data
    ; space & newline, for testing
       space       db 32
       newline     db 10
     
    ;  static array of digits
       digits: db "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     
    ;  code-character for invalid digit
       invalid_digit_char: db '?'
     
    section .bss
     
    section .text
       global _start
     
     
    ; === write digit =============================================================
    ;     digit numeric value: in al
     
    write_digit:
       ; input:
       ;        digit numeric value: on stack (low byte of word)
       ; output:
       ;        error code 1 if digit > 35: in ebx
     
       ; (no local var on stack ==> no ebp juggling)
     
       movzx ecx, byte [esp+4]    ; for writing, address of digit char is in ecx
       cmp ecx, 35                ; valid digit?
       ja write_digit_invalid     ; if not, error
     
       ; case valid digit:
       mov ebx, 0                 ; no error code in ebx
       add ecx, digits            ; point to digit char
       jmp write_digit_write
     
       ; case invalid digit:
       write_digit_invalid:
       mov ebx, 1                 ; error code in ebx
       mov ecx, invalid_digit_char; point to error char '?'
     
       ; write digit
       write_digit_write:
       mov edx, 1                 ; weight of string to be written
      ; (pointer to byte already in ecx)
       mov ebx, 1                 ; file descriptor for stdout
       mov eax, 4                 ; code for sys_write
       int 80h
     
       ret 1       ; (1 param on stack)
     
     
    ; === test with sample values =================================================
     
    ; --- test tool routines: write space & newline -------------------------------
    write_space:
       mov edx, 1
       mov ecx, space
       mov ebx, 1
       mov eax, 4
       int 80h
       ret
     
    write_newline:
       mov edx, 1
       mov ecx, newline
       mov ebx, 1
       mov eax, 4
       int 80h
       ret
     
    ; --- start = test ------------------------------------------------------------
    _start:
       xor eax, eax
     
       ; 0
       push 0
       call write_digit
       call write_space
     
       ; 1
       push 1
       call write_digit
       call write_space
     
       ; 9
       push 9
       call write_digit
       call write_space
     
       ; 10
       push 10
       call write_digit
       call write_space
     
       ; 15
       push 15
       call write_digit
       call write_space
     
       ; 16
       push 16
       call write_digit
       call write_space
     
       ; 35
       push 35
       call write_digit
       call write_space
     
       ; 36
       push 36
       call write_digit
       call write_space
     
       ; 255
       push 255
       call write_digit
       call write_space
     
       call write_newline
       jmp _end             ; useless here, but kept for generality
     
    ; --- end ---------------------------------------------------------------------
    _end:
    	mov	ebx, 0      ; exit code 0
    	mov	eax, 1      ; code for sys_exit
    	int	80h

  8. #8
    Membre confirmé Avatar de bifur
    passe le balais et l'aspirateur
    Inscrit en
    Mars 2008
    Messages
    314
    Détails du profil
    Informations personnelles :
    Âge : 38

    Informations professionnelles :
    Activité : passe le balais et l'aspirateur

    Informations forums :
    Inscription : Mars 2008
    Messages : 314
    Points : 550
    Points
    550
    Par défaut
    pour une description complète des instruction assembleur je conseille le volume 2 du document "Intel Architecture Software Developer’s Manual" c'est en british mais je ne pense pas que ça t'effrais

    pour ton probleme d'exception bizzard je flaire aussi un problème de traduction

    et si tu uttilise l'instruction div en 32 bits, est ce que tu fait bien gaffe a initialiser edx par zéro pour éviter l'exception division par zéro (déclenché en fait à chaque fois que les registres de sortie sont trop petit pour recevoir les résultats) ça pourrait être la source de ton exception bizzaroïde


    edit: après un test vite fait sur mon linux en provoquant volontairement une division par zéro j'ai le même message d'errreur donc ça signifife bien qu'il y ai une couille au niveau d'une division

  9. #9
    Membre confirmé
    Homme Profil pro
    .
    Inscrit en
    Juin 2002
    Messages
    239
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : .
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2002
    Messages : 239
    Points : 567
    Points
    567
    Par défaut
    Bonjour.

    Je ne suis pas sûr que stocker un octet seul sur la pile soit une bonne chose lorsqu'on programme en 32 bits.
    En effet, dans un programme en 32 bits, il peut s'avérer nécessaire qu'une variable soit alignée sur une frontière de DWORD.
    Et comme les variables locales sont stockées sur la pile, il est alors nécessaire que le registre ESP soit lui-même aligné sur une frontière de DWORD, ce qui ne sera pas le cas si un octet a été empilé.
    Ce que je viens d'expliquer s'applique à la programmation sous Windows.
    A titre d'exemple, les chaines unicode comptées doivent êtres alignées sur une frontière de DWORD, de même que la structure TOKEN_PRIVILEGES.
    Je ne connais pas la programmation sous Linux, mais il se pourrait qu'une contrainte similaire existe pour l'alignement de certaines variables.
    Prudence donc ...

    La plupart des compilateurs Assembleurs prennent en charge le passage des paramètres aux procédures ainsi que les variables locales, sans que le programmeur n'ait à s'occuper de choses du style [EBP +12] ou [EBP -8].
    Il suffit de déclarer la procédure et ses arguments et/ou ses variables locales.

    Exemple avec TASM :

    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
     
    .386
    locals
    .model flat,STDCALL
     
            .....
            .....
            .....
            .....
     
    Tri_fusion      proc
     
            ARG     indice_2, indice_1
            LOCAL   indice_3, compteur_1, compteur_2
     
            mov     eax,indice_1
            mov     edx,indice_2
            cmp     eax,edx
            jae     @@9               ; abandon si indice1 >= indice2
            add     edx,eax
            shr     edx,1             ; edx = (indice_1 + indice_2)/2
            mov     indice_3,edx
     
            push    eax               ; nouvel indice_1
            push    edx               ; nouvel indice_2
            call    Tri_fusion        ; appel récursif
     
            .....
            .....
            call    Compare           ; appel d'une procédure de comparaison
            jbe     @@1
            .....
            .....
     
    @@1:    .....
            .....
            .....
     
    @@9:    ret
     
    Tri_fusion      endp
    Dans cet exemple, Tri_fusion est déclaré comme une procédure, par le mot réservé PROC à l'entrée et le mot réservé ENDP à la sortie.

    Le mot réservé ARG ( pour ARGument ) signifie que indice_1 et indice_2 sont deux paramètres passés à la procédure par l'intermédiaire de la pile.
    Attention à l'ordre de l'empilement !
    Ici, il s'agit d'un programme tournant sous Windows, c'est donc l'ordre d'appel standard qui est utilisé, ce qui est précisé au début du programme par la directive STDCALL.
    Dans la pratique, le dernier paramètre placé sur la pile est le premier figurant dans la liste après ARG.

    Le mot réservé LOCAL signifie que compteur_1, compteur_2, indice_3 sont trois variables locales à la procédure.
    Si on ne précise pas la taille des variables locales, comme c'est la cas ici, ce sont des DWORD par défaut en programmation 32 bits.
    ( ce qui est signalé au début du programme par la directive .386 )

    Le compilateur TASM se charge de toute la mécanique à mettre en oeuvre pour accéder par leur nom aux arguments et aux variables locales.
    Voici une partie du listing généré par TASM :

    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
     
     9014  0000508F               Tri_fusion   proc
     9015
     9016                                ARG   indice_2, indice_1
     9017                                LOCAL indice_3, compteur_1, compteur_2
     9018
     9019  0000508F  C8 000C 00                ENTERD  0000Ch,0
     9020  00005093  8B 45 0C                  mov   eax,indice_1
     9021  00005096  8B 55 08                  mov   edx,indice_2
     9022  00005099  3B C2                     cmp   eax,edx
     9023  0000509B  0F 83 000000A8            jae   @@9               ; abandon si indice1 >= indice2
     9024  000050A1  03 D0                     add   edx,eax
     9025  000050A3  D1 EA                     shr   edx,1             ; edx = (indice_1 + indice_2)/2
     9026  000050A5  89 55 FC                  mov   indice_3,edx
     9027
     9028  000050A8  50                        push  eax               ; nouvel indice_1
     9029  000050A9  52                        push  edx               ; nouvel indice_2
     9030  000050AA  E8 FFFFFFE0               call  Tri_fusion        ; appel récursif
     
     
           ........
           ........
     
     9100  00005149  C9                  @@9:  LEAVED
     9101  0000514A  C2 0008                   RET   00008h
     9102
     9103  0000514D               Tri_fusion   endp
    L'instruction ENTER 0Ch,0 se charge de faire de la place sur la pile pour les 3 variables locales.
    Elle équivaut ici à :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
            push    ebp
            mov     ebp,esp
            sub     esp,12
    L'instruction LEAVED supprime les 3 variables locales.
    Elle équivaut ici à :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
            mov     esp,ebp
            pop     ebp
    Enfin l'instruction RET 08h dépile le registre EIP et ajoute 8 à ESP pour supprimer les deux arguments placés sur la pile.
    De plus, le compilateur se charge de convertir indice_1 en [EBP +12] ( ligne 9020 ) ou indice_3 en [EBP -4] ( ligne 9026 ).
    Ainsi le programmeur n'a pas besoin de s'occuper de ces détails, ce qui lui facilite le travail.

    Pour finir les explications, je signale que j'ai utilisé dans la procédure des labels locaux : @@1 et @@9 ( et d'autres que je n'ai pas recopiés ).
    Ils ne sont visibles qu'à l'intérieur de la procédure, ce qui permet de les utiliser ailleurs sans qu'il y ait confusion.
    Cela facilite grandement le nommage des labels, car dans un programme important il y en a des centaines ...
    Seule contrainte : leur noms doivent commencer par @@.
    L'utilisation des labels locaux est précisé au début du programme par la directive locals.

    Personnellement, j'utilise TASM ( et quelquefois MASM32 ) mais la plupart des compilateurs offrent les possibilités exposées plus haut.
    Néanmoins, la syntaxe peut être différente d'un compilateur à l'autre.
    Dans ce cas, voir dans leur documentation les directives à utiliser pour chacun d'eux.

  10. #10
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par bifur Voir le message
    pour une description complète des instruction assembleur je conseille le volume 2 du document "Intel Architecture Software Developer’s Manual" c'est en british mais je ne pense pas que ça t'effrais

    pour ton probleme d'exception bizzard je flaire aussi un problème de traduction

    et si tu uttilise l'instruction div en 32 bits, est ce que tu fait bien gaffe a initialiser edx par zéro pour éviter l'exception division par zéro (déclenché en fait à chaque fois que les registres de sortie sont trop petit pour recevoir les résultats) ça pourrait être la source de ton exception bizzaroïde


    edit: après un test vite fait sur mon linux en provoquant volontairement une division par zéro j'ai le même message d'errreur donc ça signifife bien qu'il y ai une couille au niveau d'une division
    Ah, ok, merci ! Je crois que c ça, l'exception : pas une division par zéro, mais une erreur de taille de registre.

    Denis

  11. #11
    Membre habitué
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Points : 144
    Points
    144
    Par défaut
    Citation Envoyé par Prof Voir le message
    Bonjour.

    Je ne suis pas sûr que stocker un octet seul sur la pile soit une bonne chose lorsqu'on programme en 32 bits.
    En effet, dans un programme en 32 bits, il peut s'avérer nécessaire qu'une variable soit alignée sur une frontière de DWORD.
    Et comme les variables locales sont stockées sur la pile, il est alors nécessaire que le registre ESP soit lui-même aligné sur une frontière de DWORD, ce qui ne sera pas le cas si un octet a été empilé.
    Ce que je viens d'expliquer s'applique à la programmation sous Windows.
    A titre d'exemple, les chaines unicode comptées doivent êtres alignées sur une frontière de DWORD, de même que la structure TOKEN_PRIVILEGES.
    Je ne connais pas la programmation sous Linux, mais il se pourrait qu'une contrainte similaire existe pour l'alignement de certaines variables.
    Prudence donc ...
    GCC en standard sous linux aligne sur plusieurs mots (dword ou quord en langage MS); chez moi c'est 16 bytes, donc 4 mots de 32 bits. Mais il s'agit du tas, pour la pile c'est sûrement mot par mot, sinon on pourrait pas push et pop correctement, non?

    En ce qui concerne le passage d'un octet, je voulais dire simplement le poser sur la pile et le retrouver là depuis la routine appelée. Mais il est passé à l'intérieur d'un mot, le registre eax en fait. Ensuite pour le retrouver, je fais "movzx eax, [ebp+8]" (ou une autre adresse).

    La plupart des compilateurs Assembleurs prennent en charge le passage des paramètres aux procédures ainsi que les variables locales, sans que le programmeur n'ait à s'occuper de choses du style [EBP +12] ou [EBP -8].
    Il suffit de déclarer la procédure et ses arguments et/ou ses variables locales.

    [...]

    Pour finir les explications, je signale que j'ai utilisé dans la procédure des labels locaux : @@1 et @@9 ( et d'autres que je n'ai pas recopiés ).
    Ils ne sont visibles qu'à l'intérieur de la procédure, ce qui permet de les utiliser ailleurs sans qu'il y ait confusion.
    Cela facilite grandement le nommage des labels, car dans un programme important il y en a des centaines ...
    Seule contrainte : leur noms doivent commencer par @@.
    L'utilisation des labels locaux est précisé au début du programme par la directive locals.

    Personnellement, j'utilise TASM ( et quelquefois MASM32 ) mais la plupart des compilateurs offrent les possibilités exposées plus haut.
    Néanmoins, la syntaxe peut être différente d'un compilateur à l'autre.
    Dans ce cas, voir dans leur documentation les directives à utiliser pour chacun d'eux.
    [/quote]

    Merci, prof ! Tu portes bien ton pseudo, c'est très clair.
    J'utilise Nasm, dont la syntaxe de base est très chouette (à part le fait qu'on doit répéter des tailles d'operation ou opérande) et d'après divers commentaires proche de la syntaxe Intel. (La syntaxe de Gas est insupportable, mais c'est une cible de compilo, plutôt qu'un langage de prog.)
    En Nasm, les labels locaux sont juste précédés de '.', genre ".loop_start".

    Bien, je n'ai encore exploré qu'une toute petite partie des "features" de Nasm en dehors de l'assebleur de base. Il y a très certainement l'équivalent de ce que tu montres là, mais pour l'instant je réapprends l'assembleur (après une très longue pause, et mon expérience est limitée aux processeurs 8 bits genre 6502 et Z80!). Donc, je m'en tiens au codage manuel pour quelque temps, et ça fait déjà pas mal à intégrer. C'est aussi pourquoi je ne fais pas appel aux routines C. Je me fais une petite boite à outils de débuggage comme première série de mini-projets, pour me motiver.

    Merci encore,
    Denis

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

Discussions similaires

  1. Une petite question de modélisation
    Par Emdis dans le forum Décisions SGBD
    Réponses: 3
    Dernier message: 28/10/2004, 12h13
  2. copier une petite texture sur une grosse texture
    Par gaut dans le forum DirectX
    Réponses: 5
    Dernier message: 15/10/2004, 22h12
  3. Une petite question
    Par Etienne1 dans le forum MS SQL Server
    Réponses: 3
    Dernier message: 10/08/2004, 16h19
  4. Réponses: 2
    Dernier message: 23/03/2004, 12h23
  5. Une petite aide pour les API ?
    Par Yop dans le forum Windows
    Réponses: 2
    Dernier message: 04/04/2002, 21h45

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