No já si s pointery a paměťovými modely strašně dlouho nelámal hlavu, protože takové ty typicky školní projekty v pohodě jely v 64kB limitu (na VŠ, FIT). Potom ale člověk narazil v praxi a to velmi rychle. Třeba udělat si backbuffer VGAčka 640x480, to je cirka 150kB, tedy tři segmenty. A TL;DR; tady už BC narážel i v huge režimu, ne vždycky totiž platilo, že si upravuje i segmentovou část ukazatele.
Jediné řešení - jít to Watcomu nebo DJGPP (nebo už rovnou na Windows :-), protože zdaleka ne všechny předměty povolovaly Linux.
Aký bol vlastne dôvod zavedenia segmentácie s tým bizarným prekryvom? Ak by boli segmenty bez prekryvu (tzn. výpočet efektívnej adresy DS << 16 | DX) a existovali by inštrukcie pre aritmetiku nad pármi seg:offs registrov (ideálne vrátane blokových), kompatibilita s 8080 kódom by zostala zachovaná v podobnej miere, aj tiny/small modely by fungovali rovnako úsporne, no zároveň by sa dal výrazne príčetnejšie písať kód operujúci mimo hraníc 16b adr. priestoru. V princípe by to bolo analogické riešenie k 8-bitovým CPU so 16b adresovaním pármi 8b registrov.
3. 6. 2025, 16:25 editováno autorem komentáře
IMHO Intel o tom moc nepřemýšlel a rozšířil možnosti CPU 8085 (to je taková lepší 8080). Kdyby to bylo, jak píšeš, bylo by to super, protože by od začátku mohla být paměť s 32bitovou adresou - něco co neuměla ani 68000.
Jedinej důvod, který mě napadá, je ten, že takto se nemusí použít celých 64kB například na zásobník, ale může to být fine-tunovatelné po 16 bajtech. Jinak je to celý špatně :-)
Ten pár registrů z 8-bitů moc efektivní není, bo musí přepočítat oba registry. U těch x86 segmentů se naopak dá paměť rozdělit na dostatečně malé kousky, aby jakákoliv struktura začínala na offset 0 a posun v poli mění jenom jeden registr.
Ono to v zásadě nějak stačilo docela dlouho. Peklo pak bylo adresování ve VGA, ale VGA přišla až po 32-bitové i386 . To, že byl 16-bitový DOS majoritní systém ještě 10 let po uvedené i386, je jiná pohádka. Tím nechcu říct, že to byl dobrý design...
Ohledně 68000 - ta právě uměla 32-bitové adresy od začátku, ne? (pominu-li fyzické limity)
Pohybovat segment o 16 bajtu ti umozni mit zarovnane 64k pole s ofsetem od nuly. A prijdes max o 15 bajtu na vypln.
Tvuj navrh vyzaduje komplikovane vypocty pro kazdy pristup do pole. Nebo umet nejakou instrukci [SEG:OFS]+n*INDEX.
Tvuj navrh ma problem s tim ze kdyz nebude pole zarovnane, tak klidne muze zacinat s OFS 64K-100. Takze po 100 bajtech musis zvednout SEG. Nemuzes pouzivat OFS snadno jako INDEX. Jedine ze to zarovnas, ale to mas jen 16 moznosti pro megabajt.
Museli by obetovat neco pro budoucnost, v dobe kdy tam maji 20 pinu. Takze bys kupoval v dobe co procesor byl drazsi nez clovek jen omezenejsi verzi co meli.
Proste to lepili jako snehovou kouli, nebo jako kdyz privaris k autu autokaravan, a pak dalsi jeste vetsi a dalsi. Pokud by tam nebyl ten predchozi a rovnou presli na vetsi tak by to mohlo vypadat jinak...
Nejakej smysl to davat muselo. Hlupaci to nebyli. .)
3. 6. 2025, 18:53 editováno autorem komentáře
Jj, pravda, umožňovalo to mať de facto kdekoľvek v 20b adresovom priestore 64k štruktúru, lacno adresovateľnú jedným 16b registrom. To je v situácii s jedinou 16b sčítačkou vcelku hodnotný benefit (ale za cenu skomplikovania rozšíriteľnosti do budúcna). Pre porovnateľne rýchle adresovanie v tom druhom prípade by to chcelo širšiu než 16b sčítačku.
3. 6. 2025, 20:44 editováno autorem komentáře
(resp. takto je realizovateľné úplne bez sčítačky, len bitwise operáciami niečo, čo by v tom druhom prípade vyžadovalo buď sčítačku, navyše >16b pre random access, alebo špecificky písanú logiku s podmieneným kaskádovým inc/dec pri slučkách, prípade hw implementáciu rep inštrukcií, ktorá by to zahŕňala)
Scitacky su v tom procesore (minimalne) dve. Jedna v generatore adries a druha v ALU. Scitacka sa da spravit tak, ze bude mat nizku latenciu kvoli carry, takze to az taky problem nie je. Procesor ma zaroven tiez iba jednu adresnu zbernicu nie nejakej velmi valnej rychlosti, takze viac scitaciek v generatore adries nepotrebuje.
Ze generovanie adresy bez scitacky iba maskovanim nie je uplne super napad ukazuju jednotky MPU v mikrokontrolleroch, ktore casto taky pristup pouzivaju na definiciu regionov.
Aby sa procesor vyhol nutnosti mat scitacky v jednotke MPU, definuje region podobne ako masku siete - bity bazovej adresy regionu, ktore spadaju do jeho tela musia byt nulove. To ale vynucuje, ze kazdy region musi byt zarovnany na adresu rovnu alebo vacsiu velkosti regionu, ktora zaroven musi byt mocninou 2.
To setsakra komplikuje alokaciu pamate, pretoze staci, ze region narastie o par bytov z 254 na 257 bytov a kludne sa moze stat, ze sa cely region musi presunut na ine miesto.
TL;DR: Uspora pamate a rozsahu registrov.
V tej dobe (1980s) bolo ovela dolezitejsie setrit pamatou, nez podporovat velky adresny priestor. 16MB RAM v tej dobe mohlo stat gazilion penazi. Vacsina PC v tej dobe nebola osadena ani plnymi 640kB RAM s ktorymi ten model v pohode dokazal pracovat.
Ak by segmentove registre pracovali s granularitou 64kB, vznikne jeden z dvoch problemov:
- Bud budem plytvat vsetkou RAM medzi koncom poslednych dat v predoslom segmente a zaciatkom noveho segmentu
- Alebo pridem o rozsah (max. 64kB) indexovacich registrov tym, ze data naalokujem na nenulovom offsete v ramci segmentu.
- Alebo navyse este zabezpecim vacsiu nez nutnu fragmentaciu pamate, pretoze okrem toho, kde su obsadene miesta v pamati musim pri alokacii blokov zohladnit aj to, kde su hranice segmentov. 32kB volny blok na konci jedneho segmentu a 32kB volny blok na zaciatku hned dalsieho segmentu sa proste v takom systeme nedaju naalokovat ako jeden 64kB blok, lebo to nejde rozumne zaadresovat.
Riesenie s prekryvajucimi sa segmentami poskytuje slusnu granularitu 16 bytov pri sucasnom maximalnom rozsahu indexovacich registrov 64kB.
Pre 8086tku a dajme tomu aj 286tku to bolo zitelne riesenie. Na 386tke uz spravili v Inteli tu chybu, ze dovolili subezne pouzivanie segmentacie a strankovania.
Ad: Ak by segmentove registre pracovali s granularitou 64kB, vznikne jeden z dvoch problemov
:
preto som tam explicitne uvádzal tú druhú premisu "... a existovali by inštrukcie pre aritmetiku nad pármi seg:offs registrov (ideálne vrátane blokových [prenosov])"
Čo by umožňovalo de facto lineárne adresovanie bez ohľadu na hranice segmentov. Všetky adresovacie režimy vyžadujúce sčítanie offsetov (MOV AX, [BX+n]; MOV AX, [SI+n]; MOV AX, [BX+SI]; MOV AX, [BX+SI+n], REP ...) by museli byť samozrejme realizované tak, aby si pri pretečení/podtečení offsetu vedeli korektne inkrementnúť/dekrementnúť aj segment.
No, to znie pekne len do momentu, kym si clovek uvedomi, ze napr. taku dvojicu 8 bitovych registrov slo v instrukcnej sade 8080 pouzit ako jeden 16-bitovy register iba v doslova par instrukciach. A neslo pouzit hocijake dva registre, ale iba konkretne dva registre.
Tam to malo svoj dovod - zjednodusilo to zapojenie procesora a vsetky vypocty adresy musel robit software. Spocitanu adresu potom slo priamo vystavit na adresnu zbernicu. Samozrejme to ale bolo pomale.
x86tka trpela na maly pocet registrov uz aj bez toho, takze by toto riesenie bud muselo fungovat na viacerych dvojiciach registrov, alebo by bolo zbytocne zlozite do tej miery, ze priame pouzitie 32-bitovych registrov by uz bolo jednoduchsim riesenim.
Nie je to priamociare, ale zbytocne komplikovane.
No v rámci 16b architektúry je to priamočiare riešenie, keďže to umožňuje ponechať drvivú väčšinu CPU 16-bitovú, bez potreby ťahania širších interných zberníc a nárastu množstva tranzistorov. Širšie musí fungovať len minimálna časť, týkajúca sa adresovania. To je dosť podstatný aspekt, čo sa týka nákladov (v čase vzniku). Plus teda to širšie adresovanie museli riešiť tak či tak, akurát zvolili riešenie 1. natvrdo, bez rezervy našité na 20b adr. zbernicu a 2. hodne komplikujúce kód operujúci na širších než 64k štruktúrach resp. štruktúrach ležiacich naprieč segmentami.
A čo sa týka 8080 a inštrukcií so 16b operandami, Z80 možnosti v tomto smere hodne rozšíril.
Ten komentář o problematičnosti segmentace na 8086 z pohledu pointerové aritmetiky IMHO zapomíná na skutečnost, že podle standardu jazyka C je rozumné fungování pointerové aritmetiky garantováno pouze v rámci prvků jednoho pole (a navíc pointeru bezprostředně za jeho poslední prvek). Jinak řečeno, a priori např. nemůžeme vzít dva náhodné pointery na stejný typ, odečíst je a očekávat smysluplný výsledek.
V praxi to samozřejmě vždycky nějak funguje a vývojáři si na to zvykli natolik, že na toto omezení dávno zapomněli, pokud o něm tedy vůbec kdy věděli. IMHO to ale znamená, že ten problém zmiňovaný v článku nijak zásadní není. Stačí, aby pole "žilo" buď v rámci jednoho segmentu nebo nějaké posloupnosti navazujících segmentů, a pointer, který dostaneme aplikací operátoru & na prvek pole, byl vždy reprezentován hodnotou z tohoto segmentu, a při korektním používání žádný problém nevznikne.
Myslíš problém pointerové aritmetiky jako takové nebo porovnání ukazatelů? To první je spíš past na vývojáře, který si musí hlídat, co se mu vlastně vrátí (buď rozdíl offsetů, a když má štěstí, tak rozdíl celých adres). To druhé - tyjo ono to v BC vždycky fungovalo tak napůl a ještě to záleželo na volbě Fast huge pointers (to v podstatě vypnulo normalizaci ukazatelů, ale i když je zapnutá, tak nikde není jistota, že se někde neobjeví nenormalizovaný ukazatel).
V podstatě, pokud se dělalo se strukturami pod 64kB, tak to bylo v pohodě, jinak začaly problémy - typicky se nesahalo na volby překladače, aby to nějak drželo dohromady :-)
PS: byla jiná doba, žádné velké automatické testování se nekonalo; asi na to ani nebyla infrastruktura
Myslel jsem pointerovou aritmetiku obecně. K porovnávání se tam píše tohle:
6.5.9.6 Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.
přičemž k té poslední části je poznámka pod čarou:
Two objects may be adjacent in memory because they are adjacent elements of a larger array or adjacent members of a structure with no padding between them, or because the implementation chose to place them so, even though they are unrelated. If prior invalid pointer operations (such as accesses outside array bounds) produced undefined behavior, subsequent comparisons also produce undefined behavior.
Celkově mi z toho vychází, že pokud nebudu v programu spoléhat na nedefinované chování nebo si pointery "cucat z prstu", neměla by ta nejednoznačná reprezentace vést k neočekávaným nebo nelogickým výsledkům ani při porovnávání.
Dnes je samozřejmě všechno jednodušší, když máme 64-bitové procesory a na segmenty se můžeme vykašlat.
no a praxe je taková, že například pointer ukazuje na začátek VideoRAM. Co teď s tím? Objekt to žádný není, prostě mám pointer typu (řekněme) unsigned char*. Pointerovou aritmetiku v tomto případě naprosto chceme, možnost přičítat offsety taky a mělo by to fungovat. V rámci offsetů to funguje vždy, mimo ně IMHO jen v Huge režimu u huge ukazatelů - jinak ne jestli si pamatuju.
Nemyslím, že by někdo i dneska odčítal náhodné pointery - jaký by to mělo význam?
Ten problém se segmentací je, že můžu mít pole větší než 64 kB a pak posun v rámci pole, stejně jako spočítání offset prvku, vyžaduje 32-bitovou operaci a výsledek (a v případě pointeru změnu segment i offset). Nevím, jakého typu je off_t (pokud vůbec byl v tehdejším standardu).
Tedy jenom pro Huge memory model - musel jsem si ty názvy připomenout. Klobouk dolů - psát o tomhle musel být masochismus :-D .
Podle me provnavat 2 pointery a resit problem ze ukazatel na stejne adresove misto v pameti muze byt reprezentovan vice kombinacemi nedava moc smysl.
Pokud je to vyssi jazyk tak te to ma odstinit od toho ze ty hodnoty mohou byt ruzne, protoze se prekryvaji a videt to v C jenom jako vyslednou 20 bitovou adresu.
Pokud si to placas v assembleru tak zase existuje jen jedno "normalizovane" reseni kdy mas segment na te hodnote, aby ofset mohl byt nulovy na zacatku.
Pokud do jednoho 64k segmentu davas vice mensich poli a delas si z toho datovy segment, tak si taky najdes jednu hodnotu, kde prvni pole zacina a kterou chapes jako "normalizovanou". Urcite se ti nechce porad cachrovat se segmenty. A pokud jo, tak je nahodne neporovnavas.
Pokud ti pak vystane potreba resit zda to ukazuje na stejne misto v ramci pole, tak ti staci porovnat jen offset.
Kdyz je to vic nez 64k tak si to nejak resis sam. Mas vic moznosti. Muzes si vybrat co se ti hodi. Pokud mas velke pole 16 bajtovych polozek, tak muzes mit zase nulovy offset a porovnavat jen segmenty.
Nemyslim si ze by pri navrhu procesoru tohle videli jako velky nedostatek. Skoda jen ze nepreskocili prvni iteraci a rovnou nezvolili zarovnani 256 bajtove, ze by prvni limit nebyl mega ale rovnou tech 16 mega.
PS: Priznam se ze jsem v tom dost plaval (a ted si to zase nepamatuji), protoze jak nebyl dostatek informaci, tak bylo pro me tezke poznat ktery segmentovy registr instrukce vlastne pouziva... Mam pocit ze to slo zmenit prefixem, ale to byla instrukce o bajt delsi. Takze kdyz jsem psat 256 bajtove intro do souteze, misto abych se ucil na zkousku z fotogrammetrie, tak to byl dost horor i bez toho samomodifikujiciho se kodu pro Bresenhamuv algoritmus kresleni cary v SVGA...
3. 6. 2025, 22:28 editováno autorem komentáře
Kdyz se koukam na tu tabulku:
1 Tiny krátké 64kB pro program i data, všechny čtyři segmentové registry jsou totožné (a tedy limit 64kB se vztahuje na celek) 2 Small krátké 64kB programový kód, 64kB pro data 3 Medium dlouhé pro program, krátké pro data 1MB programový kód, 64kB pro data 4 Compact krátké pro program, dlouhé pro data 64kB programový kód, 1MB pro data 5 Large dlouhé 1MB programový kód, 1MB pro data 6 Huge dlouhé 1MB pro programový kód, 1MB pro data
Tak pokud budu ignorovat podvarianty s delkou kodu tak C resi jen 3 pripady.
1) Mas jen 64 kb na vsechny data, takze segment se nemeni a C te od nej odstini a mas 16 bitovy pointer (jen offset).
2) Mas 1 mb na data, ale alokovat muzes najednou max. 64kb na pole. To same co predchozi vidis zas jen 16 bitovy pointer (jen offset). Jen nesmis porovnavat ruzne pole mezi sebou, protoze maji ruzny segment.
3) C ti povoli vetsi jak 64 kb pole. Pak se to ma idealne chovat jako 20 bitovy pointer i kdyz v 32 bitovem cisle. Spravne by ten popisek u Large mel byt celkove 1MB pro data max 64kb velka.
Zajimalo by me zda jde v tom druhem rezimu udelat vetsi pole diky tomu ze C umi delat vicerozmerna pole vice zpusoby a pokud alokujes nejaky rozmer zvlast a vyssi je jen pole pointeru... No, v te dobe jsem stejne neumel a delal v BP, protoze mel lepsi vysledny kod. BC produkoval vetsi binarky. Pridaval na konec nazvy fci a jeste neumel automaticky vynechat z kodu fce co se nepouzili. Volajici navic uklizel zasobnik po kazdem volani. Hruza, clovek se snazil prejit na C a pak prislo jen zklamani. :)
Aha tak aby mohlo v LARGE modelu fungovat pole pointeru na az 64kb dalsi pole tak ten pointer musi byt fyzicky 32 bitovy, takze te C nemuze odstinit a tvrdit ti ze je to 16 bitove, protoze segment nesmis prekrocit/zmenit.
On je vlastne pointer jakoby nezavisly na jaky objekt ukazuje. Jakoby anonymni a pokud skryjes ze kazdy objekt ma vlastni segment, tak by C ani nevedelo k jakemu teda segmentu pointer odkazuje... Musel by si to odvodit z kodu k cemu to bylo inicializovany. Takze uz by ten pointer nebyl jakoby nezavisly. Komplikace.
Nenapada me u kodu jaky mit jiny pointer nez na fci.
Model Kód Funkční pointer Data Datový pointer Max data TINY near 16 bit (near) near 16 bit (near) 64 KB celkem (kód + data) SMALL near 16 bit (near) near 16 bit (near) 64 KB COMPACT far 32 bit (far) near 16 bit (near) 64 KB MEDIUM near 16 bit (near) far 32 bit (far) >64 KB LARGE far 32 bit (far) far 32 bit (far) >64 KB (objekty max 64 KB) HUGE far 32 bit (far) far 32 bit (huge) >64 KB, i pole přes 64 KB
Tohle je asi komplexnejsi tabulka jak to vypadalo.
"Pokud je to vyssi jazyk tak te to ma odstinit od toho ze ty hodnoty mohou byt ruzne, protoze se prekryvaji a videt to v C jenom jako vyslednou 20 bitovou adresu."
právěže to vidíš jako segment:offset i přímo v tom céčku. V podstatě nikdy z toho nikdo výslednou 20bitovou adresu nepočítá (pokud to nenapíšeš explicitně sám), pořád to vnitřně je jako pár dvou 16bitových hodnot. Jsou na to ta tři makra zmíněná v článku a nějaký funkce okolo.
cely ten problem se vlastne resi jen kvuli tomu, ze bazove adresy (a bazove registry) jsou 16bitove a ne 32bitove, coz by vsechny problemy vyresilo na dlouhou dobu dopredu. Nebo jeste jinak - kdyby si IBM zvolila Motorolu 68000, tak by se to neresilo, ale IBM melo svoje pravidla (dva dodavatele) a to Motorola nesplnovala.
Tých faktorov prečo nie 68k tam bolo viacero, okrem second sources aj nedostupnosť variantu s 8b dátovou zbernicou (ktorú kvôli nákladom preferovali) dostatočne včas - 68008 bol uvedený rok po IBM 5150; nižšia hustota kódu; horšia nadväznosť na CP/M ekosystém a 8080 tooling; vyššia cena. Plus je otázka, či by to bolo vo výsledku podobne úspešné, pokojne by sa mohlo stať, že by nakoniec prerazila nejaká konkurenčná platforma s 8086/88 práve kvôli tej jednoduchej migrácii existujúceho CP/M kódu.
A proto PC zvítězilo na celé čáře, přes všechny potíže. 386, chráněný režim a V86 režim nakonec splinily požadavky na pokročilejší operační systémy a zpětnou kompatibilitu.
Nebyl bych tak přísný, 80. léta byla bouřlivá dekáda a vidět do budoucnosti bylo tehdy nanejvýš obtížné.
5. 6. 2025, 09:33 editováno autorem komentáře
spis uspelo proto, ze ty PC skladala kazda garazovka, vetsinou s "vypujcenym" BIOSem (jedina vec, kterou mela IBM licensovanou) - to by dneska uz v zadnem pripade neproslo. Nevyhrala technologie, ale cena, zatimco konkurenti si to rozdavali mezi sebou. Apple taky chvili zkousel jit cestou licencovani vyroby, ale potom z toho seslo (a ty klony nebyly cenove marny).
Nevyhrala technologie, ale cena,
Takhle ale trh do značné míry funguje. Můžete vymyslet úžasnou super sofistikovanou technologii, ale pokud ji nebudete schopen vyrábět - a hlavně prodávat - za cenu, která osloví dostatečný počet zákazníků, ekonomický úspěch z toho nebude.
Koneckonců to bylo vidět už u těch domácích osmibitových počítačů. Bylo ZX Spectrum technologicky nejlepší? Ani omylem, byl to šílený bastl a některá designová rozhodnutí byla dost šílená. Ale právě díky tomu se jim podařilo dostat na cenu, která oslovila dostatečný počet zákazníků, aby síťový efekt dokonal zbytek. S PC to bylo do určité miry podobné.
A neplatí to jen pro počítače. Byl to Fiat, kdo koupil Alfu, Maserati nebo Ferrari, ne naopak. Nebo se podívejte na seznam značek patřících pod Volkswagen Group nebo Swatch Group, spousta z nich je daleko uznávanější odborníky i laiky, ale ekonomická realita je prostě jiná.
Co se vlastne stane kdyz takovy 16 bitovy DOSovsky program se dostane s IP do situace kdy nasledujici insrukce pretece na zacatek toho CS segmentu a nebo inkrementuje CS o...1? 4096? Jinou hodnotu?
Zkousel jsem napsat:
.model medium
.stack 100h
.data
data_seg segment
msg_old db 'Overflow!$'
msg_run db 'Test start...', 0Dh, 0Ah, '$'
msg_new db 'Next segment!$'
data_seg ends
.code
CODE1 segment
assume cs:CODE1
assume ds:data_seg
mov ax, data_seg ; Zajistí správné DS i zde!
mov ds, ax
mov dx, offset msg_old
mov ah, 09h
int 21h
mov ax, 4C00h
int 21h
org 0FFEEh
start1:
mov ax, data_seg
mov ds, ax
mov dx, offset msg_run
mov ah, 09h
int 21h
REPT 6
nop
ENDM
; skok na druhý kódový segment, offset 0xF000
; push cs
; push offset start2
; retf ; far return - skočí do CODE2:0xF000
CODE1 ends
.code
CODE2 segment
assume cs:CODE2
assume ds:data_seg
start2:
mov ax, data_seg ; Zajistí správné DS i zde!
mov ds, ax
mov dx, offset msg_new
mov ah, 09h
int 21h
mov ax, 4C00h
int 21h
CODE2 ends
end start1
Test spociva v tom, ze mam za sebou dva 64kb bloky programu. Program zacina na konci prvniho a vypise hlasku ze zacal. Na zacatku prvniho bloku je vypsani ze pretekl a ukonceni programu. Na zacatku druheho bloku je vypsani ze je v dalsim segmentu a ukonceni programu.
C:\TASM\EXAMPLES>TASM \l TEST_07.ASM Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International Assembling file: TEST_07.ASM *Warning* TEST_07.ASM(44) REPT(6) Location counter overflow Error messages: None Warning messages: 1 Passes: 1 Remaining memory: 461k C:\TASM\EXAMPLES>TLINK TEST_07.OBJ Turbo Link Version 7.1.30.1. Copyright (c) 1987, 1996 Borland International C:\TASM\EXAMPLES>TEST_07.EXE Test start... Next segment!
Napsalo to sice ze to prelezlo do dalsich 64kb, ale je to DOSBOX, a jeste to nebezi na 486 nebo necem takovem.
Navic tohle je uz nekolikata iterace programu a ty predchozi skoncili chybou napriklad vypsaly misto jedne ze dvou moznych variant treti variantu ktere nemuze nastat: "Test start... Test start..." a radne se ukoncily. Kdyz jsem pridal znovu nastavit DS register a drobne to upravil tak jsem dostla tento vysledek. Bez neho to nekam pretece a vypise celou obrazovky nahodnych znaku nez se sekne.
x@user:~/Programovani/dosprog/TASM/EXAMPLES$ xxd TEST_07.EXE | grep -v '0000 0000 0000 0000 0000 0000 0000 0000' 00000000: 4d5a 4101 8200 0300 2000 0000 ffff 0000 MZA..... ....... 00000010: 0001 0000 eeff 1300 3e00 0000 0100 fb71 ........>......q 00000020: 6a72 0000 0000 0000 0000 0000 0000 0000 jr.............. 00000030: 0000 0000 0000 0000 0000 0000 0000 0100 ................ 00000040: 1300 efff 1300 0100 1310 0000 0000 0000 ................ 00000300: 4f76 6572 666c 6f77 2124 5465 7374 2073 Overflow!$Test s 00000310: 7461 7274 2e2e 2e0d 0a24 4e65 7874 2073 tart.....$Next s 00000320: 6567 6d65 6e74 2124 0000 0000 0000 0000 egment!$........ 00000330: b810 008e d8ba 0000 b409 cd21 b800 4ccd ...........!..L. 00000340: 2100 0000 0000 0000 0000 0000 0000 0000 !............... 00010310: 0000 0000 0000 0000 0000 0000 0000 b810 ................ 00010320: 008e d8ba 0a00 b409 cd21 9090 9090 9090 .........!...... 00010330: b810 008e d8ba 1a00 b409 cd21 b800 4ccd ...........!..L. 00010340: 21 ! x@user:~/Programovani/dosprog/TASM/EXAMPLES$ python3 exe_info2.py TEST_07.EXE Hlavička EXE souboru (MZ): Addr Hex bytes Hodnota Popis ------------------------------------------------------------- 0000 4d 5a MZ Signatura 'MZ' (ASCII MZ) 0002 41 01 321 Počet použitých bajtů v poslední stránce 0004 82 00 130 Celkem stránek o velikosti 512 bajtů 0006 03 00 3 Počet relokací 0008 20 00 32 (=512 bajtů) Velikost hlavičky v para (16B) 000A 00 00 0 Minimální extra paměť (v para) 000C ff ff 65535 Maximální extra paměť (v para) 000E 00 00 0x0000 Inicializační relativní SS segment 0010 00 01 0x0100 Inicializační SP offset 0012 00 00 0x0000 Checksum (obvykle 0) 0014 ee ff 0xFFEE Inicializační IP offset 0016 13 00 0x0013 Inicializační relativní CS segment 0018 3e 00 0x003E Offset relokační tabulky v hlavičce 001A 00 00 0 Overlay number (obvykle 0) Rezervovaná/neznámá oblast (0x1C - 0x3D): 001C 01 00 .. ??? (reservováno/neznámé) 001E fb 71 .q ??? (reservováno/neznámé) 0020 6a 72 jr ??? (reservováno/neznámé) 0022 00 00 .. ??? (reservováno/neznámé) 0024 00 00 .. ??? (reservováno/neznámé) 0026 00 00 .. ??? (reservováno/neznámé) 0028 00 00 .. ??? (reservováno/neznámé) 002A 00 00 .. ??? (reservováno/neznámé) 002C 00 00 .. ??? (reservováno/neznámé) 002E 00 00 .. ??? (reservováno/neznámé) 0030 00 00 .. ??? (reservováno/neznámé) 0032 00 00 .. ??? (reservováno/neznámé) 0034 00 00 .. ??? (reservováno/neznámé) 0036 00 00 .. ??? (reservováno/neznámé) 0038 00 00 .. ??? (reservováno/neznámé) 003A 00 00 .. ??? (reservováno/neznámé) 003C 00 00 .. ??? (reservováno/neznámé) Relokační tabulka: Addr Hex bytes Popis -------------------------------------------------- 003E 01 00 13 00 Relokace #0: segment:offset=0x0013:0x0001, absolutní adresa=0x00131, v souboru na=0x00331 0042 ef ff 13 00 Relokace #1: segment:offset=0x0013:0xFFEF, absolutní adresa=0x1011F, v souboru na=0x1031F 0046 01 00 13 10 Relokace #2: segment:offset=0x1013:0x0001, absolutní adresa=0x10131, v souboru na=0x10331
Ak sa dobre pamätám (a zbežne hľadám v zdrojoch), CS to samo inkrementovať nebude, pretečie len IP (na hodnotu podľa dĺźky dekódovanej inštrukcie, ktorá začíanala na konci segmentu, pričom ak tam neležala celá, bude sa to snažiť jej ďalšie bajty nafetchovať zase zo začiatku segmentu).
Korektná implementácia by bola komplikovaná, IMHO, keďže je tam 6-bajtová prefetch queue, ktorej plnenia by sa to zrejme tiež dotýkalo, nezávisle od inkrementovania samotného CS pri vykonávaní nafetchovaných inštrukcií.
CS zrejme menia len far JMP/CALL, INT a IRET.
IMHO ti to na puvodni 8086/8088 nepreleze do dalsiho segmentu. CS se obecne meni pri far jumpech a far returnech (+ pri preruseni), jinak ne. Ale zkusim to taky*
DOSBoxu se v tomto neda na 100% verit, on nekontroluje nektery limity atd. Narazil jsem na to pri ladeni prechodu do protected rezimu, kde prosly i veci, ktery by realny HW ukoncil vyjimkou.
To byl muj puvodni predpoklad ze se to bude chovat jako na Z80, a proste i kdyby to rozpulilo instrukci tak to jen preleze na zacatek. I kdyz.. pulit intrukci jsem jeste nezkousel.. :D To chce realny hardware.
TASM na tebe krici WARNINGy i kdyz se JEN dotknes 65535 adresy! I kdyby tam lezelo RET nebo ukoncujici INT 021h. Je to problematicke jako tisknout posledni znak na posledni radce a nevyvolat srolling.
Podle me kdyz je tam RET tak procesor pri vykonavani uz ukazuje na dalsi instrukci, takze pokud by to automaticky zvysilo i CS o 4096 tak bys tim RETem ktere pak zmeni jen OFFSET skoncil nekde jinde nez si myslis.
Takze bud je to nejak osetreny, nebo se to chova jinak. A okrajove pripady se mozna mohou i lisit procesor od procesoru.
Ale predstava ze kdyz se blizis s IP ke konci te to nuti delat JMP FAR o par bajtu dal je uzmevna a nebo mozna hororova. Zvlast kdyz se da ten 64 KB konec posunout libovolne po 16 bajtovych krocich, takze na te 20 bitove realne adrese predel muze lezet kdekoliv.
AI me tvrdi ze CS se samo neumelo menit, jen temi skoky, volanim a navratem volani a prerusenim. Takze je to takovy maskovany osmibit. Trosku vylepsena Z80. ZX Spectrum 128, kde nekdo pridal vic pameti. Takovy Pentagon. Jen to misto posunu o 16 bajtu prepina 16 KB banky.
A proto mozna jen HUGE rezim se chova k pointerum jak ocekavame, protoze je to jen emulovane chovani.
To už to haníš viac než tomu patrí, IMHO... Externe dobastlený bank switching, vyžadujúci I/O alebo memory zápisy na prepnutie je ešte ďaleko ďaleko neefektívnejšie a obmedzujúcejšie zúfalstvo, než segmentácia. A nie, ani 8-bit z toho segmentácia ničím nerobí, 86 je plnohodnotný 16-bit.
Čo sa týka kódu naprieč segmentami, je to okrajový prípad. Vyšší jazyk to nevyrobí, jednotlivé procedúry sú normálne rádovo kratšie než 64k a navzájom sa v príslušných modeloch môžu volať far volaniami (pričom vďaka práve tomu prekryvu segmentov po 16B sa veľa nestratí ani na ich rozložení v pamäti tak, aby kód žiadnej neležal naprieč hranicou segmentu).
Mas pravdu s tim, ze normalni kod se vlastne deli na relativne male funkce. Takze pokud vsechno volame FAR, tak problem s prekrocenim segmentu nenastane – jen za cenu toho, ze by nekdy stacilo i NEAR volani.
A ono to vlastne v assembleru jde. Muzes si vytvorit knihovnu, ktera obsahuje verejne funkce volane FAR a sama pouziva neverejne funkce volane NEAR.
Staci si jen vytvorit novy segment a kod umistit v ramci neho:
.code RANDOM_CODE_NAME segment assume cs:RANDOM_CODE_NAME ...zde mas sve funkce... RANDOM_CODE_NAME ends
Prekladac nastavi pro ten segment takovou hodnotu CS, aby na zacatku bylo IP co nejbliz nule – mozna to dokonce zarovnava na paragrafy? Takze je IP vzdy nula?
V cele oblasti RANDOM_CODE_NAME je pak CS konstantni. A i kdyz se tam volaji funkce odjinud, tak se to resi tak, ze pro CS se zvoli ta konstantni hodnota.
Tohle je strasne dulezite, protoze pokud to chces trolit, tak jsi schopen volat verejne funkce v te oblasti s jinym CS – jen pomoci toho, ze zmenis offset – a to muze vest k tomu, ze ti program spadne!
Dejme tomu, ze volas pres FAR nejakou funkci uprostred te oblasti, ale ne pres CS:16000, ale pres hodnotu CS+1000:0. Tim vytvoris uplne jiny rozsah, kam se lze relativnimi skoky dostat. Ta funkce ma uvnitr sebe relativni skok na neverejnou funkci CALL -1000.
Puvodne bys teda skoncil na adrese CS:16000-1000. Ted skoncis na adrese (CS+1000):0-1000, coz je jina fyzicka adresa. Relativni odkazy pro stejnou adresu, ale s jinym zvolenym CS, funguji jen do te doby, co skaces na prunik pristupnych oblasti, ktere kazde CS ma.
Napada me tohle pouzit jen proti nejakemu antiviru nebo analyze kodu – je to spis bolest nez uzitecne.
Zkousel jsem vymyslet, zda lze napsat funkci, kterou lze volat jak FAR, tak NEAR, a bude korektne fungovat – abys usetril bajty a takty, pokud je ta funkce v dosahu.
A ano, lze takovou funkci napsat – dokonce ve dvou variantach.
Prvni varianta: Funkce je primarne FAR a ty osetris pripad, kdy je volana NEAR.
Na to staci upravit zasobnik tak, aby se funkce mohla korektne ukoncit pres RETF.
Bohuzel Intel zvolil reseni, kdy prvni polozka pri FAR volani, kterou vytahnes ze zasobniku, je offset a ne segment – takze nestaci pred funkci pridat PUSH CS.
fce_near:
pop dx
push cs
push dx
fce_far:
mov dx, offset msg
mov ah, 09h
int 21h
retf
Kupodivu od 386 by to uz i usetrilo takty. Jinak je to ale horsi nez to volat rovnou FAR.
Druha varianta: Funkce je primarne NEAR a ty osetrujes pripad, kdy je volana FAR.
fce_far:
call fce_near
retf
fce_near:
mov dx, offset msg
mov ah, 09h
int 21h
ret
V podstate mas dve funkce, z nichz jedna je FAR a vola tu druhou NEAR.
Kupodivu i to muze usetrit bajty i takty – zalezi, v jakem pomeru FAR a NEAR tu funkci volas.
Od 286 jsem ale nasel instrukci, co umi pushnout konstantu na zasobnik.
Takze muzes nahradit CALL fce_near za pouhe ulozeni konstanty na zasobnik, kde ta konstanta ukazuje na offset nejake RETF instrukce v ramci tveho segmentu.
Nekde na zacatku musis napsat direktivu .286, jinak ti to assembler bude emulovat PUSH imm, tedy vygeneruje pomaly kod.
.code
CODE1 segment
assume cs:CODE1
lab_retf:
retf
fce_far:
push 0
fce_near:
mov dx, offset msg
mov ah, 09h
int 21h
ret
Tohle vypada dost rizikove, ale funguje.
Otazka je: jak donutit prekladac, aby nahradil 0 za offset, ktery doplni sam?
Ukazalo se, ze kdyz napises push offset lab_retf, tak to zvoli push imm16. to nechceme protoze je to pomalejsi a my muzeme umistit RETF nekam na zacatek segmentu do offsetu 0 az 127. Pak nam staci rychlejsi a kratsi varianta "push imm8".
.code
CODE1 segment
assume cs:CODE1
lab_retf:
retf
fce_far:
db 6Ah, offset lab_retf
fce_near:
mov dx, offset msg
mov ah, 09h
int 21h
ret
Nejlepsi reseni, co jsem nasel.
Pri pomeru cca 50/50 (FAR vs NEAR) je tahle varianta asi nejlepsi.
Sice na rozdil od prvni vzdy zpomaluje FAR, ale zase NEAR je bez postihu.
Druha varianta nepostihuje FAR, ale NEAR zrychluje jen mirne – a to v taktech az od 386+.
Prakticky to ale asi nejde moc pouzit.
Myslim, ze nemas zpusob, jak si overit, ze dva nezavisle segmenty jsou dostatecne blizko, aby misto FAR slo volat NEAR.
Takze jedine v ramci jednoho segmentu, kdy je nejaka funkce verejna a zaroven se pouziva vnitrne.
Chtel jsem pochopit jak je delana relokacni tabulka v DOSovskem EXE. Takze jsem si udelal maly programek, kde jsem se snazil vynutit v small rezimu (kod a data zvlast do 64 kb) dlouhy skok. Skok jen skace za sebe.
.model small
.stack 100h
.data
msg db 'Hello$'
.code
jmp far ptr start
start:
mov ax, @data
mov ds, ax
mov dx, offset msg
mov ah, 09h
int 21h
mov ax, 4C00h
int 21h
end start
Program se podarilo zkompilovat i spustit a radne ukoncit. List ukazoval tohle
Turbo Assembler Version 4.1 06/04/25 20:35:20 Page 1
TEST_02.ASM
1 0000 .model small
2 0000 .stack 100h
3
4 0000 .data
5 0000 48 65 6C 6C 6F 24 msg db 'Hello$'
6
7 0006 .code
8 0000 EB 03 90 90 90 jmp far ptr start
9 0005 start:
10 0005 B8 0000s mov ax, @data
11 0008 8E D8 mov ds, ax
12 000A BA 0000r mov dx, offset msg
13 000D B4 09 mov ah, 09h
14 000F CD 21 int 21h
15 0011 B8 4C00 mov ax, 4C00h
16 0014 CD 21 int 21h
17
18 end start
Turbo Assembler Version 4.1 06/04/25 20:35:20 Page 2
Symbol Table
Symbol Name Type Value
??DATE Text "06/04/25"
??FILENAME Text "TEST_02 "
??TIME Text "20:35:20"
??VERSION Number 040A
@32BIT Text 0
@CODE Text _TEXT
@CODESIZE Text 0
@CPU Text 0101H
@CURSEG Text _TEXT
@DATA Text DGROUP
@DATASIZE Text 0
@FILENAME Text TEST_02
@INTERFACE Text 000H
@MODEL Text 2
@STACK Text DGROUP
@WORDSIZE Text 2
MSG Byte DGROUP:0000
START Near _TEXT:0005
Groups & Segments Bit Size Align Combine Class
DGROUP Group
STACK 16 0100 Para Stack STACK
_DATA 16 0006 Word Public DATA
_TEXT 16 0016 Word Public CODE
Vsechno v poradku nez si uvedomite ze:
8 0000 EB 03 90 90 90 jmp far ptr start
Je relativni skok za tri nopy, ktere pouziva jako vypln, aby velikost kodu byla shodna. Rychlost je sice vic jak dvojnasobna, ale je to tak trochu podvod, kdyz to dela neco jineho nez po nem chci bez jakehokoliv upozorneni.
Vlastne se me ten dlouhy skok nepodarilo udelat ani s
db 0EAh dw offset start dw segment start
protoze to selze kvuli syntaxi.
Tak jako tak to vypada ze diky te segmentaci ty programy MUSI byt zarovnane po 16 bajtech. Takze relokace se dela jen u segmentu. EXE to nijak nezkrati, protoze ten ma ty zaznamy vedene jako relativni adresu do souboru a ta je 32 bitova, protoze soubor muzes byt vetsi jak 64 kb.
Navic EXE si rekl, ze kdyz nejmensi sektor na diskete ma 512 bajtu tak tohle bude nejmensi mozna jednotka. Hlavicka nebude mensi. A to zarovnani vseho na 16 bajtu, jako je zasobnik, atd. se s nama asi drzi dodnes.