GNU/Linux >> Tutoriels Linux >  >> Linux

Impossible d'appeler la fonction de bibliothèque standard C sur Linux 64 bits à partir du code d'assemblage (yasm)

Votre gcc construit des exécutables PIE par défaut (les adresses absolues 32 bits ne sont plus autorisées dans x86-64 Linux ?).

Je ne sais pas pourquoi, mais ce faisant, l'éditeur de liens ne résout pas automatiquement call puts à call [email protected] . Il reste un puts Entrée PLT générée, mais le call n'y va pas.

Lors de l'exécution, l'éditeur de liens dynamique tente de résoudre puts directement au symbole libc de ce nom et corrigez le call rel32 . Mais le symbole est à plus de +-2^31, nous recevons donc un avertissement concernant le débordement du R_X86_64_PC32 déménagement. Les 32 bits inférieurs de l'adresse cible sont corrects, mais les bits supérieurs ne le sont pas. (Ainsi votre call saute à une mauvaise adresse).

Votre code fonctionne pour moi si je compile avec gcc -no-pie -fno-pie call-lib.c libcall.o . Le -no-pie est la partie critique :c'est l'option de l'éditeur de liens. Votre commande YASM n'a pas à changer.

Lors de la création d'un exécutable traditionnel dépendant de la position, l'éditeur de liens transforme le puts symbole de la cible de l'appel en [email protected] pour vous, car nous lions un exécutable dynamique (au lieu de lier statiquement libc avec gcc -static -fno-pie , auquel cas le call pourrait aller directement à la fonction libc.)

Quoi qu'il en soit, c'est pourquoi gcc émet call [email protected] (syntaxe GAS) lors de la compilation avec -fpie (la valeur par défaut sur votre bureau, mais pas la valeur par défaut sur https://godbolt.org/), mais juste call puts lors de la compilation avec -fno-pie .

Voir Que signifie @plt ici ? pour en savoir plus sur le PLT, et aussi Désolé l'état des bibliothèques dynamiques sur Linux il y a quelques années. (Le gcc -fno-plt moderne est comme l'une des idées de ce billet de blog.)

BTW, un prototype plus précis/spécifique permettrait à gcc d'éviter de mettre à zéro EAX avant d'appeler foo :

extern void foo(); en C signifie extern void foo(...);
Vous pouvez le déclarer comme extern void foo(void); , c'est ce que () signifie en C++. C++ n'autorise pas les déclarations de fonction qui laissent les arguments non spécifiés.

améliorations asm

Vous pouvez aussi mettre message en section .rodata (données en lecture seule, liées dans le cadre du segment de texte).

Vous n'avez pas besoin d'un cadre de pile, juste quelque chose pour aligner la pile par 16 avant un appel. Un mannequin push rax va le faire.

Ou nous pouvons appeler le puts en sautant au lieu de l'appeler, avec la même position dans la pile qu'à l'entrée de cette fonction. Cela fonctionne avec ou sans PIE. Remplacez simplement call avec jmp , tant que RSP pointe vers votre propre adresse de retour.

Si vous souhaitez créer des exécutables PIE (ou des bibliothèques partagées), vous avez deux options

  • call puts wrt ..plt - appeler explicitement via le PLT.
  • call [rel puts wrt ..got] - faire explicitement un appel indirect via l'entrée GOT, comme le -fno-plt de gcc style de code-gen. (Utilisation d'un mode d'adressage relatif au RIP pour atteindre le GOT, d'où le rel mot-clé).

WRT =En ce qui concerne. Le manuel NASM documente wrt ..plt , et voir aussi section 7.9.3 :symboles spéciaux et WRT.

Normalement, vous utiliseriez default rel en haut de votre fichier afin que vous puissiez réellement utiliser call [puts wrt ..got] et obtenir toujours un mode d'adressage relatif au RIP. Vous ne pouvez pas utiliser un mode d'adressage absolu 32 bits dans le code PIE ou PIC.

call [puts wrt ..got] assemble à un appel indirect en mémoire en utilisant le pointeur de fonction que la liaison dynamique stockée dans le GOT. (Liaison anticipée, pas de liaison dynamique paresseuse.)

Documents MSNA ..got pour obtenir l'adresse des variables dans la section 9.2.3. Les fonctions des (autres) bibliothèques sont identiques :vous obtenez un pointeur du GOT au lieu d'appeler directement, car le décalage n'est pas une constante de temps de liaison et peut ne pas tenir en 32 bits.

YASM accepte également call [puts wrt ..GOTPCREL] , comme la syntaxe AT&T call *[email protected](%rip) , mais pas la NASM.

; don't use BITS 64.  You *want* an error if you try to assemble this into a 32-bit .o

default rel          ; RIP-relative addressing instead of 32-bit absolute by default; makes the [rel ...] optional

section .rodata            ; .rodata is best for constants, not .data
message:
  db 'foo() called', 0

section .text

global foo
foo:
    sub    rsp, 8                ; align the stack by 16

    ; PIE with PLT
    lea    rdi, [rel message]      ; needed for PIE
    call   puts WRT ..plt          ; tailcall puts
;or
    ; PIE with -fno-plt style code, skips the PLT indirection
    lea   rdi, [rel message]
    call  [rel  puts wrt ..got]
;or
    ; non-PIE
    mov    edi, message           ; more efficient, but only works in non-PIE / non-PIC
    call   puts                   ; linker will rewrite it into call [email protected]

    add   rsp,8                   ; remove the padding
    ret

Dans un poste-dépendant exécutable, vous pouvez utiliser mov edi, message au lieu d'un LEA relatif à RIP. Sa taille de code est plus petite et peut s'exécuter sur plus de ports d'exécution sur la plupart des processeurs.

Dans un exécutable non-PIE, vous pouvez également utiliser call puts ou jmp puts et laissez l'éditeur de liens régler le problème, à moins que vous ne souhaitiez une liaison dynamique de style no-plt plus efficace. Mais si vous choisissez de lier statiquement libc, je pense que c'est la seule façon d'obtenir un jmp direct vers la fonction libc.

(Je pense que la possibilité d'un lien statique pour les non-PIE est pourquoi ld est prêt à générer automatiquement des stubs PLT pour les non-PIE, mais pas pour les PIE ou les bibliothèques partagées. Cela vous oblige à dire ce que vous voulez dire lorsque vous liez des objets partagés ELF.)

Si vous avez utilisé call puts dans un PIE (call rel32 ), cela ne peut fonctionner que si vous avez lié statiquement une implémentation indépendante de la position de puts dans votre PIE, donc le tout était un exécutable qui serait chargé à une adresse aléatoire au moment de l'exécution (par le mécanisme de liaison dynamique habituel), mais n'avait tout simplement pas de dépendance sur libc.so.6


Le 0xe8 opcode est suivi d'un décalage signé à appliquer au PC (qui est passé à l'instruction suivante à ce moment-là) pour calculer la cible de branchement. D'où objdump interprète la cible de branche comme 0x671 .

YASM rend des zéros car il a probablement mis un déplacement sur ce décalage, c'est ainsi qu'il demande au chargeur de remplir le décalage correct pour puts pendant le chargement. Le chargeur rencontre un débordement lors du calcul de la relocalisation, ce qui peut indiquer que puts est à un décalage plus éloigné de votre appel que ce qui peut être représenté dans un décalage signé 32 bits. Par conséquent, le chargeur ne parvient pas à corriger cette instruction et vous obtenez un plantage.

66c: e8 00 00 00 00 affiche l'adresse vide. Si vous regardez dans votre tableau de relocalisation, vous devriez voir une relocalisation sur 0x66d . Il n'est pas rare que l'assembleur remplisse les adresses/décalages avec des relocalisations comme tous les zéros.

Cette page suggère que YASM a un WRT directive qui peut contrôler l'utilisation de .got , .plt , etc.

Selon S9.2.5 sur la documentation NASM, il semble que vous puissiez utiliser CALL puts WRT ..plt (en supposant que YASM a la même syntaxe).


Linux
  1. Comment appeler la fonction C en C++, la fonction C++ en C (mélanger C et C++)

  2. x86_64 Assemblage Linux System Call Confusion

  3. Appeler une fonction de l'espace utilisateur à partir d'un module du noyau Linux

  4. Comment une bibliothèque partagée (.so) peut-elle appeler une fonction qui est implémentée dans son programme de chargement ?

  5. Rediriger la sortie d'un bloc fonctionnel vers un fichier sous Linux

Principes de base de la compilation de logiciels à partir du code source sous Linux

Comment obtenir des nouvelles instantanément à partir de la ligne de commande sous Linux

Comment puis-je profiler du code C++ exécuté sous Linux ?

Bibliothèque C pour lire la version EXE de Linux?

Appeler une fonction C à partir du code C++

Appeler un appel système Linux à partir d'un langage de script