Łączna liczba wyświetleń

czwartek, 25 lutego 2016

7. Kurs Asembler x86: Instrukcje transferu danych

Autor Nauka Programowania  |  w x86  21:28


Typ operandów

Instrukcje mogą przyjmować zero, jeden, dwa lub trzy operandy.

Wyróżnia się trzy podstawowe typy operandów

  • Wartości stałe – wartość ustalona przed tłumaczeniem programu
  • Rejestr – zawartość jednego z rejestrów procesora
  • Adres – odwołuje się do jakiegoś miejsca w pamięci

Instrukcja MOV


Instrukcja MOV kopiuje dane ze źródła do miejsca docelowego. Format tej instrukcji wygląda następująco:

MOV cel, źródło

W językach wysokiego poziomu wyglądałoby to w taki sposób:

Cel = źródło

Instrukcja MOV stawia pewne wymagania

Oba operandy muszą być tego samego rodzaju

Oba operandy nie mogą być jednocześnie adresem w pamięci

Rejestr wskaźnika instrukcji nie może być rejestrem docelowym

Dozwolone kombinacje można zapisać w następujący sposób:

MOV rejestr, rejestr
MOV pamięć, rejestr
MOV rejestr, pamięć
MOV pamięć, stała
MOV rejestr, stała



Pojedyncza instrukcja mov nie może zostać użyta do przeniesienia danych spod jednego adresu do drugiego. Kod, który wykona to działanie wygląda tak:

.data
zmienna1 WORD ?
zmienna2 WORD ?

.code
main PROC
mov ax, zmienna1
mov zmienna2, ax



Kopiowanie mniejszej wartości do większej


Instrukcja MOV nie może bezpośrednio skopiować mniejszego operandu do większeg, jednak istnieje możliwość obejścia tego. Stwórzmy zmienna licznik(bez znaku, 16-bitowa), która musi zostać przeniesiona do rejestru ECX (32-bitowy). Możemy przenieść do ECX zero, w ten sposób kasując jego zawartość i wtedy skopiować do rejestru CX zmienną licznik.

.data
licznik WORD 1
.code
mov ecx, 0
mov cx, count



A co się stanie gdy w podobny sposób spróbujemy skopiować liczbe ze znakiem?

.data
zeZnakiem SWORD -16 ; FFF0h(-16)

.code
mov ecx, 0
mov cx, zeZnakiem   ; ECX = 0000FFF0h (+65520)

Jak widać wartość zmiennej została skopiowana w dosłowny sposób, bez zachowania kodowania U2, przez co w rejestrze ECX znajduje się liczba dodatnia.

Instrukcja MOVZX


MOVZX (move with zero-extend) kopiuje zawartość operanda źródłowego do operanda docelowego I wypełnia zerami rejestr do wartości 16 lub 32 bitowej. Tej instrukcji używa się tylko podczas kopiowania liczb bez znaku.

Format instrukcji

MOVZX reg32, reg/mem8
MOVZX reg32, reg/mem16
MOVZX reg16, reg/mem8



Przykład:

.data
bajt BYTE 10001111b
.code
movzx ax, bajt



Przed wywołaniem instrukcji skopiowania do rejestru, może w nim znajdować się dowolna liczba.

AX
10110011
11101111



Skopiowanie z wypełnieniem zerami, kopiuje mniejszą wartość do dolnej części rejestru a górną wypełnia zerami.

AX
00000000
10001111

Instrukcja MOVSX


MOVSX (move with sign-extend) kopiuje zawartość operandu źródłowego do miejsca docelowego i zachowuje odpowiednie kodowanie dla wypełnianych bitów. Tej instrukcji używa się tylko podczas kopiowania liczb ze znakiem.

Format instrukcji

MOVSX reg32, reg/mem8
MOVSX reg32, reg/mem16
MOVSX reg16, reg/mem8



Przykład:

.data
bajt BYTE 10001111b
.code
movsx ax, bajt



Przed wywołaniem instrukcji skopiowania do rejestru, może w nim znajdować się dowolna liczba.

AX
10110011
11101111



MOVSX zachowuje odpowiednie kodowanie liczb ujemnych podczas kopiowania.

AX
11111111
10001111



Instrukcje LAHF I SAHF


LAHF (Load status flags into AH) kopiuje dolny bajt rejestru EFLAGS do rejestru AH. Kopiowane są następujące flagi: Sign, Zero, Auxilliary Carry, Parity i Carry.

Poniższy kod kopiuje dolny bajt rejestru EFLAGS do rejestru AH a następnie kopiuje zawartość rejestru AH do zmiennej kopiaFlag.

.data
kopiaFlag BYTE ?
.code
lahf
mov kopiaFlag, ah



SAHF (store AH into status flags) kopiuje zawartość rejestru AH do dolnego bajtu rejestru EFLAGS.

mov ah, kopiaFlag
sahf



Wyświetlenie rejestru flag podczas debugowania


Żeby w Visual Studio podczas debugowania programu wyświetlić zawartość rejestru flag należy w oknie Registers kliknąc prawym przyciskiem myszy i wybrać opcję flags.



Instrukcja XCHG


XCHG (exchange data) zamienia zawartość dwóch operandów. Można użyć jej w trzech wariantach:

XCHG reg, reg
XCHG reg, mem
XCHG mem, reg

Zasady dotyczące używania instrukcji XCHG jest taka sama jak w przypadku instrukcji MOV.

Operator przesunięcia


Jak już wcześniej pisałem nazwa jakiejś zmiennej reprezentuje jej adres w pamięci. W przypadku tablicy nazwa jest adresem pierwszego elementu, a kolejne elementy ułozone są po kolei za pierwszym. W związku z tym można się do nich dostać korzystając z adresu pierwszego elementu dodając do niego przesunięcie będące wielokrotnością rozmiaru elementu.

Kod
Wynik
.386
.model flat, stdcall
.stack 4096

include kernel32.inc
includelib kernel32.lib

.data
tablicaB BYTE 10h, 20h, 30h, 40h, 50h

.code
main PROC
    mov eax, 0
    mov al, tablicaB
    mov al, [tablicaB+1]
    mov al, [tablicaB+2]
    mov al, [tablicaB+3]
    mov al, [tablicaB+4]

    invoke ExitProcess, 0
main ENDP
END main










EAX = 0
EAX = 10h
EAX = 20h
EAX = 30h
EAX = 40h



Wyrażenie [tablicaB+1] dodaje do adresu tablicaB jeden bajt, a umieszczenie tego w nawiasach [ ] mówi asemblerowi, żeby pobrał wartość elementu, który znajduje się pod tym adresem.

MASM nie sprawdza czy nie wyszliśmy poza zakres tablicy, więc zapisując [tablicaB+20] odczytam wartość znajdującą się 20 bajtów poza pierwszym adresem w tej tablicy.

W przypadku gdyby tablica przechowywała elementy typu WORD należy przesuwać jej indeks o 2 bajty, ponieważ jest to rozmiar każdego z elementów.

.data
tablicaW WORD 10h, 20h, 30h, 40h, 50h

.code
main PROC
    mov eax, 0
    mov ax, tablicaW
    mov ax, [tablicaW+2]
    mov ax, [tablicaW+4]
    mov ax, [tablicaW+6]
    mov ax, [tablicaW+8]



Gdyby tablica przechowywała dane typu DWORD należałoby przesuwać indeks o 4 bajty, jest to rozmiar każdego z elementów.



Przykładowy program


Kod przykładowego programu pokazującego działanie ostatnio wspomnianych instrukcji

.386
.model flat, stdcall
.stack 4096

include kernel32.inc
includelib kernel32.lib

.data
zmienna1 WORD 1000h
zmienna2 WORD 2000h
tablicaB BYTE 10h, 20h, 30h, 40h, 50h
tablicaW WORD 100h, 200h, 300h
tablicaD DWORD 10000h, 20000h

.code
main PROC
    mov eax, 0
    mov ebx, 0
    mov ecx, 0
    mov edx, 0

    mov bx, 0A69Bh
    movzx eax, bx
    movzx edx, bl
    movzx cx, bl

    mov bx, 0A69Bh
    movsx eax, bx
    movsx edx, bl
    mov bl, 07Bh
    movsx cx, bl

    mov ax, zmienna1
    xchg ax, zmienna2
    mov zmienna1, ax

    mov al, tablicaB
    mov al, [tablicaB+1]
    mov al, [tablicaB+2]

    mov ax, tablicaW
    mov ax, [tablicaW+2]

    mov eax, tablicaD
    mov eax, [tablicaD+4]

    invoke ExitProcess, 0
main ENDP
END main






2 komentarze:

  1. Programowanie w Assemblerze jak dla mnie jest całkowicie ciemną magią. Wiem, że jest to język programowania niskiego poziomu i za jego pomocą możemy np. sterować pracą procesora. Mimo wszystko preferuję kodowanie jak robią to ludzie z https://craftware.pl czyli programowanie przydatnych aplikacji webowych lub stacjonarnych .

    OdpowiedzUsuń
  2. Faktycznie takie rzeczy to jest już programowanie i ja muszę przyznać, że raczej nie znam się na tym. Jednak bardzo chwalę sobie korzystanie z systemów informatycznych od https://www.connecto.pl/ i muszę powiedzieć, że to dzięki nim wiem jak dobrze prowadzić firmę.

    OdpowiedzUsuń

Proudly Powered by Blogger.