Anfängerfrage



  • Hi an Alle !
    Ich bin noch relativ neu in Assembler und schon tauchen bei mir die ersten Fragen auf. ^^
    Ich hab erst neulich einen Artikel gelesen, in dem es darum ging, wie C-Funktionen intern aufgebaut sind. Eben wie der C/C++-Compiler den geschriebenen Code in Maschinensprache übersetzt. Dazu wurde der Grub Debugger (kurz: gdb) verwendet. Ich kopiere euch den disassemblierten Auszug einer C-Funktion einfach mal hier rein, solltet ihr nichts dagegen haben:

    pushl %ebp
    movl %esp, %ebp
    subl $0x4, %esp
    movl $0x5, 0xfffffffc(%ebp)
    movl 0xffffffffc(%ebp), %eax
    movl %ebp, %esp
    popl %ebp
    ret
    

    Ich hab übrigens hier den Auszug des gdb abgetippt, also wundert euch nicht, dass die zugehörigen Adressen und die "<main+3>" z.B. fehlen! (oder kurz: ich war zu Faul jede einzelne Zeile komplett abzutippen)
    Wie ihr seht subtrahiert die Anweisung Nummero 3 (subl $0x4, %esp) vom Stackpointer 4 Bytes um Platz für eine Variable des Typs 'int' zu machen. In der Darauffolgenden Zeile wird dieser neu angelegten Variable auf dem Stack der Wert 0x5 zugewiesen. So jetzt zu meiner Frage (ach ja sollte es mal wieder länger dauern - und damit meine ich meinen Schreibstil *g* - schnappt euch ein Snickers ^^)

    Wie würde denn letztere Zeile in Wirklichkeit lauten ? Der gdb liefert mir die Adresse ja nur relativ zum Register %ebp (damit meine ich 0xfffffffc = -4 dezimal). Normalerweise würde da doch eine exakte 32-Bit Adresse stehen. Was aber wenn diese Adresse zur Laufzeit des Programmes schon belegt ist, entweder von einem anderen Programm oder... sucht euch was aus :p evtl. sogar vom Betriebssystem selbst!! Wie kann der C-Compiler, bzw.,der Linker einfach eine feste Adresse in den Maschinencode schreiben ? Oder adressiert er sie auch nur relativ zum %ebp ?
    Selbst bei 'einfachsten' Sprüngen im Code ist die Zieladresse eindeutig festgelegt ? Wenn ja, so müsste doch das Programm jedes Mal an die selbe Adresse im Speicher geladen werden !

    Bitte klärt mich auf! Aber hinsichtlich meiner Frage, das andere weiß ich schon 😉

    Danke für's Zuhören
    Euer "Newbie" nennt man das glaube ich.

    Schönen Gruß an Alle



  • __decl (BlutigerAnfänger) schrieb:

    Wie würde denn letztere Zeile in Wirklichkeit lauten? Der gdb liefert mir die Adresse ja nur relativ zum Register %ebp (damit meine ich 0xfffffffc = -4 dezimal). Normalerweise würde da doch eine exakte 32-Bit Adresse stehen.

    Wie kommst du zu dieser irrigen Annahme?
    Zuerstmal kannst du bei einem auch nur halbwegs brauchbaren Disassembler schon davon ausgehen, dass er dir recht genau das zeigt, was auch in kodierter Form in deinem Programm steht.
    Neben der direkten Adressierung, gibt es auch die Adressierung ueber Register.
    Dh. die CPU nimmt sich tatsaechlich den Wert in ebp und zieht 4 ab, um die Speicheradresse zu bilden.

    __decl (BlutigerAnfänger) schrieb:

    Was aber wenn diese Adresse zur Laufzeit des Programmes schon belegt ist, entweder von einem anderen Programm oder... sucht euch was aus :p evtl. sogar vom Betriebssystem selbst!! Wie kann der C-Compiler, bzw.,der Linker einfach eine feste Adresse in den Maschinencode schreiben ? Oder adressiert er sie auch nur relativ zum %ebp ?

    Siehe oben - ich denke mal, damit fallen die uebrigen Fragen flach. Ein freies Plaetzchen fuer den Stack wird idR. vom Betriebssystem geliefert. Da kannst du davon ausgehen, dass nichts anderes wichtiges drinsteht.

    __decl (BlutigerAnfänger) schrieb:

    Selbst bei 'einfachsten' Sprüngen im Code ist die Zieladresse eindeutig festgelegt ? Wenn ja, so müsste doch das Programm jedes Mal an die selbe Adresse im Speicher geladen werden !

    Einfache Spruenge haben immer relative Adressen.
    Fixe Adressen werden je nach verwendetem Dateiformat entweder in einer Tabelle vermerkt und beim Laden des Programms vom Betriebssystem entsprechend angepasst, oder das OS laedt das Programm (wie AFAIK in Windows) tatsaechlich immer an die gleiche Adresse (paging bei der Speicheradressierung macht's moeglich, dass mehrere Programme von sich aus gesehen an der "gleichen Adresse" im Speicher liegen).

    __decl (BlutigerAnfänger) schrieb:

    Bitte klärt mich auf! Aber hinsichtlich meiner Frage, das andere weiß ich schon 😉

    Danke für's Zuhören
    Euer "Newbie" nennt man das glaube ich.

    Schönen Gruß an Alle

    hth. 🙂



  • Dh. die CPU nimmt sich tatsaechlich den Wert in ebp und zieht 4 ab, um die Speicheradresse zu bilden.

    Wie kann ich folglich der CPU klarmachen, dass es sich um einen relativen Wert zum %ebp handelt ? Muss das "Anziehen von 4" nicht explizit im Code stehen (mit subl) ?

    Ein freies Plaetzchen fuer den Stack wird idR. vom Betriebssystem geliefert.

    Dann nehme ich einmal an, 'geliefert' heißt, dass sie den %esp vorbelegt bevor das Programm gestartet wird. Gibt es im protected mode heißt er glaube ich auch noch die Adressierung über ss:sp oder dann eben ss:esp oder so ? Oder reicht da der %esp aus ?

    Einfache Spruenge haben immer relative Adressen.

    Ahm ich bin ja neu in Assembler. Habe aber noch nie einen relativen Sprungbefehl gesehen, tut mir leid! Wie würde der denn aussehen ? Oder lass es mich so formulieren: relativ zu welchem Register ?

    In diesem Sinne Ciao oder bis bald ? *bg*
    Danke für die Beantwortung meiner Fragen!
    Schönen Gruß



  • Ah entschuldige vielmals!
    Das war wirklich blöd was ich da geschrieben habe:

    Gibt es im protected mode heißt er glaube ich auch noch die Adressierung über ss:sp oder dann eben ss:esp oder so ? Oder reicht da der %esp aus ?

    Manchmal sollte man erst nachdenken bevor man labert. Oder wenn man keine Ahnung hat einfach mal die Fresse halten. 😃 (damit meine ich mich)
    Also vergesst bitte den Müll den ich da geschrieben habe.

    Aber das andere, insbesondere ob Windows den %esp vorbelegt, würde mich trotzdem interessieren.
    Ich habe nämlich k.A. wie es sonst funzen könnte.

    DANKE



  • __decl(BlutigerAnfänger) schrieb:

    Dh. die CPU nimmt sich tatsaechlich den Wert in ebp und zieht 4 ab, um die Speicheradresse zu bilden.

    Wie kann ich folglich der CPU klarmachen, dass es sich um einen relativen Wert zum %ebp handelt ? Muss das "Anziehen von 4" nicht explizit im Code stehen (mit subl) ?

    Nein. Nach Intel-Syntax kannst du zB. auch einfach [ebp - 4] schreiben.
    Darf ich dich dazu eben nochmal selbst zitieren?:

    __decl(BlutigerAnfänger) schrieb:

    movl $0x5, 0xfffffffc(%ebp)
    

    [...]
    Der gdb liefert mir die Adresse ja nur relativ zum Register %ebp (damit meine ich 0xfffffffc = -4 dezimal)

    Da hast du doch die Adresse relativ zu ebp (also ebp - 4), und genau so versteht die CPU das auch.

    __decl(BlutigerAnfänger) schrieb:

    Ein freies Plaetzchen fuer den Stack wird idR. vom Betriebssystem geliefert.

    Dann nehme ich einmal an, 'geliefert' heißt, dass sie den %esp vorbelegt bevor das Programm gestartet wird.

    Genau.

    __decl(BlutigerAnfänger) schrieb:

    Einfache Spruenge haben immer relative Adressen.

    Ahm ich bin ja neu in Assembler. Habe aber noch nie einen relativen Sprungbefehl gesehen, tut mir leid! Wie würde der denn aussehen ? Oder lass es mich so formulieren: relativ zu welchem Register ?

    Die Spruenge sind relativ zur Adresse, an der sie stehen.
    Spruenge ueber Register zielen immer auf ein absolutes Offset.

    __decl(BlutigerAnfänger) schrieb:

    In diesem Sinne Ciao oder bis bald ? *bg*
    Danke für die Beantwortung meiner Fragen!

    NP. 🙂



  • Hi ich bins nochmal, Nobuo!

    Wollte eigentlich nur noch DANKE sagen :p
    Wie alt bist du eigentlich ? Hast du Assembler studiert oder warum - ich meine das ist ja der Hammer wie gut du dich damit auskennst!

    Ich bin nun 17 und habe jetzt nach Visual Basic (ja ich weiß, die 'Spielzeugsprache', aber ich mochte sie sehr) und C++ letztendlich zu Assembler gefunden.
    Ich war nie zufrieden damit, Quelltext in C++ einzutippen, von dem ich zwar wusste, was er bezwecken würde, aber ich k.A. davon hatte wie das ganze intern umgesetzt wird. Also habe ich zu guter Letzt nun Assembler angepackt und versuche mich mit den ganzen Artikeln und Dokumentationen im Internet durchzuschlagen ohne je ein Buch darüber gelesen zu haben. Was wohl ein Fehler war.

    Natürlich bekomme ich gerade deswegen meine Wissenslücken zu spüren.
    Vieles erscheint mir logisch, doch es sind die Sprünge zwischen den Artikeln, die mir Sorgen bereiten. Das Fehlen einer Überleitung und die dadurch resultierende Unsicherheit. Meist unbedeutende, kleine Fragen, die nach einer Antwort verlangen.
    Es ist schön ein Forum zu haben, in dem man seine Fragen stellen kann.
    Und es ist schön, wenn sich freundliche Menschen dazu bereit erklären, den Anderen auch einmal zu helfen.

    DANKESCHÖN!
    AUF WIEDERSEHN!



  • __decl(BlutigerAnfänger) schrieb:

    Hi ich bins nochmal, Nobuo!

    Wollte eigentlich nur noch DANKE sagen :p

    Tja, bitte. 🙂

    __decl(BlutigerAnfänger) schrieb:

    Wie alt bist du eigentlich ?

    Hm, inzwischen auf jeden Fall schon ein paar Jaehrchen volljährig (war ich noch nicht, als ich mich auf diesem Board registriert habe).

    __decl(BlutigerAnfänger) schrieb:

    Hast du Assembler studiert oder warum - ich meine das ist ja der Hammer wie gut du dich damit auskennst!

    Naja, geht so...
    Zu meinen x86-Assemblerkenntnissen hat das TI-Studium bisher gar nichts beigetragen. Ist alles in ~6 Jahren (ui, rueckwirkend betrachtet schon ziemlich lang her... 😮 ) Programmierpraxis (aka. Frickeleien) erworbenes Halbwissen. 😃
    Mehr steht noch auf meiner HP, kannst ja mal nen Blick riskieren: http://BTM.homeip.net/ (jaja, etwas Werbung... ;D)

    __decl(BlutigerAnfänger) schrieb:

    Ich bin nun 17 und habe jetzt nach Visual Basic (ja ich weiß, die 'Spielzeugsprache', aber ich mochte sie sehr) und C++ letztendlich zu Assembler gefunden.
    Ich war nie zufrieden damit, Quelltext in C++ einzutippen, von dem ich zwar wusste, was er bezwecken würde, aber ich k.A. davon hatte wie das ganze intern umgesetzt wird. Also habe ich zu guter Letzt nun Assembler angepackt und versuche mich mit den ganzen Artikeln und Dokumentationen im Internet durchzuschlagen ohne je ein Buch darüber gelesen zu haben. Was wohl ein Fehler war.

    VB ist doch ganz brauchbar fuer den Einstieg, IMHO - hab' mit QBasic angefangen. :p

    Jeder lernt eben anders... Ich habe bis heute auch noch kein einziges Buch ueber Assembler gelesen. Mit Beispielcodes, Dokumentationen und Referenzen lerne ich halt besser (nicht nur auf Assembler beschraenkt).

    __decl(BlutigerAnfänger) schrieb:

    Es ist schön ein Forum zu haben, in dem man seine Fragen stellen kann.
    Und es ist schön, wenn sich freundliche Menschen dazu bereit erklären, den Anderen auch einmal zu helfen.

    Jo, ich geb' mir zumindest Muehe, und es tummeln sich hier oefters auch noch einige andere hilfsbereite und kompetente Assembler-Experten. 🙂

    __decl(BlutigerAnfänger) schrieb:

    AUF WIEDERSEHN!

    Jo, wuerde mich freuen. 👍



  • ... ich schau hier grad mal vorbei und kann vielleicht noch nachträglich etwas zum allgemeinen Verständnis beitragen (die Ursprungsfrage sieht nämlich irgendwie nach dem üblichen Framepointer-Thema aus):

    (1) Alle lokalen Variablen und aktuellen Parameter werden ja bekannterweise auf dem Stack abgelegt.

    (2) Return-Programpointer natürlich ebenfalls.

    (3) Zusätzlich wird bei Hochsprachen nach allgemeiner Compilerbau-Theorie und -Praxis i.d.R. noch zusätzlich ein sogenannter Framepointer benutzt. Dieser Framepointer zeigt auf den Anfang (oder alternativ Ende) des Bereichs im Stack, wo die lokalen Variablen im Stack liegen. Jetzt kann der Compiler alle Funktions-lokalen Variablen/Parameter relativ zu diesem Framepointer adressieren.

    (4) Der Framepointer wird selbst im Stack abgelegt, und zwar wird jedesmal beim Aufruf/Eintritt in eine neue Funktion der alte Framepointer auf den Stack gepackt. Hierdurch gibt es eine Rückwärtsverkettung entgegengesetzt zur aktuellen Aufrufhierarchie. Im Prinzip könnte man hierüber auch auf lokale Variablen der zuletzt aufgerufenen Funktion zugreifen (und der darüber...). Wichtig ist der Framepointer aber insbesondere auch für folgende Eigenschaft:

    Man kann beim Aufruf einer Funktion auch mehr (oder weniger) aktuelle Parameter mitgeben als im Funktionskopf als formale Parameter definiert sind. Dies wird bei Non-ANSI-C, bei manchen Systemaufrufen und im Zusammenhang mit varargs (z.B. beim printf) genutzt. Nur auf diese Art bekommt der Compiler zur Compile-Zeit es richtig hin, dass der Stack anschließend wieder richtig aufgeräumt wird. Man denke mal genau nach: (a) Die Anzahl der Parameter ist unterschiedlich, (b) beim Aufruf werden n Push-Operationen für die aktuellen Parameter gemacht, (c) beim Verlassen der Funktion baut der Compiler m Pop-Operationen für die formalen Parameter ein. => Das geht folglich schief, weil m != n und der Stackpointer an völlig falscher Position ist.
    Wie gesagt, auch hierfür sorgt der Framepointer für ein vernünftiges Aufräumen, weil er auf den Anfang der lokalen Variablen/Parameter zeigt (muss man sich in etwa als Anfangsadresse der jeweils ersten Push-Operation vorstellen). Man macht jetzt keine Pop-Operationen mehr für die lokalen Variablen/Parameter, sondern versetzt nur den Stackpointer auf den Wert des aktuellen Framepointers und hat auf einen Schlag alles aufgeräumt.

    Das ist nicht ganz einfach zu kapieren, weil hier natürlich auch nur ganz grob skizziert, und wäre mit einer Zeichnung natürlich wesentlich einfacher zu erklären, aber vielleicht klappt's auch bei genügend Nachdenken. Erklärt ist es sicherlich in entsprechenden Compilerbau-Büchern.
    Richtig kapieren (und dauerhaft inhalieren) tut man das allerdings erst, wenn man selber mal einen Compiler gebaut hat... 😉

    Viel Spaß beim Grübeln
    Jox

    P.S.: An Stelle eines Framepointers gibt es auch eine andere Methode, die aber unüblich ist. Aber das ist ein anderes Thema und trägt sonst nur zur Verwirrung bei...


Log in to reply