Hi,
hier der offizielle Thread zum Aufgabenblatt 4, welches ab heute abend zum Download bereit stehen sollte.
Hier wie versprochen noch mal ein Kommentar zum Code von Skript S. 174. Da es gut zum Aufgabenblatt 4 passt, poste ich es hier und mache keinen Extra Thread auf.
Kommentare in der Form (a) > (b) © zeigen den Momentanen Zustand des Stacks an - zuletzt wurde c gepusht, davor b, davor a. Der TOS (Top Of Stack) ist also rechts. Das ">" Zeichen zeigt an, wohin %ebp zeigt. ra ist die return adress.
rfact: ; (x) (ra)
Beim Eintritt in die Funktion liegen x und die rae auf dem Stack (das hat der Caller besorgt). ebp zeigt noch auf nichts definiertes.
pushl %ebp ; (x) (ra) (old %ebp)
Der erste Befehl sichert den alten Inhalt von %ebp auf den Stack.
movl %esp, %ebp ; (x) (ra) > (old %ebp)
Dann wird %ebp = %esp zugewiesen, womit %ebp auf dan zuletzt gepushten Wert zeigt (nämlich das alte %ebp), symbolisiert durch das ">" Zeichen. Da jeder Stackeintrag 4 Byte gross ist, lässt sich folgendermassen auf die Elemente zugreifen:
(%ebp) -> old %ebp
4(%ebp) -> ra
8(%ebp) -> x
pushl %ebx ; (x) (ra) > (old %ebp) (old %ebx)
Nun sichern wir das Register %ebx, weil wir es im Verlauf der Funktion benutzen wollen und der Callee laut Konvention für das Sichern von %ebx verantwortlich ist. Der Zugriff wäre nun:
-4(%ebp) -> old %ebx
(%ebp) -> old %ebp
4(%ebp) -> ra
8(%ebp) -> x
movl 8(%ebp), %ebx ; das Argument der Funktion nach %ebx laden
cmpl $1, %ebx ; ist die übergebene Zahl <=1 ?
jle .L78 ; ...dann springe zu L78
Warum der Vergleich mit 1? Weil wir die Fakultät von x<=1 kennen, sie ist 1. Ich glaube, das nennt man Rekursionsverankerung…?
leal -1(%ebx), %eax ; neues x ist altes x-1
pushl %eax ; (x) (ra) > (old %ebp) (old %ebx) (new x)
call rfact ; (x) (ra) > (old %ebp) (old %ebx) (new x) (new ra)
imull %ebx, %eax ; x! = (x-1)! * x
jmp .L79
Dies ist der rekursive Aufruf: Wir berechnen die Fakultät von x, indem wir die Fakultät von x-1 ausrechnen und mit x multiplizieren.
.align 4 ; an durch 4 teilbaren Adressen ausgerichtete Labels
; ...bringen Performancevorteile beim Springen
.L78: ; hier wird hingesprungen, falls x<=1 war
movl $1, %eax ; x! = 1 für x<=1
.L79: ; (x) (ra) > (old %ebp) (old %ebx) (new x)
movl -4(%ebp), %ebx ; altes %ebx wieder herstellen
movl %ebp, %esp ; (x) (ra) > (old %ebp)
popl %ebp ; (x) > (ra)
ret ; (x)
Die Zeilen ab .L79 hätte man imho auch durch
.L79 ; (x) (ra) > (old %ebp) (old %ebx) (new x)
popl %ebx ; (x) (ra) > (old %ebp) (old %ebx)
popl %ebx ; (x) (ra) > (old %ebp)
popl %ebp ; (x) (ra)
ret
ersetzen können. Any comments?
kann mir jemand folgendes erklären:
Da im bisher eingef¨uhrten Befehlssatz f ¨ ur unseren x86-Prozessor noch kein Befehl f ¨ ur die Division
enthalten ist, n¨ahern wie den Umrechnungsfaktor 5/9 durch den Wert 5/9 142/256 an, der
sich zum Beispiel mit einer Multiplikation und einem Rechts-Shift effizient umsetzen l asst.
was genau is da mit der Multiplikation und einem Rechts-Shift gemeint? kann mir das bitte wer erklären wie ich das umsetzen soll?
danke
kann mir noch mal jemand
pushl %ebx ; (x) (ra) > (old %ebp) (old %ebx)
Nun sichern wir das Register %ebx, weil wir es im Verlauf der Funktion benutzen wollen und der Callee laut Konvention für das Sichern von %ebx verantwortlich ist. Der Zugriff wäre nun: -4(%ebp) -> old %ebx (%ebp) -> old %ebp 4(%ebp) -> ra 8(%ebp) -> x
movl 8(%ebp), %ebx ; das Argument der Funktion nach %ebx laden
cmpl $1, %ebx ; ist die übergebene Zahl <=1 ?
jle .L78 ; …dann springe zu L78
Warum der Vergleich mit 1? Weil wir die Fakultät von x<=1 kennen, sie ist 1. Ich glaube, das nennt man Rekursionsverankerung…?
erklären? warum genau bzw wie kommt man auf movl 8(%ebp), %ebx
was genau is da mit der Multiplikation und einem Rechts-Shift gemeint?
Die Division ist bei den meisten Prozessoren entweder deutlich langsamer als Multiplikation (und erst recht als einfache Operationen wie Addition oder Bitshifts), oder sogar gar nicht erst vorhanden.
Deshalb bedient man sich eines Tricks: wenn man durch eine Zahl dividieren will, multipliziert man mit dem Kehrbruch (kennt Ihr aus der Schule). Den Kehrbruch richten wir jetzt so ein, dass der Nenner eine Zweierpotenz ist, weil das Teilen durch Zweierpotenzen mit Shiften nach rechts sehr effizient realisiert werden kann (T1 Stoff?).
Wenn Du also konkrekt a * (142/256) rechnen sollst, dann sagst Du der CPU, dass sie a mit 142 multiplizieren soll (dafür gibts den imull Befehl), und anschliessend shiftest Du das Ergebnis um 8 nach rechts (sarl $8, reg), denn 2^8 = 256. Klar?
warum genau bzw wie kommt man auf movl 8(%ebp), %ebx
Der Caller übergibt dem Callee die Argumente auf dem Stack. Das erste (und in diesem Fall das einzige) Argument wird zuletzt gepusht und hätte sofort danach die Adresse (%esp). Der anschliessende call-Befehl pusht aber noch die Rücksprungadresse auf den Stack, deswegen zeigt (%esp) jetzt auf die Rücksprungadresse und 4(%esp) auf das erste Argument. Der Callee selbst pusht nochmal %ebp auf den Stack, weswegen (%esp) jetzt %ebp enthält, 4(%esp) die Rücksprungadresse und 8(%esp) das erste Argument. Danach wird %ebp = %esp zugewiesen, so dass wir mit 8(%ebp) auf das erste Argument zugreifen können. Jeder weitere push ändert jetzt nichts mehr an dieser Adressierung, weil %ebp ja nicht von push beeinflusst wird.
http://tams-www.informatik.uni-hamburg.de/lehre/ws2003/vorlesungen/t3/aufgaben/Aufgabenblatt_04.pdfIch hab so meine Probleme mit Aufgabe 2:
"Schreiben Sie x86-Assemblercode fuer eine Funktion […], die […] ihren Rueckgabewert wie in der Vorlesung erlaeutert auf dem Stack uebergibt."
Ich mag mich taeuschen, aber haben wir in der Vorlesung was anderes als Ruckgabe ueber eax besprochen? Wie soll das ablaufen?
"Nach Ausfuehrung der Funktion sollen alle Datenregister (eax, ebx, …) wieder ihren vorherigen Wert enthalten"
Warum soll ich auch die Register, die eindeutig als caller-save festgelegt sind (eax, edx, ecx), sichern? Wenn der Aufrufer sie noch braucht, hat er sie selber zu sichern, wenn er das nicht tut, ist das nicht meine Schuld…
"Schreiben Sie x86-Assemblercode fuer eine Funktion […], die […] ihren Rueckgabewert wie in der Vorlesung erlaeutert auf dem Stack uebergibt."
pushaddr "%d\n"
pushl grad_fahrenheit
pushl 0 ; Hier kommt der Rückgabewert rein
call celsius
nop ; branch delay ;-) Es lebe die SPARC!
call printf
Hmmm, der Code erinnert mich ein wenig an Postscript. Aber ich glaube, in Turbo Pascal wurde das so gemacht, wenigstens für Records als Rückgabewerte.
low_level
Ne, der Prototyp der Funktion ist ja vorgegeben. Und das waere dann ja void celsius(int*, int) statt int celsius(int).
Ne, der Prototyp der Funktion ist ja vorgegeben. Und das waere dann ja void celsius(int*, int) statt int celsius(int).
Nein, int * auch nciht, denn dann wäre aufm Stack ein Pointer auf die Speicheradresse. Ich galube C hat keine notation dafür [img]
http://unimatix.sternenvolk.de/gfx/28.gif[/img]
Ich mag mich taeuschen, aber haben wir in der Vorlesung was anderes als Ruckgabe ueber eax besprochen?
Nein. Und die gängigen Calling Conventions (cdecl, stdcall, fastcall) arbeiten auch alle mit eax als Rückgabewert.
Warum soll ich auch die Register, die eindeutig als caller-save festgelegt sind (eax, edx, ecx), sichern?
Schreib 1-2 Sätze diesbezüglich zu Deiner Lösung. Studenten, die sich Gedanken machen, sind immer gern gesehen ;-)
Ne, der Prototyp der Funktion ist ja vorgegeben. Und das waere dann ja void celsius(int*, int) statt int celsius(int).
Ja und? Was sagt der Prototyp über die Implementation aus? garnichts.
Gegenbeweise mit Zitat aus dem C-Standard nehme ich gerne entgegen.
Außerdem: Das soll keine C-Funktion sein, sondern eine Assemblerfunktion. Und da ist das mit den Calling Conventions ja nicht ganz so standardisiert …
low_level
Für alle Studenten, die bei mir abgeben: ich akzeptiere auf jeden Fall Lösungen, die sich an das Skript halten, sprich: Rückgabewert in eax, Sichern nur von ebx, esi und edi.
Hallo!
Kann mir mal einer nen Tipp zu Aufgabe 1 geben???
Kann man denn den %eip aufm Stack zwischenspeichern?
Oder was gibt es sonst für Möglichkeiten an den ranzukommen?
Antje
Bei uns in der Uebungsgruppe gabs den Tip, sich mal zu ueberlegen, welche Befehle ueberhaupt irgendwas mit eip machen:
CALL, RET, JMP, Jcc (also die conditional jumps) - mehr hatten wir noch gar nicht, oder?
War dir das ne große Hilfe? Hab jedenfalls noch nicht die zündende Idee…
War dir das ne große Hilfe?
Kann ich nicht sein, ich wusste es vorher schon [img]
http://unimatix.sternenvolk.de/gfx/22.gif[/img]
Aber ueberleg Dir mal, was _genau_ die 4 Befehle unten machen. Einer davon kopiert eip irgendwo anders hin, wo man dann drauf zugreifen kann.
bei Aufgabe 4.3.1
welche C-syntax is dort gemeint?
da steht zwar C-Bibliothek <string.h>
aber wo soll die stehen??
Das braucht nirgends stehen. Das soll nur verdeutlichen, um was für eine Funktion für C-Kenner sich es handelt. Was da von Syntax-gesprochen wird, heißt, dass sobald die Zeichenkette vorbei ist, steht einfach eine dicke fette 0 im Speicher. Daran kann den Code dann erkennen, wo die Zeichenkette vorbei ist.
kann mir noch mal wer folgende zeilen genauer erklären?
so zeile für zeile.
.L79: ; (x) (ra) > (old %ebp) (old %ebx) (new x)
movl -4(%ebp), %ebx ; altes %ebx wieder herstellen
movl %ebp, %esp ; (x) (ra) > (old %ebp)
popl %ebp ; (x) > (ra)
ret ; (x)
Bei uns in der Uebungsgruppe gabs den Tip, sich mal zu ueberlegen, welche Befehle ueberhaupt irgendwas mit eip machen:
CALL, RET, JMP, Jcc (also die conditional jumps) - mehr hatten wir noch gar nicht, oder?
Entschuldigt, aber weiss jemand, wo Befehle wie CALL, RET, JMP, Jcc näher beschrieben werden? Ich meine in Bezug auf %eip finde ich nicht viel.
Im Skript ist das ja (wenn überhaupt) alles mehr schlecht als recht dargestellt, und irgendwie fehlt mir da gerade was, wo das ein bischen exakter erklärt wird.
Gab es vielleicht in der Vorlesung noch Material, was ich irgendwie verpennt habe? Ich meine nur Anhand des Skriptes finde ich das ziemlich schwierig…
Gruß Dosenwein
Entschuldigt, aber weiss jemand, wo Befehle wie CALL, RET, JMP, Jcc näher beschrieben werden? Ich meine in Bezug auf %eip finde ich nicht viel.
Im Skript ist das ja (wenn überhaupt) alles mehr schlecht als recht dargestellt, und irgendwie fehlt mir da gerade was, wo das ein bischen exakter erklärt wird.
Gab es vielleicht in der Vorlesung noch Material, was ich irgendwie verpennt habe? Ich meine nur Anhand des Skriptes finde ich das ziemlich schwierig…
Im zweiten Intel-Manual:
http://www.intel.com/design/pentium4/manuals/stehts ganz genau drin [img]
http://unimatix.sternenvolk.de/gfx/28.gif[/img]
Im zweiten Intel-Manual:
http://www.intel.com/design/pentium4/manuals/
stehts ganz genau drin [img]http://unimatix.sternenvolk.de/gfx/28.gif[/img]
Und Du meinst wirklich, das versteht jemand?
Im zweiten Intel-Manual:
http://www.intel.com/design/pentium4/manuals/
stehts ganz genau drin [img]http://unimatix.sternenvolk.de/gfx/28.gif[/img]
Und Du meinst wirklich, das versteht jemand?
Wenn man sich auf den in der Vorlesung behandelte near jumps/calls/rets beschränkt, ja.
hallo,
kann mir wer noch mal genauer erklären wie ich aufgabe 4.3.1
angehen soll?
Man nehme einen Zeiger, geht mit dem Zeichen fuer Zeichen durch den String, bis man das Stringende (=0) findet. Waehrenddessen zaehlt man mit, oder man bestimmt am Ende den Abstand zwischen Stringende und Stringanfang.
Im zweiten Intel-Manual:
http://www.intel.com/design/pentium4/manuals/
Naja, ich denke das ist wirklich SEHR ausführlich. Aber bestimmt für manche Dinge nützlicher als dieses tolle Skript.
Da stehen die Sachen manchmal so komprimert drauf, das man sich das Skript auch sparen könnte. Mal ganz zu schweigen davon, das die Skript-Sprache so gehackt ist, das man manchmal 10 Minuten nachdenken muss, bis man darauf kommt, was der Autor vielleicht damit gemeint hat. [img]
http://unimatix.sternenvolk.de/gfx/17.gif[/img]
Man nehme einen Zeiger, geht mit dem Zeichen fuer Zeichen durch den String, bis man das Stringende (=0) findet. Waehrenddessen zaehlt man mit, oder man bestimmt am Ende den Abstand zwischen Stringende und Stringanfang.
Wie man nehme einen Zeiger? Sollen wir das Programm etwa in C schreiben, und das dann mit gcc in einen Assembler-Code umwandeln?? Dann könnte ich mit nem Zeiger auch was anfangen…
Man nehme einen Zeiger, geht mit dem Zeichen fuer Zeichen durch den String, bis man das Stringende (=0) findet. Waehrenddessen zaehlt man mit, oder man bestimmt am Ende den Abstand zwischen Stringende und Stringanfang.
Wie man nehme einen Zeiger? Sollen wir das Programm etwa in C schreiben, und das dann mit gcc in einen Assembler-Code umwandeln?? Dann könnte ich mit nem Zeiger auch was anfangen…
Wenn du eien Speicheradresse in ein Register packst, hast du auch einen Zeiger. Und eine Speicheradresse bekommst du ja als Parameter übergeben.
War dir das ne große Hilfe? Hab jedenfalls noch nicht die zündende Idee…
Was unterscheidet denn den Call Befehl von dem Jump Befehl?
kann mir noch mal wer folgende zeilen genauer erklären?
so zeile für zeile.
Hier nochmal die aktuellen Werte auf dem Stack und ihre Indizes relativ zu %ebp in Bytes:
8 4 0 -4 -8
.L79: ; (x) (ra) > (old %ebp) (old %ebx) (new x)
Zur besseren Veranschaulichung ein vertikaler Stackaufbau und die Adressierung:
8(%ebp) x (argument pushed by caller)
4(%ebp) ra (return address pushed by CALL operation)
(%ebp) old %ebp (saved by callee)
-4(%ebp) old %ebx (saved by callee)
-8(%ebp) new x (argument pushed by caller and already processed by callee)
movl -4(%ebp), %ebx ; altes %ebx wieder herstellen
Also im Kommentar steht ja eigentlich schon alles - wir hatten am Anfang der Funktion ebx auf den Stack gepusht und danach verändert, also müssen wir den alten Wert wieder herstellen.
movl %ebp, %esp ; (x) (ra) > (old %ebp)
Hier wird der Stackpointer wieder restauriert - den hatten wir am Anfang der Funktion in ebp gesichert und anschliessend durch den pushl %ebx und "pushl new x" verändert.
popl %ebp ; (x) > (ra)
Hier wird das alte ebp, welches wir zu Beginn der Funktion gesichert hatten, wieder hergestellt.
ret ; (x)
Das Unterprogramm wird beendet, indem die return adress vom Stack geholt und dort hin gesprungen wird.
Entschuldigt, aber weiss jemand, wo Befehle wie CALL, RET, JMP, Jcc näher beschrieben werden? Ich meine in Bezug auf %eip finde ich nicht viel.
Such mal bei google oder hier im UniMatiX nach [edit]
Hm, ist vielleicht etwas überdimensionert. Wie wäre es mit
http://ivs.cs.uni-magdeburg.de/bs/lehre/sose99/bs1/seminare/assembler.shtmlAuch hier gilt natürlich wieder: Vorsicht INTEL Syntax.
moinsen,
das bei Aufgabe 4.3.x den String begrenzende Nullzeichen..ist "vier Byte lang" der Wert 0 oder wie soll man sich das vorstellen ?
Danke ;)
// edit Slater-Übersetzung: Das ist ja alles nicht so einfach.
Knuffe Euch und hab Euch lieb
Was will uns das genau sagen? Ich glaube solche ausbrüche kannst du sinnvoller posten
cat > /dev/null
<hier der wutausbruch>
^D
[img]
http://unimatix.sternenvolk.de/gfx/24.gif[/img]