Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 26/12/2017, à 13:31

yvesdams

Erreur de segmentation à l'appel de printf dans du code assembleur AT&

Bonjour,
Je viens de me mettre à l'assembleur AT&T et je patauge encore dans le B - a, Ba. Je suis sur
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.5) avec un petit exercice où je cherche à afficher 2 longs dans une chaîne formatée par printf. Mais, l'appel de cette fonction dans le code génère une erreur de segmentation.
Ci-dessous l'extrait du code impliqué  :

		.data			 
chaine: 				 
		.string "Les valeurs saisies sont %d et %d.\n"
valeur:
		.long 5, 4
variable:
		.lcomm var, 8

		.text 			 
		.globl _start 		 
_start: 				 
		call exemple
		...
		...
		call affichage	
...
...


		.globl affichage
		.type affichage, @function
affichage:
		pushq %rbp
		movq %rsp, %rbp
		pushq var +4
		pushq var
		pushq $chaine
		call printf
		movq %rbp , %rsp
		popq %rbp
		ret

Il a été assemblé avec :

yves@yves-VirtualBox:~/.../Exercice$ gcc -g -c -gstabs -o Essai_1.o Essai_1.s

et lié avec :

yves@yves-VirtualBox:~/.../Exercice$ ld Essai_1.o -o Essai_1 -lc -dynamic-linker /lib64/ld-linux-x86-64.so.2

Le résultat à l'exécution :

yves@yves-VirtualBox:~/.../Exercice$ ./Essai_1

Les valeurs saisies sont %d et %d.

Erreur de segmentation (core dumped)

Je sais que c'est la fonction printf qui pose problème parce qu'un bout de code dans l'exercice me permet d'afficher la chaîne telle quelle et si je commente le call printf la chaîne s'affiche sans aucun message d'erreur.
Par ailleurs si je déboggue avec dbg avec un breakpoint à la ligne du call affichage (c'est la fonction qui appelle printf) et un autre à celle du call printf, j'obtiens le résultat suivant. J'en conclue que c'est le printf qui pose souci.

yves@yves-VirtualBox:~/.../Exercice$ gdb -q Essai_1
Reading symbols from Essai_1...done.
(gdb) break 15
Breakpoint 1 at 0x40025b: file Essai_1.s, line 15.
(gdb) break 44
Breakpoint 2 at 0x4002a3: file Essai_1.s, line 44.
(gdb) run
Starting program: /home/yves/.../Exercice/Essai_1 

Les valeurs saisies sont %d et %d.

Breakpoint 1, _start () at Essai_1.s:15
15			call affichage			
(gdb) n

Breakpoint 2, affichage () at Essai_1.s:44
44			call printf
(gdb) 

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a62824 in __printf (format=0x600450 <var> "\005") at printf.c:28
28	printf.c: Aucun fichier ou dossier de ce type.
(gdb) 

J'ai cherché un fichier printf.c sur Internet pendant 2 jours je n'ai rien trouvé.
J'ai lancé une recherche sur printf dans le champ recherche du forum, j'ai trouvé beaucoup de discussions concernant printf et erreur de segmentation. Mais rien qui ressemble à mon problème.
J'ai réinstallé build-essential, apparemment tout est installé.
J'ai testé un printf("Hello, World !") dans un petit bout de code c. Cela fonctionne.
Je ne sais plus quoi faire comme recherches. Alors, j'appelle au secours. Si quelqu'un pouvait me suggérer une piste, je l'en remercie d'avance.



Modération : merci d'utiliser les balises code (explications ici).

Dernière modification par cqfd93 (Le 26/12/2017, à 17:14)

Hors ligne

#2 Le 08/01/2018, à 14:02

yvesdams

Re : Erreur de segmentation à l'appel de printf dans du code assembleur AT&

Bonjour,
J'ai trouvé les réponses dont j'avais besoin, même si j'ai encore beaucoup d'interrogations et comme j'ai vu que mon post avait suscité un minimum de curiosité, je me suis dit que peut-être que mes réponses peuvent intéresser. Aussi, je les partage pour satisfaire cette curiosité, mais aussi dans l'espoir de quelques critiques (positives) qui pourraient répondre à mes autres interrogations.
Tout d'abord, je voudrais dire que l'extrait de code que j'ai utilisé pour mon exercice est extrait d'un tuto trouvé sur Internet. Il est bien pourri. En effet, de mes pérégrinations, il ressort que printf ne va pas chercher ses arguments sur la pile mais dans des registres dédiés. J'en ai repéré 3 : RDI pour le 1er argument (l'adresse de la chaîne à formater), RSI et RDX, respectivement pour le 2è et le 3è. Je ne sais pas si on peut en utiliser d'autres et quels sont-ils ? ou si on doit constituer des chaînes intermédiaires si plus de 3 arguments.
Je peux aussi avancer que les erreurs de segmentation peuvent être provoquées par des ret intempestifs. Quand ils ne sont pas nécessaires ils polluent le résultat. D'autres choses aussi provoquent ces erreurs de segmentation mais je n'ai pas eu la présence d'esprit de les noter tous.
Que dire d'autre ? Voici le code fonctionnel :

.data
MaChaine:
		.asciz "Les valeurs du tableau sont %d et %d.\n", "La première se trouve à l'adresse %X et la seconde à l'adresse %X.\n"
var:
		.long 5, 4
		.text
		.globl _start
_start:
		push %rbx				# 1 - semble nécessaire pour aligner la pile
		movl var + 4, %edx			# printf n'appelle pas ses arguments de la pile - ici le 3è
		movl var,	%esi			# ici le 2è argument	
		lea MaChaine(%rip),	%rdi		# ici le 1er argument (l'adresse de la chaîne) calculée par lea relativement à %rip ??	
		xor %rax,	%rax	/* fruit de mes pérégrinations : la mise à 0 de %rax semble également nécessaire pour l'appel à printf. Alternativement il doit contenir le nombre d'arguments de la fonction. Mais pour printf on le met à 0 ??*/
		call printf
		mov $var + 4,	%edx			# fonctionne aussi bien avec un registre 32 bits que 64 bits
		mov $var,	%esi			# idem ci-dessus sauf qu'avec des registres 64 bits les adresses sont augmentées de 8 ??
		lea MaChaine + 38(%rdi),	%rdi
		xor %rax,	%rax
		call printf
		pop %rbx				# même chose que 1 ci-dessus
		
# Si j'ai bien compris les 3 instructions suivantes servent à rendre la main au système. Donc pas besoin de ret
		movq $1, %rax 				# on place le numéro de l 'interruption dans rax
		movq $0, %rbx 				# code de retour pour l 'interruption , ici 0
		int $0x80				# appel système 

Voici le résultat en utilisant %rdx et %rsi :

yves@yves-VirtualBox:~/.../Exercice$ ./Essai_4
Les valeurs du tableau sont 5 et 4.
La première se trouve à l'adresse 60045E et la seconde à l'adresse 600462.
yves@yves-VirtualBox:~/.../Exercice$

Et voici le résultat en utilisant %edx et %esi :

yves@yves-VirtualBox:~/.../Exercice$ ./Essai_4
Les valeurs du tableau sont 5 et 4.
La première se trouve à l'adresse 600456 et la seconde à l'adresse 60045A.
yves@yves-VirtualBox:~/.../Exercice$

Hors ligne

#3 Le 09/01/2018, à 09:00

yvesdams

Re : Erreur de segmentation à l'appel de printf dans du code assembleur AT&

Bonjour,
J'ai les réponses, au moins 2, aux questions qui étaient restées en suspens. Je les communique, me disant que cela peut aider tous ceux qui, comme moi, ne disposent que d'Internet pour collecter les informations qui leur permettent d'avancer.
- Il y a-t-il d'autres registres qui participent à l'approvisionnement d'une fonction en arguments ? La réponse est oui et se trouve dans un tableau (en fait une image de tableau) dans ce fil : https://stackoverflow.com/questions/383 … -assembler. Je n'ai pas testé.
- Je supposais une erreur dans le résultat (ou dans la démarche) dans la mesure où je n'obtenais pas les mêmes adresses pour les éléments du tableau selon que j'utilisais des registres 32 bits ou 64 bits pour les charger en tant qu'arguments de printf. En fait c'est tout à fait logique dans la mesure où les essais étaient effectués par des programmes chargés séparément. Ce que j'occultais. Mais si on fait les essais avec les registres 32 bits et 64 bits dans le même programme, les adresses sont les mêmes : il n'y a pas de magie. Maintenant, pourquoi un décalage de 8, ça je ne l'ai pas encore compris. Bien sûr je peux faire un parallèle avec 2 * 32 bits supplémentaires, donc 2 * 4 octets, donc 8 octets supplémentaires. Donc, si mathématiquement c'est logique, je ne comprends pas bien pourquoi le fait de changer la capacité des registres modifie l'adresse de stockage des données.
En tout état de cause voici le code qui me permet de supposer que la démarche et les résultats sont corrects (j'ai zappé les commentaires parce que je trouve qu'ils alourdissent la lecture du code) :

.data
MaChaine:
		.asciz "Les valeurs du tableau sont %d et %d.\n", "La première se trouve à l'adresse %X et la seconde à l'adresse %X.\n"
var:
		.long 5, 4
		.text
		.globl _start
_start:
		push %rbx		
		movl var + 4, %edx	
		movl var,	%esi		
		lea MaChaine(%rip),	%rdi	
		xor %rax,	%rax	
		call printf
		mov $var + 4,	%edx	
		mov $var,	%esi	 
		lea MaChaine + 38(%rdi),	%rdi
		xor %rax,	%rax
		call printf
# Essayons avec des registres 64 bits dans le même programme pour vérifier le résultat
		mov var + 4, %rdx	
		mov var,	%rsi		
		lea MaChaine(%rip),	%rdi	
		xor %rax,	%rax	
		call printf
		mov $var + 4,	%rdx	
		mov $var,	%rsi	
		lea MaChaine + 38(%rdi),	%rdi
		xor %rax,	%rax
		call printf
		pop %rbx		
		
/* Si j'ai bien compris les 3 instructions suivantes servent à rendre la main au système. Donc pas besoin de ret*/
		movq $1, %rax 		# on place le numéro de l 'interruption dans rax
		movq $0, %rbx 		# code de retour pour l 'interruption , ici 0
		int $0x80		# appel système

ainsi que le résultat obtenu :

yves@yves-VirtualBox:~/.../Exercice$ ./Essai_5
Les valeurs du tableau sont 5 et 4.
La première se trouve à l'adresse 600496 et la seconde à l'adresse 60049A.
Les valeurs du tableau sont 5 et 4.
La première se trouve à l'adresse 600496 et la seconde à l'adresse 60049A.
yves@yves-VirtualBox:~/.../Exercice$

Je mets mes conclusions à la disposition de ceux qui, sur ce forum ou ailleurs, sont, comme moi, à la recherche d'informations pour avancer. Mais, comme je l'ai dit dans mon premier post, je suis dans la phase 'B', 'a', 'Ba' de mon apprentissage. Aussi, s'il y avait des retours qui mettent le doigt sur là où mon raisonnement peut pêcher, cela ne serait pas pour me déplaire.
Par ailleurs, je voudrais marqué ce fil comme résolu. Mais je n'ai pas trouvé l'astuce pour le faire. Alors, la Modération, au secours ...!

Hors ligne

#4 Le 09/01/2018, à 09:21

LeJediGris

Re : Erreur de segmentation à l'appel de printf dans du code assembleur AT&

Salut,

C'est pas juste une question d'alignement en mémoire ton décalage de 8 ?

Pour un [RESOLU] tu reprends simplement ton premier message et change le titre !!

A+

Dernière modification par LeJediGris (Le 09/01/2018, à 09:22)


%NOINDEX%
Matos Asus Zenbook
"Home Made" Monstro: core i7 9700+32Go de mémoire+SSD QVO Samsung 1To +MoBo Asus Prime Z390P
+ "Terminator", core i5 3570, 16Go, SSD Intel 520 sous Mint 19.3, Freebox Revolution

Hors ligne

#5 Le 09/01/2018, à 12:02

yvesdams

Re : Erreur de segmentation à l'appel de printf dans du code assembleur AT&

Bonjour,
Pas suffisamment expert, je ne saurais être catégorique. Mais il semblerait que les :

push %rbx		
		...
		...
		...
		...
		pop %rbx

en début de préparation et à la fin de l'appel de la fonction sont là pour régler les problèmes d'alignement. Je n'ai pas bien compris la technique de l'alignement mais il semblerait que les défauts d'alignement génèrent les fautes de segmentation. Or, je n'en ai plus. A priori, j'exclue donc le défaut d'alignement. Mais, qui sait ? Celui qui en maîtrise la technique pourrait m'envoyer un petit retour. Ce sera apprécié.

Hors ligne

#6 Le 09/01/2018, à 14:13

LeJediGris

Re : Erreur de segmentation à l'appel de printf dans du code assembleur AT&

Re-

Si je me souviens bien %rbx est la pile en 64bits. Donc avant et après un appel (même si en GCC optimisé cela ne sert à rien, toujours suivant mes souvenirs...) on fait un push et un pull.

Pour l'alignement tu peux regarder ici

A+


%NOINDEX%
Matos Asus Zenbook
"Home Made" Monstro: core i7 9700+32Go de mémoire+SSD QVO Samsung 1To +MoBo Asus Prime Z390P
+ "Terminator", core i5 3570, 16Go, SSD Intel 520 sous Mint 19.3, Freebox Revolution

Hors ligne

#7 Le 10/01/2018, à 09:40

yvesdams

Re : Erreur de segmentation à l'appel de printf dans du code assembleur AT&

Bonjour,
Réponse à LeJediGris et un complément d'information à tous ceux qui seraient intéressés par la question :
Des essais complémentaires me poussent à penser qu'il ne s'agit pas d'un problème d'alignement mémoire. En effet, dans le code en question il y a 3 données : 2 chaînes de caractères et un tableau de 2 longs (2 fois 32 bits). Le problème c'est que je ne peux pas calculer l'espace mémoire occupé par les 2 chaînes parce que je ne sais pas combien d'espace occupent en mémoire un '%d' et un '\n'. Je ne sais pas non plus comment le compilateur gère le 0 introduit par .asciz. Du coup le désalignement est bien possible.
L'idée est donc de déclarer le tableau de longs avant les 2 chaînes. Ainsi les données seront, obligatoirement, correctement alignées. Et pourtant, il y a toujours ce décalage de 8 selon que l'on utilise des registres 32 bits ou 64 bits pour charger les 2è et 3è arguments.
Mais alors, qu'en serait-il si on déclarait la section .text avant la section .data : les résultats sont strictement les mêmes pour chaque type de registre.
Force est de conclure que ce décalage de 8 est normal. En effet, je pense que selon qu'il ait à gérer un registre 64 bits à la place d'un registre 32 bits, le compilateur réserve un espace correspondant dans la zone mémoire dédié à la section .text. Du coup, la zone mémoire dédiée à la section .data est décalée d'autant. Comme il a besoin de 8 octets supplémentaires pour la zone .text, la zone .data glisse d'autant.
Conclusions de ce post que je vais signaler comme résolu :
- printf ne prend pas ses arguments sur la pile (ou alors il faut me dire où je me trompe)
- son 1er argument, dont l'adresse doit être calculée par lea relativement à RIP (et si quelqu'un peut me préciser pourquoi je suis preneur) doit être passé par RDI
- le second argument doit être passé par RSI (ou ESI)
- le 3ème par RDX (ou EDX)
- le 4ème par RCX (ou ECX ?)
- le 5ème par R8
- le 6ème par R9 - je n'ai pas eu l'occasion de tester pour les 4ème, 5ème et 6ème arguments.
- RAX doit être mis à 0 pour printf (??). Mais il doit, normalement, contenir le nombre d'arguments pour les fonctions à nb d'arguments variable.
- l'alignement de la pile est assuré en empilant RBX avant l'appel de la fonction et en le dépilant (en fait on dépile la pile vers RBX) au retour de la fonction (??) Là aussi je suis preneur de toute information contradictoire  mais formatrice.
De façon générale (donc indépendamment de l'usage de printf ou autres fonctions), pour s'assurer du bon alignement mémoire, il faut essayer de déclarer ses données de telle façon qu'elles occupent un espace mémoire sous-multiple du bus de données. La question c'est : comment savoir cela ?
Désolé, je n'ai toujours pas trouvé comment on marque un sujet résolu. Alors je laisse ouvert, si quelqu'un veut le marquer pour moi, je ne lui en tiendrais pas rigueur.

Hors ligne