web-dev-qa-db-fra.com

Appel de printf en x86_64 avec GNU assembleur

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?

7
L's World

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

Autres recommandations/suggestions

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:

 enter image description here

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)

19
Michael Petch

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:

  • % rdi devrait indiquer le format, vous ne devriez pas faire référence à% rbx, cela pourrait être fait avec mov $format, %rdi
  • printf a un nombre variable d'arguments, alors vous devriez ajouter 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

2
mpromonet