Über Adressen von Variablen in Assembler
lima-city → Forum → Programmiersprachen → Sonstige Programmiersprachen
adresse
arbeitsspeicher
array
assembler
byte
code
datei
datum
inhalt
liste
offset
position
problem
programm
register
segment
speichern
url
variable zugreifen
zeiger
-
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? -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage
-
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 -
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
schreibst landet die 1 im .data-Segment und variable ist die Adresse auf diese 1.variable db 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):
Das hab ich nicht getestet, sollte aber trotzdem funktionieren.; 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
Beitrag zuletzt geändert: 3.1.2012 10:39:41 von hackyourlife -
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? -
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? -
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.
odermov eax, [ebp+4] ;relativ zu EBP
Wenn man die absolute Adresse einer Variable haben will, dann muss man sie mit dem LEA-Befehl berechnen:mov eax, [ebx] ;absolute Adresse in EBX
Das benötigt man z.B. immer dann, wenn man den Zeiger an eine Funktion übergeben will.lea eax, variable ;absolute Adresse von variable nach eax
-
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. -
darkpandemic schrieb:
Das stimmt sicher nicht.
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.
Grund:Der Direkte Zugriff auf EIP ist nicht möglich, es ist nur eine indirekte Veränderung durch Spünge möglich
toolz schrieb:
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 .
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.
bladehunter schrieb:
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.
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.
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 -
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. -
darkpandemic schrieb:
Was dann aber wieder in einer statischen Adresse resultieren würde.
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.
Abgesehen davon ist die »Idee« mit EIP doch recht sinnlos...
Das Einzige in die Richtung was ich mal gesehen habe:
Dabei wird sozusagen eine String-Konstante im Code definiert und perpush 0 call _t02 db "little test",0 _t02: call _t01 db "MessageBox without imports, funny eh?",0 _t01: push 0 call [ebp+_MessageBox] ; messagebox
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!).call
Dazu muss man folgendes wissen:
wird intern so abgearbeitet:call adresse
push eip + befehlslänge von "call" ; = Adresse des nächsten Befehls jmp adresse
-
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.
-
darkpandemic schrieb:
Und eben nur dort.
Adressierungen relativ zu EIP gibt es ja bei bei Sprüngen.
darkpandemic schrieb:
So kannst du aber im Normalfall nur konstanten speichern, da im Normalfall das Codesegment als "readable" und nicht als "writeable" markiert ist.
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.
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
-
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 ) -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage