0887 - MS-Dos - Od środka cz.2

Na czym polega różnica między plikami .COM i .EXE ? Dlaczego programy są przesunięte względem początku segmentu o 256 bajtów ? Co sprawia, że programy są przesuwalne, że mogą być wykonywane na dowolnej pozycji w pamięci ?

Przy ładowaniu do pamięci dowolnego programu - z wyjątkiem programów sterujących urządzenia (ang. device drivers) - system operacyjny MS-DOS rezerwuje w pamięci bezpośrednio przed właściwym programem 256 (100h) bajtów. W obszarze tym - nazywanym w skrócie PSP (ang. Program Segment Prefix) - znajdują się istotne informacje o programie, takie jak jego status czy parametry (rys. 1).

Rys.1 - Struktura bloku PSP poprzedzającego wprowadzany do pamięci operacyjnej program

Adres wzgl. Opis
00h ... 01h INT 20h : "długi" skok z programu w to miejsce kończy egzekucję programu
02h ... 03h Wskaźnik pierwszego wolnego segmentu pamięci (nie wykorzystywanego przez program; niemniej program może zarezerwować pamięć pod tym adresem za pomocą funkcji 48h)
04h Zarezerwowane
05h ... 09h Między segmentowe ("długie") wywołania funkcji 00h ... 24h (long call of function dispatcher via BIOS call); Numer funkcji musi się znajdować w rejestrze CL - Producent nie poleca korzystania z tego sposobu wywołania funkcji. W następnych wersjach systemu może zabraknąć tej możliwości.
0Ah ... 0Dh Zawiera adres, do którego nastąpi skok po zakończeniu programu (terminate addres); Kopia wektora przerwania 22h
0Eh ... 11h Zawiera adres, do którego nastąpi skok po naciśnięciu CTRL-BREAK (Control break exit adress); Kopia wektora przerwania 23h
12h ... 15h Zawiera adres, do którego nastąpi skok w przypadku fatalnego błędu operacji (kopia wektora przerwania 24h)
16h Zarezerwowane
2Bh ... 2Dh Adres segmentu obszaru opisującego otoczenie programowe; znajdują się tu m.in. informacje o sposobie aktywacji procesora poleceń (COMSPEC = ...), zmienne zdefiniowane za pomocą SET oraz ścieżka dostępu do programów poza bieżącym katalogiem (zdefiniowana za pomocą PATH = ...); Ciąg znaków opisujących otoczenie zaczyna się z przemieszczeniem 0, może zawierać najwyżej 32K znaków i składa się z mniejszych ciągów zakończonych znakiem 00h; Koniec całego opisu otoczenia oznaczony jest za pomocą dwóch znaków : 00h, 00h
2Eh ... 4Fh Zarezerwowane
50h ... 52h INT 21h oraz między segmentowy RET. Służy do wywołania dowolnej funkcji (00h ... 57h) za pomocą "długiego" wywołania (long call)
53h ... 5Bh Zarezerwowane
5Ch Blok sterujący pierwszego pliku (File Control Block 1 - FCB1) związany z pierwszą nazwą pliku, jeśli została podana w wywołującym program poleceniu; Format FCB dla pliku jeszcze nie otwartego
6Ch Blok sterujący drugiego pliku (File Control Block 2 - FCB2) związany z drugą nazwą pliku, jeśli została podana w wywołującym program poleceniu
80h ... FFh Format FCB dla pliku jeszcze nie otwartego obszar transmisji dyskowych (Disk Transfer Area - DTA); Po starcie programu używany jako bufor dyskowy aż do ewentualnej zmiany bufora za pomocą funkcji 1Ah; Bezpośrednio po wczytaniu programu w obszarze tym znajdują się wprowadzone po poleceniu znaki z wiersza polecenia (command line) - w komórkach o adresie 81h i 80h przechowywana jest liczba znaków

Po załadowaniu programu zawartość rejestru AX informuje o tym czy aktualnie wybrany napęd dysków jest zgodny z napędami dysków wybranymi w nie otwartych blokach FCB1 i FCB2 struktury PSP :

 AL = 00h : zgodność FCB1, AL = FFh : niezgodność FCB1;
 AH = 00h : zgodność FCB2, AL = FFh : niezgodność FCB2.

W PSP przewidziano dwa bloki sterujące dla plików FCB1 i FCB2 (ang. File Control Block), które mogą zostać otwarte w trakcie realizacji programu (funkcja 0Fh). Do momentu otwarcia pliku FCB zajmuje pozycję o przemieszczeniu (00h ... 0Bh lub - 07h ... 0Bh dla rozszerzonego FCB) względem początku danego bloku sterującego. Z chwilą otwarcia pliku blok sterujący ulega rozszerzeniu (rys. 2). Otwarcie pierwszego pliku powoduje zmazanie obszaru FCB2. Realizacja odwołania do pliku ze specjalnym atrybutem dostępu wymaga zastosowania rozszerzonego FCB (rys. 3-5).

Rys.2 - Struktura normalnego (i rozszerzonego) bloku kontrolnego pliki FCB

Adres wzgl. Opis
-7h Oznacza początek rozszerzonego bloku sterownika pliku, jeśli zawiera kod FFh
-6h ... -2h Zarezerwowane
-1h Zawiera atrybut pliku (Rys.3)
00h Numer napędu dyskowego : 0 - aktualne przydzielony napęd (default drive); 1 - Napęd A; 2 - Napęd B; Itd.
01h ... 08h Nazwa pliku (bez rozszerzenia) "dosunięta" w lewo, puste miejsca wypełnione spacjami; może zawierać także nazwę urządzenia (np. LPT:)
09h ... 0Bh Typ (rozszerzenie) pliku "dosunięty" w lewo, puste miejsca wypełnione spacjami
0Ch ... 0Dh Wskazuje aktualny rekord logiczny w pliku (rekord logiczny składa się z jednego lub więcej bloków po 128 bajtów); po otwarciu pliku (funkcja 0Fh) ustalany jest na 0
0Eh ... 0Fh Wielkość bloku w bajtach (normalnie wynosi 128 bajtów - zatem pozycja zawiera 80h,00h - może jednak po otwarciu pliku zostać zmieniona)
10h ... 13h Rozmiar pliku w bajtach (file size); pozycje 10h, 11h zawierają mniej znaczące słowo (16 bitów); przykładowo 80h, 06h, 00h, 00h oznacza wielkość pliku równą 1664 bajtom
14h ... 15h Data ostatniego zapisu do pliku (Format - Rys.4)
16h ... 17h Czas ostatniego zapisu (Format - Rys.5)
18h ... 1Fh Zarezerwowane
20h Aktualny blok o długości 128 bajtów wewnątrz rekordu logicznego określanego przez pozycję 0Ch. 0Dh; ponieważ nie jest ustawiany przez funkcję 0Fh musi zostać przed dostępem do pliku sekwencyjnego ustalony przez program
21h ... 24h Bieżący numer bloku w odniesieniu do początku pliku; używany przy dostępie swobodnym do pliku (random acces); jeśli wielkość bloku 64, używane są wszystkie cztery bajty tego wskaźnika; w innym wypadku - tylko trzy. Przed dostępem swobodnym do pliku wskaźnik ten musi być ustawiony przez program

--- ^ --- ^ ---

Rys.3 - Atrybuty plików w systemie MS-DOS. Sposób kodowania w jednym z bajtów rozszerzonego FCB (przemieszczenie - 1)

Kod atrybutu (dwójkowo) Opis
00000000 Dostęp bez ograniczeń
XXXXXXX1 Plik może być tylko czytany (read only)
XXXXXX1X Plik ukryty (hidden); nie można go otworzyć za pomocą funkcji 0Fh
XXXXX1XX Plik systemowy; nie można go otworzyć za pomocą funkcji 0Fh
XXXX1XXX Nazwa logiczna nośnika pamięci (Volume ID), umieszczona w katalogu głównym (root directory)
XXX1XXXX Nazwa podkatalogu (subdirectory)
XX1XXXXX Plik zmodyfikowany (program BACKUP pozwala archiwizować takie zbiory jednocześnie zerując ten bit)
X1XXXXXX Zarezerwowane
1XXXXXXX Zarezerwowane

Zbiory typu COM

Pliki o rozszerzeniu .COM wywodzą się z wczesnego systemu operacyjnego - systemu CP/M. Procesory pod kontrolą systemu CP/M (8080 i Z80) mogły bezpośrednio zaadresować tylko 64 KB pamięci, stąd programy użytkowe mogły mieć długość co najwyżej 64 KB (a nawet jeszcze mniej, gdyż i sam system operacyjny zajmował pewien obszar w pamięci).

Wprowadzenie procesorów 8088/8086 wyeliminowało to ograniczenie - mogą one adresować 1 MB pamięci. MS-DOS nie zerwał jednak całkowicie z przeszłością. Procesory 8088/8086 stanowią w pewnym sensie rozszerzenie procesora 8080. Istnieją programy, które przetwarzają kod maszynowy mikroprocesora 8080 na kod 8088/8086, pozwalając z programów przeznaczonych dla 8080 uzyskać identycznie działające programy dla 8088/8086. Aby uzyskać pełną zgodność w funkcjonowaniu trzeba jednak spełnić jeszcze jeden warunek: zrealizować funkcję systemu CP/M 80 w systemie MS-DOS. Tak też się stało i dzięki temu wiele programów opracowanych dla CP/M 80 (np. WordStar, dBase II, Turbo Pascal) może być egzekwowana (po przetworzeniu kodu) pod kontrolą systemu MS-DOS.

Programy takie zapisane są w plikach typu .COM i zajmują w pamięci operacyjnej co najwyżej 64 KB (jeden segment). W segmencie pamięci, do którego załadowano program umieszczone jest 100h bajtów PSP, kod programu, dane i stos (ostatnie 100h bajtów). System operacyjny, po załadowaniu programu nadaje wszystkim rejestrom segmentu (CS, DS, ES i SS) tę samą wartość, wskaźnikowi rozkazu IP wartość 100h, natomiast wskaźnikowi stosu SP wartość FFFFh (rys.6).

Przypomnijmy jak mikroprocesor 8088/8086 tworzy 20-bitowy adres absolutny :

 [adres absolutny] = [adres z rejestru segmentu] * 10h + [przemieszczenie]

Przemieszczenie pozwala zaadresować 64 KB pamięci bez zmiany zawartości rejestru segmentu. Zmieniając zawartość rejestrów segmentowych (wszystkie w ten sam sposób, tak aby nadal pozostały sobie równe) można zmienić przeznaczone na wpisanie kodu programu typu .COM "krokami", co 10h bajtów. Jeśli program nie zawiera odwołań ani skoków między segmentowych, w nowym miejscu pamięci będzie nadal wykonywał się prawidłowo.

Użytkownik ma do dyspozycji polecenia zmieniające obszaru pamięci przeznaczonego na programy użytkowe. Programy typu .COM, jako programy przesuwalne mogą być bez żadnych modyfikacji ładowane do pamięci w miejsce wyznaczone przez system operacyjny.

Przy tworzeniu programu, któremu chce się nadać typ .COM, trzeba mieć na uwadze następujące uwarunkowania :

  • po załadowaniu programu typu .COM wskaźnik rozkazu IP będzie miał wartość 100h; pod tym adresem musi się więc znajdować pierwszy rozkaz programu (dytektywa ORG 100h);
  • dane, które nie stanowią celowej modyfikacji obszaru PSP, nie mogą zajmować się poniżej adresu 100;
  • MS-DOS automatycznie rezerwuje końcowe 100h bajtów segmentu na stos programowy.

Choć programy typu .COM mogą zająć najwyżej 64 KB pamięci, mają tę nieprzyjemną własność, że blokują całą pozostałą pamięć. Kolejny program może być załadowany do pamięci za swoim poprzednikiem (wywołanie funkcji DOS z AH = 4Bh przerwaniem INT 21h) dopiero wtedy, gdy program wywołujący zwolni odpowiedni obszar pamięci (wywołanie funkcji DOS z AH = 4Ah)*. Po załadowaniu programu typu COM na szczycie stosu (przesunięcie FFFEh - FFFFh) umieszczany jest adres powrotny 0000h. Po napotkaniu rozkazu RET nastąpi więc skok pod adres 0000h znajdujący się w obszarze PSP (Rys.1) i zostanie wykonane przerwanie INT 20h kończące program. Stanie się tak jednak tylko wtedy, gdy wskaźnik stosu bezpośrednio przed wykonaniem rozkazu RET będzie miał tą samą wartość co przy wejściu do programu, wartości w rejestrach segmentu CS i SS nie zostały zmienione, podobnie jak zawartość bajtów o przemieszczeniu 0Ah ... 0Dh w obszarze PSP. Spełnienie tylu warunków wymaga zachowania szczególnej uwagi przy konstruowaniu programu. Dlatego wygodniej jest kończyć program, korzystając z wywołania przez INT 21h funkcji systemu DOS z AH = 00h lub AH = 4Ch, której nie dotyczą takie restrykcje.

Pliki z rozszerzeniem .EXE

W przeciwieństwie do plików typu .COM pliki typu .EXE są ograniczane jedynie rozmiarami pamięci operacyjnej. Składają się one zazwyczaj z wielu modułów, komunikujących się pomiędzy sobą za pomocą "długich" adresów (o efektywnej długości 20 bitów). Wynika z tego, że bez wykorzystania specjalnych mechanizmów systemu operacyjnego nie mogą one być załadowane w dowolne miejsce pamięci i wykonywane.

W każdym zbiorze typu .EXE przez właściwym programem znajduje się tzw. nagłówek programu (ang. program headder). Zawiera on wszystkie informacje niezbędne do takiej modyfikacji programu, że można go załadować w dowolne miejsce pamięci o adresie będącym wielokrotnością liczby 10h (Rys.7). Nagłówek ten znajduje się w pliku na dysku i służy jedynie do modyfikacji przez MS-DOS niektórych bajtów programu i tablicy PSP. Do pamięci operacyjnej program jest ładowany bez nagłówka.

Rys.7 - Nagłówek typu .EXE

Adres wzgl. Opis
00h ... 01h Zawiera znaki "MZ", które są wyróżnikiem pliku typu .EXE (program file signature)
02h ... 03h Liczba bajtów na ostatniej zajętej przez program stronie - 1 strona odpowiada 512 bajtom
04h ... 05h Wielkość pliku .EXE łącznie z nagłówkiem w (stronach)
06h ... 07h Liczba pozycji w tabeli relokacji (number of relocation entries)
08h ... 09h Rozmiar nagłówka - porcje po 16 bajtów (size of header in paragraphs)
0Ah ... 0Bh Minimalna liczba paragrafów, które po załadowaniu programu trzeba zarezerwować poza końcem programu (minimum allocation of memory, MINALLOC)
0Ch ... 0Dh Maksymalna liczba paragrafów, które po załadowaniu programu trzeba zarezerwować poza końcem programu (maximum allocation of memory, MAXALLOC)
0Eh ... 0Fh Wartość początkowa rejestru segmentu stosu (SS) po załadowaniu programu
10h ... 11h Wartość początkowa rejestru wskaźnika stosu (SP) po załadowaniu programu
12h ... 13h Ujemna suma kontrolna wszystkich słów 16-bitowych pliku (load error check sum)
14h ... 15h Wartość początkowa rejestru wskaźnika rozkazu (IP) po załadowaniu programu
16h ... 17h Wartość początkowa rejestru segmentu kodu (CS) po załadowaniu programu
18h ... 19h Przemieszczenie tabeli relokacji względem początku pliku
1Ah ... 1Bh Numer nakładki nadany przez konsolidator (MS-LINK) nakładek; 0 - oznacza część rezydentną programu
1Ch Tabela relokacji

Przy przesuwaniu programu typu .EXE trzeba zmodyfikować wszystkie rozkazy zawierające adresy "między segmentowe". Potrzebnych do tego celu informacji dostarcza program konsolidujący (ang. linker), który tworzy nagłówek programu, zawierającą listę miejsc, w których trzeba wprowadzić modyfikację. Kod programu po konsolidacji mógłby być natychmiast egzekwowany, gdyby umieścić go w pamięci od adresu 0000:0000. Aby wprowadzić program w miejsce pamięci, trzeba we wszystkich rozkazach zawierających adresy między segmentowe zmodyfikować je dodając odpowiednie "przemieszczenie". Jest ono równe adresowi segmentu dla obszaru PSP danego programu.

Załadowanie programu przebiega następująco :

  1. Odczytanie pozycji 00h ... 1Bh nagłówka, co pozwala na ustalenie długości programu (oznaczanej dalej symbolem SIZE), jak również MINALLOC i MAXALLOC, pozwalających na ustalenie potrzebnej wielkości pamięci. Możliwe są tu następujące sytuacje :
    1. dostępna pamięć jest mniejsza niż SIZE + MINALLOC; proces ładowania zostaje przerwany i wyświetlony jest komunikat o braku dostatecznego obszaru pamięci (insufficient memory);
    2. dostępna pamięć jest większa niż SIZE + MAXALLOC; DOS rezerwuje obszar o rozmiarach SIZE + MAXALLOC;
    3. dostępny obszar pamięci zawiera się pomiędzy wartościami a i b; MS-DOS rezerwuje całą dostępną pamięć.
  2. Na podstawie informacji zawartych w nagłówku oraz aktualnego stanu systemu DOS określa pierwszy segment pamięci, do którego można załadować program. Zapisuje tam PSP, a następnie kod programu.
  3. Odczytywana jest tablica relokacji i przeprowadzane zgodnie z nią niezbędne modyfikacje.
  4. Segment kodu i segment stosu ustalane są na podstawie wartości przemieszczenia segmentu (określonego położeniem PSP) i informacji w nagłówku (Rys.8 - ?!). Rejestry SP i IP ładowane są bezpośrednio na podstawie informacji w nagłówku. Rejestry segmentu DS oraz ES ładowane są wartością przemieszczenia segmentu, wskazują więc na obszar PSP.

Tabela relokacji zawiera adresy programu (dla wersji zaczynającej się od adresu 0000:0000), pod którym znajdują się odwołania między segmentowe, do których trzeba dodać wartość przemieszczenia segmentu. Każda pozycja w tabeli relokacji zawiera cztery bajty (dwa bajty przemieszczenia w stosunku do początku segmentu, dwa bajty adresu segmentu).

Przedstawione zależności ilustruje krótki program w języku maszynowym (Wydruk 1). Wyświetla on na ekranie pewien tekst i kończy działanie. Program celowo został tak "niezręcznie" napisany, iż dwa rozkazy muszą zawierać adresy między segmentowe. Ponieważ w chwili przetwarzania zapisu symbolicznego na kod maszynowy odpowiednie adresy segmentów nie są jeszcze znane, Assembler zapisuje je w postaci ----R (R oznacza relokowalny, wymagający wpisania przemieszczenia).

Z wydruku 2 wynika, że nagłówek ma wielkość 200h bajtów, a zatem program zaczyna się od pozycji 200h (uwaga: początkowe 20 bajtów zarezerwowane jest na stos). Względem tej pozycji wyznaczone są adresy podane w tabeli relokacji.

Występujące w tabeli relokacji dwie pozycje dotyczą miejsc oznaczonych przez Assembler za pomocą ----R.

*) - Zob: MS-DOS od środka, mikroklan lipiec '87

Opr. Zbigniew Pojmański wg. mc 11/85
Mikroklan śierpień 1987r.