7a. Rechnerarchitektur und Grundzüge der Assemblerprogrammierung Inhalt: Schichtenmodell x86-Architektur und x86-Assemblerprogrammierung Assemblersprache und Einbindung in C Peter Sobe 1 Schichtenmodell Strukturierung des Rechensystems (Hardware und Software) in mehrere aufeinander liegende Schichten. Höhere Schichten benutzen darunter liegende über Schnittstellen Nur die Schnittstellen sind nach oben sichtbar, die Implementierung der zugehörigen Schicht bleibt verborgen (’information hiding’). Schichten können ausgetauscht werden (unter Beibehaltung ihrer Schnittstelle), ohne dass die darüber liegenden Schichten geändert werden müssen. Beispiel: Verschiedene Sprachschichten High-level language level (C): A = B + C; Peter Sobe Assembly language level (MC68020): MOVE.W B, D1 ADD.W C, D1 MOVE.W D1, A 2 Beispiel Machine language level (MC68020) (in bits): 1000: MOVE.W (0x2002).W,D1 0011 0010 0010 0000 0011 0000 1000 0010 1004: ADD.W (0x2004).W,D1 1101 0010 0010 0000 0111 0000 1000 0100 1008: MOVE.W D1,(0x2000).W 0011 0010 0001 0000 1100 0000 0001 0000 2000: A 2002: B 2004: C Peter Sobe 3 Beispiel Die C-Anweisung summe = a + b + c + d; ist für einen Mikroprozessor zu komplex und muss daher in mehrere einzelne Anweisungen aufgeteilt werden. Ein Prozessor kann immer nur zwei Zahlen addieren und das Ergebnis in einer der beiden verwendeten "Variablen" (Akkumulatorregister) speichern. Das Programm unten entspricht daher eher einem Maschinenprogramm (Assembler): …das würde beim x86 so aussehen: summe = a; mov eax,[a] summe = summe + b; add eax,[b] summe = summe + c; add eax,[c] summe = summe + d; add eax,[d] Peter Sobe 4 Schichtenmodell Schnittstelle zu Level 2 entspricht einer Rechnerarchitektur Schicht Level 1 wird im Allgemeinen zur Hardware gerechnet, obwohl sie auch Mikroprogramme (Firmware) enthalten kann. Heute aber meist fest verdrahtete‚ Ablaufsteuerungen (Hardware). Einzelne Schichten sind intern selbst wieder in Schichten unterteilt. Systementwurf muss auch Wechselwirkung zwischen Schichten berücksichtigen. Peter Sobe 5 Konvertierung zwischen Schichten (1) - Compilierung von Programm Phigh auf Plow (Sprache Lhigh auf Llow) (Beispiel: C-Compiler) Source program Compiler Object program Plow (Llow) Phigh (Lhigh) Peter Sobe Object program Hardware Hardware Execution Execution 6 Konvertierung zwischen Schichten (2) Interpretation von Lhigh auf Llow (Beispiele: Mikroprogramme, BasicInterpreter) Source program Lhigh Machine instructions Interpreter Phigh (Lhigh) Llow Hardware Execution Eine umgekehrte Konvertierung von einer tieferen auf eine höhere Schicht ist im Allgemeinen nicht mehr möglich, da Semantik verloren geht (’semantic gap’) Peter Sobe 7 x86 Architektur (1) Betrachtet ausgehend vom äußeren Erscheinungsbild: Registersatz: Anzahl der Register, Freiheiten bzw. Beschränkungen bei deren Verwendung Befehlssatz: Befehlsliste und evtl. verschiedene Varianten der Befehle, wenn unterschiedliche Adressierungsarten zugelassen sind. Alles andere betrifft die Implementierung und Realisierung Peter Sobe 8 x86 Architektur (2) Intel 80x86-Familie 80186 8086 Co-Proz. Busbreite Daten/Adress. (Bit) 16/20 8087 80188 80286 80287 80386DX 80386SX 80387 80486DX 80486SX 80487 Pentium Verbesserung der Implementierung der Architektur von 12 CPI beim 8086 auf 1.5 - 3 CPI beim Pentium (CPI = Cycles Per Instruction). Peter Sobe Große Bedeutung durch Einsatz in IBM-kompatiblen PCs. Aufgrund der hohen 16/24 Stückzahlen ‚Mainstream’ der 16/24 (SX) derzeitigen 32/32 (DX) Rechnerentwicklung. 32/32 64/32 CISC-Prozessoren aus historischen Gründen „binär abwärtskompatibel“ zum ‚Urahnen’ 8086 Stetige Verbesserung der Technologie (Taktfrequenz von 4.77 bis 10 MHz beim 8086/8088 auf über 3 GHz beim Pentium). 9 Instruction Set Architecture (Intel IA-32) 32 AH AX AL EAX BH BX BL EBX CH CX CL ECX DH DX DL EDX 16 15 SI ESI DI ESI BP EBP SP ESP 87 8 x 32-Bit-Register mit 16-BitRegistern des 8086 in unteren beiden Bytes 0 Register dienen zur kurzfristigen Speicherung von Operanden, Adressen, Indexwerten innerhalb des Prozessors Peter Sobe 10 Instruction Set Architecture (Intel IA-32) V Exponent Signifikant R0 R1 8 x 80-Bit-Gleitkommaregister (internes IEEE-Format) R2 R3 R4 R5 R6 R7 79 78 64 63 0 Gleitkomma-Register dienen zur kurzfristigen Speicherung Fließkomma-Variablenwerten, um vom Fließkomma-Rechenwerk verknüpft zu werden Peter Sobe 11 Instruction Set Architecture (Intel IA-32) CS SS DS ES FS 6 x 16-Bit-Segmentregister (8086 hatte nur 4): Codesegment CS, Stacksegment SS, 4 Datensegmente DS, ES, FS und GS GS 15 31 31 Peter Sobe 0 32-Bit-Befehlszähler, 32-BitFlagregister (8086: je 16 Bit) 16 15 0 16 15 Diverse Zusatzregister z. B. für Kontrolle und Ausnahme0 behandlung 12 IA-32 Datentypen CISC-Befehlsformat (variable Länge) Adressierungsarten - unmittelbar - Register indirekt - direkt - indiziert - Register Peter Sobe 13 IA-32 Befehlssatz ohne Gleitkommabefehle Transferoperationen (s.g. Moves) MOV DST, SRC Move SRC to DST PUSH SRC Push SRC onto Stack POP DST Pop value from Stack to DST XCHG DS1, DS2 Exchange DS1 and DS2 Diese Befehle werden benutz, um Variablenwerte vor deren Verwendung in Rechenoperationen in die Register zu holen, oder von den Registern wieder zurück in den Hauptspeicher zu kopieren. Oben ist nur eine Auswahl der gebräuchlichsten Befehle angegeben. Peter Sobe 14 IA-32 Befehlssatz ohne Gleitkommabefehle Arithmetische Operationen ADD DST,SRC Add SRC to DST SUB DST,SRC Subtract SRC from DST MUL SRC Multiply EAX by SRC (unsigned) IMUL SRC Multiply EAX by SRC (signed) DIV SRC Divide EDX-EAX by SRC (unsigned) IDIV SRC Divide EDX-EAX by SRC (signed) INC DST Add 1 to DST DEC DST Subtract 1 from DST NEG DST Negate DST (subtract it from 0) Peter Sobe 15 IA-32 Befehlssatz ohne Gleitkommabefehle Steuerfluss JMP ADDR Jump to ADDR Jxx ADDR Conditional Jump to ADDR (xx zzum Beispiel NE für ‚NOT Equal‘ als Ergebnis des letzten Vergleichs mit CMP (Compare) CALL ADDR Call Procedure at ADDR RET Return from Procedure LOOPxx Loop until Condition is met Peter Sobe 16 IA-32 Befehlssatz ohne Gleitkommabefehle Boolean AND DST,SRC Boolean AND SRC into DST OR DST,SRC Boolean OR SRC into DST XOR DST,SRC Boolean Exclusive OR SRC to DST NOT DST Replace DST with 1‘s complement Shift/Rotate SAL/SAR DST,# Shift DST left/right # of Bits SHL/SHR DST,# Shift logical DST left/right # of Bits ROL/ROR DST,# Rotate DST left/right # of Bits RCL/RCR DST,# Rotate DST through carry # of Bits Vergleichsoperationen TST SRC1,SRC2 Boolean And Operands, Set Flags CMP SRC1,SRC2 Set Flags based on SRC1-SRC2 Peter Sobe 17 Mikroarchitektur Pentium 4 Umsetzung der IA-32 CISC-Befehle in 1 bis 4 Ops (interne RISC-Befehle) durch Decoder. Ausführung in supersklarer RISCArchitektur Trace Execution Cache (TEC) für Ops mit eigener Sprungvorhersage, 3 Ops pro Takt wie PentiumIII Verbesserte Sprungvorhersage für x86Befehle mit größerem BTB 20-stufige I-Pipe, Taktraten bis über 3 GHz 13 Funktionseinheiten, davon max. 6 gleichzeitig aktivierbar 8 KB Datencache (klein, aber schnell); Hardware-Prefetching mit Quad Pumped Speicherschnittstelle (3,2 GByte/s) Befehlssätze (MMX, SSE, SSE2) Optional: Hyperthreading (SMT) Peter Sobe 18 Hyperthreading Intels Implementierung von SMT: 2-fach Hyper-Threading für den P4, auch für Atom CPUs, Verhält sich für das Betriebssystem wie zwei logische Prozessoren, d. h. Multiprozessor-Software ist ohne Änderung lauffähig. P4-Pipeline mit SMT Pipeline-Register (Queues) und einige Pipelinestufen verdoppelt, die meisten Stufen werden abwechselnd von beiden Threads genutzt. Verdoppelung der Register durch RegisterRenaming implementiert. Nur 5% zusätzliche Chipfläche. Konflikte beim Nutzen der gemeinsamen Caches (Cache Aliase) können Leistung einschränken. Peter Sobe 19 x86 Architektur (2) Allgemeine Register AX – Akkumulator-Register, Ziel und Quelle für Rechenoperationen Teilung in hohes Byte (AH) und niedriges Byte (AL) BX - Basis-Register für Anfangsadressen, Teilung in hohes Byte (BH) und niedriges Byte (BL) CX – Count Register, Teilung in hohes Byte (CH) und niedriges Byte (CL), allgemein verwendbar, spezielle Bedeutung bei Schleifen DX - Daten-Register , Teilung in hohes Byte (DH) und niedriges Byte (DL) EAX EAX RAX (bei x86-64) AX AX AH AH 64 Bit Peter Sobe 32 Bit 16 Bit ALAL 8 Bit 20 x86 Architektur (3) Pointer-Register SP Stack-Pointer: zur Adressierung des Stacks verwendet BP Base-Pointer: zur Adressierung des Stacks verwendet IP Instruction-Pointer: Offset des nächsten Befehls Index-Register SI Source-Index: Unterstützung von Adressierungen esi Quelle (eng: source) für Stringoperationen DI Destination-Index: Unterstützung von Adressierungen edi Ziel (eng: destination) für Stringoperationen Segment-Register CS Code-Segment: zeigt auf aktuelles Codesegment DS Daten-Segment: zeigt auf aktuelles Datensegment SS Stack-Segment: zeigt auf aktuelles Stapelsegment ES Extra-Segment: zeigt auf weiteres Datensegment Peter Sobe 21 x86 Architektur (4) Statusflags CF Carry-Flag Übertragflag AF Auxiliary Carry-Flag Hilfsübertragflag ZF Zero-Flag Nullflag SF Sign-Flag Vorzeichenflag PF Parity-Flag Paritätsflag OF Overflow-Flag Überlaufflag Kontrollflags TF Trap-Flag Einzelschrittflag IF Interrupt Enable-Flag Interruptflag Peter Sobe 22 x86 Assembler-Programmierung (1) Die C-Anweisung summe = a + b + c + d; würde beim 80x86 Assembler so aussehen: mov eax,[a] add eax,[b] add eax,[c] add eax,[d] mov [s], eax Mit eax ist das 32 Bit breite AX Register gemeint. Alle Operationen beziehen sich damit auf 32 Bit Verarbeitungsbreite. Peter Sobe 23 x86 Assembler-Programmierung (2) Einfache if-then-else Konstrukte müssen in der AssemblerSprache in Compare und einen bedingten Sprung umgewandelt werden … if (a == 4711) {...} else { ... } Im x86 Assembler sieht das dann so aus: cmp eax,4711 jne ungleich gleich: ... jmp weiter ungleich: ... weiter: ... Peter Sobe 24 x86 Assembler-Programmierung (3) Einfache Zählschleifen werden von einem x86 Prozessor besser unterstützt. Das folgende C-Programm for (i=0; i<100; i++) { summe = summe + a; } sieht im 80x86 Assembler so aus: mov ecx,100 schleife: add eax,[a] loop schleife Der Loop-Befehl dekrementiert implizit das ecx Register und führt den Sprung nur aus, wenn der Inhalt des ecx Registers anschließend nicht 0 ist. Peter Sobe 25 x86 Assembler-Programmierung (4) Speicherzugriff Meistens reichen die Register nicht aus, um ein Problem zu lösen. In diesem Fall muss auf den Hauptspeicher des Computers zugegriffen werden, der erheblich mehr Information speichern kann. Für den Assemblerpogrammierer sieht der Hauptspeicher wie ein riesiges Array von Registern aus, die je nach Wunsch 8, 16 oder 32 Bits "breit" sind (je nach Datentyp). Die kleinste adressierbare Einheit ist ein Byte (= 8 Bits). Um auf einen bestimmten Eintrag des Arrays "Hauptspeicher" zugreifen zu können, muss der Programmierer die Adresse des Eintrages kennen. Das erste Byte des Hauptspeichers bekommt dabei die Adresse 0, das zweite die Adresse 1 usw. Peter Sobe 26 x86 Assembler-Programmierung (5) In einem Assemblerprogramm können Variablen angelegt werden, indem einer Speicheradresse ein Label zugeordnet und dabei Speicherplatz in der gewünschten Größe reserviert wird. [SECTION .data] gruss: db 'hello, world' unglueck: dw 13 million: dd 1000000 [SECTION .text] mov ax,[million] ... db … define byte, dw … define word (2 Bytes), dd … define double word Peter Sobe 27 x86 Assembler-Programmierung (6) Stack Nicht immer will man sich ein neues Label ausdenken, nur um kurzfristig mal den Wert eines Registers zu speichern, beispielsweise, weil man das Register für eine bestimmte Anweisung benötigt, den alten Wert aber nicht verlieren möchte. In diesem Fall wünscht man sich sowas wie einen „Ablagehaufen“. Den bekommt man mit dem Stack. Der Stack ist eigentlich nichts weiter als ein Stück des Hauptspeichers, nur dss dort nicht mit festen Adressen gearbeitet wird, sondern die zu sichernden Daten einfach immer oben drauf geschrieben (push) bzw. von oben heruntergeholt werden (pop). Der Zugriff ist also ganz einfach, vorausgesetzt man erinnert sich daran, in welcher Reihenfolge die Daten auf den Stapel gelegt wurden. Ein spezielles Register, der Stackpointer esp zeigt stets auf das oberste Element des Stacks. Da push und pop immer nur 32 Bits auf einmal transferieren können, ist der Stack in der folgenden Abbildung vier Bytes breit dargestellt. Peter Sobe 28 x86 Assembler-Programmierung (7) Adressierungsarten Die meisten Befehle des x86 können ihre Operanden wahlweise aus Registern, aus dem Speicher oder unmittelbar einer Konstante entnehmen. Beim mov Befehl sind (u. a.) folgende Formen möglich, wobei der erste Operand stets das Ziel und der zweite stets die Quelle der Kopieraktion angeben: Registeradressierung: Der Wert eines Registers wird in ein anderes übertragen. mov ebx,edi Peter Sobe 29 x86 Assembler-Programmierung (8) Unmittelbare Adressierung: Die Konstante wird in das Register übertragen. mov ebx,1000 Direkte Adressierung: Der Wert der an der angegebenen Speicherstelle steht, wird in das Register übertragen. mov ebx,[1000] Register-Indirekte Adressierung: Der Wert, der an der Speicherstelle steht, die durch das zweite Register bezeichnet wird, wird in das erste Register übertragen. mov ebx,[eax] Peter Sobe 30 x86 Assembler-Programmierung (9) Basis-Register Adressierung: Der Wert, der an der Speicherstelle steht, die sich durch die Summe des Inhalts des zweiten Registers und der Konstanten ergibt, wird in das erste Register übertragen. mov eax,[10+esi] Peter Sobe 31 Assembler-Einbindung in C (1) In einem C-Programm kann jede Anweisung durch einen Block von Assembler-Befehlen durch folgende Syntax ersetzt werden: _asm { <Folge von Assembler-Befehlen> } ; Jeder Assemblerbefehl muss durch Semikolon abgeschlossen sein. Die in den Assembler-Befehlen vorkommenden Hauptspeicheroperanden können Bezeichnungen des C-Programms sein. Die interne Darstellung und vor allem die Länge der Operanden muss gemäß der C-Deklaration so sein, dass sie kompatibel zum angewandten Befehl ist. Damit kann ein Datenaustausch zwischen den Assembler- und den C-Passagen erfolgen. Peter Sobe 32 Assembler-Einbindung in C (2) Beispiel: mov buf,cx; Mit cx ist das counter-Register (16 Bit) bezeichnet, folglich muss die angenommene C-Variable buf auch als eine vorzeichenlose Variable mit 16 Bit deklariert sein, d.h. unsigned short buf; Soll dagegen das 32-Bit-counter-Register adressiert werden (ecx): mov buf,ecx; so ist buf folgendermaßen zu deklarieren: unsigned int buf; Wird dies nicht beachtet, treten beim kompilieren Fehler auf. Da der C-Compiler einen Inline-Assembler benutzt, sind nicht alle Codes, wie bei einem eigenständigen Assembler zugelassen. Peter Sobe 33 Assembler-Einbindung in C (3) Beispiel: #include <stdio.h> void main() { unsigned short erg; unsigned short eingabe = 2; unsigned char z; unsigned int buf; _asm { //xor cx,cx; // cx=0 mov cx, eingabe; inc cx; // cx++ inc cx; // cx++ shl cx,3; // *8 mov erg,cx; // erg=cx mov bl,102; // bl='f‘ mov z,bl; // z=bl }; printf("\n erg=%u z=%c \n",erg,z); } Peter Sobe 34