Assembler Unterprogramme Dr.-Ing. Volkmar Sieh Department Informatik 3: Rechnerarchitektur Friedrich-Alexander-Universität Erlangen-Nürnberg SS 2008 Assembler Unterprogramme 1/43 2008-06-03
Unterprogramme Unterprogramme: Prozeduren und Funktionen Unterprogramme sind aus modernen Programmiersprachen nicht mehr wegzudenken Ideen hinter der Unterprogrammtechnik sollten auch im Assembler-Code erhalten bleiben: Need-to-Know : Aufrufer des Unterprogramms sollte nur die Parameter, Rückgabewerte und den Namen des Unterprogramms kennen müssen Need-to-Know : aufgerufene Funktion sollte kein Wissen über den Aufrufer besitzen müssen Hauptprogramm und Unterprogramm sollten getrennt schreibund übersetzbar sein jede Art direkter oder indirekter Rekursion muss möglich sein jede beliebige Schachtelungstiefe muss möglich sein (nur begrenzt durch Speichergröße) Assembler Unterprogramme 2/43 2008-06-03
Unterprogramme Aufruf eines Unterprogramms (1. Lösungsansatz): / Hauptprogramm / jmp uprog u p r o g r e t : uprog : / Unterprogramm / jmp u p r o g r e t Assembler Unterprogramme 3/43 2008-06-03
Unterprogramme Das Unterprogramm selbst muss in diesem Falle mit jmp uprogret zum Hauptprogramm zurückspringen. Problematisch, wenn Unterprogramm von mehreren Stellen aus dem Hauptprogramm heraus aufgerufen wird. Man muss sich merken, von wo aus das Unterprogramm aufgerufen wurde bzw. wohin zurückgekehrt werden soll ( Rückkehradresse, Return-Address ). Assembler Unterprogramme 4/43 2008-06-03
Unterprogramme Aufruf eines Unterprogramms (2. Lösungsansatz): / Hauptprogramm / movl $uprogret1, %eax jmp uprog u p r o g r e t 1 : movl $uprogret2, %eax jmp uprog u p r o g r e t 2 : uprog : / Unterprogramm / jmp %eax Assembler Unterprogramme 5/43 2008-06-03
Unterprogramme Unterprogramm kehrt jetzt zum jeweiligen Aufrufort zurück. Dennoch problematisch: verschachtelte Unterprogrammaufrufe (z.b. Rekursion) nicht möglich (jedes Register kann nur maximal eine Rücksprungadresse speichern, Anzahl der Register beschränkt) Unterprogramm-Aufrufer muss wissen, in welchem Register das Unterprogramm die Rückkehradresse erwartet, oder aufgerufene Funktion muss wissen, in welchem Register die Rückkehradresse steht Assembler Unterprogramme 6/43 2008-06-03
Unterprogramme Aufruf eines Unterprogramms: / Hauptprogramm / movl $uprogret1, (%esp)+ jmp uprog u p r o g r e t 1 : movl $uprogret2, (%esp)+ jmp uprog u p r o g r e t 2 : uprog : / Unterprogramm / jmp (%esp ) Assembler Unterprogramme 7/43 2008-06-03
Unterprogramme Stack %esp ( Stack-Pointer ) muss zu Programmbeginn die Adresse eines genügend großen Speicherbereiches ( Stack ) enthalten. Funktioniert für beliebig verschachtelte Unterprogramme (auch Rekursion)! Assembler Unterprogramme 8/43 2008-06-03
Unterprogramme Stack Vier verschiedene Möglichkeiten für die Stack-Verwaltung: Stack wird von den kleinen Adressen hin zu den großen beschrieben (1) von den großen Adressen hin zu den kleinen beschrieben (2) Stack-Pointer zeigt auf das zuletzt abgelegte Datum (A) den nächsten freien Speicherplatz (B) Dementsprechend sind zu verwenden: push pop 1A mov..., +(%esp) mov (%esp)-,... 1B mov..., (%esp)+ mov -(%esp),... 2A mov..., -(%esp) mov (%esp)+,... 2B mov..., (%esp)- mov +(%esp),... Assembler Unterprogramme 9/43 2008-06-03
Unterprogramme Stack Hinweise: Stack des i80x86 und m68x05 wächst von den hohen Adressen hin zu den niedrigeren Stack-Pointer des i80x86 zeigt auf das zuletzt abgelegte Datum Stack-Pointer des m68x05 zeigt auf den nächsten freien Speicherplatz andere Schreibweise: r a : movl $ra, (%esp ) jmp uprog i80x86 m68x05 c a l l uprog j s r uprog jmp (%esp)+ r e t r t s Assembler Unterprogramme 10/43 2008-06-03
Unterprogramme Parameter/Return-Wert Verschiedene Möglichkeiten: Register: Speicher: Stack: Anzahl beschränkt nicht geeignet für rekursive Unterprogramme langsam nicht geeignet für rekursive Unterprogramme langsam => Übergabe der Parameter über den Stack, Return-Wert über Register (z.t. Mischformen) Assembler Unterprogramme 11/43 2008-06-03
Unterprogramme Parameter/Return-Wert Beispiel: Übergabe der Parameter und des Return-Wertes über Register: x = sub ( y, z ) ; i n t sub ( i n t a, i n t b ) { return a b ; } => => sub : movl y, %eax movl z, %ebx c a l l sub movl %eax, x s u b l %ebx, %eax r e t Assembler Unterprogramme 12/43 2008-06-03
Unterprogramme Parameter/Return-Wert Beispiel: Übergabe der Parameter auf dem Stack und des Return-Wertes über ein Register (hier: %eax): x = sub ( y, z ) ; i n t sub ( i n t a, i n t b ) { return a b ; } => => movl $, %esp movl z, (%esp ) movl y, (%esp ) c a l l sub a d d l $8, %esp movl %eax, x sub : movl 4(%esp ), %eax s u b l 8(%esp ), %eax r e t Assembler Unterprogramme 13/43 2008-06-03
Unterprogramme Parameter/Return-Wert Übergabe der Parameter auf dem Stack und des Return-Wertes über ein Register (i80x86: %eax) ist Standard-Verfahren. Unterprogrammaufrufe sollten daher immer folgendes Format haben: p u s h l <paramn> p u s h l <param2> p u s h l <param1> c a l l <subprog> a d d l $, %esp movl %eax, <r e s u l t > Assembler Unterprogramme 14/43 2008-06-03
Unterprogramme Parameter Dass die Parameter im umgekehrter Reihenfolge auf den Stack gepusht werden, hat folgende Vorteile: <param1> liegt (unabhängig von der Anzahl der Parameter) immer an bekannter Adresse (4(%esp)). Dies ermöglicht Unterprogramm-Aufrufe mit variabler Anzahl von Parametern (z.b. C-Funktion printf). Da der Stack von hohen Adressen hin zu kleinen Adressen wächst, liegen die Parameter wie in einem Array vor. Assembler Unterprogramme 15/43 2008-06-03
Unterprogramme Lokale Variablen Viele Unterprogramme verwenden lokale (temporäre) Variablen. Speicherung dieser Variablen in Registern schneller Zugriff, kurze Befehle (einfache Adressierungsarten), wenige Register, keine Rekursion (direkt) möglich in festen Speicherzellen i.a. genügend Speicherkapazität, langsamer Zugriff, keine Rekursion (direkt) möglich, alter Wert beim widerholten Aufruf des Unterprogramms noch intakt auf dem Stack i.a. genügend Speicherkapazität, kurze Befehle (einfache Adressierungsarten), Rekursion direkt möglich, langsamer Zugriff => Mischformen aus allen Speicherarten Assembler Unterprogramme 16/43 2008-06-03
Unterprogramme Lokale Variablen Register-Variablen für die am häufigsten verwendeten Variablen (z.b. Schleifen-Zähler, temporäre Variablen bei der Ausdrucksberechnung) Variablen in festen Speicherzellen für Werte, die beim widerholten Aufruf des Unterprogramms erhalten sein müssen (static-werte) Variablen auf dem Stack für seltener verwendete Variablen für große Variablen (z.b. Records, Arrays) Zwischenspeicher für Register-Variablen (z.b. für Rekursion) Assembler Unterprogramme 17/43 2008-06-03
Unterprogramme Lokale Register-Variablen Vorsicht bei der Verwendung von Register-Variablen! Man darf in einem Unterprogramm die Register-Variablen-Werte des Hauptprogramms nicht überschreiben. Zwei Möglichkeiten: call-used: Hauptprogramm sichert Register-Werte vor Aufruf des Unterprogramms und restauriert Werte nach Rückkehr call-saved: Unterprogramm sichert Register-Werte zu Beginn der Unterprogrammausführung restauriert Werte vor Rücksprung Assembler Unterprogramme 18/43 2008-06-03
Unterprogramme Lokale Register-Variablen Call-Used (i80x86: %eax, %ecx, %edx): p u s h l %r e g c a l l uprog < uprog kann %r e g verwenden p opl %r e g Assembler Unterprogramme 19/43 2008-06-03
Unterprogramme Lokale Register-Variablen Call-Saved (i80x86: %ebx, %ebp, %edi, %esi): p u s h l %r e g < h i e r kann %r e g verwendet werden p opl %r e g r e t Assembler Unterprogramme 20/43 2008-06-03
Unterprogramme Stack-Variablen Benutzung von Variablen auf dem Stack (vorausgesetzt, %esp zeigt auf das Ende eines freien Speicherbereiches): Reservieren von Speicherplatz: s u b l $<nbytes >, %esp Nutzung der Variablen (Beispiele): movl 4(%esp ), %eax movb %bl, 8(%esp ) addw $4, 6(%esp ) Freigeben des Speicherbereiches: a d d l $<nbytes >, %esp Assembler Unterprogramme 21/43 2008-06-03
Unterprogramme Stack-Variablen Beispiel (i80x86): void proc ( void ) { long i n t a ; short i n t b ; } a = ; b = ; => proc : s u b l $6, %esp movl, 0(%esp ) movw, 4(%esp ) a d d l $6, %esp r e t Assembler Unterprogramme 22/43 2008-06-03
Unterprogramme Stack-Frame Jedes Unterprogramm kann Aufruf-Parameter bekommen verfügt über eine Rückkehr-Adresse benutzt u.u. lokale Stack-Variablen verwendet ggf. Stack für neue Unterprogramm-Aufrufe Dieser Bereich des Stacks wird als Stack-Frame des Unterprogramms bezeichnet. Assembler Unterprogramme 23/43 2008-06-03
Unterprogramme Stack-Frame void A( i n t x, i n t y ) { long i n t z ; char c ; B( z, c ) ; } Assembler Unterprogramme 24/43 2008-06-03
Unterprogramme Frame-Pointer Problem: relative Adressen (bzgl. %esp) der lokalen Stack-Variablen ändert sich bei jeder Ausführung eines push- bzw. pop-befehls. long i n t x ; proc ( x, x ) ; ergibt compiliert: s u b l $4, %esp p u s h l 0(%esp ) p u s h l 4(%esp ) // r e l. Adr. mit p u s h l m o d i f i z i e r t! c a l l proc a d d l $8, %esp => Zugriff auf Stack-Variablen / Parameter sehr mühsam und Assembler Unterprogramme 25/43 2008-06-03 fehleranfällig!
Unterprogramme Frame-Pointer Extra-Register (Frame-Pointer; i80x86: %ebp) enthält Adresse für Stack-Variablen- und Parameter-Zugriff: Beispiel ergibt compiliert: p u s h l %ebp \ movl %esp, %ebp enter $4 s u b l $4, %esp / p u s h l 4(%ebp ) p u s h l 4(%ebp ) // p u s h l a e n d e r t %ebp n i c h t! c a l l proc a d d l $8, %esp movl %ebp, %esp \ p o p l %ebp / leave r e t Assembler Unterprogramme 26/43 2008-06-03
Unterprogramme Frame-Pointer Stack-Frames mit Frame-Pointern: Durch Verfolgung der Frame-Pointer kann die gesamte Aufruf-Hierarchie der Unterprogramme verfolgt werden (Debugger). Assembler Unterprogramme 27/43 2008-06-03
Unterprogramme ABI ABI: Application Binary Interface Interface-Definition beschreibt: Namenskonvention Übergabeverfahren für Parameter Übergabeverfahren für Return-Werte Nutzung der Register ggf. Fehlerbehandlung ABI wird sinnvollerweise vom CPU-Hersteller definiert. Compiler-Hersteller sollten sich daran halten. Sonst sind von verschiedenen Compilern übersetzte Programmteile (z.b. Libraries) zueinander inkompatibel. Assembler Unterprogramme 28/43 2008-06-03
Unterprogramme ABI Namen Unterprogramm-Namen einfach für Sprachen wie z.b. C oder Pascal: Unterprogramm-Name im Assembler-Code entspricht dem Namen in der Hochsprache. Schwieriger bei überladenen Unterprogrammen. Dann existieren mehrere gleiche Hochsprachen-Unterprogramm-Namen. Lösung: Parameter- und Return-Typen in den Namen hineinkodieren. Beispiele: i n t f ( i n t x ) ; void f ( unsigned long x ) ; i n t _ f _ i n t : void_f_unsigned_long : Um Mehrdeutigkeiten (z.b. void f(unsigned long x); und void f(unsigned x, long y);) zu umgehen, ggf. noch aufwändiger. Assembler Unterprogramme 29/43 2008-06-03
Unterprogramme ABI Namen Um Kollisionen von vom Compiler vergebenen Namen (z.b. für Labels oder temporäre Variablen) und vom Programmierer vergebenen Namen zu vermeiden, wird ein _ vor die Hochsprachen-Namen oder zwei _ oder ein. vor die vom Compiler vergebenen Namen gesetzt. gcc: Hochsprachen-Namen bleiben erhalten, gcc nutzt. als Namensprefix für temporäre Namen/Labels. Assembler Unterprogramme 30/43 2008-06-03
Unterprogramme ABI Parameter/Return-Wert Standardmässig übergibt i386-gcc Parameter über den Stack. Parameter werden in Pascal: normaler C/C++: umgekehrter Java: undefinierter Reihenfolge auf dem Stack abgelegt. Return-Werte werden erwartet der i386-gcc in %eax: char-, short-, int-, long-werte und Adressen %edx/%eax: long-long-werte %st(0): float-, double, long-double-werte Strukturen und Arrays können standardmässig nicht übergeben werden! Assembler Unterprogramme 31/43 2008-06-03
Unterprogramme ABI Register ABI regelt für jedes Register, ob aufrufendes Unterprogramm oder aufgerufenes Unterprogramm benutztes Register sichern muss ( caller-saved-regs, callee-saved-regs ). Assembler Unterprogramme 32/43 2008-06-03
Unterprogramme ABI Register i386-standard: Aufrufendes Unterprogramm muss %eax, %ecx und %edx sichern, bevor es weiteres Unterprogramm aufruft. Nach dem Aufruf muss es die Werte wiederherstellen. aufgerufenes Unterprogramm muss %ebx, %edi und %esi sichern, bevor es sie verändert. Vor dem Rücksprung zum Aufrufer müssen Werte restauriert werden. %esp und %ebp werden als Stack-Pointer bzw. Frame-Pointer verwendet. (Natürlich nur, wenn die Register vom Unterprogramm verwendet werden.) Assembler Unterprogramme 33/43 2008-06-03
Unterprogramme ABI Register Beispiel: func : p u s h l %ebx // %ebx muss g e s i c h e r t werden p u s h l %edi // %edi muss g e s i c h e r t werden p u s h l %e s i // %e s i muss g e s i c h e r t werden p u s h l %eax // %eax muss g e s i c h e r t werden p u s h l %ecx // %ecx muss g e s i c h e r t werden p u s h l %edx // %edx muss g e s i c h e r t werden c a l l p opl %edx // %edx w i e d e r h e r s t e l l e n p opl %ecx // %ecx w i e d e r h e r s t e l l e n p opl %eax // %eax w i e d e r h e r s t e l l e n p opl %e s i // %e s i w i e d e r h e r s t e l l e n p opl %edi // %edi w i e d e r h e r s t e l l e n p opl %ebx Assembler // %ebx Unterprogramme w i e34/43 d e r2008-06-03 h e r s t e l l e n r e t
Unterprogramme Verschachtelte Unterprogramme Z.B. Pascal oder C-Erweiterungen erlauben verschachtelte Unterprogramme. Beispiel: i n t main ( ) { i n t y ; i n t f ( i n t x ) { return x y ; } y = ; } return f ( 4 ) ; Assembler Unterprogramme 35/43 2008-06-03
Unterprogramme Verschachtelte Unterprogramme Verschachtelte Unterprogramme haben jeweils Zugriff auf ihre Parameter, ihre lokalen Variablen, die Parameter ihrer übergeordneten Unterprogramm(e), die lokalen Variablen ihrer übergeordneten Unterprogramm(e), globale Variablen. Assembler Unterprogramme 36/43 2008-06-03
Unterprogramme Verschachtelte Unterprogramme Lösung: Als Parameter werden solchen Unterprogrammen Adressen von Stack-Frames höherer Unterprogramme mitgegeben. Über diese Adressen können sie dann auf die Variablen/Parameter ihrer übergeordneten Unterprogramme zugreifen. Das funktioniert ggf. auch rekursiv und/oder mehrstufig. Assembler Unterprogramme 37/43 2008-06-03
Unterprogramme Fehlerbehandlung i n t proc ( ) { i n t e r r o r ; } f o r ( ; ; ) { e r r o r = func1 ( ) ; i f ( e r r o r ) return e r r o r ; e r r o r = func2 ( ) ; i f ( e r r o r ) return e r r o r ; } return 0 ; Probleme: fehleranfällig zu programmieren zeitraubende if-abfragen für (hoffentlich) nie auftretende Fälle Assembler Unterprogramme 38/43 2008-06-03
Unterprogramme Fehlerbehandlung Übersichtlicher, schneller, kürzer (Java): void proc ( ) throws E r r o r { t r y { f o r ( ; ; ) { func1 ( ) ; func2 ( ) ; } } c atch ( E r r o r e r r o r ) { throw e r r o r ; } } Assembler Unterprogramme 39/43 2008-06-03
Unterprogramme Fehlerbehandlung e r r o r _ i p :. l o n g 0 e r r o r _ s p :. l o n g 0 proc :. g l o b l proc p u s h l e r r o r _ i p p u s h l e r r o r _ s p movl $catch, e r r o r _ i p movl %esp, e r r o r _ s p // Try Part movl $0, %eax // No E r r o r c a t c h : p opl e r r o r _ s p p opl e r r o r _ i p cmpl $0, %eax j e no_err movl e r r o r _ s p, %esp // throw e r r o r p u s h l e r r o r _ i p no_err : r e t Assembler Unterprogramme 40/43 2008-06-03
Unterprogramme Fehlerbehandlung Übersichtlicher(?), schneller, kürzer (C, C++): jmpbuf s t a t e ; void proc ( ) { jmpbuf o l d s t a t e = s t a t e ; i n t e r r o r ; } e r r o r = setjmp ( s t a t e ) ; i f ( e r r o r == 0) { f o r ( ; ; ) { func1 ( ) ; func2 ( ) ; } } e l s e { s t a t e = o l d s t a t e ; longjmp ( s t a t e, e r r o r ) ; } Assembler Unterprogramme 41/43 2008-06-03
Unterprogramme Fehlerbehandlung setjmp :. g l o b l setjmp movl 4(%esp ), %edx // A d r e s s e von " s t a t e " movl (%esp ), %eax // Return A d r e s s e movl %eax, (%edx ) l e a 4(%esp ), %eax // Stack P o i n t e r movl %eax, 4(%edx ) movl $0, %eax r e t // No E r r o r longjmp :. g l o b l longjmp movl 8(%esp ), %eax // E r r o r movl 4(%esp ), %edx // A d r e s s e von " s t a t e " movl 4(%edx ), %esp // Stack P o i n t e r p u s h l (%edx ) // Return A d r e s s e r e t Assembler Unterprogramme 42/43 2008-06-03
Unterprogramme Fehlerbehandlung Eigenschaften setjmp/longjmp: keine echten Unterprogramme Unterprogramme mit unvorhersehbarem Kontrollfluss wiederhergestellt werden nur %eip, %esp (und ggf. %ebp) Register-Variablen nicht wiederherstellbar Stack-Variablen behalten aktuellen Wert Assembler Unterprogramme 43/43 2008-06-03