8 Stimmen

64-Bit-Assembler, wenn kleinere Register verwendet werden sollen

In x86_64-Assembler gibt es zum Beispiel das (64-Bit-)rax-Register, aber es kann auch als 32-Bit-Register, eax, 16-Bit, ax, und 8-Bit, al, angesprochen werden. In welcher Situation würde ich nicht einfach die vollen 64 Bit verwenden, und warum, welchen Vorteil hätte das?

Ein Beispiel dafür ist dieses einfache Hallo-Welt-Programm:

section .data
msg: db "Hello World!", 0x0a, 0x00
len: equ $-msg

section .text
global start

start:
mov rax, 0x2000004      ; System call write = 4
mov rdi, 1              ; Write to standard out = 1
mov rsi, msg            ; The address of hello_world string
mov rdx, len            ; The size to write
syscall                 ; Invoke the kernel
mov rax, 0x2000001      ; System call number for exit = 1
mov rdi, 0              ; Exit success = 0
syscall                 ; Invoke the kernel

rdi und rdx benötigen zumindest nur 8 Bit und nicht 64, richtig? Aber wenn ich sie in dil bzw. dl (ihre niedrigeren 8-Bit-Entsprechungen) ändere, assembliert und verknüpft das Programm, gibt aber nichts aus.

Allerdings funktioniert es immer noch, wenn ich eax, edi und edx verwenden, so sollte ich diese eher als die volle 64-Bit verwenden? Warum oder warum nicht?

1 Stimmen

In Linux (und wahrscheinlich auch in allen anderen Systemen) sind die Parameter für einen Syscall 32-Bit breit, so dass Sie EDI und EDX verwenden sollten. win.tue.nl/~aeb/linux/lk/lk-4.html#ss4.3

0 Stimmen

Was ist mit rax, sollte das auch in eax geändert werden? Ich habe versucht, diese 3 zu ändern und es funktioniert, aber was ich wissen möchte, ist, warum ich dies tun sollte und was ist der Vorteil.

0 Stimmen

Im Fall dieses Programms besteht der einzige nennenswerte Unterschied darin, dass die literalen Werte (4, 1, 0 usw.) doppelt so groß sind, wenn sie 64-Bit sind, so dass Ihr Programm ein paar Bytes größer ist und theoretisch länger dauern könnte, um von der Festplatte/dem Speicher in die CPU zu laden.

6voto

Bo Persson Punkte 88207

Sie stellen hier mehrere Fragen.

Wenn Sie nur die unteren 8 Bits eines Registers laden, behält der Rest des Registers seinen vorherigen Wert bei. Das kann erklären, warum Ihr Systemaufruf die falschen Parameter erhält.

Ein Grund für die Verwendung von 32 Bits ist, dass viele Befehle, die EAX oder EBX verwenden, ein Byte kürzer sind als solche, die RAX oder RBX verwenden. Dies kann auch bedeuten, dass Konstanten, die in das Register geladen werden, kürzer sind.

Der Befehlssatz hat sich über eine lange Zeit entwickelt und hat einige Macken!

3voto

Matty K Punkte 3633

In erster Linie ist dies der Fall, wenn ein kleinerer Wert (z. B. 8-Bit) aus dem Speicher in ein Register geladen wird (Lesen eines Zeichens, Bearbeiten einer Datenstruktur, Deserialisieren eines Netzwerkpakets usw.).

MOV AL, [0x1234]

gegen

MOV RAX, [0x1234]
SHR RAX, 56
# assuming there are actually 8 accessible bytes at 0x1234,
# and they're the right endianness; otherwise you'd need
# AND RAX, 0xFF or similar...

Oder natürlich das Zurückschreiben des Wertes in den Speicher.


(Bearbeiten, etwa 6 Jahre später):

Da dieses Thema immer wieder auftaucht:

MOV AL, [0x1234]
  • liest nur ein einziges Byte des Speichers an 0x1234 (die Umkehrung würde nur ein einziges Byte des Speichers überschreiben)
  • behält, was auch immer in den anderen 56 Bits von RAX war
    • Dadurch entsteht eine Abhängigkeit zwischen den vergangenen und zukünftigen Werten von RAX, so dass die CPU die Anweisung nicht optimieren kann, indem sie Registerumbenennung .

Im Gegensatz dazu:

MOV RAX, [0x1234]
  • liest 8 Bytes des Speichers ab 0x1234 (die Umkehrung würde 8 Bytes des Speichers überschreiben)
  • überschreibt alle von RAX
  • geht davon aus, dass die Bytes im Speicher die gleiche Endianness haben wie die CPU (was bei Netzwerkpaketen oft nicht der Fall ist, daher meine SHR Unterricht vor Jahren)

Auch wichtig zu wissen:

MOV EAX, [0x1234]

Dann gibt es, wie in den Kommentaren erwähnt, noch etwas anderes:

MOVZX EAX, byte [0x1234]
  • liest nur ein einziges Byte des Speichers bei 0x1234
  • erweitert den Wert, um EAX (und damit RAX) vollständig mit Nullen zu füllen (wodurch die Abhängigkeit beseitigt wird und Optimierungen bei der Registerumbenennung möglich sind).

In all diesen Fällen müssen Sie, wenn Sie écrire aus dem 'A'-Register in den Speicher zu übertragen, müssen Sie die Breite wählen:

MOV [0x1234], AL   ; write a byte (8 bits)
MOV [0x1234], AX   ; write a word (16 bits)
MOV [0x1234], EAX  ; write a dword (32 bits)
MOV [0x1234], RAX  ; write a qword (64 bits)

2 Stimmen

Ähm... x86_64 ist immer Little Endian, so dass Ihre Beispiele unterschiedliche Ergebnisse liefern werden.

2 Stimmen

Die beste Wahl ist hier movzx eax, [0x1234] .

1 Stimmen

Peter Cordes hat Recht. Das "mov al" unterbricht die Abhängigkeitskette nicht.

3voto

Maxim Masiutin Punkte 2895

Wenn Sie nur 32-Bit-Register benötigen, können Sie sicher mit ihnen arbeiten, dies ist OK unter 64-Bit. Wenn Sie aber nur 16-Bit- oder 8-Bit-Register benötigen, sollten Sie diese vermeiden oder immer movzx/movsx verwenden, um die restlichen Bits zu löschen. Es ist allgemein bekannt, dass unter x86-64 die Verwendung von 32-Bit-Operanden die höheren Bits der 64-Bit-Register löscht. Der Hauptzweck dieses Verfahrens ist die Vermeidung falscher Abhängigkeits-Ketten.

Bitte beachten Sie den entsprechenden Abschnitt - 3.4.1.1 - des Dokuments Intel® 64- und IA-32-Architekturen Handbuch für Softwareentwickler Band 1 :

32-Bit-Operanden erzeugen ein 32-Bit-Ergebnis, das im Mehrzweck-Zielregister auf ein 64-Bit-Ergebnis erweitert wird

Die Unterbrechung von Abhängigkeitsketten ermöglicht die parallele Ausführung von Anweisungen in zufälliger Reihenfolge durch den Out-of-Order-Algorithmus seit dem Pentium Pro im Jahr 1995 intern in CPUs implementiert.

Ein Zitat aus dem Handbuch zur Optimierung von Intel® 64- und IA-32-Architekturen Abschnitt 3.5.1.8:

Bei Codesequenzen, die partielle Register ändern, kann es zu Verzögerungen in der Abhängigkeitskette kommen, die jedoch durch die Verwendung von Idiomen zur Aufhebung von Abhängigkeiten vermieden werden können. Bei Prozessoren, die auf der Intel Core-Mikroarchitektur basieren, kann eine Reihe von Anweisungen dazu beitragen, die Ausführungsabhängigkeit aufzulösen, wenn die Software diese Anweisungen verwendet, um den Registerinhalt auf Null zu setzen. Unterbrechen Sie Abhängigkeiten von Teilen von Registern zwischen Befehlen, indem Sie mit 32-Bit-Registern anstelle von Teilregistern arbeiten. Bei Verschiebungen kann dies mit 32-Bit-Verschiebungen oder durch Verwendung von MOVZX erreicht werden.

Assembler/Compiler-Codierung Regel 37. (M Auswirkung, MH Allgemeinheit) : Unterbrechen Sie die Abhängigkeit von Teilen von Registern zwischen Befehlen, indem Sie auf 32-Bit-Registern statt auf Teilregistern arbeiten. Bei Verschiebungen kann dies mit 32-Bit-Verschiebungen oder durch Verwendung von MOVZX erreicht werden.

MOVZX und MOV mit 32-Bit-Operanden für x64 sind gleichwertig - sie alle unterbrechen Abhängigkeitsketten.

Aus diesem Grund wird Ihr Code schneller ausgeführt, wenn Sie bei der Verwendung kleinerer Register immer versuchen, die höchsten Bits der größeren Register zu löschen. Wenn die Bits immer gelöscht sind, gibt es keine Abhängigkeiten vom vorherigen Wert des Registers, die CPU kann die Register intern umbenennen.

Umbenennung von Registern ist eine Technik, die intern von einer CPU verwendet wird, um die falschen Datenabhängigkeiten zu beseitigen, die durch die Wiederverwendung von Registern durch aufeinanderfolgende Anweisungen entstehen, zwischen denen keine echten Datenabhängigkeiten bestehen.

1voto

Jim Mischel Punkte 125706

Wenn Sie nur mit einer 8-Bit-Menge arbeiten wollen, dann arbeiten Sie mit dem AL-Register. Dasselbe gilt für AX und EAX.

So kann beispielsweise ein 64-Bit-Wert zwei 32-Bit-Werte enthalten. Sie können mit den niedrigen 32-Bits arbeiten, indem Sie auf das EAX-Register zugreifen. Wenn Sie mit den hohen 32-Bit-Werten arbeiten wollen, können Sie die beiden 32-Bit-Mengen vertauschen (die DWORDs im Register umkehren), so dass die hohen Bits jetzt in EAX stehen.

0 Stimmen

Wie würde ich vorgehen, um die 32-Bit-Mengen zu tauschen?

0 Stimmen

Wie sieht der eigentliche Unterricht in nasm aus? Ich bin ziemlich neu auf diesem Gebiet.

1 Stimmen

ROL oder ROR, für links- bzw. rechtsdrehend. In diesem Fall ist es egal, in welche Richtung. Es gibt auch RCL und RCR für das Drehen mit Übertrag, die sich geringfügig unterscheiden.

1voto

Jonathan Wood Punkte 61798

64-bit ist der größte Speicher, mit dem Sie als einzelne Einheit arbeiten können. Das heißt aber nicht, dass Sie auch so viel davon verwenden müssen.

Wenn Sie 8 Bits brauchen, verwenden Sie 8. Wenn Sie 16 brauchen, verwenden Sie 16. Wenn es egal ist, wie viele Bits Sie brauchen, dann ist es auch egal, wie viele Sie verwenden.

Zugegeben, auf einem 64-Bit-Prozessor ist der Aufwand für die Nutzung der vollen 64 Bit sehr gering. Aber wenn Sie zum Beispiel einen Byte-Wert berechnen, bedeutet die Arbeit mit einem Byte, dass das Ergebnis bereits die richtige Größe hat.

0 Stimmen

32-Bit-Operandengröße ist in der Regel am besten: kleinste Code-Größe (keine REX- oder Operandengrößen-Präfixe) und keine Teilregister-Mischung / falsche Abhängigkeiten. (8-Bit hat auch keine Präfixe notwendig, aber kann Verlangsamungen mit partiellen Register Zeug erstellen, wenn Sie nicht vorsichtig sind, die Situation für beide AMD-CPUs (keine partielle reg Umbenennung) vs. P6 / frühen SnB-Familie vs. Haswell und später zu verstehen. Warum verwendet der GCC keine Teilregister? )

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X