Assembly Iványi Péter
Miért? • Ma már ritkán készül program csak assembly-ben – Általában bizonyos kritikus rutinoknál használják – Miért nem használjuk? • Magas szintű nyelven könnyebb programozni • Nehéz más gépre átvinni a programot
– Miért tanítjuk? • Az assembly rutin kisebb és gyorsabb lehet • Közvetlen hardware elérés, amit magasabb szintű nyelvből esetleg lehetetlen • Mélyebb tudás a számítógépekről • Fordítók megértése, magasabb nyelvek megértése
Miért? • Sajnos kis és gyors rutinok írásához nagy gyakorlat, tapasztalat kell – A rendszer részletes ismerete
• Assembly esetén lemondunk szinte minden rendszer támogatásról és a saját kezünkbe vesszük a vezérlést – Gondosan válasszuk meg a programozási eszközt – Általában a középút az ideális • Program készítés • Karbantartás • Költség, munkaráfordítás, stb.
Miért? • Hatékony, gyors, kis program létrehozása idő és munkaigényes • Milyen célt szolgál a rutin? • Hányszor fut le? – Ha csak egyszer fut le, minek javítani? – Ha többször, pl. JIT compiler nem elég?
Mit? • • • •
IBM PC Intel processzorok Windows (Linux)
• Programozás alacsony szintű nyelven • Magas szintű nyelvek által generált kód
Ismétlés • Számítógép története – Ez most kihagyjuk!
• Számrendszerek közti konverzió – 2, 10, 16 alapú számrendszer
• Számítógép architektúrája – – – –
Végrehajtás Regiszterek Memória címzés Megszakítás
Tizes számrendszer • Számjegyek: 0-9
d n ×10 n + d n -1 ×10 n -1 + K + d 2 ×10 2 + d1 ×101 + d 0 ×100 • Példa:
3045 = 3 ×103 + 0 ×10 2 + 4 ×101 + 5 ×100 = 3000 + 0 + 40 + 5 = 3045
Kettes számrendszer • Számjegyek: 0-1
d n × 2 n + d n -1 × 2 n -1 + K + d 2 × 2 2 + d1 × 21 + d 0 × 20 • Példa:
101101 = 1 × 2 + 0 × 2 + 1 × 2 + 1 × 2 + 0 × 2 + 1 × 2 5
4
3
2
= 32 + 0 + 8 + 4 + 0 + 1 = 45
1
0
Tizenhatos számrendszer • Hexadecimális számok • Számjegyek: 0-9,A,B,C,D,E,F
d n ×16 n + d n -1 ×16 n -1 + K + d 2 ×16 2 + d1 ×161 + d 0 ×160 • Példa:
3AF = 3 ×16 2 + 10 ×161 + 15 ×160 = 3 × 256 + 10 ×16 + 15 = 943
Konverzió • Mennyi decimális 45 binárisan? – – – – – –
45 / 32 (25) = 1 13 / 24 = 0 13 / 23 = 1 5 / 22 = 1 1 / 21 = 0 1 / 20 = 1
4510 = 1011012
maradt 13 maradt 13 maradt 5 maradt 1 maradt 1 maradék nulla
Összeadás • Mindegyik számrendszerben működik
Előjeles számok • Hogyan reprezentáljuk a negatív számokat? • Egyszerű megoldás: – Használjunk egy bitet az előjel jelölésére • S=0 • S=1
pozitív (+) negatív (-)
– Probléma: • Két darab zérus értékünk van 1000 0000 0000 0000
Előjeles számok • Kettes komplemens
- d n × 2 + d n -1 × 2 n
n -1
+ K + d 2 × 2 + d1 × 2 + d 0 × 2 2
1
0
• Negatív szám (n) előállítása: – Invertáljuk a biteket és adjunk hozzá 1-et – Vegyük a legnagyobb számot (csupa 1), vonjunk ki net, majd adjunk hozzá 1-et – Jobbrol balra haladva másoljuk a zérusokat, az első 1est is másoljuk, a többit invertáljuk
Kettes komplemens 0100
0101 0110 6
0111
5
4
0011 0010
3 2 1
7 1000 1001
0001
-8
0
-7
-1 -6 -5
1010 1011
-4 1100
-3
-2
0000
1111 1110
1101
Kettes komplemens 1. • 8-bites számok, előjel nélkül: 0 - 255 • 4510 = 0010 11012 – Invertáljuk • 1101 00102
– Adjunk hozzá 1-et • 1101 00112
– Eredmény: • 1101 00112 = 21110 = -4510
Ellenőrzés 0010 1101 + 1101 0011 1 0000 0000
Kettes komplemens 2. • 4510 = 0010 11012 – A legnagyobb szám • 1111 11112 =25510
– Vonjunk ki 45-öt • 255 - 45 = 21010 = 1101 00102
– Adjunk hozzá 1-et • 1101 00112
– Eredmény: • 1101 00112 = 21110 = -4510
Kettes komplemens 3. • 2810 = 0001 11002 – Másoljuk a zérusokat jobbról balra • 002
– Másoljuk le az első 1-est • 1002
– Invertáljuk a többi bitet • 1110 01002 = 22810
Ellenőrzés 0001 1100 + 1110 0100 1 0000 0000
Kettes komplemens • 8 bit esetén -1 = 1111 1111 -2 = 1111 1110 -128 = 1000 0000 +1 = 0000 0001 +127 = 0111 1111 +128 = érvénytelen !!!
Kettes komplemens • 16 bit esetén -1 = 1111 1111 1111 1111 -2 = 1111 1111 1111 1110 -32768 = 1000 0000 0000 0000
8-ból 16 bites számok • 6410 – 8 bites reprezentálás: – 16 bites reprezentálás:
40h 0040h
• -6410 – 8 bites reprezentálás: C0h = 1100 0000 – 16 bites reprezentálás: FFC0h = 1111 1111 1100 0000
• Szabály: – Az előjel bitet minden további bit helyébe bemásoljuk
16-ból 8 bites számok • -448 – 16 bites reprezentálás: FE40h – 8 bites reprezentálás: nem lehet !!!
• „Sign contraction” csak akkor lehetséges ha az összes felső bit mind zérus vagy 1-es – Példa: FF80h -ból 80h lesz
Architektúra • 8086-os processzor • Regiszterek • Memória
8086 • 16 bites processzor • Busz – Címbusz • A processzor a memóriával közli a kívánt címet
– Adatbusz • A processzor és egyéb egységek közötti adatfrogalomra
– Vezérlőbusz • A vezérlőjeleket továbbítjuk
Az adatbusz „vonalainak” száma adja meg hány bites a processzor
8086 címbusz
8086 rendszer
adatbusz 16 bit vezérlőbusz
Memória és egyéb egységek
Intel processzorok Processzor Adatbusz 8088 8 8086 16 80286 16 80386dx 32 80486 32 80586/Pentium 64
Címbusz 20 20 24 32 32 32
Memória 1MB 1MB 16MB 4GB 4GB 4GB
Egyszerű mikroprocesszor
Kezdés
Következő utasítás betöltése
Utasítás dekódolása
Utasítás végrehajtása
Vége
Egyszerű mikroprocesszor, 8086
Fetch Dekód Futtat Fetch Dekód Futtat 1 1 1 2 2 2 Tölt
Vár
Vár
Tölt
Vár
Vár
idő
Processzor Busz
80486 mikroprocesszor Fetch 1
Fetch 2
Fetch 3
Fetch 4
Tárol 1
Dekód Dekód Dekód Dekód 1 2 3 4 Futtat 1
Futtat 2 Címzés 1
Futtat 3 Vár
„pipelining”
Fetch 5 Vár Futtat 4 Vár
Fetch Busz 6 Dekód Dekódoló egység 5 Vár
Végrahajtó egység
Címzés Címző egység 2
Vezérlő regiszterek • IP (Instruction pointer) – Utasításmutató, az éppen végrehajtandó utasítás címe
• SP (Stack pointer) – Veremmutató, utoljára beírt elem címe
• BP (Base pointer) – Bázismutató, a verem címzéséhez
• SI (Source index) – Kiindulási terület indexe
• DI (Destination index) – Célterület indexe
Általános regiszterek • AX (Accumulator register) – Általános célú, majdnem mindenre használható – matematikai műveleteknél gyakran használjuk – AL (Low) és AH (High) részre osztható AH
AL
8 bit
8 bit 16 bit
Általános regiszterek • BX (Base register) – Általános célú – Főleg címzésnél használjuk
BH
BL
8 bit
8 bit 16 bit
Általános regiszterek • CX (Counter register) – Számláló regiszter, ciklusszervezésnél használjuk
CH
CL
8 bit
8 bit 16 bit
Általános regiszterek • DX (Data register) – Adat regiszter – Szorzás és osztás esetén speciális szerepe van
DH
DL
8 bit
8 bit 16 bit
Szegmens regiszterek • CS (Code segment) – Az aktuális program báziscíme – Minden utasításelérésnél használja a CPU – Módosítása csak vezérlésátadással
• SS (Stack segment) – A veremként használt terület szegmensének címe – Csak ha nagyon szükséges, akkor módosítsuk
Szegmens regiszterek • DS (Data segment) – Adatszegmens regiszter – A címzett területről lehet adatot elérni – Írható/olvasható
• ES (Extra segment) – Extra szegmens regiszter – Másodlagos memória terület elérésére szolgál
Státusz regiszter 0. Bit (Carry): „átviteli” bit, egy 8 bites művelet 9. bitje 1. Bit (Parity): értéke egy, ha az eredmény byte-ban az egyes bitek száma páros, különben zérus 4. Bit (Auxiliarry carry): BCD számok kezelése esetén használt 6. Bit (Zero): ha az utolsó művelet eredménye zérus, akkor az értéke 1, egyébként zérus
Státusz regiszter 7. Bit (Sign): értéke zérus ha az eredmény pozitív, egyébként 1 8. Bit (Trap): debug-golásnál használt 9. Bit (Interrupt): megszakítás tíltás vagy engedélyezés, ha 1 minden megszakítást fogad 10. Bit (Direction): string kezelő műveletek használják, ha értéke zérus, akkor az SI és DI regisztereket automatikusan növeli, ha egy akkor csökkenti
Státusz regiszter 11. Bit (Overflow): túlcsordulás jelzése
32 bites regiszterek
Veremkezelés • Alapvető fontosságú !!! • Függvények közötti kapcsolat – Visszatérési cím – Paraméterátadás – Visszatérési érték
Függvényhívás void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
SP
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
SP
1 akármi
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
SP
1 akármi visszatérési cím 1 (argumentum)
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2 SP
1 akármi visszatérési cím 1
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2 SP
1 akármi visszatérési cím 1 visszatérési cím 1 (argumentum)
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2 a3 SP
1 akármi visszatérési cím 1 visszatérési cím 1
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); } Ekkor
a2 a3
1 akármi visszatérési cím 1 visszatérési cím 1
SP
is függvényhívás történik, de most ignoráljuk.
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2 a3 SP
1 akármi visszatérési cím 1 visszatérési cím 1
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2
SP
1 akármi visszatérési cím 1 visszatérési cím
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2
SP
1 akármi visszatérési cím 2
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2
SP
1 akármi visszatérési cím 2 visszatérési cím 2 (argumentum)
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2 a3 SP
1 akármi visszatérési cím 2 visszatérési cím 2
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); } Ekkor
a2 a3
1 akármi visszatérési cím 2 visszatérési cím 2
SP
is függvényhívás történik, de most ignoráljuk.
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2 a3 SP
1 akármi visszatérési cím 2 visszatérési cím 2
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2 a3 SP
1 akármi visszatérési cím 2 visszatérési cím 2
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2
SP
1 akármi visszatérési cím 2
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
a2
SP
1 akármi visszatérési cím 2
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
SP
1 akármi visszatérési cím
Függvényhívás a1 b1
void f3(int a3) { printf(”%d”,a3); } void f2(int a2) { f3(a2); a2 = (a2+1); f3(a2); } void f1() { int a1 = 1; int b1; f2(a1); }
SP
1 akármi
Függvényhívás • A függvényhívás során a verem adja a függvény „környezetét, állapotát” – Argumentumait – Hova kell a függvény után visszatérni
Adattípusok • Számok: – – – – –
bit: 0 vagy 1 nibble: 4 bit DB: byte = octet = 8 bit DW: word (szó) = 2 byte = 16 bit DD: double word = 4 byte = 32 bit
• Szöveg: – Karakterek, ASCII kód, 8 bit
Memória címzés • 8086-os címzés – Real mode – 20 bites lineáris memória címet kell generálni 16 bites címekből • Szegmens címet 4 bittel balra toljuk, majd egy offset címmel összeadjuk
+ =
0000 szegmens offszet lineáris cím
16 bit 16 bit 20 bit
Példa • Lineáris cím: 25553 – Szegmens:offszet = 2222:3333 – Szegmens:offszet = 2000:5553
Szegmentálás, Intel • 8086 (régi szegmentálás) – – – –
Max 1 MB memória 64 KB szegmensek 16 x 64 KB = 1MB Bármely cím elérhető, csak be kell tölteni a szegmens regiszterbe • CS : kód szegmens szelektor • DS : adat szegmens szelektor
Szegmentálás , Intel • 80386, védett módú címzés – 4 GB memória – A memória nem elérhető szabadon – A szegmens bármekkora lehet • 1 byte • 64 KB • 4 GB
Szegmenstáblák • GDT (Globális leíró tábla) – Csak egy van – Általános célú – Minden program használhatja
• IDT (Megszakítás leíró tábla) – Megszakítás kezelő rutinok címe
• LDT (Lokális leíró tábla) – Minden processzusnak lehet – Opcionális
Szegmenstáblák • Miután a memóriába helyeztük a leíró táblákat, a processzornak is meg kell mondani – 3 speciális regiszter: • GDTR, IDTR, LDTR • 32 bites regiszterek
• Szegmens regiszter (DS, CS) 16 bitesek
Szegmens címzés • Hogyan lehet egy 16 bites regiszterben 32 bites címet tárolni? – Sehogy – Vesszük a 16 bites címet – Eltoljuk jobbra 3 bittel (13 bites cím) • Ez 8192 különböző értéket jelent, ami pont megfelel a szegmenstáblák méretének
– A szegmens regiszter a szegmenstáblán belüli indexet adja meg
Szegmens címzés • Mire kell az alsó 3 bit? – 2 bit: a védelmi szint: 0-3 – 1 bit: GDT vagy LDT táblát használjuk-e?
Szegmens címzés DS:ESI
FFFF FFFF
… szegmens
DS:0 32
0
…
ESI
DS
…
15 0 0 0000 0000 0011 0 XX 48
GDT 0
GDTR
… 0000 0000
Szegmens címzés, LDT-vel DS:ESI
… szegmens
DS:0 32
…
0
ESI
DS
…
LDT
15 0 0 0000 0000 0001 1 XX
…
LDTR 48 GDTR
FFFF FFFF
GDT
0
… 0000 0000
Program és adatterület szervezése Eredeti SP SS:BP
Felhasznált verem
SS:SP Szabad verem SS
CS:IP
Program CS
DS:DI DS:SI
Adat DS
Megszakítás • Interrupt • A normál végrehajtási folyamat megszakítása • Egy folyamat felfüggesztése, a folyamathoz képesti külső esemény hatására, olyan módon hogy a folyamathoz vissza lehet térni • Hardware események váltják ki
Megszakítás kezelő Felhasználói mód
alkalmazás
megszakítás kezelő Kernel mód
Megszakítás • A 8086-os processzor egy 256 elemű megszakítás rendszerrel rendelkezik • A memória elején, a 0. címen található a táblázat • Minden interrupthoz egy 4 byte hosszú blokk tartozik • 1. szó: IP • 2. szó: CS
Megszakítás lépései • A külső egység a processzor egyik lábán jelzi, hogy megszakítást kér • A proc befejezi az utasítás végrehajtását • A kérő egység elküldi 8 biten a megszakítás számát – Valójában egy interrupt kezelő van a proc és az egységek között, – Ez sorolja be a megszakításokat, ha több is jön egyszerre
Megszakítás lépései • A megszakítás számának beolvasása után – Elmenti a státusz regisztert – Elmenti IP és CS regisztereket a veremre
• A sorszámnak megfelelő CS és IP értéket kiolvassa a táblázatból és betölti • Letiltja a megszakításokat – Az Interrupt bitet törli
• Végrehajtja a megszakítás rutinját
Megszakítás lépései • A megszakítás rutin egy IRET paranccsal tér vissza – Visszatölti a státusz, CS és IP regisztereket
Szoftver megszakítás • Programból is kiváltható a megszakítás az INT utasítással !!! • Software interrupt • A szubrutin hívás egy speciális formája – A memória bármely részére átadhatjuk a vezérlést – Nem címet adunk meg, hanem egy megszakítás számot
• Programok közötti kommunikációra is alkalmas – 60h - 7Fh : user interruptok