J'ai écrit un programme utilisant la syntaxe AT & T pour une utilisation avec l'assembleur GNU:
.data
format: .ascii "%d\n"
.text
.global main
main:
mov $format, %rbx
mov (%rbx), %rdi
mov $1, %rsi
call printf
ret
J'utiliseGCCpour assembler et créer des liens avec:
gcc -o main main.s
Je le lance avec cette commande:
./principale
Lorsque je lance le programme, je reçois une erreur de segmentation. En utilisant gdb, printf
est introuvable. J'ai essayé ".extern printf", ce qui ne fonctionne pas. Quelqu'un a suggéré que je devrais stocker le pointeur de pile avant d'appeler printf
et de restaurer avantRET, comment puis-je le faire?
Ce code pose un certain nombre de problèmes. La convention d’appel AMD64 System V ABI utilisée par Linux nécessite quelques opérations. Juste avant un APPEL, la pile doit être alignée sur au moins 16 octets (ou 32 octets):
La fin de la zone d'argument d'entrée doit être alignée sur une limite d'octet de 16 (32, si __m256 est Transmis sur pile).
Après que le C runtime appelle votre fonction main
, la pile est mal alignée de 8 car le pointeur de retour a été placé sur la pile par APPEL. Pour réaligner sur une limite de 16 octets, vous pouvez simplement appuyertout registre à usage général sur la pile et POP à la fin.
La convention d'appel exige également que AL _ contienne le nombre de registres de vecteurs utilisés pour une fonction d'argument variable:
% al est utilisé pour indiquer le nombre d'arguments vectoriels passés à une fonction nécessitant un nombre variable d'arguments
printf
est une fonction à argument variable, donc AL doit être défini. Dans ce cas, vous ne transmettez aucun paramètre dans un registre vectoriel, vous pouvez donc définir AL sur 0.
Vous déréférenciez également le pointeur $ format quand il s'agit déjà d'une adresse. Donc c'est faux:
mov $format, %rbx
mov (%rbx), %rdi
Ceci prend l'adresse de format et la place dans RBX. Ensuite, vous prenez les 8 octets à cette adresse dans RBX et vous les placez dans RDI. RDI doit être un pointeur d'une chaîne de caractères, pas les caractères eux-mêmes. Les deux lignes pourraient être remplacées par:
lea format(%rip), %rdi
Ceci utilise l'adressage relatif RIP.
Vous devriez é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 à ceci:
# global data #
.data
format: .asciz "%d\n"
.text
.global main
main:
Push %rbx
lea format(%rip), %rdi
mov $1, %esi # Writing to ESI zero extends to RSI.
xor %eax, %eax # Zeroing EAX is efficient way to clear AL.
call printf
pop %rbx
ret
Vous devez également savoir, de la part de l’ABI Linux 64 bits, que la convention d’appel requiert également des fonctions que vous écrivez pour respecter la préservation de certains registres. La liste des registres et s'ils doivent être conservés est la suivante:
Tous les registres qui indiquent Yes
dans la colonne conservés dans Register sont ceux que vous devez vous assurer qu'ils sont préservés dans votre fonction. La fonction main
est comme n'importe quelle autre fonction C.
Si vous savez que les chaînes/données seront en lecture seule, vous pouvez les placer dans la section .rodata
avec .section .rodata
au lieu de .data
.
En mode 64 bits: si vous avez un opérande de destination qui est un registre 32 bits, la CPU étendra le registre à zéro dans tout le registre 64 bits. Cela peut économiser des octets sur le codage des instructions.
Il est possible que votre exécutable soit compilé en tant que code indépendant de la position. Vous pouvez recevoir une erreur semblable à:
relocalisation R_X86_64_PC32 contre le symbole `printf @@ GLIBC_2.2.5 'ne peut pas être utilisé lors de la création d'un objet partagé; recompiler avec -fPIC
Pour résoudre ce problème, vous devez appeler la fonction externe printf
de cette façon:
call printf@plt
Ceci appelle la fonction de bibliothèque externe via le Procédure Linkage Table (PLT)
Vous pouvez consulter le code d'assemblage généré à partir d'un fichier c équivalent.
Lancer gcc -o - -S -fno-asynchronous-unwind-tables test.c
avec test.c
#include <stdio.h>
int main() {
return printf("%d\n", 1);
}
Cette sortie du code de l'Assemblée:
.file "test.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $1, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
popq %rbp
ret
.size main, .-main
.ident "GCC: (GNU) 6.1.1 20160602"
.section .note.GNU-stack,"",@progbits
Cela vous donne un exemple de code d'assemblage appelant printf que vous pouvez ensuite modifier.
En comparant avec votre code, vous devriez modifier 2 choses:
mov $format, %rdi
mov $0, %eax
L’application de ces modifications donnera quelque chose comme:
.data
format: .ascii "%d\n"
.text
.global main
main:
mov $format, %rdi
mov $1, %rsi
mov $0, %eax
call printf
ret
Et puis exécutez-le print:
1