Speicher direkt lesen / Schreiben

Diskussion zum Thema Programmierung unter DOS (Intel x86)
Antworten
Benutzeravatar
Ms Dos 1.0
MemMaker-Benutzer
Beiträge: 68
Registriert: Di 7. Nov 2006, 23:22
Wohnort: NRW
Kontaktdaten:

Speicher direkt lesen / Schreiben

Beitrag von Ms Dos 1.0 »

Moin Moin,

Ich bin eigentlich eher nen Power / Quick Basic coder aber atm möchte ich mal wieder bisel mit C rumspielen.

ich versuche derzeit unter C direkt den speicher wie unter basic per DEF SEGMENT PEEK & POKE direkt zu lesen / schreiben.

unter C finde ich dazu nur nicht viele infos... das auslesen von Offset & Segment klappt mit der Function die ich im Netz gefunden habe schon ganz gut

Code: Alles auswählen

unsigned char memread (unsigned adh, unsigned adl, unsigned n)
#include <stdio.h>
#include <dos.h>
{
   char far* zeiger=0;
   adl= adl+n;
   zeiger = (char far *) MK_FP(adh, adl);
   return(*zeiger);
}

int main()
{
unsigned a;
a = memread(0x0000,0x01,0);
return 0;
}
nur ne Function zum schreiben habe ich noch nicht gefunden.

kann mir da jemand weiter helfen?

mfg.
Das leben ist ein scheiss Game, aber mit einer verdammt guten Grafik ;)
Benutzeravatar
Ms Dos 1.0
MemMaker-Benutzer
Beiträge: 68
Registriert: Di 7. Nov 2006, 23:22
Wohnort: NRW
Kontaktdaten:

Re: Speicher direkt lesen / Schreiben

Beitrag von Ms Dos 1.0 »

So, da mir hier leider keiner helfen konnte, hab ich mal meine Bücher und die include header datein durchgeschaut und nun eine möglichkeit gefunden.

ich wollt es erst nicht glauben aber Turbo C kann auch Peek und Poke :)
unter MSC funzt das so leider nicht (zumindest nicht bei der Version die ich habe)

Code: Alles auswählen

#include  <stdio.h>
#include <dos.h>

void memwrite(unsigned segment, unsigned offset, unsigned char byte)
{
	pokeb(segment,offset,byte);
}

unsigned char memread(unsigned segment, unsigned offset)
{
	return peekb(segment,offset);
}

int main()
{
  unsigned char byte;
  memwrite(0x0000,0x10,0x11)
  byte = memread(0x0000,0x10)
  
  prinft("Wert: %i\n",byte);
  return 0;
}
so und dann gibt es noch eine möglichkeit mit inline ASM die ich im netz gefunden habe die bei mir leider nicht geht da mein Compiler bei inlineasm leider fehler wirft.

Code: Alles auswählen

// memwrite()
// schreibt ein Byte zu der RAM-Speicherzelle, die durch ihre
// Segment:Offsetadresse in adh:adl angegeben ist.

void memwrite(unsigned adh, unsigned adl, unsigned char val)
{
      asm{ mov BX, adh
           mov ES,BX
           mov BX, adl
           mov AH, val
           mov ES:[BX], AH }
}

// memread()
// Liest von der Segment:Offsetadresse adh:adl ein Byte
// Rückgabe das gelesene Byte
// Hinweis:
// Es werden drei Varianten von memread() angeboten, die alle drei
// gleiches tun und zeigen wie ein Zeiger aufgebaut ist.
// Variante 1 basiert auf einer Assemblerprogrammierung
// Variante 2 basiert auf der Benutzung von MK_FP()
// Variante 3 basiert auf der Zeigerarithmetik

unsigned char memread (unsigned adh, unsigned adl, unsigned n)
{
      asm{ mov BX, adh
           mov ES,BX
           mov BX, adl
           add BX, n
           mov AH, ES:[BX] }
      return(_AH);
}
mfg :)
Das leben ist ein scheiss Game, aber mit einer verdammt guten Grafik ;)
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: Speicher direkt lesen / Schreiben

Beitrag von freecrac »

Ms Dos 1.0 hat geschrieben:... dann gibt es noch eine möglichkeit mit inline ASM die ich im netz gefunden habe die bei mir leider nicht geht da mein Compiler bei inlineasm leider fehler wirft.
Mhm, das ist doof. Hierbei kann ich nicht helfen.

Code: Alles auswählen

// memwrite()
// schreibt ein Byte zu der RAM-Speicherzelle, die durch ihre
// Segment:Offsetadresse in adh:adl angegeben ist.

void memwrite(unsigned adh, unsigned adl, unsigned char val)
{
      asm{ mov BX, adh
           mov ES,BX
           mov BX, adl
           mov AH, val
           mov ES:[BX], AH }
}

// memread()
// Liest von der Segment:Offsetadresse adh:adl ein Byte
// Rückgabe das gelesene Byte
// Hinweis:
// Es werden drei Varianten von memread() angeboten, die alle drei
// gleiches tun und zeigen wie ein Zeiger aufgebaut ist.
// Variante 1 basiert auf einer Assemblerprogrammierung
// Variante 2 basiert auf der Benutzung von MK_FP()
// Variante 3 basiert auf der Zeigerarithmetik

unsigned char memread (unsigned adh, unsigned adl, unsigned n)
{
      asm{ mov BX, adh
           mov ES,BX
           mov BX, adl
           add BX, n
           mov AH, ES:[BX] }
      return(_AH);
}
mfg :)
Anstelle von BX als Offset-Adressregister kann man auch SI, DI, oder wie hier bei der Verwendung eines Segment-Overide-Prefixes(ES:) auch BP als Offset-Adressregister verwenden.
Anstelle des ES-Segmentregisters kann man auch das DS-Segmentregister, oder ab 80386er über das FS- oder über das GS-Segmentregister adressieren.
Ohne Angabe eines Segment-Overide-Prefixes wird das DS-Segmentregister verwendet. (Ausnahme: BP und SP sind dem SS-Segmentregister zugeordnet.)

Beispiele für Schreibzugriffe in den Ram:
mov AX, adh ; Segmentadresse
mov DS, AX ; ins DS-Segmentregister schreiben
mov SI, adl ;Offsetadresse
mov AL, val ; Wert
mov [SI], AL ; nach DS:SI schreiben

oder

mov CX, adh ; Segmentadresse
mov FS, CX ; ins FS-Segmentregister schreiben
mov BP, adl ; Offsetadresse
mov CH, val ; Wert
mov FS:[BP], CH ; nach FS:BP schreiben

Wenn man 32Bit-Register verwendet kann man jedes Allzweckregister als Offsetregister verwenden:
mov BX, adh ; Segmentadresse
mov ES, BX ; ins ES-Segmentregister schreiben
mov EAX, adl ; Offsetadresse
mov BL, val ; Wert
mov ES:[EAX], BL ; nach ES:EAX schreiben

Offsetregister lassen sich auch kombinieren:
mov ES:[SI+DI], BL
mov ES:[EAX+ECX*4], BL ; Offset-Adresse bildet sich aus: EAX + (ECX * 4)
mov ES:[EAX+1000h], BL

So eine Addition und Multiplikation zur Adressberechnung erfolgt schneller als mit dem ADD-Befehl und/oder dem MUL-Befehl.

Für String-Operationen gibt es noch andere Befehle um auf Daten im Ram zuzugreifen. Hierfür gibt es eine Festlegung auf bestimme Register.
Zum Lesen verwendet man DS:SI mit dem lodsb/lodsw/lodsd -Befehl(für bytes, word und doppelword) und zum Schreiben verwendet man ES:DI mit dem stosb/stosw/stosd -Befehl jeweils zusammen mit dem AL/AX/EAX-Register.
Hierbei wird z.B. von der Adresse in DS:SI mit lodsb ein Byte in das AL-Register geladen und SI wird je nachdem ob das Direktion-Flag gesetzt ist entweder verringert oder wenn nicht gesetzt erhöht.
Daneben kann man auch Bytes/Word/DWord kopieren mit dem movsb/movsw/movsd -Befehl.
Beipiel zum Lesen:
cld ; clear D-Flag
mov AX, adh
mov DS, AX
mov SI, adl
lodsb

Danach befindet sich im AL-Register der Inhalt aus der Adresse in DS:SI.
Beispiel zum Schreiben:
cld
mov AX, adh
mov ES, AX
mov DI, adl
mov AL, val
stosb

Beispiel zum Kopieren:
cld
mov AX, adh1 ; Quelle
mov DS, AX
mov SI, adl1
mov AX, adh2 ; Ziel
mov ES, AX
mov DI, adl2
movsb ; SI und DI wird in Abhängigkeit vom Direktion-Flag jeweils erhöht oder verringert.

Kombiniert man das mit dem Repeat-Behehl (rep) können so große Bereiche in einem Durchgang verarbeitet werden.
Dafür läd man zu Beginn in das CX-Register die Anzahl der Wiederholungen.
cld
... ; Laden der Segment und Offsetregister
...
mov AL, val
rep stosb ; Schreibt nach ES:DI den Inhalt von AL so oft wie im CX-Register angegeben wurde. Bei jedem Schritt wird DI erhöht und CX um eins veringert.
...
rep movsb ; Kopiert von DS:SI nach ES:DI soviele Bytes wie im CX-Register angegeben wurde. Bei jedem Schritt wird SI und DI erhöht und CX um eins veringert.

.................................

Zum Schluss mal ein praktisches Beispiel mit Schreibzugriff auf den VGA-Textbildschirm:
mov ax, 0B800h ; Segment-Adresse
mov es, ax
xor di, di ; Offset 0
mov cx, (80*25*2)/4 ; Anzahl
mov eax, 0F200F20h ; Farbe, ASCII, Farbe, ASCII
rep stosd

Optimierung für moderne x86:
Wenn bei einem Register nach einem Schreibzugriff darauf unmittelbar danach gleich ein Lesezugriff darauf erfolgt, dann gibt es eine Verzögerung.
So ist es ggf. sinnvoll die Befehle so umzustellen das keine Abhängigkeiten entstehen und man andere Befehle dazwischen plaziert. So eine gemischte Reihenfolge läßt sich dann schneller verarbeiten.
Auch wird der Code in Blöcken geladen, so das man so ein Block auch mit (unterschiedlichen) NOP-Befehle auffüllen kann und die Opcodes mit verscidener Anzahl an Bytes sich immer vollständig in so einem Block befinden und nicht auf zwei Blöcke verteilt sind.

Noch mehr Verzögerung bekommt man wenn man z.B. auf zwei oder vier Bytes im RAM gleichzeitig zugreifen möchte und sich die Adresse aber nicht durch zwei oder vier teilen läßt.
So ist darauf zu achten das die Ram-Zugriffe bestenfalls an einer geraden, oder durch 4 teilbaren Adresse vollzogen werden, wenn ein Word- oder DWord- Zugriff erfolgen soll.

Dirk
Benutzeravatar
Ms Dos 1.0
MemMaker-Benutzer
Beiträge: 68
Registriert: Di 7. Nov 2006, 23:22
Wohnort: NRW
Kontaktdaten:

Re: Speicher direkt lesen / Schreiben

Beitrag von Ms Dos 1.0 »

freecrac hat geschrieben:Anstelle von BX als Offset-Adressregister kann man auch SI, DI, oder wie hier bei der Verwendung eines Segment-Overide-Prefixes(ES:) auch BP als Offset-Adressregister verwenden.
Anstelle des ES-Segmentregisters kann man auch das DS-Segmentregister, oder ab 80386er über das FS- oder über das GS-Segmentregister adressieren.
Ohne Angabe eines Segment-Overide-Prefixes wird das DS-Segmentregister verwendet. (Ausnahme: BP und SP sind dem SS-Segmentregister zugeordnet.)
hm.. das ist ja verwirrend, ist das bedingt durch den entwicklungsweg den die x86 zurückgelegt hat das es da mehrere Möglichkeiten gibt? oder ist das von anfangan so gewollte das man mehrere Adressregister hat?

Ich meine, für mich ist das nicht ganz so Tragisch, da ich ASM selten verwende, und mein Skill im bestenfall dem eines Anfängers gleicht in ASM.

Aber unabhänig davon, versuche ich schon seit nen paar Tagen den Internen Timer aus zu lesen, dieser gibt nen DWORD zurück, das ganze hol ich mir mit dem Interrupt 1Ah und der Funktion 00h diese liefert aber das ergebniss logischerweise in 2 Registern CX und DX zurück, womit ich dann 2 Int werte hätte.

Wie wandle ich die dann eigentlich zum DWORD dann um.

darüber hab ich leider noch nichts in meinen Büchern und I-Net gefunden...

mfg.
Das leben ist ein scheiss Game, aber mit einer verdammt guten Grafik ;)
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: Speicher direkt lesen / Schreiben

Beitrag von freecrac »

Ms Dos 1.0 hat geschrieben:
freecrac hat geschrieben:Anstelle von BX als Offset-Adressregister kann man auch SI, DI, oder wie hier bei der Verwendung eines Segment-Overide-Prefixes(ES:) auch BP als Offset-Adressregister verwenden.
Anstelle des ES-Segmentregisters kann man auch das DS-Segmentregister, oder ab 80386er über das FS- oder über das GS-Segmentregister adressieren.
Ohne Angabe eines Segment-Overide-Prefixes wird das DS-Segmentregister verwendet. (Ausnahme: BP und SP sind dem SS-Segmentregister zugeordnet.)
hm.. das ist ja verwirrend, ist das bedingt durch den entwicklungsweg den die x86 zurückgelegt hat das es da mehrere Möglichkeiten gibt? oder ist das von anfangan so gewollte das man mehrere Adressregister hat?
Ja das denke ich schon das man von Anfang an es so haben wollte, denn es ist einfach praktischer wenn man mehrere Adressregister hat.
Man hat eigentlich immer zu wenig Register, so das man entweder den Inhalt eines Registers auf den Stack schieben muss um ein Register freizubekommen,
oder man speichert den Inhalt an eine bestimmte Speicherstelle, um dann auch andere Werte in das freie Register einladen und verarbeiten zu können.
Anschliessend wenn dieser Vorgang beendet ist, dann läd man den vorherigen Wert wieder in das Register um damit weiter zu arbeiten.
Mit der Entwicklung des 80368 kamen dann zwei weitere Segmentregister(FS und GS) hinzu und auch neue Adressierungsmöglichkeiten,
so das man jedes auf 32Bit erweiterte Allzweckregister als Offsetregister verwenden kann. Ab Pentium MMX kamen acht 64 bittige MMX-Register dazu
und dafür auch neue Integer-Befehle welche die langsameren FPU-Befehle erstzen sollen. Es folgten danach die XMM-Register die 128 bittig sind.
Mit dem 64Bit-CPUs kamen dann noch weitere 8 Allzweckregister dazu.

Im Vergleich zum C64er mit nur drei 8Bit-Register war das damals wirklich ein Krampf damit zu programmieren und so wurde ständig der Inhalt der Register
zwischen gespeichert bzw. hin und her geladen um damit überhaupt arbeiten zu können, weil man oft 16Bit-Werte verarbeiten muss und mit einem
8 Bitregister das dann nur in zwei Schritten erledigen kann in dem erst das Lowbyte und dann das Highbyte bearbeitet.
Ich meine, für mich ist das nicht ganz so Tragisch, da ich ASM selten verwende, und mein Skill im bestenfall dem eines Anfängers gleicht in ASM.
Im Umfeld haben wir es hierbei mit verschiedenen Registern, Adressierungsarten und Befehlen zu tun, dessen Wirkungsweise sich auf die Register, den Speicher
und das Flagregister auswirken, so das bestimmte Ergebnisse ausgewertet und zur Programmsteuerung verwendet werden. Je nach Ergebniss einer Operation
werden bestimmte Zustände hierbei im Flagregister signalisiert, so das Verzweigungen die darauf ausgelegt sind im wesentlichen den Verlauf eines Programms damit abbilden.
Aber unabhänig davon, versuche ich schon seit nen paar Tagen den Internen Timer aus zu lesen, dieser gibt nen DWORD zurück, das ganze hol ich mir mit dem Interrupt 1Ah und der Funktion 00h diese liefert aber das ergebniss logischerweise in 2 Registern CX und DX zurück, womit ich dann 2 Int werte hätte.

Wie wandle ich die dann eigentlich zum DWORD dann um.

darüber hab ich leider noch nichts in meinen Büchern und I-Net gefunden...

mfg.
Eine Möglichkeit besteht darin den höheren 16 Bit-Wert der sich im unteren Teil eines 32Bit-Registers befindet nach oben in das High-Word zu verschieben, um dann den niedrigeren Wert in das Low-Word des Registers zu laden.
shl ecx, 16 ; shift left
mov cx, dx
Ungeachtet welche Register man letztendlich dafür verwendet würde ich diese Variante vorziehen, da alle anderen Möglichkeiten die es hierbei gibt etwas langsamer sind.
(Einen direkten Zugriff mit dem MOV-Befehl nur auf ein High-Word eines 32 Bit-Registers gibt es nicht.)

...

Ich weiss es nicht was du eigentlich machen möchtest, etwa nur die Zeit auslesen und darstellen, oder auch andere Dinge damit steuern.
In der Vergangenheit habe ich meistens den Timer-Interrupt(IRQ 8) auf eine eigene Interrupt-Routine verbogen um einen Zeitintervall für bestimmte Aufgaben zu bekommen. Z.B um damit einen eigenen blinkenden Cursor zu zeichnen.
Innerhalb der eigenen Interrupt-Routine wird dann z.B. eine Speichstelle als Flag benutzt um zu signalisieren das etwas ausgeführt werden soll. In meiner Hauptroutine wird dann dieses Flag ausgewertet und entsprechend verzweigt.
Danach wird das Flag wieder zurückgesetzt, so das der Timerinterrupt im nächsten Durchlauf es erneut wieder setzen kann.
Zum Abschluss dieser eigenen Interrupt-Routine springe ich dann mit einen Jump-Far zur ursprüglichen Interrupt-Routine weiter, damit die dortigen Dinge auch weiterhin ausgeführt werden.

mov ax, DATEN
mov ds, ax
xor ax, ax
mov es, ax

;---auf neue IRQ-Routine legen----
cli
mov ebx, DWORD PTR es:[8*4] ; alten Vektor retten
mov DWORD PTR[ALTVEC], ebx
mov cs:DWORD PTR[OLDVEC], ebx
mov es:[8*4], OFFSET NEUVEC
mov es:[8*4+2], cs
sti

; Die folgende Hauptroutine die das FLag auswertet und den Cursor zeichnet bzw. die darunter sich befindenden Zeichen invertiert ist doch etwas zu lang und würde den Rahmen sprengen und daher lasse ich diesen Teil mal weg.

;-----------Zum Abschluss wieder restaurieren.
cli
mov ebx, DWORD PTR[ALTVEC]
mov DWORD PTR es:[IrqVec], ebx
sti

:----------------------- Subroutinen-----------------
NEUVEC: inc BYTE PTR[CURFLAG+1]
cmp BYTE PTR[CURFLAG+1], 7 ; Cursor_Speed
jb short NOHIT
mov BYTE PTR[CURFLAG+1], 0
xor BYTE PTR[CURFLAG], 1 ; Aktiv-Flag umkehren
NOHIT: DB 0EAh ; jmp far
OLDVEC DD 0

;-----------------------Datenbereich--------------
CURFLAG DB 0, 0, 0, 0
ALTVEC DD 0

Dirk
Antworten