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.