wake-up-neo.com

Printf in x86_64 mit aufrufen GNU Assembler

Ich habe ein Programm geschrieben, das AT & T-Syntax zur Verwendung mit GNU Assembler verwendet:

            .data
format:   .ascii "%d\n"  

            .text
            .global main  
main:
            mov    $format, %rbx
            mov    (%rbx), %rdi
            mov    $1, %rsi
            call     printf
            ret

Ich verwendeGCC, um Folgendes zu erstellen und zu verknüpfen:

gcc -o main main.s 

Ich führe es mit diesem Befehl aus:

./Main

Wenn ich das Programm starte, erhalte ich einen seg-Fehler. Durch die Verwendung von gdb wird printf nicht gefunden angezeigt. Ich habe ".extern printf" ausprobiert, was nicht funktioniert. Jemand schlug vor, den Stapelzeiger vor dem Aufruf von printf zu speichern und vorRETwiederherzustellen. Wie mache ich das?

7
L's World

Es gibt eine Reihe von Problemen mit diesem Code. Die von Linux verwendete AMD64 System V ABI - Aufrufkonvention erfordert einige Dinge. Vor einem CALL muss der Stack mindestens 16-Byte (oder 32-Byte) ausgerichtet sein:

Das Ende des Eingabeargumentbereichs muss an einer 16-Byte-Grenze (32, wenn __m256 Auf Stack übergeben wird) ausgerichtet sein.

Nachdem die C-Laufzeit Ihre main-Funktion aufgerufen hat, ist der Stapel um 8 falsch ausgerichtet, da der Rückkehrzeiger von CALL auf dem Stapel platziert wurde. Um sich an der 16-Byte-Grenze neu auszurichten, können Sie einfach Push any Allzweckregister auf den Stack setzen und POP am Ende ausschalten.

Die Aufrufkonvention erfordert außerdem, dass AL die Anzahl der Vektorregister enthält, die für eine variable Argumentfunktion verwendet werden:

% al wird verwendet, um die Anzahl der Vektorargumente anzugeben, die an eine Funktion übergeben werden, für die eine variable Anzahl von Argumenten erforderlich ist

printf ist eine variable Argumentfunktion, daher muss AL gesetzt werden. In diesem Fall übergeben Sie keine Parameter in einem Vektorregister. Sie können AL auf 0 setzen.

Dereferenzieren Sie auch den $ - Formatzeiger, wenn es sich bereits um eine Adresse handelt. Das ist also falsch:

mov  $format, %rbx
mov  (%rbx), %rdi 

Dies nimmt die Adresse des Formats und setzt es in RBX. Dann nehmen Sie die 8 Bytes an dieser Adresse in RBX und setzen sie in RDI ein. RDI muss ein Zeiger für eine Zeichenfolge sein, nicht die Zeichen selbst. Die zwei Zeilen könnten ersetzt werden durch:

lea  format(%rip), %rdi

Hierbei wird die relative RIP-Adressierung verwendet.

Sie sollten auch NUL Ihre Zeichenfolgen beenden. Anstelle von .ascii können Sie .asciz auf der x86-Plattform verwenden.

Eine funktionierende Version Ihres Programms könnte folgendermaßen aussehen:

# 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

Andere Empfehlungen/Vorschläge

Sie sollten auch von der 64-Bit-Linux-ABI wissen, dass die aufrufende Konvention auch Funktionen erfordert, die Sie schreiben, um die Beibehaltung bestimmter Register zu berücksichtigen. Die Liste der Register und ob sie aufbewahrt werden sollten, lautet wie folgt:

 enter image description here

Jedes Register, das Yes in der Spalte Preserved across Register enthält, muss unbedingt in Ihrer Funktion erhalten bleiben. Die Funktion main ist wie jede andere C Funktion. 


Wenn Sie Strings/Daten haben, von denen Sie wissen, dass sie nur gelesen werden, können Sie sie mit .rodata anstelle von .section .rodata in den Abschnitt .data einfügen.


Im 64-Bit-Modus: Wenn Sie einen Zieloperanden haben, der ein 32-Bit-Register ist, wird die CPU das Register über das gesamte 64-Bit-Register auf Null setzen. Dadurch können Bytes bei der Befehlsverschlüsselung gespeichert werden.


Es ist möglich, dass Ihre ausführbare Datei als positionsunabhängiger Code kompiliert wird. Möglicherweise erhalten Sie eine Fehlermeldung ähnlich der folgenden:

verschiebung R_X86_64_PC32 gegen das Symbol `printf @@ GLIBC_2.2.5 'kann nicht verwendet werden, wenn ein gemeinsam genutztes Objekt erstellt wird; mit -fPIC neu kompilieren

Um dies zu beheben, müssen Sie die externe Funktion printf folgendermaßen aufrufen:

call [email protected] 

Dadurch wird die externe Bibliotheksfunktion über die Procedure Linkage Table (PLT) aufgerufen.

19
Michael Petch

Sie können sich den aus einer äquivalenten c-Datei generierten Assemblycode anzeigen lassen.
Ausführen von gcc -o - -S -fno-asynchronous-unwind-tables test.c mit test.c

#include <stdio.h>
int main() {
   return printf("%d\n", 1);
}

Dies gab den Assembly-Code aus:

        .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

Damit erhalten Sie ein Beispiel für einen Assembly-Code, der printf aufruft, den Sie dann ändern können.


Verglichen mit Ihrem Code sollten Sie zwei Dinge ändern:

  • % rdi sollte auf das Format verweisen,% rbx sollte nicht referenziert werden. Dies könnte mit mov $format, %rdi erfolgen.
  • printf hat eine variable Anzahl von Argumenten, dann sollten Sie mov $0, %eax hinzufügen.

Das Anwenden dieser Modifikationen ergibt etwa Folgendes:

    .data
format: .ascii "%d\n"  
.text
    .global main  
main:
  mov  $format, %rdi
  mov  $1, %rsi
  mov  $0, %eax
  call printf
  ret

Und dann drucken Sie es aus:

1

2
mpromonet