[PC] Program w EPROM - making of

Kącik dla elektroniki retro - układy, urządzenia, podzespoły, literatura itp.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

[PC] Program w EPROM - making of

Postautor: gaweł » sobota 01 cze 2019, 21:47

Terminal ANSI
bios01_il00.jpg


Wyrzucanie sprawnych rzeczy na śmietnik zawsze boli. Szczególnie dotyczy to urządzeń elektronicznych. Zamiast powiększać góry śmieci, można znaleźć jakieś sensowne zastosowanie dla urządzeń, dla których spokojne oczekiwanie na niebyt jest jedynym celem. Sam mam w szufladzie kilka starych płyt po komputerach PC. W dzisiejszych czasach, przy wyśrubowanych wymaganiach stawianych przez systemy operacyjne, takie płyty nie mają żadnych szans na użycie. Jednak bazując na tych płytach w sensie sprzętowym, możliwe jest wykorzystanie ich w innym zastosowaniu niż zostały zaprojektowane. Wystarczy popatrzeć ile jest budowanych urządzeń bazujących przykładowo na mikrokontrolerach AVR. W porównaniu z płytą PC, środowisko oferowane przez wspomniane mikrokontrolery w wielu przypadkach jest dosyć ubogie. Owszem, takie mikrokontrolery mają rozbudowany zestaw peryferii, ale w porównaniu z zasobami pamięci RAM, to porównywanie nie ma żadnego sensu, małe procki odpadają w przedbiegach. Szeroka gama wyposażenia dodatkowego, jak klawiatura czy karta video również bije mikrokontrolery na głowę.
Więc co stoi na przeszkodzie by zrobić selektywny odzysk?
Prawdę mówiąc, nic. Posiłkując się standardowym kompem, można „wyprodukować” program do umieszczenia w pamięci EPROM dla płyty PC, czyli zastąpić standardowy BIOS swoim własnym programem. Takie rozwiązanie, poza w jakimś sensie funkcją odśmiecającą świat, ma parę fajnych zalet. Wystarczy wspomnieć, że po włączeniu takie urządzenie nie musi bootować z dysku lub sieci. Jest gotowe do działania natychmiast i to jest cenne.
Stworzyć program dla kompa PC to obecnie żadna filozofia. Codziennie powstają tony różnego oprogramowania do uruchomienia pod kontrolą jakiegokolwiek systemu (DOS, windoza, linuch czy cokolwiek innego). Tutaj wszystko jest proste. Inaczej rzecz ma się w przypadku programów do umieszczenia w pamięci EPROM (lub FLASH). Tu obowiązuje troszkę inna filozofia niż ta, do której jesteśmy przyzwyczajeni na co dzień. Ignorowanie pewnych zasad skutkuje tym, że coś może nie zadziałać. Nawet jeżeli program jest „wytrzepany” przykładowo pod DOS, tu może nie zadziałać. Nie mam tu na myśli tego, że zostanie użyta jakaś funkcja DOS'owa, których nie ma w środowisku w przypadku programu zawartego w EPROM. Można latami lampić się na kawałek kodu i nie dostrzegać problemu. Ot choćby to, że pamięć EPROM (oraz FLASH) są pamięciami tylko do odczytu, więc nie da się do nich zapisać. Jakie są gwarancje, że kompilator nam nie „sprzedał” jakiejś niespodzianki?
Nie ma żadnego systemu operacyjnego, więc wszystko należy zrealizować samemu a to nie jest wbrew pozorom takie skomplikowane. Każde zagadnienie da się rozwiązać i można zastosować każdą koncepcję. To jak jazda na rolkach bez trzymanki.

Trochę filozofii

W każdym procku istotny jest stan po sygnale reset. Jakkolwiek jest on wygenerowany (współczesne mikrokontrolery często pozwalają skonfigurować nóżkę reset do funkcji pinu jakiegoś portu, co pozbawia układ wejścia reset) zawsze występuje ciąg czynności realizowanych przy okazji tej operacji. W przypadku procka z rodziny 86 (8086, 8088, 80186, 80286, 80383 itp.) rozpoczęcie realizacji programu zaczyna się od wpisania w rejestr CS (code segment) wartości FFFF hex oraz do rejestru IP (instruction pointer) wartości 0. Dla tych, co nie mają świadomości o poruszanych tematach, informuję, że wymienione procki mają różne tryby pracy: tryb rzeczywisty i tryb ochronny (protekcyjny). Procek po sygnale reset rusza w trybie rzeczywistym (wszystkie dalsze rozważania dotyczą tego trybu) spod adresu CS:IP = FFFF:0 hex. W sensie adresu wystawionego na szynie adresowej jest to: FFFF0 hex czyli 16 bajtów przed końcem pierwszego megabajta w przestrzeni adresowej. W tamtym miejscu ma być zawarta pierwsza instrukcja do wykonania przez procesor.
Realny adres wystawiony na szynę adresową wynika ze złożenia zawartości dwóch rejestrów adresowych. W zależności od charakteru instrukcji używane są różne rejestry, jednak zawsze występuje rejestr typu segmentowego (CS – w przypadku instrukcji skoków, wywołania, SS – w przypadku operacji na stosie) oraz ofsetowego (IP – w przypadku instrukcji skoków, SP – w przypadku stosu). Zapisuje się to symbolicznie jako CS:IP (finalnie wskazuje miejsce w pamięci, gdzie znajduje się wykonanie programu). Podobnie jest w przypadku stosu: mamy rejestr segmentowy SS i rejestr ofsetowy SP. Operacja dotycząca stosu jest adresowana jako SS:SP. To złożenie adresowe przekłada się na adres fizyczny w następujący sposób: CS << 4 + IP (w przypadku skoku lub wywołania procedury), ogólnie: rejestr segmentowy przesunięty o 4 bity i dodany (arytmetycznie) rejestr ofsetowy (łatwo zauważyć, że istnieje wiele różnych kombinacji zawartości rejestrów dających finalnie ten sam adres fizyczny – istnieje duża różnorodność). Oba rejestry są 16-bitowe i ta operacja finalnie daje 20-bitowy adres na szynie adresowej (w trybie pracy Real Mode, w trybie protekcyjnym wygląda to inaczej). Tu warto pamiętać, że procesory 8086 (8088) miały tylko tryb pracy Real Mode. Procesory 80286 miały dodany tryb pracy protekcyjny, ale raczej należy ten model procka uznać za trochę nieudany. Tryb ochronny (protekcyjny) spełniający w pełni swoją rolę pojawił się dopiero w modelu 80386. Oczywiście każdy z tych procków dysponuje trybem real mode i w rzeczywistości startuje w tym trybie po sygnale reset. Przejście do trybu protekcyjnego następuje w wyniku wykonania odpowiedniej instrukcji i nie ma już z niego powrotu (aby wrócić potrzebna jest pomoc z zewnątrz i taką funkcję realizuje między innymi procesor od klawiatury).
Wektory przerwań procka są zawarte w odpowiedniej tabeli. Jest ona umieszczona w pamięci w ściśle określonym adresie (0:0 co daje fizycznie adres 0). Zawartych jest tam 256 wektorów przerwań. Wektor przerwania to jest adres (w sensie rejestru segmentowgo i rejestru ofsetowego – w sumie 4 bajty → cała tabela wektorów przerwań to 1kB) miejsca gdzie znajduje się prawdziwa obsługa przerwania. Tu jest inaczej niż w większości mikrokontrolerów, gdzie w odpowiednim miejscu znajduje się pierwsza instrukcja obsługi przerwania. W prockach x86 wszystkie przerwania należy traktować jako przerwania programowe i raczej trudno mówić tu o przerwaniach sprzętowych. Kontroler przerwań 8259 reagujący na przerwania sprzętowe generuje instrukcję przerwania programowego (po prostu obsługa przerwania wynikająca z reakcji na zdarzenie sprzętowe jest nierozróżnialna od przerwań software'owych.
Programy ładowane do pamięci (binarne) przykładowo pod DOS są ładowane do określonego miejsca w przestrzeni adresowej (wcale nie od zera, tam jest tabela wektorów przerwań). Oznacza to, że muszą w swoim kodzie mieć już wylinkowane adresy (przykładowo adresy skoków). Kompilator (plus linker) generując program nie są w stanie odkreślić w jakim miejscu będzie on umieszczony w fazie wykonania. Zauważmy, że nasza znakomita koleżanka tasza tworząc program dla CA80 generuje go do ściśle określonej lokacji adresowej, tam go ładuje i uruchamia. Nie ma możliwości załadowania i wykonania go w innym miejscu, to nie zadziała. W przypadku PC i procków x86 obowiązują dokładnie te same zasady. Więc jak to się dzieje, że to jakoś działa?
W DOS są dwa formaty binarnych programów wykonywalnych. Są one rozróżniane po rozszerzeniu nadanym dla pliku. Są to *.COM i *.EXE. Program COM jest wylinkowany niejako relatywnie, to znaczy zadziała poprawnie nawet jeżeli będzie załadowany i uruchomiony w wielu miejscach przestrzeni adresowej. Dlaczego? Program COM jest programem jednosegmentowym, czyli mieści się w obrębie adresowym jednego segmentu. Oznacza to, że wszystko jest adresowane relatywnie w obrębie rejestru segmentowego a lokacje adresowa są determinowane jedynie zawartością rejestru ofsetowego. Procesor dysponuje dwoma wariantami instrukcji skoku (wywołania procedury): z adresem długim (far, czyli w kodzie instrukcji skoku podana jest nowa zawartość rejestru segmentowego i rejestru ofsetowego → adres docelowy skoku jest 16-bitów [ofset] i 16 bitów [segment] w sumie 4 bajty) lub krótkim (near, czyli w kodzie instrukcji skoku podana jest nowa zawartość tylko rejestru ofsetowego → adres docelowy nadal wyznacza para rejestrów CS [który nie uległ zmianie] i IP [podany jako operand instrukcji]). Limituje to oczywiście wielkość programu typu COM do maksymalnie 64k. Jeżeli teraz ktoś (przykładowo DOS) załaduje program COM do pamięci na określoną lokację w przestrzeni i go uruchomi (poprzez przykładowo długi skok, to ten COM dalej już sam pojedzie). W takiej sytuacji wszystkie elementy są znane. DOS wie gdzie załadował program (czyli zna wartość rejestru segmentowego odpowiadającą dla programu), początek programu COM jest w stałym miejscu: ORG 100H. Wystarczy wykonać JMP <segm>:100H i już pojedzie (<segm> to miejsce gdzie został załadowany program). Jeżeli w programie nie będzie użytych instrukcji z długim adresem to program się nie zgubi. Dokładnie to istnieje możliwość poznania własnego przeznaczenia, czyli program może dowiedzieć się jaką ma zawartość rejestru segmentowego i w dalszej konsekwencji może już manipulować tą informacją. Przedstawione wyżej (skrótowe) informację są trochę uproszczone, gdyż przed uruchomieniem programu konieczne jest określenie większej liczby rejestrów segmentowych: minimum to jeszcze rejestr segmentowy danych (DS), rejestr segmentowy stosu (SS). Miejsce w przestrzeni adresowej, gdzie może być załadowany program nie jest w pełni dowolne. Gradacja początku programu wynosi 1 paragraf (jest to taka jednostka o wielkości 16 bajtów) → adres fizycznego początku programu musi być podzielny przez 16.
Inaczej sprawa ma się w przypadku programów typu EXE. Taki program może być wielosegmentowy i wymaga w trakcie ładowania do pamięci niewielkich korekt adresowych. Na razie nie będę tego tematu rozwijał i zostawię go na później.
Na początek najprostsza forma rozwiązania opierająca się na przepisaniu programu typu COM do pamięci RAM i tam uruchomienie go.
Po zaprogramowaniu pamięci EPROM, włożeniu kostki do podstawki, sprzęcior jest gotowy do użycia. Jest to płyta PC AT ze złączami typu ISA i prockiem 80386. Ma złącze zasilające w innym standardzie niż współczesne kompy, więc z racji, że aktualnie nie mam sprawnego zasilacza typu AT, dorobiłem przejściówkę. Nie jest to wielki problem, stary standard jest podzbiorem nowego. Zachowując kolorki kabelków można adaptować zasilacz ATX do współdziałania z komputerem AT (napięcie +3,3V nie jest używane, więc można je pominąć). Nie należy zapominać o kabelku „Power Good”, jest istotny. Może się tak zdarzyć, że zasilacz ATX nie ma napięcia -5V (biały kabelek w zasilaczu), nie jest to problem. Można zignorować. To napięcie już od dawna nie jest wykorzystywane i występuje jedynie z powodów historycznych (pierwsze pamięci dynamiczne stosowane w starożytnych kompach wymagały takiego napięcia). Przy okazji do złączki przyłączyłem włącznik bistabilny (łączący kabelek zielony zasilacza z czarnym), co pozwoliło na włączanie zasilania (w czasach starożytnych nie było elektronicznego włączania zasilania i trzeba było ręcami przełożyć odpowiedni hebelek :) ).
bios01_il01.jpg
Program zawarty w EPROM pełni funkcję terminala ANSI. Każde naciśnięcie znaku na klawiaturze powoduje wysłanie via serial jego kodu, każdy znak przychodzący z serial jest wyświetlany na ekranie plus realizowane są specjalne funkcje ekranowe (chodzi o sekwencje sterujące zgodne ze standardem ANSI). W rzeczywistości zostały trochę poszerzone (dodano kilka funkcji nie występujących w terminalach ANSI). Z racji realizowanej funkcji, płyta główna musi zostać wyposażona w kartę grafiki VGA
bios01_il02.jpg
raz kartę multi IO, która zawiera układy transmisji szeregowej.
bios01_il03.jpg
Zastosowany egzemplarz ma dwa kanały RS232 określane jako COM1 i COM2 (ten drugi to ma nawet złącze typu DB25, w gruncie rzeczy to oba kanały są na smyczce na GOLDPIN, więc co się tam przyłączy, to się ma). Dodatkowo jest port drukarkowy typu CENTRONIX.
bios01_il04.jpg
Razem to się prezentuje następująco:
bios01_il05.jpg
Po odpaleniu:
bios01_il06.jpg
Tu w trakcie prac „zepsuł mi się monitor”. Nie żebym się wkurzył i ze złości czymś w niego rzucił. Po prostu matryca sama wzięła i pękła a ponieważ „ciekłe kryształy” nie wyciekły jeszcze do końca, to jakoś tam działa. Będę musiał zmienić na nowy (tylko jak to powiedzieć: nowych w formacie 3:4 nie ma, więc może tak: na nowy z odzysku).
Program do EPROM (jest w formacie binarnym, nie intel-hex gdyż w tamtych czasach nie umiałem przetwarzać takich plików, a program do EPROM jest wygenerowany przez oryginalne autorskie oprogramowanie z tamtych czasów):
ANSI.ZIP
(raczej powinien pasować do większości płyt z prockiem 386).
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » sobota 01 cze 2019, 22:19

Terminal ANSI
rozruch


Start programu: od włączenia zasilania:
bios01_f01.mov


Po drugiej stronie kabla RS232 jest komp (windoza), skąd został wysłany tekstowo źródłowo program.
bios01_f02.mov
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » niedziela 02 cze 2019, 03:16

Nuta na terazhttps://www.youtube.com/watch?v=i6_VhQ-sogs

Program składa się z dwóch kawałków. Jeden z nich jest napisany w asemblerze, drugi w języku Modula-2. Jest to paskalopodobny język programowania. Użyłem sformułowania paskalopodobny, gdyż wiele cech dziedziczy po paskalu a z drugiej strony jest na tyle językiem elastycznym i otwartym, że można powiedzieć, że tą cechą przewyższa nawet C. W Moduli każdy detal można wymienić (nawet robi się to łatwiej niż w C). Oprócz tego, ten język jest optymalizowany do zastosowań wieloprocesowych (wielowątkowych). Naturalną w tym języku możliwością jest powołanie oddzielnego procesu nawet wielu, które od tej chwili stają się niezależnym bytem w programie. Jako proces uważam jakiś fragment kodu uruchomiony jako program (takie małe zjawisko fraktalne). Ujmując w uproszczeniu, tworzona jest struktura będąca stosem dla procesu oraz zostaje uruchomiona wskazana procedura jako program związany ze swoim stosem. Razem z kompilatorem jest dostępne fajne demo ilustrujące to zjawisko, może kiedyś, jak będzie zainteresowanie, to wrócę do tematu. Te procesy przełączają się zgodnie z podziałem czasowym (przełączenie następuje w wyniku przerwania generowanego przez timer). Dodatkowo, procesy mogą się porozumiewać między sobą. No, ale nie o tym chciałem, nie będę uprawiać modulowego marketingu... Więc program obsługi terminala w sensie programu w Moduli jest jednowątkowy.
Program w Moduli jest podzielony na moduły (ciekawe zapętlenie słowne). Są dwa rodzaje modułów: normalne MODULE i IMPLEMENTATION MODULE (do którego w parze jest DEFINITION MODULE). Normalne MODULE jest tą częścią programu, która zawiera procedurę, od której zaczyna się program (w sensie analogii do C będzie to taki kawałek, w którym znajduje się funkcja main). IMPLEMENTATION MODULE to taki kawałek, w którym znajdują się funkcje i procedury, które są dolinkowane do programu. DEFINITION MODULE filozoficznie odpowiada plikowi nagłówkowemu. Poza filozoficzną analogią istnieje pewna istotna różnica: moduł zawiera procedurę inicjującą (która w kodzie, w sensie linkera, nazywa się jak moduł). Jest ona wywołana jednorazowo na starcie programu (takie coś jak event OnFormCreate w Lazarus). Jeżeli w tekście programu nie występuje taki kawałek, to kompilator wygeneruje do pliku półkompilatu pustą procedurę. Kolejna istotna informacje niezbędna w przypadku miksowania różnych języków jest taka, że kompilator w pliku OBJ (półkompilacie) generuje nazwy elementów łącząc nazwę modułu z nazwą funkcji/procedury oraz używa znaku @ pomiędzy tymi nazwami (kompilator C przykładowo do nazw funkcji dodaje znak podkreślenia z przodu).

Trochę wiedzy tajemnej...

Istnieje możliwość sterowania generacją kodu wprost z programu źródłowego. Odpowiednie zaklęcia są pisane jako dyrektywy sterujące pisane wewnątrz komentarza w programie (w Lazarus jest podobnie). Kompilator Moduli dysponuje następującym zestawem:

Kod: Zaznacz cały

                  Compiler Directives

{$A+/-}   Enable(default)/disable aliased behavior on global
          variables.

{$B+/-}   Include(default)/exclude a control-break handler in
          the program.

{$C h}    Selects which registers are preserved by procedures.
          AX=1, CX=2, DX=4, BX=8, DS=10, ES=20, SI=40, DI=80.
          Default is F0 = DS+ES+SI+DI. BP is always preserved.

{$D n}    Specifies the name of the (data-)segment in which
          to put the global variables declared by the module.
          Default is to use the module name.

{$E+/-}   Enable/disable(default) relaxed alias-treatment of
          variant-records.

{$F}      Procedures will be called with FAR calls. Likewise,
          procedure types are 32-bit. This is  default and
          applies only to global procedures; local procedures
          are always NEAR.

{$G+/-}   Enable(default)/disable module-prefixes in external
          names.

{$H+/-}   Enable/disable(default) treating constant aggregates
          as variables, allowing them to be modified.

{$I+/-}   Enable/disable(default) index-checking. If enabled,
          accessing a non-existent array-element will produce
          a run-time  error.

{$J+/-}   Enable/disable(default) interrupt procedures
          generating IRET instead of RET instructions.

{$K+/-}   Enable/disable(default) C-language calling
          convention for procedures.

{$M n}    Specifies the name of the (code-)segment in which to
          place code. Default is to use the module name.

{$N}      Procedures will be called with NEAR calls. This
          requires that callers are in the same  code-segment.
          Likewise, procedure types are 16-bit.

{$O+/-}   Enable/disable(default) overflow checking on
          whole number operations.

{$P+/-}   Enable/disable(default) generating external names
          for local procedures.

{$Q+/-}   Enable/disable(default) procedure tracing. If
          enabled, procedures will execute an INT 60H on entry
          and an INT 61H on exit.

{$R+/-}   Enable/disable(default) subrange checking.

{$S h}    Specifies the stack size in hex for the program.
          This directive must be in the main module.

{$S+/-}   Enable/disable(default) stack-overflow checking.

{$V+/-}   Enable(default)/disable copying of open-array value
          parameters.

{$W+/-}   Enable/disable(default) volatile variables.
          Volatile variables are not kept in registers across
          statements.

{$X+/-}   Enable/disable(default) 8087 spilling for
          procedures. Spilling is required if nested function
          calls exhaust the 8087 stack.

{$Y+/-}   Enable/disable(default) coinciding variant fields.
          If enabled, it is allowed to use the same name for
          fields in distinct variant alternatives, provided
          they have the same type and are at the same offset
          in the record.

{$Z+/-}   Enable/disable(default) checks for dereferencing of
          NIL pointers, which then generate a run-time error.
          When enabled, all local variables are initialized
          to zero. If enabled in the main module, all global
          variables are also zeroed.
W części modulowej jest napisane:

Kod: Zaznacz cały

(*$V-,N,C FE,p+,M ANSICODE,D ANSICODE *)

IMPLEMENTATION MODULE ROMAnsi ;

FROM SYSTEM IMPORT
           In             ,Out            ,DI             ,EI             ;
oznacza to, że wystosowujemy prośbę między innymi o: by kompilator generował skoki i instrukcje wywołania jako krótkie (near – z podaniem jedynie ofsetu bez zmiany zawartości rejestrów segmentowych), by funkcje i procedury były public, by nazwa segmentu kodu i danych programu (de facto modułu) miały określone nazwy (te same nazwy występują w kawałku asemblerowym, więc linker wszystko to sklei do jednego segmentu). Z racji wystąpienie zaklęcia FROM SYSTEM IMPORT... niejawnie zostało zrealizowane zainicjowanie modułu SYSTEM, czyli w sensie języka assembler wystąpi wywołanie:
CALL SYSTEM@
Do wyprodukowania binarnego kodu programu zastosowane jest makro MAKEROM.BAT o zawartości:

Kod: Zaznacz cały

masm ansi;
m2/c romansi
link @ansi.lnk
del ansi.com
exe2bin ansi.exe ansi.com
exp32k
  • MASM – kompilator języka assembler dla x86: kompiluje plik ansi.asm do pliku asni.obj
  • M2 – kompilator języka Modula 2: z parametrem /c jedynie kompilacja, kompiluje plik romansi.mod do romansi.obj
  • LINK – linker (ale nie od moduli): łączy elementy zgodnie ze specyfikacją zawartą w pliku ansi.lnk → produkuje ansi.exe,
  • exe2bin – konwertuje plik wykonywalny z formatu EXE do formatu COM (z ansi.exe zrobi ansi.com),
  • exp32k – mój program, który z ansi.com zrobi ansi.rom (do włożenia do EPROM), wynik jest w postaci binarnej (nie intel-hex), bo wtedy nie umiałem.
Kawałek napisany w moduli (jako IMPLEMENTATION MODULE) jest standardowo napisanym kawałkiem. Jedyną rzeczą jakiej należy się pilnować to: nie używać funkcji DOS ani BIOS (a dokładniej możliwe jest używanie przerwań do BIOS jedynie w zakresie obsługi karty video, czyli instrukcja INT 10H jest dopuszczalna jeżeli w systemie jest karta VGA). Jeżeli przez nieuwagę gdzieś zostanie użyta jakakolwiek funkcja INT z innym numerem, to biedy nie będzie, gdyż każdy możliwy wariant jest akceptowalny z tym, że nic nie zrobi.
W części asemblerowej jest:

Kod: Zaznacz cały

.286p
;******************************************************************************
;******************************************************************************
;******************************************************************************
PUBLIC          SYSTEM@
;
EXTRN           ROMANSI@TeleType : NEAR
EXTRN           ROMANSI@KeyBoard : NEAR
EXTRN           ROMANSI@Terminal : NEAR
EXTRN           ROMANSI@InitControl : NEAR
EXTRN           ROMANSI@INT_RS_BODY : NEAR
EXTRN           ROMANSI@InitProcPar : NEAR
EXTRN           ROMANSI@StartTerminal : NEAR
EXTRN           ROMANSI@SetVideoRAM : NEAR
EXTRN           ROMANSI@ResetKeyb : NEAR
EXTRN           ROMANSI@BeepService : NEAR
EXTRN           ROMANSI@GetCMOSPlLetter : NEAR
EXTRN           ROMANSI@WriteToCMOS : NEAR
EXTRN           ROMANSI@ReadFromCMOS : NEAR
EXTRN           ROMANSI@GetCMOSCOMn : NEAR
;
;******************************************************************************
;******************************************************************************
;******************************************************************************
;
;       COM1    : 03F8H , IRQ 4
;       COM2    : 02F8H , IRQ 3
;
KBD_Data_Port           EQU     60H     ; hardware keyb. data port address
KBD_Ctrl_Port           EQU     61H     ; hardware keyb. control port address
I8259                   EQU     20H     ; hardware interrupt controler address
I8237                   EQU     0       ;
I8254                   EQU     40H     ;
VGA_Color_Video         EQU     0B800H  ;
VGA_Mono_Video          EQU     0B000H  ;
;
IRQ1                    EQU     1       ;
IRQ3                    EQU     3       ;
IRQ4                    EQU     4       ;
;
;******************************************************************************
;******************************************************************************
;******************************************************************************
;
;=========== odkomentarzowac dla pracy pod DOS /start/
;STACK_AREA      SEGMENT STACK
;Stack_Size      EQU     800H
;_Stack          DB      Stack_Size DUP ( ? )
;_Reserwe        DB      10H DUP ( ? )
;STACK_AREA      ENDS
;=========== odkomentarzowac dla pracy pod DOS /stop/
;
;******************************************************************************
;******************************************************************************
;******************************************************************************
;
C_ANSICODE      SEGMENT PUBLIC WORD 'FCODE'
                ASSUME  CS : C_ANSICODE
                ASSUME  DS : C_ANSICODE
;=========== zakomentarzowac dla pracy pod DOS /start/
                ORG     0H
Prog_Entry :                                    ;
                DB      55H , 0AAH , 10H        ;
;=========== zakomentarzowac dla pracy pod DOS /stop/
;=========== odkomentarzowac dla pracy pod DOS /start/
;                ORG     100H
;Prog_Entry :                                    ;
;=========== odkomentarzowac dla pracy pod DOS /stop/
                CLI                             ;
                CLI                             ;
                CLI                             ;
                CLD                             ;
                MOV     AL , 8DH                ; zablokowanie NMI
                OUT     70H , AL                ;
INCLUDE         STARTUP.INC
                MOV     AX , 4000H              ;
                MOV     SS , AX                 ;
                MOV     AX , 0FFF0H             ;
                MOV     SP , AX                 ;
                CALL    Make_Shadow_RAM
__Shadow_Start  :                               ;
                CALL    SetAll_IRQ_Vect         ;
                CALL    Init_8254               ;
                CALL    Init_8237               ;
                CALL    Init_8259               ;
                CALL    Init_8255               ;
                STI                             ;
                JMP     Main_Entry              ;
W części asemblerowej jest procedura SYSTEM@ (by zadowolić Modulę, inaczej linker będzie się darł, że są mising objecty) zgłoszona jako public (dostępna dla innych kawałków OBJ). Kolejne zaklęcia extern zadawalają kompilator masm, by ten z kolei przestał zgłaszać wąty, bo jest użytych trochę funkcji i procedur fizycznie zawartych w kawałku modulowym. W rzeczywistości odpowiednik funkcji main jest zawarty tutaj. Program jest napisany niejako dwuwariantowo: do ćwiczenia pod DOS i do realizacji do EPROM. Różnice wynikają z następujących potrzeb: pod DOS ma być to normalny program, czyli ma mieć stos i entry point na ofsecie 100h. Spełniać warunki formalne, gdyż właściwie w wersji DOS jest to program-bandyta utłucze DOS i do niego już nie wróci (z programu wychodzi się klawiszem reset). W wersji pakowanej do EPROM, ma nie być stosu (znaczy jak będzie to jest zmarnowane miejsce) oraz entry point programu jest w innym miejscu (nie ma powodu by marnować ileś pamięci na odłogi). Występuje tu również taki „dziwny zapis” lokowany na ORG 0:

Kod: Zaznacz cały

Prog_Entry :                                    ;
                DB      55H , 0AAH , 10H        ;
                CLI                             ;
do czego służy? Właściwie do niczego, to jest taki „future connector”. Jeżeli na początku jakiegoś paragrafu (miejsca w przestrzeni o adresie podzielnym przez 16) znajdzie się taka specyficzna sygnatura 055H i 0AAH, to nawet jeżeli program zostanie umieszczony w pamięci EPROM w innej lokacji (są karty z własnym programem, czego klasycznym przykładem jest karta video – ma własny kawałek programu), to nawet standardowy BIOS może uruchomić taki program. Jedną z funkcji standardowego BIOS'a jest właśnie poszukiwanie ziomali w przestrzeni adresowej. Jeżeli BIOS gdzieś namierzy taką sekwencję (55, AA, te 10H oznacza jakąś wielkość, już mi uleciało z głowy czego, kiedyś wiedziałem), to zostanie taki kawałek softu zainicjowany. Robi się to w ten sposób, że należy wykonać instrukcję CALL na +3 baty dalej od początku takiej sekwencji. Właśnie w ten sposób BIOS startuje kartę video. Jeżeli nasz program zostanie w ten sposób zainicjowany, to również ucieknie spod kontroli i stanie się niezależny. Zdarzają się karty, przykładowo sieciowe, zawierające podstawkę pod program obsługi karty. Teoretycznie (bo tego nie sprawdziłem) istnieje możliwość włożenia programu terminala ANSI w takie miejsce i nawet w najlepszych płytach PENTIUM program zrobi zwoje nie będąc BIOSEM. Tego nie sprawdziłem, to tylko przypuszczenie, ale warte sprawdzenia. Mam taką przypadłość, że sprawdzam wiedzę na sobie.

Dla tropicieli, całość źródłowo:
TERMANSI.ZIP
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » poniedziałek 03 cze 2019, 02:38

W oryginale, BIOS tej płyty PC, zawarty jest w pamięci typu 27C512, jest pamięcią EPROM o pojemności 64k*8. Po utworzeniu binarnej wersji programu i przekonwertowaniu go do postaci nadającej się do umieszczenia w pamięci EPROM, plik ma objętość 32kB (dokładnie 32768 bajtów). W pewnym sensie jest to istotna różnica, jednak jak łatwo można się przekonać program pomimo tego działa. Można zadać sobie pytanie: dlaczego tak się dzieje jak się dzieje?
Rozpatrzmy „potencjalne” rozwiązanie z punktu widzenia elektronicznego. Może ono być jak na ilustracji:
bios-3_il01.png
Jak napisałem wcześniej, procki z rodziny x86 ruszają z „końca” przestrzeni adresowej w trybie rzeczywistym, czyli wszelkiej maści dekodery adresowe jakkolwiek są rozwiązane muszą odpowiadać powyższemu rysunkowi. Bazując na 20-bitowej szynie adresowej, część adresów trafia do pamięci EPROM (rzecz jasna ta najmłodsza, gdyż „rozdzielczość” musi wynosi jeden bajt). Pozostałe adresy przechodzą przez jakąś logikę, która na żądanie dostępu do pamięci (zaznaczyłem to po „zilogowemu” jako MEMRQ – w tym procku również występuje sygnał o tym samym znaczeniu) i w oparciu o pozostałe linie adresowe produkuje sygnał aktywacji dla fizycznego układu.
Same układy pamięci (typu 27C512 i 27C256) są w gruncie rzeczy do siebie podobne. Występuje praktycznie pełna zgodność pinoligiczna (z wyjątkiem nóżek zaznaczonych na czerwono).
bios-3_il02.png
Pamięć 27C256 stała się nieczuła na sygnał A15. Bez względu na stan logiczny wybranej linii adresowej, układ 27C256 „wyda” odpowiednie dane. Wygumkujmy sobie w wyobraźni linię A15 na powyższym schemacie. Jaki jest efekt? Ten sam program będzie widoczny w dwóch miejscach w przestrzeni adresowej (dla A15=0 oraz A15=1). Pobranie pierwszej instrukcji do wykonania nastąpi spod adresu FFFF:0 (A15=1) co fizycznie przekłada się na FFFF0 hex (16 bajtów przed końcem pamięci EPROM). Znajduje się tam instrukcja bezwarunkowego skoku długiego (far, z podaniem nowej zawartości rejestru segmentowego i ofsetowego w jakieś miejsce w przestrzeni). Program jakoś sam dalej już pojedzie.
Jednak jeżeli ktoś wpadnie na szalony pomysł i przeprogramuje wyjętą z płyty pamięć EPROM, włoży ją do płyty i uruchomi, to może spotkać go niemiła niespodzianka. Zwyczajowo, programatory chcąc umieścić jakąś zawartość zaczną programować pamięć od początku. W tej sytuacji wystąpi przypadek, gdzie pojemność pamięci jest dwukrotnie większa od wielkości pliku. By wszystko było zgodne z prawami natury, należy kod programu umieścić w drugiej połówce pamięci EPROM 27C512.
Zdaję sobie sprawę, że pamięci EPROM powoli wychodzą z użycia. Niektóre sprzedawalnie już wycofują te układy ze swojej oferty (ewentualnie pozostaje znany serwis internetowy: alle drogo). Opcją może być zastosowanie układów typu FLASH. Nietrudno jest wygooglać, że równoległe pamięci FLASH to mają oznaczenie 28F512. Jednak tutaj, nie obędzie się bez dodatkowego wsparcia → konieczna jest przejściówka. Pamięć typu 27C512 ma 28 nóżek, pamięć typu 28C512 ma już tych nóżek 32.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » wtorek 04 cze 2019, 05:01

Może czas przejść na wyższy level. Załadowanie do EPROM programu typu .COM nie jest problemem. Występuje tu trochę ograniczeń związanych przede wszystkim z liczbą segmentów programu, czyli inaczej mówiąc nie każdy program .EXE da się przerobić na .COM. Może więc warto rozpracować pliki .EXE, by móc je obrabiać. Ten plik ma już swoją strukturę i wymaga trochę większej rozkminy niż .COM.
Zanim pokażę jak sobie poradzić z plikiem .EXE, warto poznać jego budowę, gdyż wiadomo, że jak się pozna przeciwnika, to walka z nim staje się prostsza. Otóż .EXE ma swój nagłówek (zaczynający się od „magicznego kodu” 4D5A w zapisie hex co odpowiada literom MZ; wystarczy podejrzeć mikrosoftowy program EXE by się o tym przekonać). Struktura nagłówka jest następująca (w sensie definicji typu dla MODULA: typ CARDINAL to 16 bitowa liczba bez znaku oraz LONGCARD to 32-bitowa liczba bez znaku):

Kod: Zaznacz cały

RealMode_Header     = RECORD
                        Magic_number            : CARDINAL ; (* 05A4DH *)
                        Bytes_on_last_page      : CARDINAL ;
                        Pages_in_file           : CARDINAL ;
                        FixupNumber             : CARDINAL ;
                        Paragraphs_in_header    : CARDINAL ;
                        Extra_paragraphs_needed : CARDINAL ;
                        Extra_paragraphs_wanted : CARDINAL ;
                        SS                      : CARDINAL ;
                        SP                      : CARDINAL ;
                        CRC                     : CARDINAL ;
                        IP                      : CARDINAL ;
                        CS                      : CARDINAL ;
                        Fixup_table_address     : CARDINAL ;
                        Overlay_number          : CARDINAL ;
                        Reserve_words           : ARRAY [1..16] OF CARDINAL ;
                        New_header_position     : LONGCARD ;
                      END (* RECORD *) ;
rm_Fixups           = ARRAY [ 1 .. 16383 ] OF RECORD;
                                         rm_Fixup_offs   : CARDINAL ;
                                         rm_Fixup_para   : CARDINAL ;
                                              END (* RECORD *) ;
W dalszej części znajduje się kilka istotnych informacji, wśród których są przede wszystkim istotne w punktu widzenia uruchomienia programu zawartości kilku rejestrów. Mam tu na myśli CS:IP oraz SS:SP czyli: gdzie jest entry point dla programu oraz gdzie jest stos. Oprócz tego ważną informacją jest liczba fixup'ów i ich położenie. Liczba fixup'ów, jak sama nazwa mówi jest liczbą (w sensie sztuk). Położenie fixup'ów jest ofsetem w pliku, gdzie znajduje się tablica fixup (lity obszar, gdzie znajdują się kolejnej elementy tablicy). Co to jest ten magiczny fixup? Jest to położenie słowa (liczby 16-bitowej) w programie, które należy skorygować. Dokładniej do znajdującej się tam zawartości dodać adres podstawy ładowania (miejsca gdzie został załadowany program jako zawartości rejestru segmentowego). Można to sobie wyobrazić jakby program został wylinkowany w taki sposób, że jest ładowany do pamięci od adresu 0. Jeżeli do operandu w instrukcjach skoku lub instrukcjach wołania procedury dodać jakąś stałą wartość, to powstanie nowy twór jako program wylinkowany na inną lokację w przestrzeni adresowej. Analogicznie jest zrealizowane przesyłanie danych (wartości zmiennych). Obszar danych jest adresowany relatywnie w sensie rejestru segmentowego, w programie musi wystąpić instrukcja wpisania wartości do tego rejestru. Ta instrukcja ma swój operand. Jeżeli będzie wiadomo, gdzie takie operandy się znajdują, to można je skorygować. Jedyny problem to, wiedza co jest co i gdzie to jest. Kto o tym wie? Odpowiedź: LINKER. Linker wie wszystko i tą informację podsuwa nam pod nos, jest nią tabela fixup'ów. Można to przećwiczyć. Weźmy jakikolwiek program EXE.
Kiedyś, jak pokonywałem problem, napisałem taki oto programik, który teraz lekko zmodyfikowałem, by uwypuklić pewne zagadnienia (istotny jego fragment):

Kod: Zaznacz cały

PROCEDURE ViewExeFile ;

  TYPE
    FNameType           = ARRAY [ 0 .. 15 ] OF CHAR ;

  VAR
    A0                  : ADDRESS ;
    ExeFile             : FILE ;
    FName               : FNameType ;
    StartPos            : CARDINAL ;
    ExeHeader           : POINTER TO RealMode_Header ;
    TermKey             : CARDINAL ;
    Loop                : CARDINAL ;
    EndLoop             : CARDINAL ;
    ExeFName            : FNameType ;
    Fx_Ptr              : POINTER TO rm_Fixups ;

  BEGIN (* ViewExeFile *)
    ClrScr ;
    GoToXY ( 0 , 2 ) ;
    Write ( 'View EXE header (IBM PC)' ) ;
    GoToXY ( 0 , 3 ) ;
    Write ( 'EXE file name : ' ) ;
    FName := '' ;
    StartPos := 0 ;
    REPEAT
      ReadString ( FName , TSIZE ( FNameType ) , 16 , 3 , TermKey ,
                   StartPos , TRUE ) ;
    UNTIL ( TermKey = crSt ) OR ( TermKey = escSt ) ;
    IF TermKey = escSt THEN
      RETURN ;
    END (* IF *) ;
    Loop := 0 ;
    LOOP
      IF Loop > HIGH ( FName ) THEN
        EXIT ;
      END (* IF *) ;
      IF FName [ Loop ] = '.' THEN
        FName [ Loop ] := 0C ;
        EXIT ;
      END (* IF *) ;
      IF FName [ Loop ] = 0C THEN
        EXIT ;
      END (* IF *) ;
      INC ( Loop ) ;
    END (* LOOP *) ;
    ExeFName := FName ;
    Append ( ExeFName , '.EXE' ) ;
    OPEN ( ExeFile , ExeFName , read ) ;
    IF ExeFile . AN <> 0 THEN
      Write ( 'file not open ' ) ;
      Write ( ExeFName ) ;
      RETURN ;
    END (* IF *) ;
    ALLOCATE ( A0 , 512 ) ;
    SeqRead ( ExeFile , A0 , TSIZE ( Sector ) ) ;
    ExeHeader := ADDRESS ( A0 ) ;
    CLOSE ( ExeFile ) ;
    WITH ExeHeader ^ DO
      WriteLn ;
      WriteLn ;
      Write ( 'Magic_number            = ' ) ;
      WriteHex ( Magic_number ) ;
      WriteLn ;
      Write ( 'Bytes_on_last_page      = ' ) ;
      WriteHex ( Bytes_on_last_page ) ;
      WriteLn ;
      Write ( 'Pages_in_file           = ' ) ;
      WriteHex ( Pages_in_file ) ;
      WriteLn ;
      Write ( 'FixupNumber             = ' ) ;
      WriteHex ( FixupNumber ) ;
      WriteLn ;
      Write ( 'Paragraphs_in_header    = ' ) ;
      WriteHex ( Paragraphs_in_header ) ;
      WriteLn ;
      Write ( 'Extra_paragraphs_needed = ' ) ;
      WriteHex ( Extra_paragraphs_needed ) ;
      WriteLn ;
      Write ( 'Extra_paragraphs_wanted = ' ) ;
      WriteHex ( Extra_paragraphs_wanted ) ;
      WriteLn ;
      Write ( 'Fixup_table_address     = ' ) ;
      WriteHex ( Fixup_table_address ) ;
      WriteLn ;
      Write ( 'SS                      = ' ) ;
      WriteHex ( SS ) ;
      WriteLn ;
      Write ( 'SP                      = ' ) ;
      WriteHex ( SP ) ;
      WriteLn ;
      Write ( 'CS                      = ' ) ;
      WriteHex ( CS ) ;
      WriteLn ;
      Write ( 'IP                      = ' ) ;
      WriteHex ( IP ) ;
      WriteLn ;
      EndLoop := FixupNumber ;
      IF EndLoop > 5 THEN
        EndLoop := 5 ;
      END (* IF *) ;
      Fx_Ptr := [ Seg ( A0 ^ ) : Ofs ( A0 ^ ) + Fixup_table_address ] ;
      FOR Loop := 1 TO EndLoop DO
        Write ( '[' ) ;
        WriteHex ( Loop ) ;
        Write ( ']  offs= ' ) ;
        WriteHex ( Fx_Ptr ^ [ Loop ] . rm_Fixup_offs ) ;
        Write ( ' para= ' ) ;
        WriteHex ( Fx_Ptr ^ [ Loop ] . rm_Fixup_para ) ;
        WriteLn ;
      END (* FOR *) ;
    END (* WITH *) ;
  END ViewExeFile ;
Po kompilacji i uruchomieniu, obróbka pliku test.exe (jakiś program) jest jak następuje:
bios-03-il01.png
Program ten raportuje, że delikwent wymaga ustawienie stosu na 138A:4000 oraz ma entry point programu w miejscu 00B6:0002. Dodatkowo wyświetla 5 pierwszych elementów fixup. W DOS jest taki program EXEHDR, który wyświetla podobną informację. Jego uruchomienie daje:
bios-03-il02.png
Wyniki są zbieżne z dokładnością do jednego szczegółu:
  • u mnie jest: Bytes on last page = 0
  • u mikrego softa jest: Bytes on last page = 200 hex.
Ktoś tu oszukuje. Nie ma takiej opcji bym był to ja, to niemożliwe.
bios-03-il03.png
W pliku jest 0, ja zawsze mówię prawdę. Przy okazji:
bios-03-il04.png
oraz tabela FIXUP'ów
bios-03-il05.png
Sam program do oblukania jest na końcu w załącznikach. Co można z tego uzyskać w efekcie: w kolejnych odcinkach.
Dla tropicieli:
VIEWEXE.ZIP
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony piątek 07 cze 2019, 16:55 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » wtorek 04 cze 2019, 23:26

Całkiem wypadło mi z głowy, nie napisałem nic o programie generującym zawartość pamięci EPROM, a to tygryski chyba lubią najbardziej. Pamięć jakoś zawodna, ale jakiś anioł z tyłu głowy ciągle przypomina (niektórzy chyba nazywają to intuicją).
No więc, program (wyżej używany w makropolecniu MAKEROM.BAT) EXP32K formatuje fizyczną zawartość dla układów pamięci. Po uruchomieniu program wczytuje dwie informacje:
  • nazwę pliku z programem COM (podać nazwę bez rozszerzenia),
  • datę do umieszczenia w BIOS.
W oparciu o podaną nazwę pliku, program tworzy sobie nazwy kolejnych plików. Do nich należą:
  • <nazwa pliku>.TXT – nazwa pliku zawierająca nasz opis pozwalający na identyfikację zawartości EPROM (dane wejściowe),
  • <nazwa pliku>.COM – nazwa pliku zawierająca program do włożenia w EPROM (dane wejściowe),
  • <nazwa pliku>.ROM – nazwa pliku zawierająca dane do zaprogramowania w EPROM (dane wyjściowe).
W większości przypadków zdarza się, że zostaje trochę wolnej pamięci w EPROM. Przykładowo EPROM 27C256 ma 32kB a ANSI.COM ma ciut więcej niż 22kB. Zostaje kawałek niezagospodarowanego obszaru, który można sensownie spożytkować. Można utworzyć dowolnym edytorem plik tekstowy, który jako swego rodzaju komentarz, zostanie umieszczony w pamięci EPROM. Co zostanie wpisane do pliku, to znajdzie się w pamięci. To taka elektroniczna sygnatura. Jak kostki pamięci pomieszają się nam w szufladzie, to można wczytać zawartość EPROM i sobie przypomnieć. Aaaa, to jest ten, co to …. (to mój pomysł, by coś takiego robić).
Natomiast data (chyba powstania) jest umieszczana na końcu każdego BIOS'a w płycie PC'towej (to już pomysł IBM'a [chyba, bo kogo innego?]). Fizycznie ostatnie bajty to jakiś kod binarny, chyba związany z jakimś typem a może przeznaczeniem płyty PC, mało istotne w tej chwili. Wczytywana data nie jest weryfikowana w sensie formatu zapisu, co wpiszemy to mamy.
Przykładowo, plik ANSI.TXT zawiera:
bios-04_ilu01.png
i jest on włożony na koniec przestrzeni adresowej pamięci EPROM. Podglądając ANSI.ROM programem do przeglądania plików, po zawartości
bios-04_ilu02.png
trudno skojarzyć co to jest. Jednak przewijając na koniec pliku (wyświetlając tekstowo ze znakami ANSI w trybie DOS [inaczej piękne ramki nie chcą się wyświetlać]):
bios-04_ilu03.png
klepnąć się w czoło, i powiedzieć: „aaaaaaaa.... już wiem;) .
Zwróćmy baczną uwagę na ostatnie 16 bajtów w pamięci EPROM.
bios-04_ilu04.png
Mamy tu kod instrukcji jaką wykona procesor po sygnale reset plus wklepana data (1-VI-2019 – moje dziecko miało wtedy swoje święto i dostało prezent).
Datę można wykorzystać w programie, jak przykładowo robi to program obsługi terminala ANSI:

Kod: Zaznacz cały

PROCEDURE StartTerminal ;

  VAR
    TermSpeed           : CARDINAL ;
    _Mode               : CARDINAL ;
    _Lgt                : CARDINAL ;
    _Stop               : CARDINAL ;
    _Par                : CARDINAL ;
    _ParT               : CARDINAL ;
    Loop                : CARDINAL ;
    PlLetters           : CARDINAL ;
    StrPtr              : POINTER TO ARRAY [ 0 .. 15 ] OF CHAR ;

  BEGIN (* StartTerminal *)
    WITH ProcessParam DO

( . . . )

      ClrScr ;
      DFrame ( 0 , 0 , 79 , 3 ) ;
      StrPtr := [ 0FFFFH : 0 ] ;          (* <----------------------- TU *)
      SetXY ( 4 , 1 ) ;
      OutString ( 'Terminal SX2000AV wersja 1.1' ) ;
      OutString ( '    BIOS syg.: ' ) ;
      FOR Loop := 5 TO 15 DO
        OutString ( StrPtr ^ [ Loop ] ) ; (* <----------------------- TU *)
      END (* FOR *) ;
      SetXY ( 4 , 2 ) ;
      OutString ( 'Bialystok, 1994   remake: 01-06-2019 (www.microgeek.eu)' ) ;
      DFrame ( 0 , 6 , 79 , 11 ) ;
      SetXY ( 12 , 8 ) ;
( . . )
    END (* WITH *) ;
  END StartTerminal ;
Mamy zmienną StrPtr jako wskaźnik do tablicy znakowej o 16 znakach. Wskaźnik jest zainicjowany na wartość FFFF:0 hex. Można przedrukować na ekran coś co tam jest zapisane. To taki dziwny rodzaj pamięci: w programie nie ma a na ekranie jest → prawie magia. Rzecz jasna na indeksie 0 będzie kod instrukcji długiego skoku (o wartości EA hex).
Istotny fragment programu generującego zawartość EPROM to:

Kod: Zaznacz cały

PROCEDURE MakeROMFile ;

  TYPE
    FNameType           = ARRAY [ 0 .. 15 ] OF CHAR ;

  VAR
    Loop                : CARDINAL ;
    OutCt               : CARDINAL ;
    TermKey             : CARDINAL ;
    StartPos            : CARDINAL ;
    BIOSDate            : ARRAY [ 0 .. 7 ] OF CHAR ;
    FName               : FNameType ;
    ComFName            : FNameType ;
    RomFName            : FNameType ;
    CommentFName        : FNameType ;

  BEGIN (* MakeROMFile *)
    LOOP
      ClrScr ;
      GoToXY ( 0 , 4 ) ;
      Write ( 'ROM maker for IBM PC, type 27256' ) ;
      GoToXY ( 0 , 6 ) ;
      Write ( 'COM file name : ' ) ;
      FName := '' ;
      StartPos := 0 ;
      REPEAT
        ReadString ( FName , TSIZE ( FNameType ) , 16 , 6 , TermKey ,
                     StartPos , TRUE ) ;
      UNTIL ( TermKey = crSt ) OR ( TermKey = escSt ) ;
      IF TermKey = escSt THEN
        RETURN ;
      END (* IF *) ;
      Loop := 0 ;
      LOOP
        IF Loop > HIGH ( FName ) THEN
          EXIT ;
        END (* IF *) ;
        IF FName [ Loop ] = '.' THEN
          FName [ Loop ] := 0C ;
          EXIT ;
        END (* IF *) ;
        IF FName [ Loop ] = 0C THEN
          EXIT ;
        END (* IF *) ;
        INC ( Loop ) ;
      END (* LOOP *) ;
      GoToXY ( 0 , 7 ) ;
      Write ( 'BIOS date     : ' ) ;
      BIOSDate := '' ;
      StartPos := 0 ;
      REPEAT
        ReadString ( BIOSDate , SIZE ( BIOSDate ) , 16 , 7 , TermKey ,
                     StartPos , TRUE ) ;
      UNTIL ( TermKey = crSt ) OR ( TermKey = escSt ) ;
      IF TermKey = escSt THEN
        RETURN ;
      END (* IF *) ;
      ComFName := FName ;
      RomFName := FName ;
      CommentFName := FName ;
      Append ( ComFName , '.COM' ) ;
      Append ( RomFName , '.ROM' ) ;
      Append ( CommentFName , '.TXT' ) ;
      GoToXY ( 0 , 9 ) ;
      Write ( '.COM file      : ' ) ;
      Write ( ComFName ) ;
      WriteLn ;
      Write ( '.ROM file      : ' ) ;
      Write ( RomFName ) ;
      WriteLn ;
      Write ( '.TXT file      : ' ) ;
      Write ( CommentFName ) ;
      WriteLn ;
      IF AcceptQuestion ( 'Param O.K.' ) THEN
        EXIT ;
      END (* IF *) ;
    END (* LOOP *) ;
    OPEN ( RomFile , RomFName , write ) ;
    OPEN ( ComFile , ComFName , read ) ;
    IF ComFile . AN = 0 THEN
      FOR Loop := 0 TO HIGH ( Buffer ) DO
        Buffer [ Loop ] := 0377C ;
      END (* FOR *) ;
      FOR Loop := 0 TO HIGH ( CommentBuffer ) DO
        CommentBuffer [ Loop ] := 0377C ;
      END (* FOR *) ;
      OPEN ( CommentFile , CommentFName , read ) ;
      IF CommentFile . AN = 0 THEN
        SeqRead ( CommentFile , ADR ( CommentBuffer ) ,
                  SIZE ( CommentBuffer ) ) ;
      END (* IF *) ;
      SeqRead ( ComFile , ADR ( Buffer ) , SIZE ( Buffer ) ) ;
      IF ComFile . Lgt < 7BF0H THEN
        OutCt := 7BF0H ;
        FOR Loop := 0 TO 1023 DO
          Buffer [ OutCt ] := CommentBuffer [ Loop ] ;
          INC ( OutCt ) ;
        END (* FOR *) ;
      ELSE
        IF ComFile . Lgt < 7DF0H THEN
          OutCt := 7DF0H ;
          FOR Loop := 0 TO 511 DO
            Buffer [ OutCt ] := CommentBuffer [ Loop ] ;
            INC ( OutCt ) ;
          END (* FOR *) ;
        ELSE
          IF ComFile . Lgt < 7EF0H THEN
            OutCt := 7EF0H ;
            FOR Loop := 0 TO 255 DO
              Buffer [ OutCt ] := CommentBuffer [ Loop ] ;
              INC ( OutCt ) ;
            END (* FOR *) ;
          END (* IF *) ;
        END (* IF ... ELSE *) ;
      END (* IF ... ELSE *) ;
      Buffer [ 7FF0H ] := CHR ( 0EAH ) ;
      Buffer [ 7FF1H ] := CHR ( 003H ) ;
      Buffer [ 7FF2H ] := CHR ( 000H ) ;
      Buffer [ 7FF3H ] := CHR ( 000H ) ;
      Buffer [ 7FF4H ] := CHR ( 0F8H ) ;
      Buffer [ 7FF5H ] := BIOSDate [ 0 ] ;
      Buffer [ 7FF6H ] := BIOSDate [ 1 ] ;
      Buffer [ 7FF7H ] := BIOSDate [ 2 ] ;
      Buffer [ 7FF8H ] := BIOSDate [ 3 ] ;
      Buffer [ 7FF9H ] := BIOSDate [ 4 ] ;
      Buffer [ 7FFAH ] := BIOSDate [ 5 ] ;
      Buffer [ 7FFBH ] := BIOSDate [ 6 ] ;
      Buffer [ 7FFCH ] := BIOSDate [ 7 ] ;
      Buffer [ 7FFDH ] := ' ' ;
      Buffer [ 7FFEH ] := ' ' ;
      Buffer [ 7FFFH ] := ' ' ;
      SeqWrite ( RomFile , ADR ( Buffer ) , 8000H ) ;
      CLOSE ( ComFile ) ;
    ELSE
      WriteLn ;
      WriteLn ;
      Write ( 'File : ' ) ;
      Write ( ComFName ) ;
      Write ( ' - open error' ) ;
    END (* IF ... ELSE *) ;
    CLOSE ( RomFile ) ;
  END MakeROMFile ;
W zależności od wielkości wolnego miejsca w EPROM zostanie dołączony na jego końcu 1kB, 512B lub 256B pliku komentarzowego lub nic (bo już się nie mieści).
Zwróćmy uwagę na fragment (Buffer jest tablicą znakową o wielkości 32kB):

Kod: Zaznacz cały

Buffer              : ARRAY [ 0 .. 7FFFH ] OF CHAR ;


Buffer [ 7FF0H ] := CHR ( 0EAH ) ;
Buffer [ 7FF1H ] := CHR ( 003H ) ;
Buffer [ 7FF2H ] := CHR ( 000H ) ;
Buffer [ 7FF3H ] := CHR ( 000H ) ;
Buffer [ 7FF4H ] := CHR ( 0F8H ) ;
Buffer [ 7FF5H ] := BIOSDate [ 0 ] ;
Buffer [ 7FF6H ] := BIOSDate [ 1 ] ;
Buffer [ 7FF7H ] := BIOSDate [ 2 ] ;
Buffer [ 7FF8H ] := BIOSDate [ 3 ] ;
Buffer [ 7FF9H ] := BIOSDate [ 4 ] ;
Buffer [ 7FFAH ] := BIOSDate [ 5 ] ;
Buffer [ 7FFBH ] := BIOSDate [ 6 ] ;
Buffer [ 7FFCH ] := BIOSDate [ 7 ] ;
Buffer [ 7FFDH ] := ' ' ;
Buffer [ 7FFEH ] := ' ' ;
Buffer [ 7FFFH ] := ' ' ;
Na żywca wstawiony jest tam kod instrukcji długiego skoku (EA hex). Ta instrukcja ma dwa operandy: zawartość rejestru IP (ofset) oraz zawartość rejestru CS (segment). Pamiętając o „wielkości indianina” dla procków PC, starsza część jest na starszym adresie: powyższa instrukcja jest następująca: JMP F800:3. Dlaczego tyle? Trochę rachunków:
Instrukcja od reset znajduje się na adresie w sensie procka x86 (wszystko w zapisie hex): FFFF:0 co daje adres na szynie adresowej FFFF << 4 + 0 = FFFF0 + 0 = FFFF0. Jest to 16 bajtów przed fizycznym końcem przestrzeni (fizyczny koniec to FFFFF). Pamięć EPROM 27C256 to 8000 bajtów. Obliczając lokację tego układu to swój koniec ma na FFFFF zaś początek na F8000. Adres F8000 w sensie zawartości rejestrów do adresowania to: F800:0. Pamiętając, że na samym początku w części asemblerowej jest „future connector”, realny początek programu jest 3 bajty dalej. Stąd adres do długiego skoku po reset to F800:3.
Dla tropicieli:
Exp32k.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony piątek 07 cze 2019, 16:57 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » czwartek 06 cze 2019, 01:53

Opisałem wyżej strukturę pliku z programem EXE. Ta cenna informacja pozwoli na wykonanie kolejnego kroku do celu. Rozpatrzmy taki kawałek programu, występują tu znane już struktury danych. Istotny fragment jest następujący (program nazywa się RUNEXE.EXE, Anno Domini 1994):

Kod: Zaznacz cały

PROCEDURE LoadAndExecuteExe ;

  VAR
    Base                : ADDRESS ;
    A0                  : ADDRESS ;
    NewProgEntry        : ADDRESS ;
    NewProgram          : POINTER TO PROCEDURE ;
    ExeFile             : FILE ;
    ExeHeader           : POINTER TO RealMode_Header ;
    Fx_Ptr              : POINTER TO rm_Fixups ;
    ProgBase            : CARDINAL ;
    Inx                 : CARDINAL ;
    Pages               : CARDINAL ;
    LinkBase            : CARDINAL ;
    ParaNum             : POINTER TO CARDINAL ;

  BEGIN (* LoadAndExecuteExe *)
    ClrScr ;
    OPEN ( ExeFile , 'WINDEMO.EXE' , read ) ;
    IF ExeFile . AN <> 0 THEN
      Write ( 'file not open' ) ;
      RETURN ;
    END (* IF *) ;
    ALLOCATE ( A0 , 512 ) ;
    Base := [ Seg ( A0 ^ ) + ( Ofs ( A0 ^ ) >> 4 ) : 0 ] ;
    A0 := Base ;
    REPEAT
      SeqRead ( ExeFile , A0 , TSIZE ( Sector ) ) ;
      A0 := [ Seg ( A0 ^ ) + ( TSIZE ( Sector ) >> 4 ) : 0 ] ;
    UNTIL ExeFile . AN <> 0 ;
    CLOSE ( ExeFile ) ;
    ExeHeader := Base ;
    WITH ExeHeader ^ DO
      GoToXY ( 0 , 0 ) ;
      Write ( 'Bytes_on_last_page      = ' ) ;
      WriteHex ( Bytes_on_last_page ) ;
      WriteLn ;
      Write ( 'Pages_in_file           = ' ) ;
      WriteHex ( Pages_in_file ) ;
      WriteLn ;
      Write ( 'FixupNumber             = ' ) ;
      WriteHex ( FixupNumber ) ;
      WriteLn ;
      Write ( 'Paragraphs_in_header    = ' ) ;
      WriteHex ( Paragraphs_in_header ) ;
      WriteLn ;
      Write ( 'SS                      = ' ) ;
      WriteHex ( SS ) ;
      WriteLn ;
      Write ( 'SP                      = ' ) ;
      WriteHex ( SP ) ;
      WriteLn ;
      Write ( 'IP                      = ' ) ;
      WriteHex ( IP ) ;
      WriteLn ;
      Write ( 'CS                      = ' ) ;
      WriteHex ( CS ) ;
      WriteLn ;
      Write ( 'Fixup_table_address     = ' ) ;
      WriteHex ( Fixup_table_address ) ;
      WriteLn ;
      Pages := Pages_in_file ;
      ProgBase := Seg ( Base ^ ) + Paragraphs_in_header ;
      LinkBase := ProgBase ;
      Write ( 'ProgBase                = ' ) ;
      WriteHex ( ProgBase ) ;
      WriteLn ;
      Write ( 'LinkBase                = ' ) ;
      WriteHex ( LinkBase ) ;
      WriteLn ;
      Fx_Ptr := [ Seg ( Base ^ ) : Ofs ( Base ^ ) + Fixup_table_address ] ;
      FOR Inx := 1 TO FixupNumber DO
        WITH Fx_Ptr ^ [ Inx ] DO
          ParaNum := [ rm_Fixup_para + ProgBase : rm_Fixup_offs ] ;
          ParaNum ^ := ParaNum ^ + LinkBase ;
        END (* WITH *) ;
      END (* FOR *) ;
      WriteLn ;
      WriteLn ;
      WriteLn ;
      Write ( 'SS : SP = ' ) ;
      WriteHex ( SS + LinkBase ) ;
      Write ( ' : ' ) ;
      WriteHex ( SP ) ;
      WriteLn ;
      Write ( 'CS : IP = ' ) ;
      WriteHex ( CS + LinkBase ) ;
      Write ( ' : ' ) ;
      WriteHex ( IP ) ;
      WriteLn ;
      NewProgEntry := [ CS + ProgBase : IP ] ;
      NewProgram := ADR ( NewProgEntry ) ;
      NewProgram ^ ;
    END (* WITH *) ;
  END LoadAndExecuteExe ;
Ten program (nie wczytuje nazwy pliku, tylko ma podstawioną [WINDMO.EXE], w końcu służył do sprawdzenia technologii i przekonania się o słuszności koncepcji, poczynań), wczytuje do pamięci RAM zawartość programu EXE, dokonuje korekt adresowych i uruchamia tak „wylinkowany” program. Program normalnie działa i jak skończy swoją pracę, to następuje normalny powrót do domu (DOS'a). Jest tylko jeden szczegół, który (moim zdaniem) odróżnia uruchomienie programu od akcji realizowanej przez DOS. Mianowicie, program [WINDEMO.EXE] idzie „w kosztach” programu uruchamiającego [RUNEXE.EXE], czyli korzysta z jego stosu. Nie jest zrealizowana akcja podmiany zawartości rejestrów SS:SP, ale jak wspomniałem nie to jest celem tego doświadczenia. Skupiałem się nad procedurą manipulacji środowiska do tego stopnia, by umożliwić sprawną realizację tematu.

Kod: Zaznacz cały

    ALLOCATE ( A0 , 512 ) ;
    Base := [ Seg ( A0 ^ ) + ( Ofs ( A0 ^ ) >> 4 ) : 0 ] ;
    A0 := Base ;
Pierwszym krokiem jest uzyskanie „przyczółka zaczepnego” od DOS. Akcja ALLOCATE (odpowiednik memalloc) daje wskaźnik do jakiegoś obszaru. W DOS zwyczajowo ten obszar jest lokowany za programem. Można uznać, że w ten sposób program RUNEXE.EXE nie zrobi krzywdy sam sobie ani innym, co mogłoby się zdarzyć. Z drugiej strony jest to zachowanie trochę niefrasobliwe, gdyż program RUNEXE.EXE traktuje pamięć RAM od tego miejsca po horyzont jako swoją piaskownicę. Nie realizuje kolejnych wywołań ALLOCATE, gdyż nie ma gwarancji, że kolejny fragment będzie na styk z poprzednim a jest potrzebny obszar lity bez „obcych wtrąceń”.
Po przydzieleniu obszaru, realizowana jest akcja „uparafgafienia” obszaru. Wszystko musi odbyć się zgodnie z prawem i wymogami środowiska: program musi się znaleźć na adresie podzielnym przez 16. Tu warto zwrócić uwagę, że wielkość nagłówka oraz tablicy FixUp'ów jest wyrażona w paragrafach (jest wielokrotnością 16 bajtów). Daje to gwarancję, że sam kod programu jest również w „oczkach” paragrafów. Po powyższej akcji pokaźnik Base wskazuje na poprawne miejsce w pamięci RAM.

Kod: Zaznacz cały

    REPEAT
      SeqRead ( ExeFile , A0 , TSIZE ( Sector ) ) ;
      A0 := [ Seg ( A0 ^ ) + ( TSIZE ( Sector ) >> 4 ) : 0 ] ;
    UNTIL ExeFile . AN <> 0 ;
W dalszej kolejności jest wczytana zawartość pliku z uruchamianym programem. Dane są transmitowane do obszaru wskazanego przez pokaźnik A0 (startowo identyczny jako pokaźnik Base). Po każdej transmisji (po 512 bajtów), pokaźnik A0 jest przesuwany o 512 bajtów dalej (w sensie realnego adresu, bo zwiększana jest zawartość segmentowej części adresu, pozostawiając część ofsetową zerową).

Kod: Zaznacz cały

      ProgBase := Seg ( Base ^ ) + Paragraphs_in_header ;
      LinkBase := ProgBase ;
Istotne podstawienia: ProgBase (zmienna typu unsigned short) będzie zawierała segmentową część adresu, gdzie jest załadowany uruchamiany program. Pokaźnik Base wskazuje początek obszaru, do którego wczytany jest kod programu, czyli wskazuje na nagłówek exeka w pamięci RAM i dodana jest wielkość nagłówka wyrażona w paragrafach. W ten sposób mamy zawartość rejestru CS dla ładowanego programu. Ten wczytany kod programu należy wylinkować w pamięci. Mamy zmienną LinkBase określającą na jaki obszar jest linkowany program. W tym przypadku są to tożsame informacje, ale tak nie musi być (o czym napiszę w kolejnej części).

Kod: Zaznacz cały

      Fx_Ptr := [ Seg ( Base ^ ) : Ofs ( Base ^ ) + Fixup_table_address ] ;
      FOR Inx := 1 TO FixupNumber DO
        WITH Fx_Ptr ^ [ Inx ] DO
          ParaNum := [ rm_Fixup_para + ProgBase : rm_Fixup_offs ] ;
          ParaNum ^ := ParaNum ^ + LinkBase ;
        END (* WITH *) ;
      END (* FOR *) ;
Kolejny fragment: na obszar zawierający listę FixUp'ów jest „nałożona” lokalna zmienna. Zawiera ona lokacje w obszarze kodu ładowanego programu, gdzie należy wnieść korektę. To miejsce musi byś zwiększone o zawartość LinkBase (w tym przypadku tożsame z ProgBase → bez oszustw i kantów program staje się „prawdziwy” i możliwe jest jego uruchomienie).
I to co tygryski lubią najczęściej:

Kod: Zaznacz cały

      NewProgEntry := [ CS + ProgBase : IP ] ;
      NewProgram := ADR ( NewProgEntry ) ;
      NewProgram ^ ;
[ CS + ProgBase : IP ] to nic innego jak entry point do ładowanego programu. Zmienna NewProgram to pokaźnik do zmiennej typu procedure bez parametrów. NewProgram ^ ; jest skokiem (właściwnie CALL'em) na entry point nowego programu. Nie zachodzi tu podmiana stosu, bo nie to jest celem eksperymentu. Nawet jak nowy program zostanie zakończony (przez odpowiednie odwołanie do DOS), to program RUNEXE.EXE zostanie zakończony. Bidula DOS nie ma bladego pojęcia, że zaistniał szacher macher i ekstrakod kończący pracę programu wydany przez WINDEMO.EXE na klatę weźmie program RUNEXE.EXE.
Program WINDEMO.EXE jest napisany po „bożemu”. Grzecznie używa funkcji BIOS'a i DOS'a, co gwarantuje, że sprawnie pojedzie przykładowo w okienku DOSBOX.
bios-05_ilu01.png
Można to sobie puścić: dołączam na końcu wszystko do oblukania. Puszczając program WINDEMO.EXE (jest to wielowątkowy program w moduli) wymagane jest trochę cierpliwości, program kończy pracę po naciśnięciu dowolnego klawisza, ale to troszkę trwa (czyli bez paniki).
Tak już przy okazji, program WINDEMO.MOD (firmowa reklamówka z Jensen & Partners International), w ciekawym fragmencie to:

Kod: Zaznacz cały

PROCEDURE Demo ;
VAR
   WD : Window.WinDef ;
BEGIN
   Lib.RANDOMIZE ;
   Initialize := TRUE ;
   Window.SetProcessLocks(Process.Lock,Process.Unlock) ;
   Window.CursorOff ;
   Window.Clear ;
    WITH WD DO
      X1 := 10 ; Y1 := 2 ;
      X2 := 60 ; Y2 := 8 ;
      Foreground := Window.White ;
      IF IsBW THEN
        Background := Window.Black ;
      ELSE
        Background := Window.Red ;
      END ;
      CursorOn   := FALSE ;
      WrapOn     := TRUE ;
      Hidden     := FALSE ;
      FrameOn    := TRUE ;
      FrameDef   := Window.SingleFrame ;
      FrameFore  := Window.White ;
      FrameBack  := Background ;
    END ;
    W1 := Window.Open(WD) ;
    Window.SetTitle(W1,' Process 1 ',Window.CenterUpperTitle);
    WITH WD DO
      X1 := 40 ; Y1 := 2 ;
      X2 := 70 ; Y2 := 12 ;
      Foreground := Window.White ;
      Background := Window.Blue ;
      CursorOn   := FALSE ;
      WrapOn     := TRUE ;
      Hidden     := FALSE ;
      FrameOn    := TRUE ;
      FrameDef   := Window.DoubleFrame ;
      FrameFore  := Window.Black ;
      FrameBack  := Window.Cyan ;
      IF IsBW THEN
         Background := Window.Black ;
         FrameBack := Window.LightGray
      END ;
    END ;
    W2 := Window.Open(WD) ;
    Window.SetTitle(W2,' Process 2 ',Window.CenterUpperTitle);
    RandPalette(pal) ;
    WITH WD DO
      X1 := 40 ; Y1 := 6 ;
      X2 := 50 ; Y2 := 17 ;
      pal[0].Fore:= Window.Yellow ;
      pal[0].Back:= Window.Black ;
      CursorOn   := FALSE ;
      WrapOn     := TRUE ;
      Hidden     := FALSE ;
      FrameOn    := TRUE ;
      FrameDef   := Window.SingleFrame ;
      pal[1].Fore:= Window.White ;
      pal[1].Back:= Window.Green ;
      IF IsBW THEN
         pal[0].Fore:= Window.LightGray ;
         pal[1].Back:= Window.Black ;
      END ;
    END ;
    W3 := Window.PaletteOpen(WD,pal) ;
    Window.SetTitle(W3,'Process 3',Window.CenterUpperTitle);
    WITH WD DO
      X1 := 10 ; Y1 := 10 ;
      X2 := 70 ; Y2 := 20 ;
      Foreground := Window.Black ;
      Background := Window.LightGray ;
      CursorOn   := FALSE ;
      WrapOn     := TRUE ;
      Hidden     := FALSE ;
      FrameOn    := TRUE ;
      FrameDef   := Window.DoubleFrame ;
      FrameFore  := Window.White ;
      FrameBack  := Window.Green ;
      IF IsBW THEN
        FrameBack  := Window.Black ;
      END ;
    END ;
    W4 := Window.Open(WD) ;
    Window.SetTitle(W4,' Process 4 ',Window.CenterUpperTitle);

    Process.StartScheduler ;

    Process.StartProcess(P1,2000,0) ;
    Process.StartProcess(P2,2000,0) ;
    Process.StartProcess(P3,2000,0) ;
    Process.StartProcess(P4,2000,0) ;
    Process.StartProcess(P5,2000,0) ;
    Process.StartProcess(P6,2000,0) ;
    Process.StartProcess(P7,2000,0) ;
    Process.StartProcess(P8,2000,0) ;
    Process.StartProcess(P9,2000,0) ;
    Process.Init(DS) ;
    Process.WAIT(DS) ;
    Window.CursorOn ;
END Demo;
Program tworzy 4 okienka i uruchamia 9 procesów. Od tej chwili procedury o nazwach P1 .. P9 stają się nowymi niezależnymi bytami. Każdy ma swój indywidualny stos i nowy odpowiednik funkcji main. Przykładowo:

Kod: Zaznacz cały

(* P1 prints out numbers in Hex, Dec and Binary *)

  PROCEDURE P1;
  VAR S : ARRAY[0..30] OF CHAR;
      i : CARDINAL;
      b : BOOLEAN ;
  BEGIN
    Window.Use(W1) ;
    LOOP
      FOR i := 0 TO 1000 DO
        Str.CardToStr(VAL(LONGCARD,i),S,2,b); IO.WrStr(S);
        IO.WrCharRep(' ',16-Str.Length(S));
        IO.WrCard(i,5) ;
        IO.WrStr('   ') ;
        Str.CardToStr(VAL(LONGCARD,i),S,16,b); IO.WrStr(S);
        IO.WrCharRep(' ',5-Str.Length(S));
        IO.WrLn;
      END;
    END ;
  END P1;
bierze we władanie okienko W1 i coś tam smaruje.

Kod: Zaznacz cały

(* P5 Re-sizes and Re-arranges text *)

  PROCEDURE P5;
  BEGIN
    LOOP
      Process.Delay(10+Lib.RANDOM(20)) ;
      CASE Lib.RANDOM(4) OF
        0 : Window.PutOnTop(W1) |
        1 : Window.PutOnTop(W2) |
        2 : Window.PutOnTop(W3) |
        3 : Window.PutOnTop(W4)
      END ;
      Process.Delay(10+Lib.RANDOM(20)) ;
      CASE Lib.RANDOM(4) OF
        0 : RChangeSize(W1) |
        1 : |
        2 : |
        3 : RChangeSize(W4)
      END ;
    END  ;
  END P5;
Proces P5 wahluje okienkami.

Kod: Zaznacz cały

(* P6 Moves window 1 *)

  PROCEDURE P6 ;
  BEGIN
    Rmove(W1) ;
  END P6 ;
Proces P6 zmienia położenie okienka W1. Tu widać ciekawe zjawisko, proces P1 (ten który smaruje po ekranie) nie jest w żaden sposób świadomy tego, że jest sterowany. Podobnie P6 nie rozumie, dlaczego piękne okienko, które on przesuwa nagle przestało być widoczne, bo przykryło je inne. No taka jest rzeczywistość.
Dla tropicieli:
runexe.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony piątek 07 cze 2019, 17:00 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » czwartek 06 cze 2019, 23:26

Canon... a może kanony należy łamać....https://www.youtube.com/watch?v=XZgiNnGB8m4

Rozwijając powyższą koncepcję można stworzyć program adaptacji programu w formacie EXE (taki klasyczny wygenerowany pod DOS). Miałem rozkminiony format EXE pod systemu OS2, który generalnie jest zgodny z dos'owym. Tam dochodzi mechanizm DLL, czyli doładowywania fragmentów kodu z oddzielnych plików (o rozszerzeniu DLL), które są linkowane dynamicznie w trakcie ładowania. Na razie nie znalazłem w swoich CD'ach śladów po tych eksperymentach, ale jestem pełen nadziei. Szczegółów nie pamiętam więc nie będę tematu rozwijał, chyba że stanie się... jakiś cud i się odnajdzie (chociaż w jakiś sposób pofragmentowany jest na posiadanych CD'ach, ale temat jest złożony).
Można by stworzyć taki uniwersalny kawałek do BIOS'a, który ładowałby do RAM z pamięci EPROM wprost EXE, który nie jest problemem już wylinkować na określoną lokację. Można, ale … No i rozchodzi się o to ale → trochę szkoda zasobów w EPROM. Jakiś program o większej objętości może mieć sporą tablicę fix up'ów, która ma znaczenie jedynie w fazie ładowania programu i później nie jest potrzebna do czegokolwiek. Trochę serce boli patrząc na marnotrawstwo, więc następuje odzysk w najczystszej postaci. A gdyby tak za pomocą dedykowanego programu stworzyć zawartość do EPROM już wylinkowaną na określoną lokację w RAM. Problem żaden ale ta operacja wymaga, by część istotnych danych została przeniesiona z jednego świata do drugiego (choćby entry point lub lokacja stosu zapisana w EXE musi w jakiś sposób przenieść się do EPROM). No więc rozwiązaniem może być stworzenie własnego, prywatnego nagłówka, który znajdzie się w EPROM a za nim będzie kod programu w żywej formie. To doprowadziło do postania programu, któremu nadałem nazwę LINKEXE.
Na nagłówek przewidziałem następującą strukturę:

Kod: Zaznacz cały

TYPE
    RomHeader           = RECORD
                            Signature       : CARDINAL ;
                            ExtraPar        : CARDINAL ;
                            Entry_CS        : CARDINAL ;
                            Entry_IP        : CARDINAL ;
                            Stack_SS        : CARDINAL ;
                            Stack_SP        : CARDINAL ;
                            LinkLocation    : CARDINAL ;
                            Size            : CARDINAL ;
                          END (* RECORD *) ;
Jeżeli BIOS przeszukałby przestrzeń adresową pamięci w poszukiwaniu ściśle określonej sygnatury lokowanej w oczkach paragrafów, to taki EPROM może być lokowany gdziekolwiek i nie musi być umieszczony razem z BIOS'em startowym, choć to nie wyklucza takiego rozwiązania. Sensownym obszarem do przeszukanie jest druga połowa przestrzeni adresowej pamięci. W pierwszej jest pamięć RAM (nawet sięga do 640kB), druga to już różnej maści karty (jak przykładowo BIOS karty VGA, ale on miałby inną sygnaturę). Na końcu tej pamięci znajduje się sam EPROM zawierający BIOS. Jeżeli program z prywatnym nagłówkiem będzie lokowany w EPROM BIOS'a, to też zostanie namierzony, w przyrodzie nic nie ginie. W nagłówku, oprócz wskazania na stos i entry point, przewidziałem informację na jaką lokację został wylinkowany program. BIOS może wziąć pod uwagę te dane i przepisać program z EPROM do RAM (wiadomo dokąd) no i go uruchomić.
Najciekawszy fragment programu LINKEXE to:

Kod: Zaznacz cały

PROCEDURE MakeRom ;

  TYPE
    FNameType           = ARRAY [ 0 .. 15 ] OF CHAR ;

  VAR
    Base                : ADDRESS ;
    A0                  : ADDRESS ;
    NewProgEntry        : ADDRESS ;
    ExeFile             : FILE ;
    RomFile             : FILE ;
    ExeHeader           : POINTER TO RealMode_Header ;
    Fx_Ptr              : POINTER TO rm_Fixups ;
    StartPos            : CARDINAL ;
    TermKey             : CARDINAL ;
    Loop                : CARDINAL ;
    ProgBase            : CARDINAL ;
    Inx                 : CARDINAL ;
    Pages               : CARDINAL ;
    LinkedAt            : CARDINAL ;
    ParaNum             : POINTER TO CARDINAL ;
    ROM_Header          : RomHeader ;
    FName               : FNameType ;
    ExeFName            : FNameType ;
    RomFName            : FNameType ;

  BEGIN (* MakeRom *)
    ClrScr ;
    GoToXY ( 0 , 4 ) ;
    Write ( 'EXE TO ROM maker for IBM PC' ) ;
    GoToXY ( 0 , 6 ) ;
    Write ( 'EXE file name : ' ) ;
    FName := '' ;
    StartPos := 0 ;
    REPEAT
      ReadString ( FName , TSIZE ( FNameType ) , 16 , 6 , TermKey ,
                   StartPos , TRUE ) ;
    UNTIL ( TermKey = crSt ) OR ( TermKey = escSt ) ;
    IF TermKey = escSt THEN
      RETURN ;
    END (* IF *) ;
    Loop := 0 ;
    LOOP
      IF Loop > HIGH ( FName ) THEN
        EXIT ;
      END (* IF *) ;
      IF FName [ Loop ] = '.' THEN
        FName [ Loop ] := 0C ;
        EXIT ;
      END (* IF *) ;
      IF FName [ Loop ] = 0C THEN
        EXIT ;
      END (* IF *) ;
      INC ( Loop ) ;
    END (* LOOP *) ;
    ExeFName := FName ;
    RomFName := FName ;
    Append ( ExeFName , '.EXE' ) ;
    Append ( RomFName , '.ROM' ) ;
    OPEN ( ExeFile , ExeFName , read ) ;
    IF ExeFile . AN <> 0 THEN
      Write ( 'file not open ' ) ;
      Write ( ExeFName ) ;
      RETURN ;
    END (* IF *) ;
    ALLOCATE ( A0 , 512 ) ;
    Base := [ Seg ( A0 ^ ) + ( Ofs ( A0 ^ ) >> 4 ) + 1 : 0 ] ;
    A0 := Base ;
    REPEAT
      SeqRead ( ExeFile , A0 , TSIZE ( Sector ) ) ;
      A0 := [ Seg ( A0 ^ ) + ( TSIZE ( Sector ) >> 4 ) : 0 ] ;
    UNTIL ExeFile . AN <> 0 ;
    CLOSE ( ExeFile ) ;
    ExeHeader := Base ;
    WITH ExeHeader ^ DO
      WriteLn ;
      WriteLn ;
      Write ( 'Bytes_on_last_page      = ' ) ;
      WriteHex ( Bytes_on_last_page ) ;
      WriteLn ;
      Write ( 'Pages_in_file           = ' ) ;
      WriteHex ( Pages_in_file ) ;
      WriteLn ;
      Write ( 'FixupNumber             = ' ) ;
      WriteHex ( FixupNumber ) ;
      WriteLn ;
      Write ( 'Paragraphs_in_header    = ' ) ;
      WriteHex ( Paragraphs_in_header ) ;
      WriteLn ;
      Write ( 'SS                      = ' ) ;
      WriteHex ( SS ) ;
      WriteLn ;
      Write ( 'SP                      = ' ) ;
      WriteHex ( SP ) ;
      WriteLn ;
      Write ( 'IP                      = ' ) ;
      WriteHex ( IP ) ;
      WriteLn ;
      Write ( 'CS                      = ' ) ;
      WriteHex ( CS ) ;
      WriteLn ;
      Pages := Pages_in_file ;
      ProgBase := Seg ( Base ^ ) + Paragraphs_in_header ;
(*      LinkedAt := ProgBase ;*)
      LinkedAt := 400H ;
      Write ( 'ProgBase                = ' ) ;
      WriteHex ( ProgBase ) ;
      WriteLn ;
      Write ( 'Linked At               = ' ) ;
      WriteHex ( LinkedAt ) ;
      WriteLn ;
      Fx_Ptr := [ Seg ( Base ^ ) : Ofs ( Base ^ ) + Fixup_table_address ] ;
      FOR Inx :=1 TO FixupNumber DO
        WITH Fx_Ptr ^ [ Inx ] DO
          ParaNum := [ rm_Fixup_para + ProgBase : rm_Fixup_offs ] ;
          ParaNum ^ := ParaNum ^ + LinkedAt ;
        END (* WITH *) ;
      END (* FOR *) ;
      WriteLn ;
      WriteLn ;
      WITH ROM_Header DO
        Signature := 0A5A5H ;
        Entry_CS := CS + LinkedAt ;
        Entry_IP := IP ;
        IF ( SS = 0 ) AND ( SP = 0 ) THEN
          Stack_SS := 0 ;
          Stack_SP := 0 ;
        ELSE
          Stack_SS := SS + LinkedAt ;
          Stack_SP := SP ;
        END (* IF ... ELSE *) ;
        ExtraPar := Extra_paragraphs_needed ;
        Size := ( Pages * TSIZE ( Sector ) ) DIV 16 - Paragraphs_in_header ;
        Pages := Size DIV 32 ;
        Write ( '=== ROM Header ===' ) ;
        WriteLn ;
        Write ( 'SS : SP = ' ) ;
        WriteHex ( Stack_SS ) ;
        Write ( ' : ' ) ;
        WriteHex ( Stack_SP ) ;
        WriteLn ;
        Write ( 'CS : IP = ' ) ;
        WriteHex ( Entry_CS ) ;
        Write ( ' : ' ) ;
        WriteHex ( Entry_IP ) ;
        WriteLn ;
        Write ( 'Extra paragraphs     = ' ) ;
        WriteHex ( ExtraPar ) ;
        WriteLn ;
        Write ( 'Code (in paragraphs) = ' ) ;
        WriteHex ( Size ) ;
        WriteLn ;
        Write ( 'Code in ROM (pages)  = ' ) ;
        WriteHex ( Pages ) ;
        WriteLn ;
      END (* WITH *) ;
      OPEN ( RomFile , RomFName , write ) ;
      AppendRec ( RomFile , ADR ( ROM_Header ) , TSIZE ( RomHeader ) ) ;
      A0 := [ ProgBase : 0 ] ;
      FOR Inx := 1 TO Pages DO
        AppendRec ( RomFile , A0 , TSIZE ( Sector ) ) ;
        A0 := [ Seg ( A0 ^ ) + ( TSIZE ( Sector ) >> 4 ) : 0 ] ;
      END (* FOR *) ;
      CLOSE ( RomFile ) ;
    END (* WITH *) ;
  END MakeRom ;
Program nie wymaga większego komentarza. Istotne i „trudne” elementy zostały już omówione (tak mi się wydaje, jakbym o czymś zapomniał a sobie przypomnę, to można wrócić, droga jest otwarta). W tym przypadku, LinkBase i ProgBase nie są tożsame. ProgBase to miejsce, gdzie zachodzi akcja: tu i teraz. LinkBase, to miejsce, gdzie będzie zachodzić akcje później po uruchomieniu programu z EPROM, prawdziwy adres domowy dla programu. ProgBase nie jest prawdziwym adresem, to tylko tymczasowe miejsce pobytu wynikające z procesu technologicznego. Po realizacji całej operacji, program LINKEXE zapisuje swoje wypociny do zbioru. W pierwszej kolejności jest nasz nowy nagłówek (zajmuje nieprzypadkowo 16 bajtów – jeden paragraf). W dalszej części jest żywy kod programu. Niestety w tamtych czasach nie przetwarzałem plików w formacie intel-hex (chyba już o tym pisałem, zaczynam się powtarzać). Plik jest zapisany binarnie. Program LINKEXE jest napisany na tyle „po bożemu”, że można go puścić w okienku DOSBOX (używa interruptów DOS'owych do pisania po ekranie), więc wszystko jest podane jawnie na ekranie.
bios-06_ilu01.png
Jest to przykład ćwiczenia samego siebie. Z wyświetlonych informacji można wywnioskować, że program ma 4000 hex (=16kB) stosu programu (tyle wynosi SP). W wyniku pracy programu powstał nowy element rzeczywistości: LINKEXE.ROM.
bios-06_ilu02.png
Lektura może i mało fascynująca, jednak warto sprawdzić do się dzieje na początku pliku:
bios-06_ilu03.png
Mamy między innymi sygnaturę A5A5, CS:IP=045B:0004, SS:SP=05D2:4000, docelowe miejsce ładowania (LinkBase=0400). Wszystko zgadza się na sztuki.

I to by było na tyle, czyli tyle zrobiłem do tej pory (w sensie oprogramowania). Koncepcja nie jest skomplikowana, jest do opanowania. Wiadomo, że czasami bywają programy trochę większe niż to co da się zmieścić w jednym układzie EPROM, tym nawet 27C512. To daje możliwość tworzenia programów do 64kB. A co dalej? Apetyty rosną i może dałoby się sięgnąć tak dalego jak gwiazdy na niebie. Do wciągnięcia programu do pamięci wystarczy prosty zasobnik. W gruncie rzeczy potrzebny jest dostęp sekwencyjny do źródła takiego programu. Poczyniłem nawet pewne rozważania sprzętowe. Generalnie schemat nie wymaga (chyba) większego komentarza, no może z wyjątkiem kilku miejsc. Więc konstrukcja rodem z prehistorii: XX-wiek.
bios-06_ilu04.png
bios-06_ilu05.png
Dałem złącze 2*32 piny takie klasyczne jakie jest stosowane do eurokarty. Normalne karty wkładane w slot wymagają złocenia, a to był kiedyś jakiś tam problem. Tak sobie patrzę, że standardowe złącze do ISA ma ten sam footprint co dwurzędowe złącze stosowane w eurokartach. Pomyślałem sobie, że wywrę ją (złączkę) z płyty głównej i wlutuję złącze do eurokarty. Koncepcja jak koncepcja. Możliwe było ewentualnie inne rozwiązanie. Miałem trochę przejściówek do kart, taki kawałek laminatu ze złoconymi stykami i wlutowaną złączką ISA (takie kątowe). Jest to też jakieś rozwiązanie.
bios-06_ilu06.png
Dekoder adresowy planowałem zrealizować na bazie układów GAL.
bios-06_ilu08.png
Na karcie byłaby jakaś banda pamięci EPROM adresowana z liczników 7493. Potrzebny jest dostęp sekwencyjny, więc wystarczy, że karta będzie miała możliwość wyzerowania licznika, by zawrócić na początek, możliwość inkrementowania adresu i odczytu bajtu z EPROM wskazywanego stanem liczników.
bios-06_ilu09.png
Jakby zaistniała taka karta, to warto ja wyposażyć w kanały transmisji szeregowej, klasyczne rozwiązanie rodem z PC. To zwalnia z konieczności stosowania standardowego Multi IO. Tak patrzę na powyższy schemat i dostrzegam błędy. Coś w interfejsie RS232 znajdują się same odbiorniki. No cóż, każdy popełnia błędy, ważne jest by je naprawić. W tym przypadku to nie ma znaczenia, bo konstrukcja nie wyszła poza fazę schematu, jednak muszę bezkrytycznie i uczciwie przyznać: na schemacie jest szmata (którą dostrzegłem dopiero teraz pisząc ten tekst, więc nic nie będę zmieniał).
bios-06_ilu10.png
Ten kawałeczek z niczym mi się nie kojarzy, nie wiem do czego był przewidziany.
bios-06_ilu11.png
Proc C51 ze sporym własnym systemem. Nie pamiętam, co poeta miał na myśli tworząc ten schemat. Można jedynie spekulować, niemniej taki kawałeczek zaistniał.
Powyższe rozwiązanie jakoś nie wzbudziło we mnie zachwytu, więc doczekało się pewnej modyfikacji. Warto czasami przemyśleć to co zaszło i wnieść zmiany na lepsze. Ja zawsze wierzę w zmiany na lepsze. Reedycja jest już rozwiązaniem rodem z XXI wieku (na dokumentach widzę datę 2001 rok, to prawie współczesność).
bios-06_ilu12.png
bios-06_ilu13.png
bios-06_ilu14.png
bios-06_ilu15.png
bios-06_ilu16.png
Tu jest ten sam błąd. Dziwne, do układów 16C450 doprowadzone są sygnały jedynie na wejście szeregowe. A może to nie jest błąd. To mi wygląda (tak sobie teraz spekuluję) na sprzętowe podsłuchiwanie transmisji szeregowej. Dziwne połączenie złącza męskiego z żeńskim... Wszystkie linie przechodzą przez odbiornik RS232 i jako sygnały TTL wchodzą na UARTY (na jeden z nich wchodzi RxD, na drugi TxD). Pozostałe sygnały modemowe wychodzą na LED'y (następny rysunek). Normalnie totalna inwigilacja, jakaś mania podsłuchiwania.
bios-06_ilu17.png
bios-06_ilu18.png
bios-06_ilu19.png
Podstawowe różnice to takie, że została zwiększona pamięć EPROM i zredukowana rola procka C51. Poza elementarną funkcją obsługi drukarki, najważniejszą jego rolą jest podsuwanie dla softu w PC danych nieulotnych. W środowisku proca C51 została umieszczona pamięć EEPROM, więc softowi głównemu (dla proca x86) nie grozi demencja. Zawsze będzie istniał ktoś, co przypomni i podeśle dane nieulotne.
Tym razem powstał projekt nawet karty (jako projekt PCB). Jak na razie skończyło się jedynie na projekcie, fabryka PCB nie zarobiła.
bios-06_ilu20.png
bios-06_ilu21.png
bios-06_ilu22.png
bios-06_ilu23.png
Tyle zrobiłem. Przyszły nowe czasy i nowe wyzwania, stare koncepcje powoli zacierały się w pamięci egzystując jedynie na płytach CD. Zainteresowania z C51 przeszły na AVR, co z kolei przyniosły kolejne wyzwania. Przyszedł rok Anno Domini 2005 gdzie skupiłem swoją uwagę na innych tematach. Powspominać, pomarzyć dobra rzecz. Teraz również mam jakieś marzenie, może warto wyciągnąć coś z lamusa, przetrzeć szmatką i nadać temu nowy blask. Niektóre rzeczy wymagają dokończenia, inne realizacji na nowo. Z pewnością niektóre elementy trzeba zebrać, uwspółcześnić. Powstaje pytanie: czy warto? Uważam, że warto: po to tu jestem. To duże wyzwanie. Cóż najbliższy czas pokaże...
Dla tropicieli:
linkexe.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [PC] Program w EPROM - making of

Postautor: gaweł » sobota 08 cze 2019, 04:38

W pracy "przeszkadzała" muzahttps://www.youtube.com/watch?v=DRarsWWGnXM

No można powiedzieć, że stał się cud... (chociaż tak nie do końca). Namierzyłem w CD'ach swój program, od obróbki bardziej złożonych EXE'ków. Jest to program, który nie był tworzony pod DOS, więc sprawia mi masę problemów z uruchomieniem. O ile daje się skompilować, to mam kłopot z jego wylinkowaniem. Watcom'owny linker się zaparł i tyle! Próbowałem wielu sposobów: nie i koniec. No może to i za skomplikowane dla DOS'a, trzeba trochę odpuścić. Prawdę mówiąc to jest złożony program tworzony do uruchamiania pod systemem protekcyjnym, a to dla DOS'a jest czarną magią. Tak po prawdzie, to objętość programu jest niczego sobie... więc trudno mieć jakąkolwiek pretensję. Po paru próbach z zerowym efektem postanowiłem „skrócić” program do minimum funkcjonalności, czyli uwaliłem jego okienkowość. Również by mieć obiekt do ćwiczeń wygarnąłem ze starego CD'a jakiś program (w sumie mało istotne co robi), który był przeznaczony do pracy pod kontrolą systemu trochę bardziej złożonego niż DOS. Generalnie programy dosyć łatwo jest odwiązać od systemu tworząc jeden (a właściwie kilka) moduł uwzględniający specyfikę danego systemu operacyjnego. W sumie każdy SysOp posiada w miarę podobne funkcje: wszędzie trzeba otworzyć jakiś plik, czytać i pisać z/do pliku oraz coś nasmarować na ekranie oraz coś zgarnąć z klawiatury. Te funkcje są zgromadzone w odpowiednich objectach (w systemie, dla którego było to tworzone wchodziło to jako DLL).
Binarniaki dla systemów lepszych niż DOS zawierają w jednym pliku EXE tak naprawdę dwa programy. Znaczy się mają dwa nagłówki i dwa warianty kodu. Jeden nagłówek jest dla pracy w trybie rzeczywistym (DOS), drugi do pracy w trybie protekcyjnym. Łatwo o tym się przekonać próbując uruchomić jakiś program przeznaczony pod windozę w okienku DOSBOX.
bios-07_ilu01.png
Czy program się uruchomi? Oczywiście, jak najbardziej. Program zostanie załadowany i uruchomiania zostanie jego część przeznaczona do pracy w trybie rzeczywistym i to ona właśnie informuje mnie, że nie ma ochoty na współpracę. Czy tak będzie zawsze? Niekoniecznie, wystarczy zmienić środowisko na odpowiednie i dalsza współpraca zajdzie bez większego problemu.
Wracając do tematu....
Struktury danych i podstawowe stałe obowiązujące dla programów (przynajmniej takie były ponad 20 lat temu), czy dzisiaj coś się zmieniło? Jak do tej pory nie stwierdziłem.

Kod: Zaznacz cały

#define RM_ReservSize   16
#define PM_ReserveSize  8         /* Eight bytes reserved (now) */

#define NE_UNKNOWN      0x0       /* Unknown (any "new-format" OS) */
#define NE_OS2          0x1       /* OS/2 (default)  */
#define NE_WINDOWS      0x2       /* Windows */
#define NE_DOS4         0x3       /* DOS 4.x */
#define NE_DEV386       0x4       /* Windows 386 */

#define NENOTP          0x8000    /* Not a process */
#define NEIERR          0x2000    /* Errors in image */
#define NEBOUND         0x0800    /* Bound Family/API */
#define NEAPPTYP        0x0700    /* Application type mask */
#define NENOTWINCOMPAT  0x0100    /* Not compatible with P.M. Windowing */
#define NEWINCOMPAT     0x0200    /* Compatible with P.M. Windowing */
#define NEWINAPI        0x0300    /* Uses P.M. Windowing API */
#define NEFLTP          0x0080    /* Floating-point instructions */
#define NEI386          0x0040    /* 386 instructions */
#define NEI286          0x0020    /* 286 instructions */
#define NEI086          0x0010    /* 8086 instructions */
#define NEPROT          0x0008    /* Runs in protected mode only */
#define NEPPLI          0x0004    /* Per-Process Library Initialization */
#define NEINST          0x0002    /* Instance data */
#define NESOLO          0x0001    /* Solo data */

typedef struct                  /* real mode .EXE header */
               {
         USHORT      RM_Magic ;      /* Magic number                     */
         USHORT      RM_BtsOnLaPa ;  /* Bytes on last page of file       */
         USHORT      RM_PagesInFi ;  /* Pages in file                    */
         USHORT      RM_FixupNo ;    /* Relocations                      */
         USHORT      RM_ParInHdr ;   /* Size of header in paragraphs     */
         USHORT      RM_MinAlloc ;   /* Minimum extra paragraphs needed  */
         USHORT      RM_MaxAlloc ;   /* Maximum extra paragraphs needed  */
         USHORT      RM_Initial_SS ; /* Initial SS value                 */
         USHORT      RM_Initial_SP ; /* Initial SP value                 */
         USHORT      RM_ChkSum ;     /* Checksum                         */
         USHORT      RM_Initial_IP ; /* Initial IP value                 */
         USHORT      RM_Initial_CS ; /* Initial (relative) CS value      */
         USHORT      RM_FixTabEnt ;  /* File address of relocation table */
         USHORT      RM_OvlNo ;      /* Overlay number                   */
         USHORT      RM_Res [ RM_ReservSize ] ;
                                     /* Reserved words                   */
         ULONG       RM_ProtHdrEnt ; /* File address of new exe header   */
               } RealModeExeHeaderRecT ;

typedef struct                  /* protect mode .EXE header */
               {
         USHORT      PM_Magic ;          /* Magic number NE_MAGIC */
         UCHAR       PM_LinkerVer ;      /* Version number */
         UCHAR       PM_LinkerRev ;      /* Revision number */
         USHORT      PM_EntTabOfs ;      /* Offset of Entry Table */
         USHORT      PM_EntTabLgt ;      /* Number of bytes in Entry Table */
         ULONG       PM_Crc ;            /* Checksum of whole file */
         USHORT      PM_AplicFlags ;     /* Flag word */
         USHORT      PM_AutoData ;       /* Automatic data segment number */
         USHORT      PM_HeapSize ;       /* Initial heap allocation */
         USHORT      PM_StackSize ;      /* Initial stack allocation */
         USHORT      PM_Initial_IP ;     /* Initial IP */
         USHORT      PM_Initial_CS ;     /* Initial CS */
         USHORT      PM_Initial_SP ;     /* Initial SP */
         USHORT      PM_Initial_SS ;     /* Initial SS */
         USHORT      PM_SegmentsCnt ;    /* Count of file segments */
         USHORT      PM_EntInModRefTab ; /* Entries in Module Reference Table */
         USHORT      PM_NonResTabSize ;  /* Size of non-resident name table */
         USHORT      PM_SegTabOfs ;      /* Offset of Segment Table */
         USHORT      PM_ResTabOfs ;      /* Offset of Resource Table */
         USHORT      PM_ResidTabOfs ;    /* Offset of resident name table */
         USHORT      PM_ModuleTabOfs ;   /* Offset of Module Reference Table */
         USHORT      PM_ImpTabOfs ;      /* Offset of Imported Names Table */
         ULONG       PM_NonResidTabEnt ; /* Offset of Non-resident Names Table*/
         USHORT      PM_MovableEnt ;     /* Count of movable entries */
         USHORT      PM_SectorSize ;     /* Segment alignment shift count */
         USHORT      PM_CoResEnt ;       /* Count of resource entries */
         UCHAR       PM_ExeType ;        /* Target operating system */
         UCHAR       PM_AplicFlagsOthers;/* Other .EXE flags */
         UCHAR       PM_Res [ PM_ReserveSize ] ;
                                         /* Pad structure to 64 bytes */
               } ProtModeExeHeaderRecT ;

typedef struct                  /* .EXE segment table entry */
              {
         USHORT      SD_SecotrNo ;        /* File sector of start of segment */
         USHORT      SD_FileSegmLgt ;     /* Number of bytes in file */
         USHORT      SD_SegAttrib ;       /* Attribute flags */
         USHORT      SD_MemSegmLgt ;      /* Minimum allocation in bytes */
              } SegmentDescrRecT ;
/*
 *  Format of SegmentDescrRecT . SD_SegAttrib
 *
 *  Flag word has the following format:
 *
 *      15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0  - bit no
 *          |  |  |  |  | | | | | | | | | | |
 *          |  |  |  |  | | | | | | | | +-+-+--- Segment type DATA/CODE
 *          |  |  |  |  | | | | | | | +--------- Iterated segment
 *          |  |  |  |  | | | | | | +----------- Movable segment
 *          |  |  |  |  | | | | | +------------- Segment can be shared
 *          |  |  |  |  | | | | +--------------- Preload segment
 *          |  |  |  |  | | | +----------------- Execute/read-only for code/data segment
 *          |  |  |  |  | | +------------------- Segment has relocations
 *          |  |  |  |  | +--------------------- Code conforming/Data is expand down
 *          |  |  |  +--+----------------------- I/O privilege level
 *          |  |  +----------------------------- Discardable segment
 *          |  +-------------------------------- 32-bit code segment
 *          +----------------------------------- Huge segment/GDT allocation requested
 *
 */

#define SD_TYPE         0x0007          /* Segment type mask */

#define SD_CODE         0x0000          /* Code segment */
#define SD_DATA         0x0001          /* Data segment */
#define SD_ITER         0x0008          /* Iterated segment flag */
#define SD_MOVE         0x0010          /* Movable segment flag */
#define SD_SHARED       0x0020          /* Shared segment flag */
#define SD_PRELOAD      0x0040          /* Preload segment flag */
#define SD_EXRD         0x0080          /* Execute-only (code segment), or
                                           read-only (data segment) */
#define SD_RELOC        0x0100          /* Segment has relocations */
#define SD_CONFORM      0x0200          /* Conforming segment */
#define SD_EXPDOWN      0x0200          /* Data segment is expand down */
#define SD_DPL          0x0C00          /* I/O privilege level (286 DPL bits) */
#define SD_DISCARD      0x1000          /* Segment is discardable */
#define SD_32BIT        0x2000          /* 32-bit code segment */
#define SD_HUGE         0x4000          /* Huge memory segment, length of
                                         * segment and minimum allocation
                                         * size are in segment sector units */
#define SD_GDT          0x8000          /* GDT allocation requested */

typedef struct              {
                UCHAR         fix_type ;
                UCHAR         fix_attr ;
                USHORT        offset ;
                USHORT        seg_fix ;
                USHORT        ofs_fix ;
              } FixupRecT ;
#define Fixup_LOBYTE            0
#define Fixup_BASE              2
#define Fixup_PTR               3
#define Fixup_OFFSET            5

#define E32RESBYTES3            20
typedef struct                  /* protect 32 bit mode .EXE header */
               {
         USHORT     PM32b_magic ;      /* Magic number E32_MAGIC */
         UCHAR      PM32b_border ;     /* The byte ordering for the .EXE */
         UCHAR      PM32b_worder ;     /* The word ordering for the .EXE */
         ULONG      PM32b_level ;      /* The EXE format level for now = 0 */
         USHORT     PM32b_cpu ;        /* The CPU type */
         USHORT     PM32b_os ;         /* The OS type */
         ULONG      PM32b_ver ;        /* Module version */
         ULONG      PM32b_mflags ;     /* Module flags */
         ULONG      PM32b_mpages ;     /* Module # pages */
         ULONG      PM32b_startobj ;   /* Object # for instruction pointer */
         ULONG      PM32b_eip ;        /* Extended instruction pointer */
         ULONG      PM32b_stackobj ;   /* Object # for stack pointer */
         ULONG      PM32b_esp ;        /* Extended stack pointer */
         ULONG      PM32b_pagesize ;   /* .EXE page size */
         ULONG      PM32b_pageshift ;  /* Page alignment shift in .EXE */
         ULONG      PM32b_fixupsize ;  /* Fixup section size */
         ULONG      PM32b_fixupsum ;   /* Fixup section checksum */
         ULONG      PM32b_ldrsize ;    /* Loader section size */
         ULONG      PM32b_ldrsum ;     /* Loader section checksum */
         ULONG      PM32b_objtab ;     /* Object table offset */
         ULONG      PM32b_objcnt ;     /* Number of objects in module */
         ULONG      PM32b_objmap ;     /* Object page map offset */
         ULONG      PM32b_itermap ;    /* Object iterated data map offset */
         ULONG      PM32b_rsrctab ;    /* Offset of Resource Table */
         ULONG      PM32b_rsrccnt ;    /* Number of resource entries */
         ULONG      PM32b_restab ;     /* Offset of resident name table */
         ULONG      PM32b_enttab ;     /* Offset of Entry Table */
         ULONG      PM32b_dirtab ;     /* Offset of Module Directive Table */
         ULONG      PM32b_dircnt ;     /* Number of module directives */
         ULONG      PM32b_fpagetab ;   /* Offset of Fixup Page Table */
         ULONG      PM32b_frectab ;    /* Offset of Fixup Record Table */
         ULONG      PM32b_impmod ;     /* Offset of Import Module Name Table */
         ULONG      PM32b_impmodcnt ;  /* Number of entries in Import Module Name Table */
         ULONG      PM32b_impproc ;    /* Offset of Import Procedure Name Table */
         ULONG      PM32b_pagesum ;    /* Offset of Per-Page Checksum Table */
         ULONG      PM32b_datapage ;   /* Offset of Enumerated Data Pages */
         ULONG      PM32b_preload ;    /* Number of preload pages */
         ULONG      PM32b_nrestab ;    /* Offset of Non-resident Names Table */
         ULONG      PM32b_cbnrestab ;  /* Size of Non-resident Name Table */
         ULONG      PM32b_nressum ;    /* Non-resident Name Table Checksum */
         ULONG      PM32b_autodata ;   /* Object # for automatic data object */
         ULONG      PM32b_debuginfo ;  /* Offset of the debugging information */
         ULONG      PM32b_debuglen ;   /* The length of the debugging info. in bytes */
         ULONG      PM32b_instpreload ;/* Number of instance pages in preload section of .EXE file */
         ULONG      PM32b_instdemand ; /* Number of instance pages in demand load section of .EXE file */
         ULONG      PM32b_heapsize ;   /* Size of heap - for 16-bit apps */
         ULONG      PM32b_stacksize ;  /* Size of stack */
         UCHAR      PM32b_res [ E32RESBYTES3 ] ;
                                        /* Pad structure to 196 bytes */
               } Prot32BModeExeHeaderRecT ;
No więc trochę zdegradowany program po uruchomieniu coś wysmarował na ekranie.
bios-07_ilu02.png
Świsnęło i przeleciało, więc drugie podejście z przekierowaniem strumienia wyjściowego do pliku. Program wyprodukował coś takiego (dla TEST.EXE):

Kod: Zaznacz cały

Segmented-EXE Header Analyser

Real mode header :  [0000..003F]
  Magic number                    : 5A4D 'MZ'
  Bytes on last page of file      :     23 [0017]
  Pages in file                   :     45 [002D]
  Relocations                     :      0 [0000]
  Size of header in paragraphs    :      4 [0004]
  Minimum extra paragraphs needed :      0 [0000]
  Maximum extra paragraphs needed :  65535 [FFFF]
  Initial stack SS:SP             : 0000:00B8   [0:184]
  Entry point CS:IP               : 0000:0000   [0:0]
  Checksum                        : 0000
  File address of relocation table:       64 [00000040]
  File address of new exe header  :      128 [00000080]
  Reserved words                  :
                        0000 0000 0000 0000 0000 0000 0000 0000
                        0000 0000 0000 0000 0000 0000 0000 0000



Protect mode header :  [0080..00BF]
  Magic number                    : 454E 'NE'
  Module                          :
  Entry point CS:IP               : 0001:0000   [1:0]
  Initial stack SS:SP             : 0003:0000   [3:0]
  Checksum                        : C0721026
  Linker version number           : 5.10
  Flag word                       : 0009
                        Runs in protected mode only
                        Data Shared
  Automatic data segment number   :      3 [0003]
  Initial heap allocation         :      0 [0000]
  Initial stack allocation        :      0 [0000]
  Target operating system         :   OS/2
  Offset of Entry Table           :      301 [0000012D] Size =      2 [0002]
  Entries in Module Reference Tab :    129 [0081]
  Offset of Segment Table         :    192 [00C0]
  Count of file segments          :      2
  Offset of Resource Table        :      208 [000000D0] Size =      0 [0000]
  Offset of resident name table   :    208 [00D0]
  Offset of Module Reference Table:    220 [00DC]
  Offset of Imported Names Table  :    222 [00DE]
  Offset of Non-resident Names Tab:      303 [0000012F] Size =     38 [0026]
  Count of movable entries        :      0
  Segment alignment shift count   :      9 [512]
  Other .EXE flags                :      0

Import liste


Segments :

  No.  [Sect][File ofs][File seg lgt][Mem seg lgt][Status]
     1   0001  00000200     559B         559C       0D20
                        EXEC & READ CODE,NONCONFORMING,LOADONCALL,NOIOPL,16 bit SEG,
                        SHARED,RELOCS,
     2   002C  00005800     0017         0018       0C20
                        EXEC & READ CODE,NONCONFORMING,LOADONCALL,NOIOPL,16 bit SEG,
                        SHARED,


Fixup liste : Segment 1, number of fixup 9
       TYPE   OFFSET TARGET
     1  BASE   387D segment 1 offset 0000
     2  BASE   34C1 segment 2 offset 0000
     3  PTR    36D6 TermLib.? 0009
     4  PTR    25EA TermLib.? 0013
     5  PTR    25E5 TermLib.? 001D
     6  PTR    3701 TermLib.? 0025
     7  PTR    3B14 TermLib.? 0033
     8  PTR    3AF7 TermLib.? 003D
     9  PTR    1E72 TermLib.? 0045
Program jest kompatybilny z systemem OS/2, a TermLib to jest właśnie DLL dolinkowywany przez system dynamicznie w chwili uruchomienia programu.
Programy windozowe również są protekcyjne. Napisany program mówi, że (dla TEST2.EXE – jakiś program wygarnięty pod windozy):

Kod: Zaznacz cały

Segmented-EXE Header Analyser

Real mode header :  [0000..003F]
  Magic number                    : 5A4D 'MZ'
  Bytes on last page of file      :     80 [0050]
  Pages in file                   :      2 [0002]
  Relocations                     :      0 [0000]
  Size of header in paragraphs    :      4 [0004]
  Minimum extra paragraphs needed :     15 [000F]
  Maximum extra paragraphs needed :  65535 [FFFF]
  Initial stack SS:SP             : 0000:00B8   [0:184]
  Entry point CS:IP               : 0000:0000   [0:0]
  Checksum                        : 0000
  File address of relocation table:       64 [00000040]
  File address of new exe header  :      256 [00000100]
  Reserved words                  :
                        0000 0000 0000 0000 0000 0000 0000 0000
                        0000 0000 0000 0000 0000 0000 0000 0000



Unknown type of new exe header
  Magic number                    : 4550 'PE'
… nie rozumie. No cóż, bywa i tak, w końcu windoza to nie OS2.

Do oblukania (wariant „starożytny”, który sprawia kłopoty):
OS2_Viewexe.zip

Do oblukania (wariant obecny, działający):
DOS_Viewexe.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „Retro”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 12 gości