Suche…


Einführung

Die x86-Familie gibt es schon lange, und so gibt es viele Tricks und Techniken, die entdeckt und entwickelt wurden und die allgemein bekannt sind - oder vielleicht nicht so öffentlich. Die meisten dieser Tricks nutzen die Tatsache, dass viele Anweisungen dasselbe bewirken, aber unterschiedliche Versionen sind schneller, sparen Speicher oder beeinflussen die Flags nicht. Hier sind eine Reihe von Tricks entdeckt worden. Jeder hat seine Vor- und Nachteile, sollte also aufgeführt werden.

Bemerkungen

Im Zweifelsfall können Sie sich immer auf das ziemlich umfassende Intel 64- und IA-32-Architekturen-Referenzhandbuch beziehen, das eine großartige Ressource des Unternehmens hinter der x86-Architektur ist.

Ein Register auf Null setzen

Der offensichtliche Weg, ein Register auf Null zu setzen, ist das MOV in einer 0 - zum Beispiel:

B8 00 00 00 00    MOV eax, 0

Beachten Sie, dass dies eine 5-Byte-Anweisung ist.

Wenn Sie bereit sind, die Flags zu löschen ( MOV wirkt sich nicht auf die Flags aus), können Sie die XOR Anweisung verwenden, um das Register bitweise mit XOR zu versehen:

33 C0             XOR eax, eax

Diese Anweisung benötigt nur 2 Byte und wird auf allen Prozessoren schneller ausgeführt .

Carry-Flag in ein Register verschieben

Hintergrund

Wenn das Carry-Flag ( C ) einen Wert enthält, den Sie in ein Register aufnehmen möchten, besteht die naive Methode in der folgenden Weise:

    mov  al, 1
    jc   NotZero
    mov  al, 0
NotZero:

Verwenden Sie 'sbb'

Ein direkterer Weg, den Sprung zu vermeiden, ist "Subtrahieren mit Ausleihen":

    sbb  al,al    ; Move Carry to al

Wenn C Null ist, ist al Null. Ansonsten ist es 0xFF ( -1 ). Wenn Sie 0x01 , fügen Sie 0x01 hinzu:

    and  al, 0x01 ; Mask down to 1 or 0

Pros

  • Ungefähr dieselbe Größe
  • Zwei oder eine weniger Anweisungen
  • Kein teurer Sprung

Cons

  • Es ist für einen Leser, der mit der Technik nicht vertraut ist, undurchsichtig
  • Es ändert andere Flaggen

Testen Sie ein Register für 0

Hintergrund

Um herauszufinden, ob ein Register eine Null enthält, ist die naive Technik folgendes:

    cmp   eax, 0

Wenn Sie sich jedoch den Opcode ansehen, erhalten Sie Folgendes:

83 F8 00      cmp   eax, 0

test

    test   eax, eax      ; Equal to zero?

Überprüfen Sie den Opcode, den Sie erhalten:

85 c0         test   eax, eax

Pros

  • Nur zwei Bytes!

Cons

  • Undurchsichtig für einen Leser, der mit der Technik nicht vertraut ist

Sie können auch einen Blick auf die Q & A-Frage zu dieser Technik werfen.

Linux-Systemaufrufe mit weniger Aufblähung

In 32-Bit-Linux werden Systemaufrufe normalerweise mit der sysenter-Anweisung ausgeführt (ich sage normalerweise, weil ältere Programme das nun veraltete int 0x80 ). Dies kann jedoch recht viel Speicherplatz in einem Programm beanspruchen und daher gibt es Möglichkeiten kann Ecken schneiden, um die Dinge zu verkürzen und zu beschleunigen.
Dies ist normalerweise das Layout eines Systemaufrufs unter 32-Bit-Linux:

mov eax, <System call number>
mov ebx, <Argument 1> ;If applicable
mov ecx, <Argument 2> ;If applicable
mov edx, <Argument 3> ;If applicable
push <label to jump to after the syscall>
push ecx
push edx
push ebp
mov ebp, esp
sysenter

Das ist riesig richtig! Aber es gibt ein paar Tricks, um dieses Chaos zu vermeiden.
Das erste ist, ebp auf den Wert von esp zu setzen, der um die Größe von 3 32-Bit-Registern, d. H. 12 Bytes, verringert ist. Dies ist großartig, solange Sie mit dem Überschreiben von ebp, edx und ecx in Ordnung sind (zum Beispiel, wenn Sie einen Wert direkt in diese Register verschieben), können Sie dies mit der LEA-Anweisung tun, so dass wir dies nicht brauchen den Wert von ESP selbst beeinflussen.

mov eax, <System call number>
mov ebx, <Argument 1>
mov ecx, <Argument 2>
mov edx, <Argument 3>
push <label to jump to after the syscall>
lea ebp, [esp-12]
sysenter

Wir sind jedoch noch nicht fertig, wenn der Systemaufruf sys_exit ist, können wir davonkommen, dass wir nichts auf den Stack legen.

mov eax, 1
xor ebx, ebx ;Set the exit status to 0
mov ebp, esp
sysenter

Multiplizieren Sie mit 3 oder 5

Hintergrund

Um das Produkt eines Registers und einer Konstanten zu erhalten und in einem anderen Register zu speichern, ist dies auf naive Weise:

    imul ecx, 3      ; Set ecx to 5 times its previous value
    imul edx, eax, 5 ; Store 5 times the contend of eax in edx

lea

Multiplikationen sind kostspielige Operationen. Es ist schneller, eine Kombination aus Schichten und Additionen zu verwenden. Für den besonderen Fall von muliplying des behaupten ein 32- oder 64 - Bit - Register , das nicht esp oder rsp von 3 oder 5 ist , können Sie den LEA - Befehl verwenden. Diese verwendet die Adressberechnungsschaltung, um das Produkt schnell zu berechnen.

    lea ecx, [2*ecx+ecx] ; Load 2*ecx+ecx = 3*ecx into ecx
    lea edx, [4*edx+edx] ; Load 4*edx+edx = 5*edx into edx

Viele Monteure werden es auch verstehen

    lea ecx, [3*ecx]
    lea edx, [5*edx]

Für alle möglichen Multiplikanden mit ebp oder rbp ist die resultierende Befehlslänge dieselbe wie bei der Verwendung von imul .

Pros

  • Führt viel schneller aus

Cons

  • Wenn Ihr Multiplikand ebp oder rbp , dauert es ein Byte mehr, imul Sie imul
  • Mehr zu tippen, wenn Ihr Assembler die Verknüpfungen nicht unterstützt
  • Undurchsichtig für einen Leser, der mit der Technik nicht vertraut ist


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow