1.7 Assembler Programmierung Die nach außen sichtbare Programmierschnittstelle eines Prozessors ist der Befehlscode. Dies ist eine binäre Dateninformation, die vom Prozessor Byte für Byte abgearbeitet wird. Dieser binäre Code kann über eine Low Level Programmiersprache (Assembler) generiert werden. Assembler Programmierung wird in der Regel nur noch bei ganz maschinennahen Operationen benutzt z.b. Programmierung der Hardware, Device-Treiber, Interrupt- Routinen. Assemblercode ist eine Textdatei und kann mit jedem Editor, der ASCII-Code erzeugen kann generiert werden. Der Assembler ist ein Programm, das die Ass.Textdatei in eine binäre Datei umwandelt (Objekt-Datei). Sowohl die Segment- als auch die symbolischen Informationen sind mit in der Datei gespeichert. -> Mehrere Quelltextdateien können einzeln assembliert, dann zu einem Gesamtprogramm zusammengebunden werden -> Linker (Montierer) Der Linker sammelt die Segmente vom gleichen Type und löst die Querreferenzen (Bezüge auf Definitionen oder Sprungadressen in anderen Objektdateien) auf und erzeugt die ausführbare Datei. Das Betriebssystem ist verantwortlich für die Einlagerung eines Programms in den Speicher und zum Start der Ausführung. Die tatsächliche Adresse im Speicher ist also i.d.r. zum Zeitpunkt der Assemblierung nicht bekannt. Alle Befehle eines Programms, deren Adressbezüge sich bei einer Programmverschiebung verändern würden, werden in einer Tabelle gekennzeichnet, die mit dem Programmcode gespeichert ist. Das Betriebssystem korrigiert dann alle markierten Adressen durch eine Offsetberechnung auf die gewünschte Adresse (Relokation). Jede höhere Programmiersprache erzeugt i.d.regel Assembler als Zwischencode. Oder es lässt sich der Assemblercode zumindest als Option generieren. Binärcode lässt sich zu Assemblercode zurückverwandeln -> Disassemblierung. Binärcode lässt sich schrittweise ausführen und alle Änderungen der Registerinhalte des Prozessors sind darstellbar -> zur Fehlersuche -> Debugger Der Debugger: Dies ist eine Software zur Analyse von Programmen. Es lassen sich interaktiv Programmteile ausführen, Haltepunkte (breakpoints) einbringen und Variablen oder Datenbereiche analysieren. Durch Debug-Informationen im Programm kann der Debugger eine Verbindung des binären Codes zu dem lesbaren Quellcode herstellen (symbolisches debuggen). Softwarewerkzeuge der Nachrichtentechnik Vorlagen zur Vorlesung WS 2006/2007 17
Identifikation der Assemblerstruktur an einem Beispiel Ausgabe eines Textes auf dem Bildschirm mit Hilfe einer Standardfunktion (DOS-Call) ; Festlegung von symbolischen Namen -> Assemblierer-Anweisungen BILDAUS equ 9 ; MS-DOS-Funktion: Bildschirmausgabe DOSINT equ 21h ; MS-DOS-Interruptnummer CR equ 13 ; ASCII-Code für <Carriage Return> LF equ 10 ; ASCII-Code für <Line Feed> EDB equ '$' ; Textbegrenzer für die MS-DOS-Funktion 9.DATA ; Ab hier stehen Datendefinition -> Datensegment GRUSS db 'Guten Morgen!' CR, LF, EDB.CODE mov ax, @data mov ds, ax ; Ab hier stehen Maschinenbefehle -> Codesegment ; @data (Standard-Assemblierer-Variable) ; zeigt auf die Adresse des Datensegments ; Datensegment-Register setzen mov dx, OFFSET GRUSS ; Adresse des Ausgabetextes nach DX holen mov ah, BILDAUS ; MS-DOS-Funktionsnummer nach AH holen int DOSINT ; und Funktion ausführen... Folgende Strukturen sind zu erkennen: Definitionen von symbolischen Konstanten Deklarationen von Daten (Namensgebung, Speicherbelegung) Strukturanweisungen (Segmente) Programmcode in Form von Assemblerzeilen Kommentarfelder Definitionen von symbolischen Konstanten [Name] equ [Ausdruck] Bs: CR equ 13 LF equ CR 3 Definitionen können voneinander abhängen, jedoch ist die Reihenfolge wichtig. Die Berechnung der rechten Seite wird vom Assembler ausgeführt und benötigt keinen Speicherplatz oder Rechenzeit während des Programmlaufs. Softwarewerkzeuge der Nachrichtentechnik Vorlagen zur Vorlesung WS 2006/2007 18
Deklaration von Daten Durch Anweisungen wie db, dt usw. wird Speicherplatz reserviert und benannt. Dieser Speicher wird entweder durch Konstanten im Operatorfeld vorbesetzt und beim Laden des Programms eingetragen oder durch? als undefiniert deklariert. z.b: db? reserviert ein Byte. Durch die dup Anweisung kann die Deklaration erweitert werden. Z.B: ZEILE db 80 dub (?) reserviert 80 Bytes ohne sie zu initialisieren und gibt der Speicherstelle den Namen ZEILE Der Name, der mit der Speicherstelle verknüpft ist, wird in einer internen Liste des Assemblers zusammen mit dem Segment, der Segmentadresse und dem Offset innerhalb des Segments angelegt. Abbildung 2: Befehle zur Datendefinition Im Programm kann man dann zugreifen auf 1) Zeiger auf die Adresse: mov ax, [Text_Puffer] Diese Anweisung lädt den Wert aus der Speicherstelle Text_Puffer ins Register ax 2) Die Adresse selbst (im Segment) : mov dx, offset Text_Puffer mov ax, [dx] Softwarewerkzeuge der Nachrichtentechnik Vorlagen zur Vorlesung WS 2006/2007 19
Strukturanweisungen (s.unten) Programmcode Anhand des Beispielprogramms kann man leicht die Struktur der ausführbaren Assemblerzeile erkennen: Abbildung 3:Aufbau einer Assemblerzeile Diskussion der Befehlsliste Steuerbefehle Ein-Byte Datenkorrektur- und konvertierungsbefehle Operandenbefehle o Transportbefehle (mov), fast alle denkbaren Kombinationen zwischen Register, Konstanten, Variablen und Registerinhalten sind möglich Arithmetische Operationen o Innerhalb der Integereinheit: Addition, Subtraktion, Multiplikation, Division, Inkrement, Dekrement, Negierung, Boolsche Operationen: AND, OR, NOT, XOR o Innerhalb der Gleitkomma-Recheneinheit: Die floating point unit (FPU) ist eine eigenständige Einheit mit speziellen Registern und eigenen Assemlerbefehlen. Ab Pentium Prozessoren ist die FPU bei Intel in der CPU integriert. Sprungbefehle o Unbedingte Sprünge, relativ oder absolut; innerhalb des Segments oder über Segmentgrenzen hinaus. o Bedingte Sprünge: in Abhängigkeit von Statusflags der CPU: z.b Carry, Zero, Overflow, Sign, Parity Unterprogrammaufrufe (call) Stapelbefehle (push, pop) Softwarewerkzeuge der Nachrichtentechnik Vorlagen zur Vorlesung WS 2006/2007 20
Segmentanweisungen Segmentanweisungen dienen dazu, 1) die Zuordnung der im Programm abgelegten Anweisungen und Daten zu den Segmentregistern der CPU zu erklären. 2) die Speicherbereiche physikalisch trennbar zu machen. D.h. Trennung von Code und Daten, damit z.b. die geschützte Information des Codesegments nicht durch fehlerhafte Datenzugriffe zerstört wird, oder damit Code in einen Adressbereich gelegt werden kann, wo nur Read Only Memory existiert. 3) Informationen über gemeinsame Anordnung von Segmenten über Dateigrenzen hinaus (werden vom Linker ausgewertet). ; Segmentanfang: <SegName> SEGMENT [Anordnung] [Verbindung] [Verwendung] ['Klasse'] ASSUME <Segmentregister>:<SegName> <SegName> ENDS ; Segmentende Diese vollständige Deklaration von Segmenten ist nicht unbedingt nötig und u.u. sogar störend. In Verbindung mit höheren Programmiersprachen erzeugen die Compiler eigene Segmentnamen, die man übernehmen muss, damit die Programme zueinander passen. Es ist daher meist eine vereinfachte Deklaration vorgesehen, die das Code- bzw. Datensegment der Hochsprache mit fortsetzt. Codesegment: Datensegment: Stacksegment :.CODE.DATA.STACK [Größe] Softwarewerkzeuge der Nachrichtentechnik Vorlagen zur Vorlesung WS 2006/2007 21