Vlákno názorů k článku Problematika ukazatelů v překladačích jazyka C pro systém DOS od forth - Co se vlastne stane kdyz takovy 16 bitovy...

  • Článek je starý, nové názory již nelze přidávat.
  • 5. 6. 2025 12:02

    forth

    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
  • 5. 6. 2025 14:46

    CHe

    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.

  • 5. 6. 2025 15:29

    Pavel Tišnovský
    Zlatý podporovatel

    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.

  • 5. 6. 2025 17:25

    forth

    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.

  • 5. 6. 2025 19:52

    CHe

    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).

  • 6. 6. 2025 12:53

    forth

    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.

  • 10. 6. 2025 14:52

    Wladows

    Pokud chceš volat FAR funkci ležící ve *stejném* kódovém segmentu, je možné použít NEAR CALL instrukci. Jen je nutné na zásobník předem uložit obsah CS registru pro kompletní návratovou adresu:

    push cs
    call near func
    ...

    func:
    ...
    retf

    Řada překladačů (myslím i Borland C++) to tak dělala.