Szukaj…


Wprowadzenie

Rodzina x86 istnieje już od dłuższego czasu i dlatego odkryto i opracowano wiele sztuczek i technik, które są znane publicznie - a może nie tak publicznie. Większość tych sztuczek wykorzystuje fakt, że wiele instrukcji skutecznie robi to samo - ale różne wersje są szybsze, oszczędzają pamięć lub nie wpływają na flagi. Oto kilka sztuczek, które zostały odkryte. Każdy ma swoje zalety i wady, dlatego należy je wymienić.

Uwagi

W razie wątpliwości zawsze możesz odwołać się do dość obszernego Podręcznika na temat optymalizacji architektury Intel 64 i IA-32 , który jest świetnym zasobem od firmy stojącej za architekturą x86.

Zerowanie rejestru

Oczywistym sposobem zerowania rejestru jest MOV w 0 - na przykład:

B8 00 00 00 00    MOV eax, 0

Zauważ, że jest to instrukcja 5-bajtowa.

Jeśli chcesz zablokować flagi ( MOV nigdy nie wpływa na flagi), możesz użyć instrukcji XOR do bitowego-XOR-u samego rejestru:

33 C0             XOR eax, eax

Ta instrukcja wymaga tylko 2 bajtów i jest wykonywana szybciej na wszystkich procesorach .

Przenoszenie flagi Carry do rejestru

tło

Jeśli flaga Carry ( C ) zawiera wartość, którą chcesz umieścić w rejestrze, naiwnym sposobem jest zrobienie czegoś takiego:

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

Użyj „sbb”

Bardziej bezpośrednim sposobem uniknięcia skoku jest użycie opcji „Odejmij z pożyczeniem”:

    sbb  al,al    ; Move Carry to al

Jeśli C wynosi zero, to al będzie wynosić zero. W przeciwnym razie będzie to 0xFF ( -1 ). Jeśli potrzebujesz 0x01 , dodaj:

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

Plusy

  • O tym samym rozmiarze
  • Dwie lub jedna instrukcja mniej
  • Bez drogiego skoku

Cons

  • Jest nieprzejrzysty dla czytelnika niezaznajomionego z techniką
  • Zmienia inne flagi

Przetestuj rejestr pod kątem 0

tło

Aby dowiedzieć się, czy rejestr zawiera zero, naiwną techniką jest zrobienie tego:

    cmp   eax, 0

Ale jeśli spojrzysz na kod operacji, otrzymujesz:

83 F8 00      cmp   eax, 0

Użyj test

    test   eax, eax      ; Equal to zero?

Sprawdź otrzymany kod operacyjny:

85 c0         test   eax, eax

Plusy

  • Tylko dwa bajty!

Cons

  • Nieprzezroczysty dla czytelnika niezaznajomionego z techniką

Możesz także zajrzeć do pytania i odpowiedzi na temat tej techniki .

Wywołania systemowe w systemie Linux są mniej rozdęte

W 32-bitowym systemie Linux wywołania systemowe są zwykle wykonywane przy użyciu instrukcji sysenter (mówię zwykle, ponieważ starsze programy używają teraz przestarzałej int 0x80 ), jednak może to zająć sporo miejsca w programie, a więc są sposoby, że jeden może skracać rogi, aby skracać i przyspieszać.
Zwykle jest to układ wywołania systemowego w 32-bitowym systemie 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

To ogromne prawo! Ale jest kilka sztuczek, które możemy wyciągnąć, aby uniknąć tego bałaganu.
Pierwszym z nich jest ustawienie ebp na wartość esp zmniejszoną o rozmiar 3 32-bitowych rejestrów, czyli 12 bajtów. Jest to świetne, o ile jesteś w stanie nadpisywać ebp, edx i ecx śmieciami (np. Kiedy i tak przeniesiesz wartość do tych rejestrów bezpośrednio po tym), możemy to zrobić za pomocą instrukcji LEA, abyśmy nie potrzebowali wpływać na wartość samego ESP.

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

Jednak nie skończyliśmy, jeśli wywołanie systemowe to sys_exit, możemy uciec od nie wypychania niczego na stos!

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

Pomnóż przez 3 lub 5

tło

Aby uzyskać produkt rejestru i stałej i przechowywać go w innym rejestrze, naiwnym sposobem jest zrobienie tego:

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

Użyj lea

Mnożenie to kosztowne operacje. Szybciej jest używać kombinacji przesunięć i dodatków. W szczególnym przypadku zmultiplikowania rywalizacji rejestru 32- lub 64-bitowego, który nie jest esp lub rsp przez 3 lub 5, możesz użyć instrukcji lea. Wykorzystuje obwód obliczania adresu do szybkiego obliczania produktu.

    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

Wielu asemblerów również zrozumie

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

Dla wszystkich możliwych mnożników innych ebp lub rbp instrukcji jest taka sama jak przy użyciu imul .

Plusy

  • Wykonuje się znacznie szybciej

Cons

  • Jeśli twój multiplicand to ebp lub rbp , zajmuje im jeden bajt więcej za pomocą imul
  • Więcej do wpisania, jeśli asembler nie obsługuje skrótów
  • Nieprzezroczysty dla czytelnika niezaznajomionego z techniką


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow