GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Die C-Standardbibliotheksfunktion kann unter 64-Bit-Linux nicht aus Assemblycode (yasm) aufgerufen werden

Ihr gcc erstellt standardmäßig ausführbare PIE-Dateien (absolute 32-Bit-Adressen sind in x86-64-Linux nicht mehr zulässig?).

Ich bin mir nicht sicher warum, aber dabei löst der Linker call puts nicht automatisch auf bis call [email protected] . Es gibt noch eine puts PLT-Eintrag generiert, aber der call geht nicht dorthin.

Zur Laufzeit versucht der dynamische Linker, puts aufzulösen direkt zum libc-Symbol dieses Namens und korrigieren Sie den call rel32 . Aber das Symbol ist mehr als +-2^31 entfernt, also erhalten wir eine Warnung über den Überlauf von R_X86_64_PC32 Verlegung. Die unteren 32 Bits der Zieladresse sind korrekt, die oberen Bits jedoch nicht. (Also Ihr call springt zu einer falschen Adresse).

Ihr Code funktioniert für mich, wenn ich mit gcc -no-pie -fno-pie call-lib.c libcall.o baue . Die -no-pie ist der kritische Teil:Es ist die Linker-Option. Ihr YASM-Befehl muss nicht geändert werden.

Beim Erstellen einer herkömmlichen positionsabhängigen ausführbaren Datei dreht der Linker den puts Symbol für das Anrufziel in [email protected] für Sie, da wir eine dynamische ausführbare Datei verknüpfen (anstatt libc statisch mit gcc -static -fno-pie zu verknüpfen , in diesem Fall call könnte direkt gehen zur libc-Funktion.)

Wie auch immer, das ist der Grund, warum gcc call [email protected] ausgibt (GAS-Syntax) beim Kompilieren mit -fpie (der Standard auf Ihrem Desktop, aber nicht der Standard auf https://godbolt.org/), sondern nur call puts beim Kompilieren mit -fno-pie .

Siehe Was bedeutet @plt hier? für mehr über PLT und auch Entschuldigung für den Zustand dynamischer Bibliotheken unter Linux von vor einigen Jahren. (Der moderne gcc -fno-plt ist wie eine der Ideen in diesem Blogbeitrag.)

Übrigens, ein genauerer/spezifischerer Prototyp würde gcc vermeiden lassen, EAX auf Null zu setzen, bevor foo aufgerufen wird :

extern void foo(); in C bedeutet extern void foo(...);
Sie könnten es als extern void foo(void); deklarieren , was () ist bedeutet in C++. C++ erlaubt keine Funktionsdeklarationen, die die Argumente unspezifiziert lassen.

asm-Verbesserungen

Sie können auch message eingeben in section .rodata (schreibgeschützte Daten, verlinkt als Teil des Textsegments).

Sie brauchen keinen Stapelrahmen, nur etwas, um den Stapel vor einem Anruf um 16 auszurichten. Ein Dummy push rax werde es tun.

Oder wir können puts per Tail-Call aufrufen durch Springen zu ihr, anstatt sie aufzurufen, mit derselben Stapelposition wie beim Eintritt in diese Funktion. Dies funktioniert mit oder ohne PIE. Ersetzen Sie einfach call mit jmp , solange RSP auf Ihre eigene Absenderadresse zeigt.

Wenn Sie ausführbare PIE-Dateien (oder gemeinsam genutzte Bibliotheken) erstellen möchten, haben Sie zwei Möglichkeiten

  • call puts wrt ..plt - explizit über die PLT aufrufen.
  • call [rel puts wrt ..got] - Führen Sie explizit einen indirekten Aufruf durch den GOT-Eintrag durch, wie -fno-plt von gcc Stil der Code-Generierung. (Verwenden eines RIP-relativen Adressierungsmodus, um das GOT zu erreichen, daher der rel Schlüsselwort).

WRT =In Bezug auf. Das NASM-Handbuch dokumentiert wrt ..plt , und siehe auch Abschnitt 7.9.3:Sonderzeichen und WRT.

Normalerweise würden Sie default rel verwenden am Anfang Ihrer Datei, damit Sie call [puts wrt ..got] tatsächlich verwenden können und trotzdem einen RIP-relativen Adressierungsmodus erhalten. Sie können keinen absoluten 32-Bit-Adressierungsmodus in PIE- oder PIC-Code verwenden.

call [puts wrt ..got] Zusammenbau zu einem speicherindirekten Aufruf unter Verwendung des Funktionszeigers, der im GOT gespeichert ist. (Frühes Binden, nicht faules dynamisches Verlinken.)

NASM-Dokumente ..got zum Abrufen der Adresse von Variablen in Abschnitt 9.2.3. Funktionen in (anderen) Bibliotheken sind identisch:Sie erhalten einen Zeiger von der GOT, anstatt sie direkt aufzurufen, da der Offset keine Linkzeitkonstante ist und möglicherweise nicht in 32-Bit passt.

YASM akzeptiert auch call [puts wrt ..GOTPCREL] , wie die AT&T-Syntax call *[email protected](%rip) , aber NASM nicht.

; 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

In einer Position - abhängig ausführbar ist, können Sie mov edi, message verwenden anstelle eines RIP-relativen LEA. Es hat eine kleinere Codegröße und kann auf den meisten CPUs auf mehr Ausführungsports ausgeführt werden.

In einer ausführbaren Nicht-PIE-Datei können Sie auch call puts verwenden oder jmp puts und lassen Sie es den Linker regeln, es sei denn, Sie möchten eine effizientere dynamische Verknüpfung im No-PLT-Stil. Aber wenn Sie sich dafür entscheiden, libc statisch zu linken, denke ich, dass dies der einzige Weg ist, wie Sie einen direkten jmp zur libc-Funktion bekommen.

(Ich denke, die Möglichkeit der statischen Verlinkung für Nicht-PIE ist warum ld ist bereit, PLT-Stubs automatisch für Nicht-PIE zu generieren, aber nicht für PIE oder gemeinsam genutzte Bibliotheken. Sie müssen sagen, was Sie meinen, wenn Sie gemeinsame ELF-Objekte verknüpfen.)

Wenn Sie call puts verwendet haben in einem PIE (call rel32 ), könnte es nur funktionieren, wenn Sie eine positionsunabhängige Implementierung von puts statisch verknüpfen in Ihre PIE, also war das Ganze eine ausführbare Datei, die zur Laufzeit an einer zufälligen Adresse geladen wurde (durch den üblichen dynamischen Linker-Mechanismus), aber einfach keine Abhängigkeit von libc.so.6 hatte


Der 0xe8 Auf opcode folgt ein vorzeichenbehafteter Offset, der an den PC (der zu diesem Zeitpunkt zum nächsten Befehl vorgerückt ist) angelegt wird, um das Verzweigungsziel zu berechnen. Daher objdump interpretiert das Verzweigungsziel als 0x671 .

YASM gibt Nullen wieder, weil es wahrscheinlich eine Verschiebung an diesem Offset vorgenommen hat, und fordert so den Loader auf, den korrekten Offset für puts zu füllen während des Ladens. Der Loader stellt beim Berechnen der Verschiebung einen Überlauf fest, was darauf hindeuten kann, dass puts befindet sich in einem weiteren Offset von Ihrem Aufruf, als in einem 32-Bit-Offset mit Vorzeichen dargestellt werden kann. Daher kann der Loader diese Anweisung nicht beheben und Sie erhalten einen Absturz.

66c: e8 00 00 00 00 zeigt die unbesetzte Adresse. Wenn Sie in Ihrer Umzugstabelle nachsehen, sollten Sie einen Umzug auf 0x66d sehen . Es ist nicht ungewöhnlich, dass der Assembler Adressen/Offsets mit Verschiebungen als reine Nullen füllt.

Diese Seite schlägt vor, dass YASM einen WRT hat Direktive, die die Verwendung von .got steuern kann , .plt usw.

Gemäß S9.2.5 der NASM-Dokumentation sieht es so aus, als könnten Sie CALL puts WRT ..plt verwenden (vorausgesetzt, YASM hat die gleiche Syntax).


Linux
  1. Wie lade ich Linux-Kernel-Module aus C-Code?

  2. x86_64 Assembly Linux-Systemaufruf-Verwirrung

  3. C-Bibliothek zum Lesen der EXE-Version von Linux?

  4. Rufen Sie eine Userspace-Funktion innerhalb eines Linux-Kernel-Moduls auf

  5. Rufen Sie eine C-Funktion aus C++-Code auf

Grundlagen zum Kompilieren von Software aus dem Quellcode unter Linux

So erhalten Sie Nachrichten sofort von der Befehlszeile in Linux

Linux – Systemweite Überwachung von Aufrufen einer Bibliotheksfunktion?

Wie kann ich C++-Code profilieren, der unter Linux ausgeführt wird?

Kann exit() den Prozess nicht beenden?

Wie kann ich spezielle HTML-Entitäten einfach aus einem Standard-Eingabestream in Linux konvertieren?