Précédent   Forum du club des développeurs et IT Pro > Autres langages > Assembleur > x86 32-bits / 64-bits
x86 32-bits / 64-bits Architectures x86 32/64 bits et leurs outils (assembleurs, debuggers, émulateurs...)
Partagez cette discussion sur d'autres réseaux sociaux : Viadeo Twitter Google Facebook Digg Delicious MySpace Yahoo
Réponse
 
Outils de la discussion
Publicité
'
Vieux 23/02/2013, 18h58   #1
denispir
Membre habitué
 
amateur
Inscription : 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 : 119
Points : 119
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 :
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
denispir est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 23/02/2013, 21h14   #2
bifur
Membre habitué
 
Technicien maintenance
Inscription : mars 2008
Messages : 111
Détails du profil
Informations personnelles :
Âge : 28

Informations professionnelles :
Activité : Technicien maintenance

Informations forums :
Inscription : mars 2008
Messages : 111
Points : 126
Points : 126
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
bifur est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2013, 11h25   #3
denispir
Membre habitué
 
amateur
Inscription : 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 : 119
Points : 119
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 !

Citation:
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 :
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
denispir est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2013, 14h40   #4
bifur
Membre habitué
 
Technicien maintenance
Inscription : mars 2008
Messages : 111
Détails du profil
Informations personnelles :
Âge : 28

Informations professionnelles :
Activité : Technicien maintenance

Informations forums :
Inscription : mars 2008
Messages : 111
Points : 126
Points : 126
Citation:
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?
bifur est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2013, 16h32   #5
denispir
Membre habitué
 
amateur
Inscription : 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 : 119
Points : 119
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
denispir est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2013, 21h52   #6
bifur
Membre habitué
 
Technicien maintenance
Inscription : mars 2008
Messages : 111
Détails du profil
Informations personnelles :
Âge : 28

Informations professionnelles :
Activité : Technicien maintenance

Informations forums :
Inscription : mars 2008
Messages : 111
Points : 126
Points : 126
j'ai réussi a modifier ton ptit programme pour passer les argument par la pile
Code :
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)
bifur est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 25/02/2013, 12h59   #7
denispir
Membre habitué
 
amateur
Inscription : 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 : 119
Points : 119
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 :
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 :
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
denispir est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 25/02/2013, 21h07   #8
bifur
Membre habitué
 
Technicien maintenance
Inscription : mars 2008
Messages : 111
Détails du profil
Informations personnelles :
Âge : 28

Informations professionnelles :
Activité : Technicien maintenance

Informations forums :
Inscription : mars 2008
Messages : 111
Points : 126
Points : 126
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
bifur est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 26/02/2013, 16h39   #9
Prof
Membre confirmé
 
Inscription : juin 2002
Messages : 113
Détails du profil
Informations personnelles :
Localisation : France, Rhône (Rhône Alpes)

Informations forums :
Inscription : juin 2002
Messages : 113
Points : 217
Points : 217
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 :
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 :
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 :
1
2
3
4
 
        push    ebp
        mov     ebp,esp
        sub     esp,12
L'instruction LEAVED supprime les 3 variables locales.
Elle équivaut ici à :

Code :
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.
Prof est déconnecté   Envoyer un message privé Réponse avec citation 10
Vieux 28/02/2013, 03h18   #10
denispir
Membre habitué
 
amateur
Inscription : 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 : 119
Points : 119
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
denispir est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 28/02/2013, 03h47   #11
denispir
Membre habitué
 
amateur
Inscription : 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 : 119
Points : 119
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).

Citation:
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
denispir est déconnecté   Envoyer un message privé Réponse avec citation 00
Réponse Cette discussion est résolue.
Outils de la discussion

Navigation rapide


Fuseau horaire GMT +2. Il est actuellement 14h22.


 
 
 
 
Partenaires

Hébergement Web