L'appel de printf en x86_64 utilisant l'assembleur GNU
J'ai écrit un programme en utilisant AT&T syntaxe pour une utilisation avec de l'assembleur GNU:
.data
format: .ascii "%d\n"
.text
.global main
main:
mov $format, %rbx
mov (%rbx), %rdi
mov $1, %rsi
call printf
ret
- Je utiliser GCC de les assembler et de les lien avec:
gcc-o main.s
Je le lance avec cette commande:
./principal
Quand je lance le programme, j'obtiens un seg fault. Par l'utilisation de gdb, il dit printf
pas trouvé. J'ai essayé ".extern printf", qui ne fonctionne pas. Quelqu'un m'a suggéré de stocker le pointeur de pile avant l'appel de printf
et restaurer avant de RET, Comment dois-je faire?
- Vous devriez vraiment lire sur le SysV x86-64 ABI. D'un coup d'oeil, vous avez un mauvais alignement de la pile avant l'appel, vous n'avez pas de zéro
%rax
, vous n'utilisez pas le droit de registres pour les bons arguments et je soupçonne que vous êtes un déréférencementformat
lorsque vous ne devriez pas.
Vous devez vous connecter pour publier un commentaire.
Il y a un certain nombre de questions avec ce code. Le Système AMD64 V ABI convention d'appel utilisé par Linux a besoin de quelques choses. Il exige que juste avant un APPEL que la pile soit au moins de 16 octets (ou 32 octets) alignés:
Après la C d'exécution des appels de votre
main
fonction de la pile n'est pas correctement alignée par 8 car le retour pointeur est placé sur la pile par APPEL. Le réalignement de 16 octets à la limite vous pouvez simplement POUSSER tout registre sur la pile et POP à la fin.L'appel de la convention exige également que AL contenir le nombre de vecteurs registres utilisés pour une variable en fonction d'argument:
printf
est une variable en fonction d'argument, donc AL doit être défini. Dans ce cas, vous ne passez pas tous les paramètres en un vecteur de registre de sorte que vous pouvez définir AL à 0.Vous aussi déréférencer le format de $pointeur lorsqu'il est déjà une adresse. Donc, c'est faux:
Cela prend l'adresse de format et le place dans RBX. Ensuite, vous prenez les 8 octets à l'adresse RBX et de les placer dans RDI. RDI doit être un pointeur à une chaîne de caractères, pas les personnages eux-mêmes. Les deux lignes pourrait être remplacé par:
Il utilise RIP Adressage Relatif.
Vous devez également NUL mettre fin à vos chaînes. Plutôt que d'utiliser
.ascii
vous pouvez utiliser.asciz
sur la plate-forme x86.Une version de travail de votre programme pourrait ressembler à:
Autres Recommandations/Suggestions
Vous devriez aussi être conscient de la 64 bits de Linux ABI, que la convention d'appel exige également des fonctions que vous écrivez à l'honneur la préservation de certains registres. La liste des registres et s'ils devraient préservé est comme suit:
Un registre qui dit
Yes
dans le Conservé dans leRegistre colonne sont celles que vous devez vous assurer sont conservées dans votre fonction. Fonction
main
est comme tous les autres C fonction.Si vous avez des cordes, des données que vous savez sera en lecture seule, vous pouvez les placer dans
.rodata
section avec.section .rodata
plutôt que.data
En mode 64 bits: si vous avez un opérande de destination qui est une version 32 bits de registre, le PROCESSEUR zéro étendre le registre à travers l'ensemble de la 64 bits de registre. Cela peut économiser des octets sur le codage d'instruction.
Il est possible que votre exécutable est compilé en code indépendant de la position. Vous pouvez recevoir un message d'erreur similaire à:
Pour y remédier, vous devrez appeler la fonction externe
printf
de cette façon:Ce appels externes de la fonction de la bibliothèque via le Procedure Linkage Table (PLT)
printf
plutôt que APPEL et d'éliminer la pile de l'alignement avec le PUSH/POP. Qui était en dehors de la portée de ma réponse, mais on peut toujours regarder la littérature sur QUEUE APPEL optimisationsxor %eax,%eax
est la meilleure façon de mettre en AL ou RAX à zéro (il n'est donc pas nocif pour dire que variadic fonctions de regarder%rax
au lieu de%al
), et les autres étaient juste des détails supplémentaires / commentaires que j'ai fait depuis une modification était nécessaire de toute façon pour corriger l'ABI lien.Vous pouvez regarder code assembleur généré à partir d'un équivalent c fichier.
L'exécution de
gcc -o - -S -fno-asynchronous-unwind-tables test.c
avec ce test.cCette sortie de l'assemblée de code:
Cela vous donne un exemple d'un assemblage de code d'appel de printf, que vous pourrez ensuite modifier.
La comparaison avec votre code, vous devez modifier 2 choses:
mov $format, %rdi
mov $0, %eax
L'application de ces modifications va donner quelque chose comme :
Puis de l'exécuter d'impression :
printf
. Il peut travailler dans de nombreux scénarios, mais pas tous. Poussant un 64-bit registre après votre fonctionmain
commence et de les restaurer à la fin permettrait de garder les choses alignés. La version 64 bits de Linux ABI nécessite un minimum de 16 octets d'alignement (32 octets aligné si le passage de 256 vecteurs de bits à une fonction). Au point juste avant l'appel de la fonction de la pile besoins 16(ou 32) alignement d'octets. Après le APPEL instruction transfère le contrôle à une fonction (main
est comme les autres C function)l'adresse de retour est placé sur la pile à désaligner par 8.printf
. Dans ce cas, l'alignement est maintenue (sans le PUSH/POP) depuis JMP ne place pas une adresse de retour sur la pile comme un APPEL.-O3
): godbolt.org/g/sX5yCe. Il utilise unjmp
pour la queue-appel de sorte que la pile de l'alignement reste la même que lors de l'entrée surmain
. Il utilise égalementxor
à zéro%al
, au lieu d'un moins efficacemov
. Et bien sûr, il met la constante de chaîne dans.rodata
, pas.data
. À l'aide de sortie du compilateur comme un point de départ pour l'optimisation est un bon plan, mais uniquement si vous démarrez avec-O2
ou-O3
de sortie! Sinon, vous pourriez faire pire que le compilateur.