e-mail    Debatní kniha    Mapa stránek    Hlavní  
 INT21h 
 

DOS a chráněný režim - 3. díl

Přišel mi email s dotazem k prvnímu článku o DOSu a chráněném režimu. Tazatel si přeje rychlou a stručnou odpověď, ale pokládá zajímavý dotaz, který je užitečné rozpitvat hlouběji. Následující text se celý týká chráněného režimu Borland pascalu 7. Použíté postupy většinou nebudou fungovat ani v reálném režimu ani ve Freepascalu.

„Dobrý den,
zaujal mě Váš článek na Int21h.cz o chráněném režimu. Měl bych tu otázku ohledně protektu v BP70.
Jak můžu používat mem[segment:offset] v režimu protekt? Myslím, jak si mám alokovat paměť?
Jistě, v protektu můžu alokovat array ale mně by se líp hodilo mem.
Děkuji za krátké vysvětlení.“


Dobrý den!
Děkuji za dotaz. V prvé řadě je třeba poznamenat, že na rozdíl od Freepascalu je v BP7.0 možné pomocí MEM[] přistupovat i k paměti nejen pod 1MB, ale díky 16-bitové adresaci i prostor nad 1MB. Proto si můžeme dovolit toto:
var a:array[0..4095] of byte;
    i:word;
begin
for i:=0 to 4095 do Mem[seg(a):ofs(a)+i]:=0; {provede vynulovani pole}
end.
Hlavní použití MEM[] je ale k přístupu do DOSové paměti pod 1MB. Oproti reálném módu nelze používat běžné segmenty paměti, ale je třeba je "předpřipravit", t.j. udělat z nich selektory.
Např. víme, že na adrese $B800:0000 začíná v textovém módu videopaměť.
V reálném módu můžeme příkazem „MEM[$B800:0]:=42“ zapsat do levého horního rohu znak hvězdička. V protektu bychom ale takhle vyvolali chybu 216. Důvodem je to, že jsme použili "syrový" segment, a ne konvertovaný, t.j. převedený na selektor. Jak tedy převést segment na selektor? BP nám bohužel nepomůže, takže si musíme pomoci sami.
Function Segment_Na_Deskriptor(w:word):word;assembler;
asm
mov ax,2
mov bx,w
int 31h
end;

var w:word;
begin
w:=Segment_Na_Deskriptor($b800);
MEM[w:0]:=42;
end.
Takto už všechno proběhne v pořádku.
Takže jakoukoliv pevně specifikovanou segmentovou adresu, kterou chceme v protektu používat, musíme protáhnout přes funkci Segment_na_Deskriptor.
Všechno až doposud se týkalo přístupu do předem definované oblasti paměti. V některých případech si ale můžeme přát alokovat v DOSovém prostoru vlastní blok paměti, pomocí MEM ho nějak modifikovat a pak ho třeba předat nějakému rezidentu. Alokaci paměti můžeme v zásadě provést třemi způsoby: obrátit se přímo na DOS, obrátit se přímo na DPMI nebo použít funkci GlobalDOSAlloc z jednotky WinAPI. Nejjednodušší je asi ta třetí možnost. Výhodou této varianty totiž je, že rovnou získáme jak SEGMENT požadovaného bloku, tak i SELEKTOR. Nemusíme tudíž manuálně volat funkci Segment_na_dekriptor. A znovu platí, že z našeho protekmódového programu se k realmódovému bloku odkazujeme právě pomocí selektoru. Pokud ale chceme tento blok použít pro realmódový kód běžící mimo náš program, typicky pro funkci nějakého přerušení, tak použijeme naopak SEGMENT. Níže uvedený program by měl být samovysvětlující.
uses winapi,lacrt;
Procedure Nech_DOS_vyplnit_buffer(segm,ofst:word);
(* V REALNEM MODU BYCHOM POUZILI TENTO KOD
asm
push ds          {DS musime ulozit}
push si          {SI take}

mov ax,4700h;    {sluzba $47 - vrat nastavenou cestu}
mov dx,3         {3.disk, t.j. jednotka C:}

mov ds,segm      {segmentova cast adresy bufferu}
mov si,ofst      {offsetova cast adresy bufferu}
int 21h          {JDEM Na TO!}

pop si           {a ted je obnovime}
pop ds           {kdyz to neudelame, tak pocitac zhavaruje}
end;
*)

{V protektu ale musime volat toto DOSove preruseni oklikou, pomoci DPMI
 wrapperu INT31h.

 BP 7.0 sice dovoluje volat vetsinu sluzeb i primo, ale v tomto pripade to
 selhava}

var DPMIregs:record
      EDI,ESI,EBP,Reserved,EBX,EDX,ECX,EAX:Longint;
      Flags,ES,DS,FS,GS,IP,CS,SP,SS:word;
      end;
begin
DPMIregs.EAX:=$4700;
DPMIregs.EDX:=3;
DPMIregs.DS:=segm;
DPMIregs.ESI:=ofst;
asm
push ds
push es
mov ax,300h   {funkce "volej DOSove preruseni"}
mov bx,21h    {budeme na dalku volat INT21h}
mov cx,0      {nebudeme nic predavat na zasobniku}

push ss           {adresa DPMIregs je odvozena od SS, proto musime udelat}
pop es            {ES=SS}
lea di,DPMIregs
int 31h
pop es
pop ds
end;
end;

var i:longint;
    selektor,segment:word;
    b:byte;
    n:word;
    s:string;

begin
i:=GlobalDOSAlloc(1024);  {pripravime buffer}

selektor:=word(i);        {nizsi dva bajty jsou selektor}
segment:=word(i shr 16);  {vyssi dva bajty segment}

{vynulujeme buffer v konvencni pameti pomoci MEM.
 (i kdyz, uprime receno, pro beh programu to neni nutne)}
for n:=0 to 1023 do MEM[selektor:n]:=0;  {pouzijeme selektor}

Nech_DOS_vyplnit_buffer(segment,0);      {pouzijeme segment}

{A ted nejak informaci z DOSovskeho segmentu prectu do normalni promenne}
i:=0;
s:='';
repeat
b:=MEM[selektor:i];        {Znak po znaku buffer prectu a zkopiruju}
if b<>0 then s:=s+char(b);
inc(i);
until b=0;                 {jde o ASCII 0 ukonceny retezec}

writeln('Aktualni cesta na disku "C" je: ',s);
GlobalDosFree(selektor);  {uvolnime pamet}
end.
Zmínku zaslouží jedna zajímavost. Proč jsme volali přerušení INT 21h/47h nepřímo, přes službu DPMI, když víme, že zrovna tuhle funkci zvládá RTM extender Borland pascalu emulovat? Tenhle program přece funguje, tak proč to nepřímé volání?
uses Strings;
var vysledek:string;
    p:pchar;
begin
p:=@vysledek;
asm
mov ah,47h
mov dx,3
lds si,p
int 21h
end;
vysledek:='C:'+StrPas(p)+'';
writeln(vysledek);
readln;
end.
Odpověď je, že BP70 se snaží tak moc být přátelský, že za nás provádí konverzi selektorů na segmenty. BP70 tedy předpokládá, že do DS:SI jsme vložili protektomódovou adresu SELEKTOR:OFFSET a skrytě ještě před skutečným spuštěním přerušení provede konverzi na SEGMENT:OFFSET. Pokud mu tedy dáme realmódovou adresu rovnou, tak je zmaten. Jestliže nicméně borlandímu extenderu opravdu důvěřujeme, tak můžeme udělat to, co od nás očekává, dodat mu SELEKTOR:OFFSET a program následujícím způsobem modifikovat. Tento postup nelze použít vždy, ale v případě INT21h/47h to fungovat bude.
uses winapi,lacrt;

Procedure Nech_DOS_vyplnit_buffer_pres_RTM(sele,ofst:word);assembler;
asm
push ds          {DS musime ulozit}
push si          {SI take}

mov ax,4700h;    {sluzba $47 - vrat nastavenou cestu}
mov dx,3         {3.disk, t.j. jednotka C:}

mov ds,sele      {selektorová cast adresy bufferu}
mov si,ofst      {offsetova cast adresy bufferu}
int 21h          {JDEM Na TO!}

pop si           {a ted je obnovime}
pop ds           {kdyz to neudelame, tak pocitac zhavaruje}
end;

var i:longint;
    selektor,segment:word;
    b:byte;
    n:word;
    s:string;

begin
i:=GlobalDOSAlloc(1024);  {pripravime buffer}

selektor:=word(i);        {nizsi dva bajty jsou selektor}
segment:=word(i shr 16);  {vyssi dva bajty segment}

{vynulujeme buffer v konvencni pameti pomoci MEM.
 (i kdyz, uprime receno, pro beh programu to neni nutne)}
for n:=0 to 1023 do MEM[selektor:n]:=0;  {pouzijeme selektor}

Nech_DOS_vyplnit_buffer_pres_RTM(selektor,0);  {tentokrat i tady pouzijeme selektor}

{A ted nejak informaci z DOSovskeho segmentu prectu do normalni promenne}
i:=0;
s:='';
repeat
b:=mem[selektor:i];
if b<>0 then s:=s+char(b);
inc(i);
until b=0;
writeln('Aktualni cesta na disku "C" je: ',s);
GlobalDosFree(selektor); {uvolnime pamet}              
end.



Pascal - hlavní
Překladače
Vlastní články
Převzaté články
Věci na stáhnutí
Odkazy k tématu
BP7 buglist
Chyba Run-time 200

BASIC