kostenloser Webspace werbefrei: lima-city


Über Adressen von Variablen in Assembler

lima-cityForumProgrammiersprachenSonstige Programmiersprachen

  1. Autor dieses Themas

    toolz

    Kostenloser Webspace von toolz

    toolz hat kostenlosen Webspace.

    Worüber ich mir seltsamerweise noch gar keine Gedanken gemacht habe und auch von keiner Seite her einen Wink erhielt, möchte ich hier eine Frage stellen, die sich an all jene richtet, die etwas fundierteres Grundwissen über Maschinensprachen haben, als ich...
    Zumindest in Assembler ist es ja möglich, Variablen zu deklarieren. Damit meine ich nicht, dass man sich eine Adresse im Speicher sucht und dann dort einen Wert einfügt, sondern ähnlich wie in Hochsprachen tatsächlich einen Bezeichner hat und mittels dem auf die Variable zugreifen kann. Ich habe nun nämlich ein Problem:
    Möchte ich eine Liste in Assembler implementieren, dann geht das nur mithilfe einer Variable, von der ich weiß, was in ihr steht (als Zeiger auf das erste Element). Wenn ich aber wissen will, welchen Wert sie hat, dann muss ich auch wissen, wo sie steht. Würde ich das nun mit Speicheroperationen lösen käme ich in eine gedankliche Endlosschleife - Ich brauche dann wieder eine Variable, die mir zeigt, wo ich nach der nächsten suchen soll. Kurz: Ich brauche einen Fixpunkt (sozusagen eine feste Adresse), den kann ich mir allerdings nicht einfach aussuchen, möglicherweise überschreibe ich dann systemkritische Daten im Arbeitsspeicher. Nun kommen die Variablen ins Spiel. Von ihnen weiß ich, was sie beinhalten.

    Nur wie ist das auf Maschinenebene gelöst? Wo stehen denn die Variablen, die ich deklarieren kann und besonders: Wie kann ich die Adresse einer solchen Variable zur Laufzeit ermitteln?
  2. Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!

    lima-city: Gratis werbefreier Webspace für deine eigene Homepage

  3. Also ich kann dir nicht ganz folgen. Was für eine Art von Liste möchtest du erstellen? Eine Array-artige Liste oder eine verkettete Liste? Und was genauso wichtig ist: Wo willst du diese Liste ablegen? Auf dem Stack? Auf dem Heap? in der BSS oder Data Bereichen?

    Wenn du in deiner Assemblersprache eine Variable definierst, ist diese Variable auch nichts anderes als ein Synonym für ein Speicherfeld. Also ist es egal, ob du jetzt foo oder 0x3508AE4 schreibst. Der Assembler wandelt foo automatisch in letzteres um, sobald du ihn auf deinen Code loslässt.

    Daher: Kannst du bitte dein Problem noch genauer beschreiben? Und welchen Assembler verwendest du?

    Falls du ein Array definieren willst und du den GNU Assembler verwendest, kannst du leicht herausfinden, wie das gemacht wird. Erstelle jeweils eine der folgenden .c Dateien (die wirklich nur aus einer Zeile bestehen) und compiliere sie mit gcc -S meineDatei.c -o meineDatei.s

    Nur deklariertes Array:
    int a[6];


    Array mit Inhalt
    int a[6] = {1,2,3,4,5,6};



    Wenn du Nasm verwendest, dann solltest du die db-Direktive verwenden:
    meineVariable db 1,2,3,4,5,6



    Beitrag zuletzt geändert: 2.1.2012 17:35:39 von bladehunter
  4. hackyourlife

    Moderator Kostenloser Webspace von hackyourlife

    hackyourlife hat kostenlosen Webspace.

    Variablen in Assembler:

    .data
    ein Segment gefüllt mit Daten. im exe steht der anfängliche Inhalt und natürlich die Größe.
    Wenn du irgendwo
    variable db 1
    schreibst landet die 1 im .data-Segment und variable ist die Adresse auf diese 1.

    .bss
    von diesem Segment ist im exe die Größe definiert, der anfängliche Inhalt ist unbekannt.
    Wenn du irgendwo eine Variable so definierst dass sie am Anfang keinen Wert hat muss sie ja nicht im .data-Segment stehen und dort unnötig Platz verbrauchen. Deshalb bekommt so eine Variable eine Adresse im .bss-Segment.

    Systemkritische Daten kannst du unter Windows / Linux / ... nicht überschreiben. Wenn du das auch nur versuchst wird dein Programm mit "Segmentation Fault" (Linux) oder "Acces Violation" (Windows) abgebrochen.
    Das einzige mir bekannte PC-Betriebsystem wo es möglich ist Systemkritische Daten zu überschreiben ist DOS.

    Zur Laufzeit musst/kannst du keine Adresse von Variablen ermitteln, die Adressen werden schon zur Übersetzungszeit festgelegt (vom Assembler).
    Die einzige Ausnahme ist dynamisch belegter Speicher, da ist aber vorher bekannt (Variable) wo die Adresse hingeschrieben wird.

    Jetzt ist es abhängig vom verwendeten Assembler wie du auf die Adresse selbst zugreifen kannst:
    ; MASM (Microsoft Macro Assambler)
    mov eax,offset variable ; lädt die Adresse von "variable" nach eax
    mov eax,variable        ; lädt den Wert von "variable" nach eax
    
    ; FASM (flatassembler)
    mov eax,variable   ; lädt die Adresse von "variable" nach eax
    mov eax,[variable] ; lädt den Wert von "variable" nach eax


    Edit:
    Variablen auf dem Stack (eine Möglichkeit):
    ; erstellen
    push 7  ; variable 2 (ich weiß jetzt nicht mehr wie man dem sagt dass es ein dword ist...)
    push 13 ; variable 1
    mov ebp,esp
    
    ; zugreifen
    mov eax,[ebp]     ; variable 1
    mov ebx,[ebp + 4] ; variable 2
    
    ; und wieder löschen
    sub ebp,8
    mov esp,ebp
    Das hab ich nicht getestet, sollte aber trotzdem funktionieren.


    Beitrag zuletzt geändert: 3.1.2012 10:39:41 von hackyourlife
  5. Autor dieses Themas

    toolz

    Kostenloser Webspace von toolz

    toolz hat kostenlosen Webspace.

    Ja, sowas ähnliches wollte ich hören. Nur:
    Wie ich das jetzt verstanden habe besteht doch immernoch das Problem, dass man ein Programm theoretisch nicht zeitgleich mehrfach laufen lassen könnte, wenn ich in den Quelltext schreibe, an welcher Adresse die Variablen stehen. Die würden alle auf die selbe Variable zugreifen, was sie aber natürlich nicht sollen.
    Das mit der Liste war nur ein Beispiel, bei dem mir obiger Konflikt aufgefallen ist - hängt euch daran nicht auf.
    Bleibt also das Problem, dass ich zumindest eine Variable brauche, von der ich weiß, wo sie steht - auch wenn diese Aufgabe der Assembler für mich übernimmt. In Maschinensprache ist das aber dann nurnoch möglich, wenn dieselbe eine feste Adresse hat?
  6. toolz schrieb:
    Wie ich das jetzt verstanden habe besteht doch immernoch das Problem, dass man ein Programm theoretisch nicht zeitgleich mehrfach laufen lassen könnte, wenn ich in den Quelltext schreibe, an welcher Adresse die Variablen stehen.
    Die würden alle auf die selbe Variable zugreifen, was sie aber natürlich nicht sollen.

    Moderne Betriebssysteme verwenden Virtual Memory. Das bedeutet jeder Prozess kriegt seinen eigenen Speicherbereich zugewiesen und für den Prozess sieht es aus, als würde er alles kontrollieren. Adresse 0x42 in Prozess A ist also ganz woanders als Adresse 0x42 in Prozess B.

    Beantwortet das deine eigentliche Frage?
  7. Hallo toolz,

    die Adressen, die der Assembler in Dein Binary (Object-File, ausführbare Datei, etc.) schreibt, sind im allgemeinen Offsets, welche die Position relativ zum EIP-Zeiger (aktuelle Position im Programm) angeben. D.h. es handelt sich nicht um absolute Adressen im (virtuellen) Arbeitsspeicher.
    Ausnahmen sind Sachen wie z.B.
    mov eax, [ebp+4]    ;relativ zu EBP
    oder
    mov eax, [ebx]    ;absolute Adresse in EBX
    Wenn man die absolute Adresse einer Variable haben will, dann muss man sie mit dem LEA-Befehl berechnen:
    lea eax, variable    ;absolute Adresse von variable nach eax
    Das benötigt man z.B. immer dann, wenn man den Zeiger an eine Funktion übergeben will.
  8. darkpandemic schrieb:
    Hallo toolz,

    die Adressen, die der Assembler in Dein Binary (Object-File, ausführbare Datei, etc.) schreibt, sind im allgemeinen Offsets, welche die Position relativ zum EIP-Zeiger (aktuelle Position im Programm) angeben.

    Jetzt bin ich etwas verwirrt. Bist du dir sicher, dass es das EIP Register ist und nicht etwas anderes? Weil der EIP Zeiger ändert sich ja mit jeder ausgeführten Instruktion und wenn jetzt die Varible foo an der relativen(!) Position 0x42 wäre, dann wäre das nach der nächsten Instruktion nicht mehr richtig, da EIP sich weiterbewegt hat.

    Allgemeine Anmerkung: Ansonsten kann man meines Wissens benannte Variablen nur in der DATA oder BSS Region ablegen. Also gewissermaßen globale Variablen. Bei einer Variable auf dem Stack oder auf dem Heap lässt sich ja schlecht vorhersehen, wo sie liegen wird. Und das hat Darkpandemic auch gut für den Stack illustriert, wo man das EBP-Register als Bezugspunkt für die Position von lokalen (namenlosen) Variablen nimmt.
  9. hackyourlife

    Moderator Kostenloser Webspace von hackyourlife

    hackyourlife hat kostenlosen Webspace.

    darkpandemic schrieb:
    die Adressen, die der Assembler in Dein Binary (Object-File, ausführbare Datei, etc.) schreibt, sind im allgemeinen Offsets, welche die Position relativ zum EIP-Zeiger (aktuelle Position im Programm) angeben.
    Das stimmt sicher nicht.
    Grund:
    Der Direkte Zugriff auf EIP ist nicht möglich, es ist nur eine indirekte Veränderung durch Spünge möglich


    toolz schrieb:
    Wie ich das jetzt verstanden habe besteht doch immernoch das Problem, dass man ein Programm theoretisch nicht zeitgleich mehrfach laufen lassen könnte, wenn ich in den Quelltext schreibe, an welcher Adresse die Variablen stehen. Die würden alle auf die selbe Variable zugreifen, was sie aber natürlich nicht sollen.
    Nicht einmal unter DOS stimmt das, da dort die Register CS,DS,SS jeweils wo anders hin zeigen (können). Und das obwohl DOS eigentlich kein Multitasking schafft ;-).
    bladehunter schrieb:
    Moderne Betriebssysteme verwenden Virtual Memory. Das bedeutet jeder Prozess kriegt seinen eigenen Speicherbereich zugewiesen und für den Prozess sieht es aus, als würde er alles kontrollieren. Adresse 0x42 in Prozess A ist also ganz woanders als Adresse 0x42 in Prozess B.
    Stimmt. Es ist deshalb auch unnötig auf sagen wir mal EIP oder sonst etwas bezug zu nehmen, da jeder Prozess sowieso einen eigenen Speicher hat.


    Um wieder zurück zur Behauptung von darkpandemic zu kommen:
    Die Segmentregister gibt es immer noch. So zeigt CS auf das Codesegment, DS auf das Datensegment und SS auf das Stacksegment. Jetzt wird (auf die Art wie unter DOS) über [ds:offset] auf die Variable zugegriffen. Das merkt nur normalerweise niemand weil der Assembler aus mov eax,[variable] automatisch mov eax,[ds:variable] macht.

    Beitrag zuletzt geändert: 3.1.2012 10:59:04 von hackyourlife
  10. Hallo miteinander,

    da habe ich mir tatsächlich einen Patzer erlaubt. Die Offsets sind tatsächlich relativ zu EDS und nicht EIP (da habe ich an Short Jumps gedacht und mich verleiten lassen).
    Allerdings wäre innerhalb einer Übersetzungseinheit auch eine Adressierung relativ zu EIP kein Problem da, der Assembler ja weiß, wo er die Daten im Modul abgelegt hat. D.h. er könnte den relativen Abstand zwischen EIP und der Variable berechnen.
  11. hackyourlife

    Moderator Kostenloser Webspace von hackyourlife

    hackyourlife hat kostenlosen Webspace.

    darkpandemic schrieb:
    Allerdings wäre innerhalb einer Übersetzungseinheit auch eine Adressierung relativ zu EIP kein Problem da, der Assembler ja weiß, wo er die Daten im Modul abgelegt hat. D.h. er könnte den relativen Abstand zwischen EIP und der Variable berechnen.
    Was dann aber wieder in einer statischen Adresse resultieren würde.
    Abgesehen davon ist die »Idee« mit EIP doch recht sinnlos... ;-)

    Das Einzige in die Richtung was ich mal gesehen habe:
    push	0
    		call	_t02
    		db	"little test",0
    _t02:		call	_t01
    		db	"MessageBox without imports, funny eh?",0
    _t01:		push	0
    		call	[ebp+_MessageBox]			; messagebox
    Dabei wird sozusagen eine String-Konstante im Code definiert und per
    call
    wird dann EIP auf den Stack gelegt. Damit hast du dann eine Variable an der Adresse [EDS:EIP - Stringlänge] gespeichert. Vollständiger Code hier (Achtung, die meisten Virenscanner interpretieren das exe (was ihr selber übersetzen müsst) als Virus!).
    Dazu muss man folgendes wissen:
    call adresse
    wird intern so abgearbeitet:
    push eip + befehlslänge von "call" ; = Adresse des nächsten Befehls
    jmp adresse

    :biggrin: :biggrin: :biggrin:
  12. Hallo hackyourlife,

    Adressierungen relativ zu EIP gibt es ja bei bei Sprüngen. Wenn ein Sprungziel relativ nahe am Ausgangspunkt liegt, dann kann ein solcher Befehl als Opcode durchaus nur zwei Byte groß sein.
    0x75 0x14 heißt z.B. wenn ZF=0 dann EIP = EIP+20.
    Diese Adresse ist letztenendes natürlich wieder statisch aber keineswegs absolut, da sie unabhängig davon ist, wo das Programm im Arbeitsspeicher liegt. Das spart in erster Linie Speicherplatz, da man den Offset in nur einem Byte kodieren kann.
    Bei Daten kann einen Adressierung relativ zu EIP auch Sinn ergeben. Und bei manchen Compilern habe ich den Eindruck (sicher weiß ich es nicht), dass diese auch so etwas ähnliches machen.
    Wenn die Daten, die an einer bestimmten Stelle im Programm benötigt werden, möglichst nahe an dieser Codestelle abgelegt sind, dann ist die Wahrscheinlichkeit sehr hoch, dass sich diese noch im Cache befinden und nicht aus dem Arbeitsspeicher geladen werden müssen. Das kann dann Einfluß auf die Performance haben.
    Von daher wäre es zumindest nicht verkehrt, wenn es Opcodes für MOV und ähnliches gäbe, welche diesem Umstand Rechnung tragen.
  13. hackyourlife

    Moderator Kostenloser Webspace von hackyourlife

    hackyourlife hat kostenlosen Webspace.

    darkpandemic schrieb:
    Adressierungen relativ zu EIP gibt es ja bei bei Sprüngen.
    Und eben nur dort.
    darkpandemic schrieb:
    Wenn die Daten, die an einer bestimmten Stelle im Programm benötigt werden, möglichst nahe an dieser Codestelle abgelegt sind, dann ist die Wahrscheinlichkeit sehr hoch, dass sich diese noch im Cache befinden und nicht aus dem Arbeitsspeicher geladen werden müssen. Das kann dann Einfluß auf die Performance haben.
    Von daher wäre es zumindest nicht verkehrt, wenn es Opcodes für MOV und ähnliches gäbe, welche diesem Umstand Rechnung tragen.
    So kannst du aber im Normalfall nur konstanten speichern, da im Normalfall das Codesegment als "readable" und nicht als "writeable" markiert ist.
    Opcodes für "MOV und ähnliches" mit relativer Adressierung zu EIP gibt es nicht (jedenfall bis zum 80486 nicht...).

    Der einzige mir bekannte Befehl der vom Prozessor aus dazu benutzt werden kann relativ zu EIP zu speichern ist das (was ich schon mal geschrieben hab):
    ; wie man relativ zu EIP speichert
    		call _string:
    		db 'hallo',0
    _string:	pop eax
    		; jetzt steht in eax ein Pointer auf 'hallo'
    
    		; das gleiche funktioniert auch mit Variablen:
    		call _variable
    		db 1
    		db 2
    _variable:	pop eax
    		mov dl,[eax]
    		mov dh,[eax + 1]
    		; jetzt steht in DL 1 und in DH 2


    Damit du dich selbst überzeugen kannst das es KEINE relative Adressierung zu EIP gibt:
    MOV - Move Byte or Word
            Usage:  MOV     dest,src
            Modifies flags: None
            Copies byte or word from the source operand to the destination
            operand.  If the destination is SS interrupts are disabled except
            on early buggy 808x CPUs.  Some CPUs disable interrupts if the
            destination is any of the segment registers
                                     Clocks                 Size
            Operands         808x  286   386   486          Bytes
    
            reg,reg           2     2     2     1             2
            mem,reg          9+EA   3     2     1            2-4  (W88=13+EA)
            reg,mem          8+EA   5     4     1            2-4  (W88=12+EA)
            mem,immed       10+EA   3     2     1            3-6  (W88=14+EA)
    
            reg,immed         4     2     2     1            2-3
            mem,accum         10    3     2     1             3   (W88=14)
            accum,mem         10    5     4     1             3   (W88=14)
            segreg,reg16      2     2     2     3             2
            segreg,mem16     8+EA   5     5     9            2-4  (W88=12+EA)
    
            reg16,segreg      2     2     2     3             2
            mem16,segreg     9+EA   3     2     3            2-4  (W88=13+EA)
            reg32,CR0/CR2/CR3 -     -     6     4
            CR0,reg32         -     -     10    16
            CR2,reg32         -     -     4     4             3
    
            CR3,reg32         -     -     5     4             3
            reg32,DR0/DR1/DR2/DR3   -     22   10             3
            reg32,DR6/DR7     -     -     22   10             3
            DR0/DR1/DR2/DR3,reg32   -     22   11             3
            DR6/DR7,reg32     -     -     16   11             3
    
            reg32,TR6/TR7     -     -     12    4             3
            TR6/TR7,reg32     -     -     12    4             3
            reg32,TR3                           3
            TR3,reg32                           6
            - when the 386 special registers are used all operands are 32 bits
    
            88 / r MOV r/m8,r8 Move r8 to r/m8
    
            89 / r MOV r/m16,r16 Move r16 to r/m16
            89 / r MOV r/m32,r32 Move r32 to r/m32
            8A / r MOV r8,r/m8 Move r/m8 to r8
            8B / r MOV r16,r/m16 Move r/m16 to r16
            8B / r MOV r32,r/m32 Move r/m32 to r32
            8C / r MOV r/m16,Sreg** Move segment register to r/m16
    
            8E / r MOV Sreg,r/m16** Move r/m16 to segment register
            A0 MOV AL, moffs8* Move byte at ( seg:offset) to AL
            A1 MOV AX, moffs16* Move word at ( seg:offset) to AX
            A1 MOV EAX, moffs32* Move doubleword at ( seg:offset) to EAX
            A2 MOV moffs8*,AL Move AL to ( seg:offset)
            A3 MOV moffs16*,AX Move AX to ( seg:offset)
            A3 MOV moffs32*,EAX Move EAX to ( seg:offset)
    
            B0+ rb MOV r8,imm8 Move imm8 to r8
            B8+ rw MOV r16,imm16 Move imm16 to r16
            B8+ rd MOV r32,imm32 Move imm32 to r32
            C6 / 0 MOV r/m8,imm8 Move imm8 to r/m8
            C7 / 0 MOV r/m16,imm16 Move imm16 to r/m16
            C7 / 0 MOV r/m32,imm32 Move imm32 to r/m32
    
            0F 22 / r MOV CR0, r32 Move r32 to CR0
            0F 22 / r MOV CR2, r32 Move r32 to CR2
            0F 22 / r MOV CR3, r32 Move r32 to CR3
            0F 22 / r MOV CR4, r32 Move r32 to CR4
            0F 20 / r MOV r32,CR0 Move CR0 to r32
            0F 20 / r MOV r32,CR2 Move CR2 to r32
    
            0F 20 / r MOV r32,CR3 Move CR3 to r32
            0F 20 / r MOV r32,CR4 Move CR4 to r32
            0F 21/ r MOV r32, DR0-DR7 Move debug register to r32
            0F 23 / r MOV DR0-DR7, r32 Move r32 to debug register
  14. Hallo hackyourlife,

    ich habe doch schon in meine zweiten Posting meinen Fehler aus dem ersten Posing eingeräumt. Und ich habe doch nirgendwo anders behauptet, dass es abgesehen von Short Jumps eine Adressierung relativ zu EIP gibt.
    Ich habe nur ein Gedankenexperiment gemacht, das zeigen sollte, dass es, sofern es auf die Einsparung von ein paar Bytes ankommt, durchaus Sinn ergeben könnte, EIP-relative Opcodes zu haben. (Wenn ich den Konjunktiv verwende, dann denke ich mir dabei meistens auch etwas ;-))
  15. Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!

    lima-city: Gratis werbefreier Webspace für deine eigene Homepage

Dir gefällt dieses Thema?

Über lima-city

Login zum Webhosting ohne Werbung!